Newsfeeds

Cheeky Monkey Media: The Drupal Checklist Every Developer Needs

Planet Drupal - 8 November 2017 - 11:49am
The Drupal Checklist Every Developer Needs cody Wed, 11/08/2017 - 19:49

Are you almost finished setting up your Drupal website? At a glance, everything might look ready to go.

But, before you hit "publish," you need to make sure you haven't made any mistakes.

A writer proofreads before they post an article. Similarly, a developer should double check their work.

The last thing you want is to go live with your site and have something go wrong. Finding problems before you launch can save some headaches and embarrassment.

We've compiled a pre-launch, Drupal checklist. When it's complete, you'll rest easy knowing that your website is ready to go.

Security

Security is the first on this Drupal checklist because it's so important. Of course you want to rest easy knowing that your site is secure when it launches. You also want your users to have peace of mind knowing that their information is safe.

Double checking your site's security will ensure that there's nothing you've missed that could make you vulnerable to hackers.

Categories: Drupal

Evolving Web: Profiling and Optimizing Drupal Migrations with Blackfire

Planet Drupal - 8 November 2017 - 11:34am

A few weeks ago, us at Evolving Web finished migrating the Princeton University Press website to Drupal 8. The project was over 70% migrations. In this article, we will see how Blackfire helped us optimize our migrations by changing around two lines of code.

Before we start
  • This article is mainly for PHP / Drupal 8 back-end developers.
  • It is assumed that you know about the Drupal 8 Migrate API.
  • Code performance is analyzed with a tool named Blackfire.
  • Front-end performance analysis is not in the scope of this article.
The Problem

Here are some of the project requirements related to the problem. This would help you get a better picture of what's going on:

  • A PowerShell script exports a bunch of data into CSV files on the client's server.
  • A custom migration plugin PUPCSV uses the CSV files via SFTP.
  • Using hook_cron() in Drupal 8, we check hashes for each CSV.
  • If a file's MD5 hash changes, the migration is queued for import using the Drupal 8 Queue API.
  • The CSV files usually have 2 types of changes:
    • Certain records are updated here and there.
    • Certain records are added to the end of the file.
  • When a migration is executed, migrate API goes line-by-line, doing the following things for every record:
    • Read a record from the data source.
    • Merge data related to the record from other CSV files (kind of an inner join between CSVs).
    • Compute hash of the record and compare it with the hash stored in the database.
    • If a hash is not found in the database, the record is created.
    • If a hash is found and it has changed, the record is updated.
    • If a hash is unchanged, no action is taken.

While running migrations, we figured out that it was taking too much time for migrations to go through the CSV files, simply checking for changes in row hashes. So, for big migrations with over 40,000 records, migrate was taking several minutes to reach the end of file even on a high-end server. Since we were running migrate during cron (with Queue Workers), we had to ensure that any individual migration could be processed below the 3 minute PHP maximum execution time limit available on the server.

Analyzing migrations with Blackfire

At Evolving Web, we usually analyze performance with Blackfire before any major site is launch. Usually, we run Blackfire with the Blackfire Companion which is currently available for Google Chrome and Firefox. However, since migrations are executed using drush, which is a command line tool, we had to use the Blackfire CLI Tool, like this:

$ blackfire run /opt/vendor/bin/drush.launcher migrate-import pup_subjects Processed 0 items (0 created, 0 updated, 0 failed, 0 ignored) - done with 'pup_subjects' Blackfire Run completed

Upon analyzing the Blackfire reports, we found some 50 unexpected SQL queries being triggered from somewhere within a PUPCSV::fetchNextRow() method. Quite surprising! PUPCSV refers to a migrate source plugin we wrote for fetching CSV files over FTP / SFTP. This plugin also tracks a hash of the CSV files and thereby allows us to skip a migration completely if the source files have not changed. If the source hash changes, the migration updates all rows and when the last row has been migrated, we store the file's hash in the database from PUPCSV::fetchNextRow(). As a matter of fact, we are preparing another article about creating custom migrate source plugin, so stay tuned.

We found one database query per row even though no record was being created or updated. Didn't seem to be very harmful until we saw the Blackfire report.

Code before Blackfire

Taking a closer look at the RemoteCSV::fetchNextRow() method, a call to MigrateSourceBase::count() was found. It was found that the count() method was taking 40% of processing time! This is because it was being called for every row in the CSV. Since the source/cache_counts parameter was not set to TRUE in the migration YAML files, the count() method was iterating over all items to get a fresh count for each call! Thus, for a migration with 40,000 records, we were going through 40,000 x 40,000 records and the PHP maximum execution time was being reached even before migrate could get to the last row! Here's a look at the code.

protected function fetchNextRow() { // If the migration is being imported... if (MigrationInterface::STATUS_IMPORTING === $this->migration->getStatus()) { // If we are at the last row in the CSV... if ($this->getIterator()->key() === $this->count()) { // Store source hash to remember the file as "imported". $this->saveCachedFileHash(); } } return parent::fetchNextRow(); }Code after Blackfire

We could have added the cache_counts parameter in our migration YAML files, but any change in the source configuration of the migrations would have made migrate API update all records in all migrations. This is because a row's hash is computed as something like hash($row + $source). We did not want migrate to update all records because we had certain migrations which sometimes took around 7 hours to complete. Hence, we decided to statically cache the total record count to get things back in track:

protected function fetchNextRow() { // If the migration is being imported... if (MigrationInterface::STATUS_IMPORTING === $this->migration->getStatus()) { // Get total source record count and cache it statically. static $count; if (is_null($count)) { $count = $this->doCount(); } // If we are at the last row in the CSV... if ($this->getIterator()->key() === $count) { // Store source hash to remember the file as "imported". $this->saveCachedFileHash(); } } return parent::fetchNextRow(); }Problem Solved. Merci Blackfire!

After the changes, we ran Blackfire again and found things to be 52% faster for a small migration with 50 records.

For a bigger migration with 4,359 records the migration import time reduced from 1m 47s to only 12s which means a 98% improvement. Asking why we didn't include the screenshot for the bigger migration? We did not (or rather could not) generate a report for the big migration because of two reasons:

  • While working, Blackfire stores function call and other information to memory. Running a huge migration with Blackfire might be a bit slow. Besides, our objective was to find the problem and we could do that more easily while looking at smaller figures.
  • When running a migration with thousands of rows, the migration functions are called over thousands of times! Blackfire collects data for each of these function calls, hence, the collected data sometimes becomes too heavy and Blackfire rejects the huge data payload with an error message like this:
The Blackfire API answered with a 413 HTTP error () Error detected during upload: The Blackfire API rejected your payload because it's too big.

Which makes a lot of sense. As a matter of fact, for the other case study given below, we used the --limit=1 parameter to profile code performance for a single row.

A quick brag about another 50% Improvement?

Apart from this jackpot, we also found room for another 50% improvement (from 7h to 3h 32m) for one of our migrations which was using the Touki FTP library. This migration was doing the following:

  • Going through around 11,000 records in a CSV file.
  • Downloading the files over FTP when required.

A Blackfire analysis of this migration revealed something strange. For every row, the following was happening behind the scenes:

  • If a file download was required, we were doing FTP::findFileByName($name).
  • To get the file, Touki was:
    • Getting a list of all files in the directory;
    • Creating File objects for every file;
    • For every file object, various permission, owner and other objects were created.
    • Passing all the files through a callback to see if it's name was $name.
    • If the name was matching, the file was returned and all other File objects were discarded.

Hence, for downloading every file, Touki FTP was creating 11,000 File objects of which it was only using one! To resolve this, we decided to use a lower-level FTP::get($source, $destination) method which helped us bypass all those 50,000 or more objects which were being created per record (approximately, 11,000 * 50,000 or more for all records). This almost halved the import time for that migration when working with all 11,000 records! Here's a screenshot of Blackfire's report for a single row.

So the next time you think something fishy is going on with code you wrote, don't forget to use use Blackfire! And don't forget to leave your feedback, questions and even article suggestions in the comments section below.

More about Blackfire

Blackfire is a code profiling tool for PHP which gives you nice-looking reports about your code's performance. With the help of these reports, you can analyze the memory, time and other resources consumed by various functions and optimize your code where necessary. If you are new to Blackfire, you can try these links:

Apart from all this, the paid version of Blackfire lets you set up automated tests and gives you various recommendations for not only Drupal but various other PHP frameworks.

Next Steps
  • Try Blackfire for free on a sample project of your choice to see what you can find.
  • Watch video tutorials on Blackfire's YouTube channel.
  • Read the tutorial on creating custom migration source plugins written by my colleague (coming soon).
+ more awesome articles by Evolving Web
Categories: Drupal

CSV Importer

New Drupal Modules - 8 November 2017 - 11:29am
Categories: Drupal

Midweek Snippets

Tabletop Gaming News - 8 November 2017 - 11:00am
It’s half-over, people. Actually, slightly moreso, since many of us get Friday off (thank you, Veterans). As we continue along, I’m feeling a bit peckish. Methinks I’ll nom on some bite-sized gaming stories. Today on the platter we have: Invasion X 1950s and 60s Sci Fi range released by Killer B Games, Acolyte Miniatures Releases […]
Categories: Game Theory & Design

The Enigma Box Game Up On Kickstarter

Tabletop Gaming News - 8 November 2017 - 10:00am
Calling The Enigma Box just a “game” is really selling it very short. It’s puzzles. It’s challenges. It’s connecting the dots in a broader narrative. It’s tabletop. It’s Augmented Reality. It uses the latest in technology like 3D printing and VR. It uses the most mundane such as a paper and pencil. And it’s not […]
Categories: Game Theory & Design

React Admin UI

New Drupal Modules - 8 November 2017 - 9:55am

All issues and development takes place on Github.

Setup

Ensure you have a moderately recent version of Node.js(8.9.1LTS) and Yarn installed. Currently the bundle is not included as this is under active development.

cd /js && yarn run build:js

Categories: Drupal

IDW Games Posts Designer Spotlight With Richard Launius About Planet of the Apes

Tabletop Gaming News - 8 November 2017 - 9:00am
Planet of the Apes is a fairly iconic movie. Even if you’ve not seen it, yourself, I can all-but guarantee you’ve seen countless things that were spin-offs or parodies of it. You’ve heard Charlton Heston talking about stinking paws and where they should or should not be, and maniacs blowing things up. Soon, you’ll be […]
Categories: Game Theory & Design

Lullabot: Styling the WYSIWYG Editor in Drupal 8

Planet Drupal - 8 November 2017 - 8:42am

Drupal 8 ships with a built-in WYSIWG editor called CKEditor. It’s great to have it included in core, but I had some questions about how to control the styling. In particular, I wanted the styling in the editor to look like my front-end theme, even though I use an administration theme for the node form. I spent many hours trying to find the answer, but it turned out to be simple if a little confusing.

In my example, I have a front-end theme called “Custom Theme” that extends the Bootstrap theme. I use core’s “Seven” theme as an administration theme, and I checked the box to use the administration theme for my node forms. 

My front end theme adds custom fonts to Bootstrap and uses a larger than normal font, so it’s distinctively different than the standard styling that comes with the WYSIWYG editor. 

Front End Styling undefined WYSIWYG Styling

Out of the box, the styling in the editor looks very different than my front-end theme. The font family and line height are wrong, and the font size is too small.

undefined

It turns out there are two ways to alter the styling in the WYSIWYG editor, adding some information to the default theme’s info.yml file, or implementing HOOK_ckeditor_css_alter() in either a module or in the theme. The kicker is that the info changes go in the FRONT END theme, even though I’m using an admin theme on the node form.

I added the following information to my default theme info file, custom_theme.info.yml. The font-family.css and style.css files are the front-end theme CSS files that I want to pass into the WYSIWYG editor. Even if I select the option to use the front-end theme for the node form, the CSS from that theme will not make it into the WYSIWYG editor without making this change, so this is necessary whether or not you use an admin theme on the node form!  

name: "Custom Theme" description: A subtheme of Bootstrap theme for Drupal 8. type: theme core: 8.x base theme: bootstrap ckeditor_stylesheets: - https://fonts.googleapis.com/css?family=Open+Sans - css/font-family.css - css/style.css libraries: ... WYSIWYG Styling

After this change, the font styles in the WYSIWYG editor match the text in the primary theme.

undefined

When CKEditor builds the editor iframe, it checks to see which theme is the default theme, then looks to see if that theme has values in the info.yml file for ckeditor_stylesheets. If it finds anything, it adds those CSS files to the iframe. Relative CSS file URLs are assumed to be files in the front-end theme’s directory, or you can use absolute URLs to other files.

The contributed Bootstrap module does not implement ckeditor_stylesheets, so I had to create a sub-theme to take advantage of this. I always create a sub-theme anyway, to add in the little tweaks I want to make. In this case, my sub-theme also uses a Google font instead of the default font, and I can also pass that font into the WYSIWYG editor.

TaDa!

That was easy to do, but it took me quite a while to understand how it worked. So I decided to post it here in case anyone else is as confused as I was.

More Information

To debug this further and understand how to impact the styling inside the WYSIWYG editor, you can refer to the relevant code from two files in core, ckeditor.module:  

/** * Retrieves the default theme's CKEditor stylesheets. * * Themes may specify iframe-specific CSS files for use with CKEditor by * including a "ckeditor_stylesheets" key in their .info.yml file. * * @code * ckeditor_stylesheets: * - css/ckeditor-iframe.css * @endcode */ function _ckeditor_theme_css($theme = NULL) { $css = []; if (!isset($theme)) { $theme = \Drupal::config('system.theme')->get('default'); } if (isset($theme) && $theme_path = drupal_get_path('theme', $theme)) { $info = system_get_info('theme', $theme); if (isset($info['ckeditor_stylesheets'])) { $css = $info['ckeditor_stylesheets']; foreach ($css as $key => $url) { if (UrlHelper::isExternal($url)) { $css[$key] = $url; } else { $css[$key] = $theme_path . '/' . $url; } } } if (isset($info['base theme'])) { $css = array_merge(_ckeditor_theme_css($info['base theme']), $css); } } return $css; }

and Plugin/Editor/CKEditor.php:  

/** * Builds the "contentsCss" configuration part of the CKEditor JS settings. * * @see getJSSettings() * * @param \Drupal\editor\Entity\Editor $editor * A configured text editor object. * @return array * An array containing the "contentsCss" configuration. */ public function buildContentsCssJSSetting(Editor $editor) { $css = [ drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css', drupal_get_path('module', 'system') . '/css/components/align.module.css', ]; $this->moduleHandler->alter('ckeditor_css', $css, $editor); // Get a list of all enabled plugins' iframe instance CSS files. $plugins_css = array_reduce($this->ckeditorPluginManager->getCssFiles($editor), function($result, $item) { return array_merge($result, array_values($item)); }, []); $css = array_merge($css, $plugins_css); $css = array_merge($css, _ckeditor_theme_css()); $css = array_map('file_create_url', $css); $css = array_map('file_url_transform_relative', $css); return array_values($css); }
Categories: Drupal

MQTT Integration

New Drupal Modules - 8 November 2017 - 8:30am

This module allows Drupal sites to connect to an MQTT broker. The module is still under development and as of now, only allows publish operations over TCP. Other protocols and operations will be added eventually.

Categories: Drupal

November Deals Happening At Warlord Games

Tabletop Gaming News - 8 November 2017 - 8:00am
The folks over at Warlord Games are looking to make some room in their warehouse. To facilitate that, they’re lowering prices on overstocked items and offering special bundle deals. Want to get some games and gaming magazines for cheaper than usual? Now’s your chance. And it’s not even Black Friday yet. From the post: Our […]
Categories: Game Theory & Design

Response Code Condition

New Drupal Modules - 8 November 2017 - 7:20am

Provides a condition to show or hide a block depending on the response code.

Categories: Drupal

Fantasy Flight Games Previews World-Building In Genesys

Tabletop Gaming News - 8 November 2017 - 7:00am
The Genesys RPG coming from Fantasy Flight Games can be used for any realm, any setting, any era, any location, any… anything, really. But with that blank canvass to start out with, it can be a bit to try and start with that first brush stroke. So… where do you begin? How does world-building happen […]
Categories: Game Theory & Design

Share & Embed Unity WebGL Games Just about Anywhere! - by Rocco Balsamo

Gamasutra.com Blogs - 8 November 2017 - 6:54am
I've built a website called SIMMER.io that makes sharing Unity WebGL games as easy as using YouTube. It's free too!
Categories: Game Theory & Design

The 3 Factors that Lead to Emergent Gameplay - by Josh Bycer

Gamasutra.com Blogs - 8 November 2017 - 6:52am
Emergent Gameplay is a concept that many games claim they have, but few do. In this post, we're going to examine some of the conditions that need to be in a game for emergent elements to be possible.
Categories: Game Theory & Design

Day 36 of 100 Days of VR: Getting Our FPS Game Running in VR - by Josh Chang

Gamasutra.com Blogs - 8 November 2017 - 6:51am
Today, we’re finally going to work in VR! We're going to try and convert our simple FPS into a VR experience that we run from our phone!
Categories: Game Theory & Design

Selecting by Weighted Angle - by E McNeill

Gamasutra.com Blogs - 8 November 2017 - 6:44am
A selection algorithm, especially useful for VR games that use laser pointer controls.
Categories: Game Theory & Design

Block in form

New Drupal Modules - 8 November 2017 - 6:26am

This module allows you to add content blocks in a Drupal entity form

Categories: Drupal

Privateer Press Previews February Releases

Tabletop Gaming News - 8 November 2017 - 6:00am
February is for lovers… Though I’m not quite sure that the Deathjack, Hexeris, or Bog Trogs are really the way I’d personify love. Be that as it may, it’s the sign of love the Privateer Press is giving you, as they’re getting new versions in that month. Have yourself a look. Crafted by a bokor […]
Categories: Game Theory & Design

Valuebound: Enabling custom web font in Drupal website

Planet Drupal - 8 November 2017 - 4:21am

This blog will walk you through one the contributed module in Drupal community that has been a heave of sigh for me whenever I was in trouble for web building activity. A couple of weeks back, I have been assigned a task where the requirement was to enable ‘Benton-sans Regular’ font throughout the site. Initially, I thought it would be an easy task and can be done easily. But I was wrong.

No issues! If you facing similar difficulties. Here, I am going to discuss how you can enable ‘Benton-sans Regular’ font seamlessly using Drupal font-your-face module.

Categories: Drupal

In All Their Looks & Words: The Tomb Of Niankhkhnum And Khnumhotep Part 1

Gnome Stew - 8 November 2017 - 3:00am

Engraving from the tomb of Niankhnum & Khnumhotep, taken from Flickr by user Kairoinfo4u and licensed through CC BY-NC-SA 2.0

Our first history lesson comes from Ancient Egypt, and may possibly be the very first historical record of same-sex relationships. It’s important to remember that just because it’s the first record we have does not make it the first to exist. People have been loving people of the same sex for longer than history has been a thing. Someone deciding to write something down doesn’t mean it hadn’t existed before. That said, this lesson comes to us from the Fifth Dynasty of Egypt, approximately 2400 BCE. It’s really old. It’s the tomb of Nianknkhnum  and Khnumhotep.

In 1964, Mounir Basta, an Egyptian archaeologist, opened a tomb in the Saqqara burial ground and discovered a unique display. Many tombs in the necropolis were burial chambers for prominent husband and wife couples and their families, but this tomb displayed two men in various displays, both holding an equal share of the scenery and often together in affectionate poses. After seeing the two men in intimate portraits, Basta’s argument was that these two men must be brothers, a father and son duo, or perhaps really good friends, because what other option could there be for such a loving duo? Just some best friends getting buried together in a room full of pictures of them holding each other, no big deal.

Engraving from the tomb of Niankhnum & Khnumhotep, taken from Flickr by user Kairoinfo4u and licensed through CC BY-NC-SA 2.0

It’s important to note that among the many scenes depicted on the walls of their tomb, and amid the various inscriptions in the tomb, not once is any mention of a biological relationship. What is contained on the walls is art which places the two men in various images that mimic scenes often used in husband/wife tombs from the same time period and geography. Decorations like a large statue of Nianknkhnum & Khnumhotep holding hands, dozens of images of them holding or supporting one another, and one scene where they enjoy the outdoors together, going fishing and bird hunting and sharing in daily activities. The most intimate of the images is at the entrance to their offering chamber, where they are shown nose to nose, kissing as their belt buckles touch, joining them at the waist. In some hieroglyphs, their names are joined in a wordplay that could suggest that they are now joined in death as they were in life. One inscription features a musician calling for a song about The Two Divine Brothers, possibly a reference to the myth of Horus and Set. Probably meant as a ribald reference to what was a rowdy tale about two male gods having a sexual encounter, this reference further supports the idea that Nianknkhnum & Khnumhotep were involved in a same-sex relationship with one another.

 The most intimate of the images is at the entrance to their offering chamber, where they are shown nose to nose, kissing as their belt buckles touch, joining them at the waist. Outside of what art survives the span of time, we don’t have much to go on for learning about these two men’s actual lives. They are listed in hieroglyphics as manicurists, hairdressers, and royal confidants. This means that they occupied a special position as one of very few people who could actually touch the Pharaoh. Tombs were extremely expensive, so the fact that these two shared one together meant that they were very powerful while alive. Both of their families were buried in the tomb with them, but the tomb was made specifically for these two. During the 4th through 6th dynasties, some experimentation was common with how tombs were displayed between husband and wife, speculation suggests that these two were able to take advantage of that experimentation to craft a lavish burial chamber for themselves.

Unfortunately, the theory of their relationship being one of desire between two men is still challenged within academia. Many scholars are hesitant to support a same-sex-affection reading of their tomb as the belief is that this interpretation may pull in a desire to read ancient culture in a modern lens. In other words, scholars think that a modern audience may try to pull in modern views on sexuality to artifacts that predate the idea of a “homosexual” by thousands of years. A challenge to that is being voiced by some that relationships between men and women in these artifacts are not expected to hold up to intense scrutiny. We just assume that men and women were involved romantically and sexually. Why must a relationship with two people of the same sex undergo such skepticism when the subjects in question follow the patterns of a different sex couple?

Use in your games

Knowing very little about both of these men may hamper your inclusion of them within your games, but there’s still many ways to incorporate them into some pretty popular RPGs out there. Thankfully we have a great number of words detailing their tomb, available online from many sources. It would take very little effort to overlay this floor plan on graph paper, jot down the art descriptions as some box text, and viola! Instant location to explore for your game. That’s probably not enough, though, so let’s look a little further.

Limestone relief from the tomb of Khnumhotep. Fifth Dynasty, about 2400 BC. From Saqqara. (British Museum)

If we are to take the hesitation of academia to embrace a same-sex reading of this tomb as a direct inspiration, we can play a little with what that does at the table. Investigative games like Gumshoe, Call of Cthulhu, or any campaign with detectives and high levels of roleplay would be good vehicles to delve into this. I’ve done some prep to come up with some locations, characters, and clues to build a conspiracy of an archaeologist trying to stem back this kind of reading and this prep will be included in the next installment of this series.

If you wanted to put the romance between these two men in the forefront, you might want to look at games that play with emotions & relationships. A common trope that could be useful here is that of parted lovers trying, or having struggles with trying, to reunite. In a romance setting that may mean a modern approach with Nianknkhnum & Khnumhotep as young men in a school setting. I’ve laid out some prep for Monsterhearts that could be applicable to other PbtA games as well. The new edition of Monsterhearts slimmed down prep but I still enjoy drafting up Menaces & Threats, so there’ll be a couple to be found in the next article.

Finally, if you’re just looking to smash & grab in a dungeon crawl, but at the same time introduce some cool historical context, you can use the burial chambers as a dungeon setting. I’ve prepped a Dungeon Starter for Dungeon World below as well.

As with anything historical in your games, it’s important to do some research before you hit the table, especially if you’re looking to address the subject material respectfully. Let this be the first article you read, and seek out other sources as well. Greg Reeder is an archaeologist who has written about the tomb and presented about these two men, I would suggest at least briefly glancing at his work. Most sources reference his work to some degree. I’ll provide some further reading opportunities below.

One other thing to think about is the context of same-sex desire in ancient cultures. I’ve deliberately not used the word “gay” to address the relationship between these two men. Homosexuality as it’s thought of in a modern construction is still a recent development. People even a couple centuries back did not define or express non-normative sexuality or orientations the same way we do today and it becomes a grey area when using modern speech to talk about ancient cultures. Ancient Egypt almost assuredly had a different view of same-sex desire than we do now.

It’s important to do some research before you hit the table, especially if you’re looking to address the subject material respectfully. Cultural relativity also becomes important because if we start to project modern ideas of sexuality onto the past it becomes easy to connect stereotypes or caricatures onto history. In an interview with the Dallas Morning News, Reeder expressed some hesitations with giving a modern audience some historical information, saying “people laugh when you say manicurists.” A stereotype of many gay men, especially of men of color, is one of a grooming confidant, but that’s only a superficial modern take on what these two men were. It will be vital to respectfully address their position within the royal structure and the important role they played in court without engaging in harmful modern stereotypes. In addition, the western lens on ancient Egypt is fraught with exotification. Avoid the “jewel of the desert” stereotypes, the best way to do this is with research and context. Challenge some of the mythology we’re fed as westerners and look for papers or histories written by Egyptians about their own history.

Check the next installment for the game prep deliverables I promised above!

Categories: Game Theory & Design

Pages

Subscribe to As If Productions aggregator