Repository: cognitiveailab/TextWorldExpress (language: python)

Issue:
Add new environments
This PR is one of two with the goal of bringing games from the `newgame1` branch into the main one.

This first PR brings the new environments: peckingorder, simonsays, and sorting.
This second PR will focus on the symbolic modules.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 329fd2f..c7e961e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ TextWorldExpress is a highly optimized reimplementation of three text game bench
 
 
 # Quickstart
-**Before running:** You will have to have `Java 1.8` installed on your system (shipped with most linux distributions).
+**Before running:** You will need `Java 1.8` installed on your system (shipped with most linux distributions).
 
 Install with pip:
 
@@ -67,6 +67,11 @@ them in the box”), and must then perform the arithmetic then take 18 apples an
 ### Sorting ("sorting")
 The Sorting environment requires agents to pick objects and place them in an answer box one at a time based on order of increasing quantity. To add complexity to the game, quantities optionally include units (e.g. 5kg of copper, 8mg of steel, 2g of iron) across measures of volume, mass, or length. The score is the normalized proportion of objects sorted in the correct order, where perfect sorts receive a score of 1.0, and errors cause the score to revert to zero and the game to end. The number of objects to sort is between 3 and 5. At the moment, task complexity cannot be controlled yet.
 
+### Simon Says ("simonsays")
+The Simon Says environment is rather simple and it is mainly used as a sanity check for learning agents. The task is to repeat exactly the action requested of you. Task complexity can be controlled by varying the game length, the number of distractor actions, and whether the agent has to memorize the entire action sequence at its first step.
+
+### Pecking Order ("peckingorder")
+The Pecking Order environment is rather simple and it is mainly used as a sanity check for learning agents. The task is to read and follow the instructions found in the environment until game completion. At the moment, the task complexity cannot be controlled.
 
 # Usage
 
@@ -129,7 +134,7 @@ Environments initialize with default parameters.  To change the parameters, supp
 | Parameter      | Description | Valid range |
 | ----------- | ----------- |  ----------- |
 | numLocations        | The number of locations in the environment  | 1-11 |
-| numDistractorItems   | The number of distractor (i.e. non-coin) items in the environment    | 0-10 |
+| numDistractorItems  | The number of distractor (i.e. non-coin) items in the environment    | 0-10 |
 | includeDoors        | Whether rooms have doors that need to be opened    | 0 or 1 |
 | limitInventorySize  | Whether the size of the inventory is limited       | 0 or 1 |
 
@@ -148,6 +153,16 @@ This environment has no tweakable parameters yet.
 **Sorting:**
 This environment has no tweakable parameters yet.
 
+**Simon Says:**
+| Parameter      | Description | Valid range |
+| ----------- | ----------- |  ----------- |
+| gameLength      | The number of actions to repeat in order  | 1-10 |
+| numDistractors  | The number of irrelevant actions    | 0-10 |
+| memorization    | Whether the entire sequence of actions is shown at the first step (and only then)     | 0 or 1 |
+
+**Pecking Order:**
+This environment has no tweakable parameters yet.
+
 **Querying current game parameters:** Sometimes you may want to know what parameters the current game is generated with.  These can be queried using the `getGenerationProperties()` method:
 ```python
 print("Generation properties: " + str(env.getGenerationProperties()))
diff --git a/examples/human.py b/examples/human.py
index 68b7b1c..83b9684 100644
--- a/examples/human.py
+++ b/examples/human.py
@@ -50,13 +50,13 @@ def userConsole(args):
     print("Selected Seed: " + str(gameSeed))
     print("Generation properties: " + str(env.getGenerationProperties()) )
 
+    # Initialize a random `gameName` game.
+    obs, infos = env.reset(seed=gameSeed, gameFold=gameFold, generateGoldPath=generateGoldPath)
+
     # Task description
     print("Task Description: " + env.getTaskDescription())
     print("")
 
-    # Initialize a random `gameName` game.
-    obs, infos = env.reset(seed=gameSeed, gameFold=gameFold, generateGoldPath=generateGoldPath)
-
     if (generateGoldPath == True):
         print("Gold path: " + str(env.getGoldActionSequence()))
 
diff --git a/simulator/src/main/scala/textworldexpress/EntryPoint.scala b/simulator/src/main/scala/textworldexpress/EntryPoint.scala
index 89da8c6..82c8348 100644
--- a/simulator/src/main/scala/textworldexpress/EntryPoint.scala
+++ b/simulator/src/main/scala/textworldexpress/EntryPoint.scala
@@ -1,8 +1,7 @@
 package textworldexpress
 
-import textworldexpress.games.{CoinGame, CoinGameGenerator, KitchenGame, KitchenGameGenerator, TWCGame, TWCGameGenerator}
 import textworldexpress.generator.GameGenerator
-import textworldexpress.goldagent.KitchenGoldAgent
+import textworldexpress.goldagent.CookingWorldGoldAgent
 import textworldexpress.objects.{Counter, FastObject, Fridge, Room}
 import textworldexpress.struct.TextGame
 
diff --git a/simulator/src/main/scala/textworldexpress/EntryPointCloned.scala b/simulator/src/main/scala/textworldexpress/EntryPointCloned.scala
index e9f0ca4..b7215f0 100644
--- a/simulator/src/main/scala/textworldexpress/EntryPointCloned.scala
+++ b/simulator/src/main/scala/textworldexpress/EntryPointCloned.scala
@@ -1,9 +1,9 @@
 package textworldexpress
 
 
-import textworldexpress.games.{CoinGame, CoinGameGenerator, KitchenGame, KitchenGameGenerator, TWCGame, TWCGameGenerator}
+import textworldexpress.games.{CoinGame, CoinGameGenerator, CookingWorldGame, CookingWorldGameGenerator, TWCGame, TWCGameGenerator}
 import textworldexpress.generator.GameGenerator
-import textworldexpress.goldagent.KitchenGoldAgent
+import textworldexpress.goldagent.CookingWorldGoldAgent
 import textworldexpress.objects.{Counter, FastObject, Fridge, Room}
 import textworldexpress.struct.TextGame
 
diff --git a/simulator/src/main/scala/textworldexpress/data/TWKitchenObject.scala b/simulator/src/main/scala/textworldexpress/data/CookingWorldObject.scala
similarity index 88%
rename from simulator/src/main/scala/textworldexpress/data/TWKitchenObject.scala
rename to simulator/src/main/scala/textworldexpress/data/CookingWorldObject.scala
index 464a6ba..6eba0eb 100644
--- a/simulator/src/main/scala/textworldexpress/data/TWKitchenObject.scala
+++ b/simulator/src/main/scala/textworldexpress/data/CookingWorldObject.scala
@@ -7,7 +7,7 @@ import scala.collection.mutable.ArrayBuffer
 import scala.io.Source
 import scala.util.Random
 
-case class TWKitchenObject(name:String, indefinite:String, properties:Set[String], locations:Array[String]) {
+case class CookingWorldObject(name:String, indefinite:String, properties:Set[String], locations:Array[String]) {
 
   // Convert to a FastObject
   def toFastObject():FastObject = {
@@ -61,14 +61,14 @@ case class RecipeIngredient(name:String, preparation:Set[String]) {
 }
 
 // Loads the data from TWC Kitchen
-class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILENAME) {
+class LoadCookingWorldDataJSON(filename:String = LoadCookingWorldDataJSON.DEFAULT_FILENAME) {
 
   val (allObjs, lutObj, lutLocation, foodSplitsTrain, foodSplitsDev, foodSplitsTest, foodPrepTrain, foodPrepDev, foodPrepTest) = this.load(filename)
 
   /*
    * Getters
    */
-  def getObjByName(name:String):TWKitchenObject = {
+  def getObjByName(name:String):CookingWorldObject = {
     return lutObj(name)
   }
 
@@ -78,7 +78,7 @@ class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILE
     if (lutObj.contains(name)) return lutObj(name).toFastObject()
     // Some objects don't exist, so make a default one
     // TODO: SLOW?
-    val obj = new TWKitchenObject(name, indefinite = "", properties = Set("raw", "uncut", "cuttable", "cookable", "edible"), locations = Array("counter", "shelf", "kitchen cupboard"))
+    val obj = new CookingWorldObject(name, indefinite = "", properties = Set("raw", "uncut", "cuttable", "cookable", "edible"), locations = Array("counter", "shelf", "kitchen cupboard"))
     return obj.toFastObject()
      */
   }
@@ -132,7 +132,7 @@ class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILE
    */
 
   // Load the JSON file, and convert to storage classes referenced by look-up-tables.
-  def load(filenameIn:String):(Array[TWKitchenObject], Map[String, TWKitchenObject], Map[String, ArrayBuffer[TWKitchenObject]], Set[String], Set[String], Set[String], Map[String, Array[Set[String]]], Map[String, Array[Set[String]]], Map[String, Array[Set[String]]]) = {
+  def load(filenameIn:String):(Array[CookingWorldObject], Map[String, CookingWorldObject], Map[String, ArrayBuffer[CookingWorldObject]], Set[String], Set[String], Set[String], Map[String, Array[Set[String]]], Map[String, Array[Set[String]]], Map[String, Array[Set[String]]]) = {
     // Step 1: Load the TextWorld Common Sense object file
     val jsonString = Source.fromFile(filenameIn).getLines.mkString
     val dataRaw = ujson.read(jsonString).value.asInstanceOf[mutable.LinkedHashMap[String, Any]]
@@ -151,7 +151,7 @@ class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILE
     //println ("foodsCompact: " + foodsCompact)
 
     // Step 2A: Objects (foods_compact): Convert from the JSON format to an internal storage class (TWCObject)
-    val out = new ArrayBuffer[TWKitchenObject]()
+    val out = new ArrayBuffer[CookingWorldObject]()
     for (key <- foodsCompact.keySet) {
       val record = foodsCompact(key).asInstanceOf[ujson.Obj].value
       //println("key: " + key + "   " + record.toString())
@@ -170,11 +170,11 @@ class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILE
 
       if (alternateNames.length == 0) {
         // Single name
-        out.append( new TWKitchenObject(name, indefinite, properties, locations) )
+        out.append( new CookingWorldObject(name, indefinite, properties, locations) )
       } else {
         // This object has multiple names
         for (altName <- alternateNames) {
-          out.append(new TWKitchenObject(altName, indefinite, properties, locations))
+          out.append(new CookingWorldObject(altName, indefinite, properties, locations))
         }
       }
       //println("obj: " + obj.toString())
@@ -187,16 +187,16 @@ class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILE
      */
 
     // Step 2A: Make object look-up-table
-    val lutObj = mutable.Map[String, TWKitchenObject]()
+    val lutObj = mutable.Map[String, CookingWorldObject]()
     for (obj <- out) {
       lutObj(obj.name) = obj
     }
 
     // Step 2B: Make location look-up-table
-    val lutLocation = mutable.Map[String, ArrayBuffer[TWKitchenObject]]()
+    val lutLocation = mutable.Map[String, ArrayBuffer[CookingWorldObject]]()
     for (obj <- out) {
       for (location <- obj.locations) {
-        if (!lutLocation.contains(location)) lutLocation(location) = new ArrayBuffer[TWKitchenObject]()
+        if (!lutLocation.contains(location)) lutLocation(location) = new ArrayBuffer[CookingWorldObject]()
         lutLocation(location).append(obj)
       }
     }
@@ -265,12 +265,12 @@ class LoadTWKitchenDataJSON(filename:String = LoadTWKitchenDataJSON.DEFAULT_FILE
 }
 
 
-object LoadTWKitchenDataJSON {
+object LoadCookingWorldDataJSON {
   val DEFAULT_FILENAME = "cooking_world.json"
 
   def main(args:Array[String]): Unit = {
-    val d = new LoadTWKitchenDataJSON()
+    val d = new LoadCookingWorldDataJSON()
     println ("Loaded " + d.allObjs.length + " objects")
   }
 
-}
\ No newline at end of file
+}
diff --git a/simulator/src/main/scala/textworldexpress/games/ArithmeticGame.scala b/simulator/src/main/scala/textworldexpress/games/ArithmeticGame.scala
index 09c521d..4c2ff86 100644
--- a/simulator/src/main/scala/textworldexpress/games/ArithmeticGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/ArithmeticGame.scala
@@ -1,6 +1,6 @@
 package textworldexpress.games
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON, MathProblemGenerator}
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON, MathProblemGenerator}
 import textworldexpress.goldagent.{ArithmeticGoldAgent, CoinGoldAgent}
 import textworldexpress.objects.{Backyard, Bathroom, Bedroom, Box, BundleOfObjects, Coin, Corridor, DoorMaker, Driveway, FastObject, Kitchen, LaundryRoom, LivingRoom, MathProblem, Pantry, Room, Street, Supermarket}
 import textworldexpress.preprocessing.ArithmeticProblem
@@ -556,7 +556,7 @@ class ArithmeticGame(val locations:Array[Room], val mathProblemObj:MathProblem,
 
 class ArithmeticGameGenerator {
   val TWCObjectDatabase = new LoadTWCDataJSON()
-  val TWKitchenObjectDatabase = new LoadTWKitchenDataJSON()
+  val CookingWorldObjectDatabase = new LoadCookingWorldDataJSON()
   val doorMaker = new DoorMaker()
 
 
@@ -688,7 +688,7 @@ class ArithmeticGameGenerator {
         //println("location: " + location.name)
 
         val distractorItem = TWCObjectDatabase.mkRandomObjectByLocation(r, container.name, fold)
-        //val distractorItem = TWKitchenObjectDatabase.mkRandomObjectByLocation(r, container.name)
+        //val distractorItem = CookingWorldObjectDatabase.mkRandomObjectByLocation(r, container.name)
         if (distractorItem.isDefined) {
           if (!objectNamesAdded.contains(distractorItem.get.name)) {
             container.addObject(distractorItem.get)
diff --git a/simulator/src/main/scala/textworldexpress/games/CoinGame.scala b/simulator/src/main/scala/textworldexpress/games/CoinGame.scala
index 504f126..5fe776c 100644
--- a/simulator/src/main/scala/textworldexpress/games/CoinGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/CoinGame.scala
@@ -1,7 +1,7 @@
 package textworldexpress.games
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON, RecipeIngredient}
-import textworldexpress.goldagent.{CoinGoldAgent, KitchenGoldAgent}
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON, RecipeIngredient}
+import textworldexpress.goldagent.{CoinGoldAgent, CookingWorldGoldAgent}
 import textworldexpress.objects.{Backyard, Bathroom, Bedroom, Coin, Cookbook, Corridor, DoorMaker, Driveway, FastObject, Kitchen, LaundryRoom, LivingRoom, Meal, Pantry, Room, Street, Supermarket}
 import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
 
@@ -539,7 +539,7 @@ class CoinGame(val locations:Array[Room], val taskObjects:ArrayBuffer[FastObject
 
 class CoinGameGenerator {
   val TWCObjectDatabase = new LoadTWCDataJSON()
-  val TWKitchenObjectDatabase = new LoadTWKitchenDataJSON()
+  val CookingWorldObjectDatabase = new LoadCookingWorldDataJSON()
   val doorMaker = new DoorMaker()
 
 
@@ -609,7 +609,7 @@ class CoinGameGenerator {
         //println("location: " + location.name)
 
         val distractorItem = TWCObjectDatabase.mkRandomObjectByLocation(r, container.name, fold)
-        //val distractorItem = TWKitchenObjectDatabase.mkRandomObjectByLocation(r, container.name)
+        //val distractorItem = CookingWorldObjectDatabase.mkRandomObjectByLocation(r, container.name)
         if (distractorItem.isDefined) {
           if (!objectNamesAdded.contains(distractorItem.get.name)) {
             container.addObject(distractorItem.get)
diff --git a/simulator/src/main/scala/textworldexpress/games/KitchenGame.scala b/simulator/src/main/scala/textworldexpress/games/CookingWorldGame.scala
similarity index 96%
rename from simulator/src/main/scala/textworldexpress/games/KitchenGame.scala
rename to simulator/src/main/scala/textworldexpress/games/CookingWorldGame.scala
index 75eb750..b237781 100644
--- a/simulator/src/main/scala/textworldexpress/games/KitchenGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/CookingWorldGame.scala
@@ -1,7 +1,7 @@
 package textworldexpress.games
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON, RecipeIngredient}
-import textworldexpress.goldagent.KitchenGoldAgent
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON, RecipeIngredient}
+import textworldexpress.goldagent.CookingWorldGoldAgent
 import textworldexpress.objects.{Backyard, Bathroom, Bedroom, Cookbook, Corridor, Counter, DoorMaker, Driveway, FastObject, Kitchen, Knife, LaundryRoom, LivingRoom, Meal, Pantry, Room, Street, Supermarket}
 import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
 
@@ -12,7 +12,7 @@ import scala.util.control.Breaks._
 
 
 // 'recipe' and 'taskObjects' are parallel arrays representing the same objects
-class KitchenGameScoring(val recipe:ArrayBuffer[RecipeIngredient], val taskObjects:ArrayBuffer[FastObject]) extends Scorer {
+class CookingWorldGameScoring(val recipe:ArrayBuffer[RecipeIngredient], val taskObjects:ArrayBuffer[FastObject]) extends Scorer {
   var maxScoreFromPrep:Double = 0.0
   var ingredientsFound:Array[Boolean] = Array.fill(taskObjects.length) { false }
 
@@ -103,7 +103,7 @@ class KitchenGameScoring(val recipe:ArrayBuffer[RecipeIngredient], val taskObjec
 
 }
 
-class KitchenGame(val locations:Array[Room], val recipe:ArrayBuffer[RecipeIngredient], val taskObjects:ArrayBuffer[FastObject], limitInventorySize:Boolean, val seed:Long = 0, val generationProperties:Map[String, Int]) extends TextGame {
+class CookingWorldGame(val locations:Array[Room], val recipe:ArrayBuffer[RecipeIngredient], val taskObjects:ArrayBuffer[FastObject], limitInventorySize:Boolean, val seed:Long = 0, val generationProperties:Map[String, Int]) extends TextGame {
 
   // Inventory
   var agentInventory = new FastObject("inventory")
@@ -115,7 +115,7 @@ class KitchenGame(val locations:Array[Room], val recipe:ArrayBuffer[RecipeIngred
   val deletedObjects = new ArrayBuffer[FastObject]()
 
   // Scorer
-  val scorer = new KitchenGameScoring(recipe, taskObjects)
+  val scorer = new CookingWorldGameScoring(recipe, taskObjects)
   var meal:Option[FastObject] = None    // Prepared meal (for scoring)
 
   // A list of the most recently generated valid actions (for step() )
@@ -135,7 +135,7 @@ class KitchenGame(val locations:Array[Room], val recipe:ArrayBuffer[RecipeIngred
   def deepCopy():TextGame = {
     println ("TODO: Cloning not implemented -- creating shallow copy.")
 
-    return new KitchenGame(locations, recipe, taskObjects, this.limitInventorySize, seed=this.seed, this.generationProperties)
+    return new CookingWorldGame(locations, recipe, taskObjects, this.limitInventorySize, seed=this.seed, this.generationProperties)
     /*
     val clonedTaskObjects = new ArrayBuffer[FastObject]
 
@@ -151,7 +151,7 @@ class KitchenGame(val locations:Array[Room], val recipe:ArrayBuffer[RecipeIngred
     // Step 3: Create new game
     val recipeCloned = new ArrayBuffer[RecipeIngredient]
     recipeCloned.insertAll(0, recipe)
-    val game = new KitchenGame(locationsClone, recipeCloned, clonedTaskObjects, this.limitInventorySize, seed = this.seed)
+    val game = new CookingWorldGame(locationsClone, recipeCloned, clonedTaskObjects, this.limitInventorySize, seed = this.seed)
 
     // Also clone the agent inventory
     game.agentInventory = this.agentInventory.deepCopy(existingTaskObjects = taskObjects, copyTaskObjects = clonedTaskObjects)
@@ -747,9 +747,9 @@ class KitchenGame(val locations:Array[Room], val recipe:ArrayBuffer[RecipeIngred
 }
 
 
-class KitchenGameGenerator {
+class CookingWorldGameGenerator {
   val TWCObjectDatabase = new LoadTWCDataJSON()
-  val TWKitchenObjectDatabase = new LoadTWKitchenDataJSON()
+  val CookingWorldObjectDatabase = new LoadCookingWorldDataJSON()
   val doorMaker = new DoorMaker()
 
 
@@ -790,7 +790,7 @@ class KitchenGameGenerator {
 
 
     // Randomly generate recipe
-    val recipeIngredients = TWKitchenObjectDatabase.mkRandomRecipe(r, numIngredients, fold)
+    val recipeIngredients = CookingWorldObjectDatabase.mkRandomRecipe(r, numIngredients, fold)
 
     // Add recipe ingredients to environment
     val taskObjects = this.addIngredientItems(r, locations, recipeIngredients)
@@ -876,8 +876,8 @@ class KitchenGameGenerator {
     // Place the recipe ingredients
     for (i <- 0 until recipeIngredients.length) {
       val ingredient = recipeIngredients(i)
-      //println (TWKitchenObjectDatabase.lutObj.keySet.toArray.sorted.mkString(", "))
-      val recipeItem = TWKitchenObjectDatabase.mkFastObjectByName(ingredient.name)
+      //println (CookingWorldObjectDatabase.lutObj.keySet.toArray.sorted.mkString(", "))
+      val recipeItem = CookingWorldObjectDatabase.mkFastObjectByName(ingredient.name)
       val validLocations = recipeItem.canonicalLocations
 
       // Try to place the object in a random location
@@ -935,7 +935,7 @@ class KitchenGameGenerator {
         //println("location: " + location.name)
 
         //val distractorItem = TWCObjectDatabase.mkRandomObjectByLocation(r, container.name)
-        val distractorItem = TWKitchenObjectDatabase.mkRandomObjectByLocation(r, container.name)
+        val distractorItem = CookingWorldObjectDatabase.mkRandomObjectByLocation(r, container.name)
         if (distractorItem.isDefined) {
           if (!objectNamesAdded.contains(distractorItem.get.name)) {
             container.addObject(distractorItem.get)
@@ -1190,7 +1190,7 @@ class KitchenGameGenerator {
   }
 
 
-  def mkGame(seed:Long, numLocations:Int = 12, numDistractorItems:Int = 10, numIngredients:Int = 3, includeDoors:Boolean = true, limitInventorySize:Boolean = true, fold:String = "train"):KitchenGame = {
+  def mkGame(seed:Long, numLocations:Int = 12, numDistractorItems:Int = 10, numIngredients:Int = 3, includeDoors:Boolean = true, limitInventorySize:Boolean = true, fold:String = "train"):CookingWorldGame = {
     // Store properties in a form that are user accessible later on
     val props = mutable.Map[String, Int]()
     props("seed") = seed.toInt
@@ -1204,13 +1204,13 @@ class KitchenGameGenerator {
     // Generate Game
     val r = new Random(seed)
     val (locations, recipe, taskObjects) = mkEnvironment(r, numLocations, numDistractorItems, numIngredients, includeDoors, fold)
-    val game = new KitchenGame( locations.toArray, recipe, taskObjects, limitInventorySize, generationProperties = props.toMap )
+    val game = new CookingWorldGame( locations.toArray, recipe, taskObjects, limitInventorySize, generationProperties = props.toMap )
 
     return game
   }
 
 
-  def mkGameWithGoldPath(seed:Long, numLocations:Int = 12, numDistractorItems:Int = 10, numIngredients:Int = 3, includeDoors:Boolean = true, limitInventorySize:Boolean = true, fold:String = "train"):(KitchenGame, Array[String]) = {
+  def mkGameWithGoldPath(seed:Long, numLocations:Int = 12, numDistractorItems:Int = 10, numIngredients:Int = 3, includeDoors:Boolean = true, limitInventorySize:Boolean = true, fold:String = "train"):(CookingWorldGame, Array[String]) = {
     val MAX_ATTEMPTS:Int = 50
     val rg = new Random(seed)
 
@@ -1219,7 +1219,7 @@ class KitchenGameGenerator {
     breakable {
       while (attempts < MAX_ATTEMPTS) {
         val game = this.mkGame(seed, numLocations, numDistractorItems, numIngredients, includeDoors, limitInventorySize, fold)
-        val goldAgent = new KitchenGoldAgent(game)
+        val goldAgent = new CookingWorldGoldAgent(game)
         val (success, _goldPath) = goldAgent.mkGoldPath(rg)
         if (success) goldPath = _goldPath
 
diff --git a/simulator/src/main/scala/textworldexpress/games/MapReaderConstraintsGame.scala b/simulator/src/main/scala/textworldexpress/games/MapReaderConstraintsGame.scala
index 370d3a9..c87169d 100644
--- a/simulator/src/main/scala/textworldexpress/games/MapReaderConstraintsGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/MapReaderConstraintsGame.scala
@@ -1,6 +1,6 @@
 package textworldexpress.games
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON}
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON}
 import textworldexpress.goldagent.{CoinGoldAgent, MapReaderConstraintsGoldAgent}
 import textworldexpress.objects.{Alley, Backyard, Bathroom, Bedroom, Box, Coin, Corridor, DoorMaker, Driveway, FastObject, Foyer, Garage, Kitchen, LaundryRoom, LivingRoom, Mapbook, Pantry, Room, Sideyard, Street, Supermarket}
 import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
@@ -500,7 +500,7 @@ class MapReaderConstraintsGame(val locations:Array[Room], val taskObjects:ArrayB
 
 class MapReaderConstraintsGameGenerator {
   val TWCObjectDatabase = new LoadTWCDataJSON()
-  val TWKitchenObjectDatabase = new LoadTWKitchenDataJSON()
+  val CookingWorldObjectDatabase = new LoadCookingWorldDataJSON()
   val doorMaker = new DoorMaker()
 
 
@@ -742,7 +742,7 @@ class MapReaderConstraintsGameGenerator {
         //println("location: " + location.name)
 
         val item = TWCObjectDatabase.mkRandomObjectByLocation(r, container.name, fold)
-        //val distractorItem = TWKitchenObjectDatabase.mkRandomObjectByLocation(r, container.name)
+        //val distractorItem = CookingWorldObjectDatabase.mkRandomObjectByLocation(r, container.name)
         if (item.isDefined) {
           //println ("Item: " + item.get.name)
 
diff --git a/simulator/src/main/scala/textworldexpress/games/MapReaderGame.scala b/simulator/src/main/scala/textworldexpress/games/MapReaderGame.scala
index d941351..83d693e 100644
--- a/simulator/src/main/scala/textworldexpress/games/MapReaderGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/MapReaderGame.scala
@@ -1,7 +1,7 @@
 package textworldexpress.games
 
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON}
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON}
 import textworldexpress.goldagent.{MapReaderGoldAgent}
 import textworldexpress.objects.{Box, Coin, DoorMaker, FastObject, Mapbook, Room}
 import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
@@ -502,7 +502,7 @@ class MapReaderGame(val locations:Array[Room], val taskObjects:ArrayBuffer[FastO
 
 class MapReaderGameGenerator {
   val TWCObjectDatabase = new LoadTWCDataJSON()
-  val TWKitchenObjectDatabase = new LoadTWKitchenDataJSON()
+  val CookingWorldObjectDatabase = new LoadCookingWorldDataJSON()
   val doorMaker = new DoorMaker()
 
 
@@ -770,7 +770,7 @@ class MapReaderGameGenerator {
         //println("location: " + location.name)
 
         val item = TWCObjectDatabase.mkRandomObjectByLocation(r, container.name, fold)
-        //val distractorItem = TWKitchenObjectDatabase.mkRandomObjectByLocation(r, container.name)
+        //val distractorItem = CookingWorldObjectDatabase.mkRandomObjectByLocation(r, container.name)
         if (item.isDefined) {
           //println ("Item: " + item.get.name)
 
diff --git a/simulator/src/main/scala/textworldexpress/games/PeckingOrderGame.scala b/simulator/src/main/scala/textworldexpress/games/PeckingOrderGame.scala
new file mode 100644
index 0000000..bff4144
--- /dev/null
+++ b/simulator/src/main/scala/textworldexpress/games/PeckingOrderGame.scala
@@ -0,0 +1,649 @@
+package textworldexpress.games
+
+import textworldexpress.goldagent.PeckingOrderGoldAgent
+import textworldexpress.objects.{Backyard, Bathroom, Bedroom, Box, BundleOfObjects, Coin, Corridor, DoorMaker, Driveway, FastObject, Instructions, Kitchen, LaundryRoom, LivingRoom, MathProblem, Pantry, Room, Street, Supermarket}
+import textworldexpress.preprocessing.ArithmeticProblem
+import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
+
+import scala.collection.mutable
+import scala.collection.mutable.{ArrayBuffer, ListBuffer}
+import scala.util.Random
+import scala.util.control.Breaks.{break, breakable}
+
+
+
+class PeckingOrderScoring(val objectOrder:Array[FastObject], val inventory:FastObject) extends Scorer {
+
+  def doScoring(): Unit = {
+    // Check status of each object
+    var curScore:Double = 0
+    var taskFailure:Boolean = false
+    var taskSuccess:Boolean = false
+
+
+    // Check how many items are in the inventory
+    val numItems = inventory.contents.size
+    // If the number of items in the inventory is greater than the number of task items, then there is too much in the inventory -- failure
+    if (numItems > objectOrder.length) {
+      val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+      this.curScore = scores
+      return
+    }
+
+    // Check that the first 'numItems' items are in the inventory
+    for (i <- 0 until numItems) {
+      if (objectOrder(i).currentContainer.name == "inventory") {
+        // In the correct spot
+        curScore += 1
+      } else {
+        // Task failure
+        val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+        this.curScore = scores
+        return
+      }
+    }
+
+    // If we failed the task, then set the score to -1, regardless of progress
+    if (taskFailure) {
+      val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+      this.curScore = scores
+      return
+    }
+
+    // If we're at the maximum score, set the task success to be true
+    if (curScore >= this.calculateMaxScore()) {
+      taskSuccess = true
+    }
+
+    // Store current scores in 'this.curScore', for the accessor in the base class
+    val normalizedScore = curScore/maxScore
+    val scores = new GameScore(scoreRaw = curScore, scoreNormalized = normalizedScore, taskSuccess = taskSuccess, taskFailure = taskFailure)
+    this.curScore = scores
+  }
+
+  // Calculate the maximum possible score for this recipe
+  def calculateMaxScore():Double = {
+    var maxScore:Double = this.objectOrder.size.toDouble
+    return maxScore
+  }
+
+}
+
+
+
+class PeckingOrderGame(val locations:Array[Room], val objectOrder:Array[FastObject], val instructionBook:Instructions, val seed:Long = 0, val generationProperties:Map[String, Int]) extends TextGame {
+
+  // Inventory
+  var agentInventory = new FastObject("inventory")
+
+  // Initial location
+  var agentLocation:Room = locations(0)
+
+  // Objects that have been deleted from the environment, but whose status should still be tracked
+  val deletedObjects = new ArrayBuffer[FastObject]()
+
+  // Scorer
+  val scorer:Scorer = new PeckingOrderScoring(objectOrder, this.agentInventory)
+
+  // A list of the most recently generated valid actions (for step() )
+  var lastValidActions = ListBuffer.empty[(String, Int, Array[FastObject])]
+
+  // The action/observation history
+  var history = new ArrayBuffer[ActionHistory]
+
+  // Maximum capacity of the inventory (if limitInventorySize is enabled)
+  val inventoryMaxCapacity = 100    // Essentially unlimited
+
+  // Internal game random number generator -- primarily for randomizing valid action list.
+  val random = new Random(seed)
+
+  /*
+   * Cloning
+   */
+
+  // TODO: Not implemented
+  def deepCopy():PeckingOrderGame = {
+    println ("NOTE: deepCopy() not implemented -- returning a shallow copy")
+    // Return
+    return new PeckingOrderGame(locations, objectOrder, instructionBook, seed, generationProperties)
+  }
+
+
+
+  /*
+   * Generation Properties
+   */
+
+  def getGenerationProperties():Map[String, Int] = return this.generationProperties
+
+
+  /*
+   * Task Description
+   */
+  def getTaskDescription():String = {
+    return "Your task is to read and follow the instructions book found in the environment. Repeat this until the game is completed."
+  }
+
+  /*
+   *  Action helpers
+   */
+
+  // Get visible objects in the environment from the agent's current location (EXCLUDING inventory)
+  def getVisibleObjects():ListBuffer[FastObject] = {
+    agentLocation.collectVisibleObjects()
+  }
+
+  def getCurrentRoomDescription():String = {
+    return this.agentLocation.getDescription()
+  }
+
+  /*
+   * History
+   */
+  def getHistory():ArrayBuffer[ActionHistory] = {
+    return this.history
+  }
+
+  /*
+   * Score
+   */
+  // Returns (score, taskSuccess, taskFailure)
+  def getScore():GameScore = this.scorer.getCurrentScore()
+
+  /*
+   * Actions
+   */
+
+  def actionTake(params:Array[FastObject]):String = this.actionTake(params(0))
+  def actionTake(obj:FastObject):String = {
+    val numItemsInInventory = this.agentInventory.contents.size
+    if (numItemsInInventory >= inventoryMaxCapacity) {
+      return "Your inventory currently has " + numItemsInInventory + " items, and is full.  You can't pick up another item. "
+    }
+
+    // Step 1: remove the object from it's current container
+    obj.removeFromCurrentContainer()
+    // Step 2: Add to the agent inventory
+    agentInventory.addObject(obj)
+
+    return "You take the " + obj.name + "."
+  }
+
+  def actionPutIn(params:Array[FastObject]):String = this.actionPutIn(params(0), params(1))
+  def actionPutIn(invObject:FastObject, newContainer:FastObject):String = {
+    // Step 1: Remove the object from it's current container
+    invObject.removeFromCurrentContainer()
+    // Step 2: Add to the new container
+    newContainer.addObject(invObject)
+
+    return "You put the " + invObject.name + " in the " + newContainer.name + "."
+  }
+
+  def actionOpenContainer(params:Array[FastObject]):String = this.actionOpenContainer(params(0))
+  def actionOpenContainer(obj:FastObject):String = {
+    obj.isOpen = true
+
+    if (obj.contents.isEmpty) {
+      return  "You open the " + obj.name + ". It's empty inside."
+    }
+
+    val os = new StringBuilder
+    os.append("You open the " + obj.name + ". ")
+    os.append("The " + obj.name + " contains ")
+    os.append(obj.contents.map(_.getDescription()).mkString(", ") + ".")
+
+    return os.toString()
+
+  }
+
+  def actionCloseContainer(params:Array[FastObject]):String = this.actionCloseContainer(params(0))
+  def actionCloseContainer(obj:FastObject):String = {
+    obj.isOpen = false
+
+    return "You close the " + obj.name + "."
+  }
+
+  def actionEat(params:Array[FastObject]):String = this.actionEat(params(0))
+  def actionEat(obj:FastObject):String = {
+    obj.removeFromCurrentContainer()
+    obj.isDeleted = true
+    obj.isEaten = true
+    deletedObjects.append(obj)
+
+    return "You eat the " + obj.name + ".  It is delicious."
+  }
+
+  def actionCook(params:Array[FastObject]):String = this.actionCook(params(0), params(1))
+  def actionCook(obj:FastObject, device:FastObject):String = {
+    // TODO: Note different kinds of cooking based on stove versus oven.
+    obj.isRaw = false
+    if (device.name == "stove") {
+      obj.isFried = true
+      return "You fry the " + obj.name + " with the " + device.name + "."
+    } else if (device.name == "oven") {
+      obj.isRoasted = true
+      return "You roast the " + obj.name + " with the " + device.name + "."
+    } else if (device.name == "barbeque") {
+      obj.isGrilled = true
+      return "You grill the " + obj.name + " with the " + device.name + "."
+    }
+
+
+    return "You cook the " + obj.name + " with the " + device.name + "."
+  }
+
+  def actionChop(params:Array[FastObject]):String = this.actionChop(params(0))
+  def actionChop(obj:FastObject):String = {
+    obj.isChopped = true
+    obj.isCut = true
+    return "You chop the " + obj.name + "."
+  }
+
+  def actionSlice(params:Array[FastObject]):String = this.actionSlice(params(0))
+  def actionSlice(obj:FastObject):String = {
+    obj.isSliced = true
+    obj.isCut = true
+    return "You slice the " + obj.name + "."
+  }
+
+  def actionDice(params:Array[FastObject]):String = this.actionDice(params(0))
+  def actionDice(obj:FastObject):String = {
+    obj.isDiced = true
+    obj.isCut = true
+    return "You dice the " + obj.name + "."
+  }
+
+  def actionLookAround(params:Array[FastObject]):String = this.actionLookAround()
+  def actionLookAround():String = {
+    return this.getCurrentRoomDescription()
+  }
+
+  def actionExamine(params:Array[FastObject]):String = this.actionExamine(params(0))
+  def actionExamine(obj:FastObject):String = {
+    return obj.getDescription()
+  }
+
+  def actionMove(params:Array[FastObject]):String = this.actionMove(params(0), params(1))
+  def actionMove(door:FastObject, newLoc:FastObject):String = {
+    newLoc match {
+      case loc:Room => {
+        if ((door != null) && (!door.isOpen)) {
+          return "You can't move there, the door is closed. "
+        }
+        this.agentLocation = loc
+        return this.agentLocation.getDescription ()
+      }
+      case _ => throw new RuntimeException("ERROR: Attempting to move to a location that is not a room (" + newLoc.name + ")")
+    }
+  }
+
+  def actionRead(params:Array[FastObject]):String = this.actionRead(params(0))
+  def actionRead(obj:FastObject):String = {
+    obj.hasBeenRead = true
+    return obj.readText
+  }
+
+  def actionInventory(params:Array[FastObject]):String = this.actionInventory()
+  def actionInventory():String = {
+    val os = new StringBuilder()
+    os.append("Inventory: \n")
+
+    if (this.agentInventory.contents.isEmpty) {
+      os.append("  Your inventory is currently empty.\n")
+    } else {
+      for (obj <- this.agentInventory.contents) {
+        os.append("  " + obj.getDescription() + "\n")
+      }
+    }
+
+    return os.toString
+  }
+
+
+  def actionOpenDoor(params:Array[FastObject]):String = this.actionOpenDoor(params(0), params(1))
+  def actionOpenDoor(door:FastObject, locationBeyond:FastObject):String = {
+    if (!door.isOpen) {
+      door.isOpen = true
+      return ("You open the " + door.getDescription() + ", revealing the " + locationBeyond.name + ". ")
+    } else {
+      return "That is already open. "
+    }
+  }
+
+  def actionCloseDoor(params:Array[FastObject]):String = this.actionCloseDoor(params(0), params(1))
+  def actionCloseDoor(door:FastObject, locationBeyond:FastObject):String = {
+    if (door.isOpen) {
+      door.isOpen = false
+      return ("You close the " + door.getDescription() + " to the " + locationBeyond.name + ". ")
+    } else {
+      return "That is already closed. "
+    }
+  }
+
+
+  val ACTION_TAKE         = 1
+  val ACTION_PUTIN        = 2
+  val ACTION_OPEN         = 3
+  val ACTION_CLOSE        = 4
+  val ACTION_EAT          = 5
+  val ACTION_COOK         = 6
+  val ACTION_CHOP         = 7
+  val ACTION_SLICE        = 8
+  val ACTION_DICE         = 9
+  val ACTION_LOOKAROUND   = 10
+  val ACTION_MOVE         = 11
+  val ACTION_READ         = 12
+  val ACTION_PREPAREMEAL  = 13
+  val ACTION_INVENTORY    = 14
+  val ACTION_EXAMINE      = 15
+  val ACTION_OPENDOOR     = 16
+  val ACTION_CLOSEDOOR    = 17
+
+  val ACTION_INVALID      = 0
+
+
+  /*
+   * Action runner/interpreter
+   */
+  def runAction(actionIdx:Int, params:Array[FastObject]):String = {
+
+    actionIdx match {
+      case ACTION_TAKE => return this.actionTake(params)
+      case ACTION_PUTIN => return this.actionPutIn(params)
+      //case ACTION_OPEN => return this.actionOpenContainer(params)
+      //case ACTION_CLOSE => return this.actionCloseContainer(params)
+      //case ACTION_EAT => return this.actionEat(params)
+      //case ACTION_COOK => return this.actionCook(params)
+      //case ACTION_CHOP => return this.actionChop(params)
+      //case ACTION_SLICE => return this.actionSlice(params)
+      //case ACTION_DICE => return this.actionDice(params)
+      case ACTION_LOOKAROUND => return this.actionLookAround(params)
+      case ACTION_MOVE => return this.actionMove(params)
+      case ACTION_READ => return this.actionRead(params)
+      //case ACTION_PREPAREMEAL => return this.actionPrepareMeal(params)
+      case ACTION_INVENTORY => return this.actionInventory(params)
+      //case ACTION_EXAMINE => return this.actionExamine(params)
+      case ACTION_OPENDOOR => return this.actionOpenDoor(params)
+      case ACTION_CLOSEDOOR => return this.actionCloseDoor(params)
+
+      case _ => return "That is not a command that I recognize."
+    }
+
+  }
+
+  /*
+   * Action generation
+   */
+  def mkActions(visibleObjects:ListBuffer[FastObject]): ListBuffer[ (String, Int, Array[FastObject]) ] = {
+    val actionsOut = new ListBuffer[(String, Int, Array[FastObject])]    // (action, callback function, parameters)
+
+    // Generic action
+    actionsOut.append( ("look around", ACTION_LOOKAROUND, Array.empty[FastObject]) )
+    actionsOut.append( ("inventory", ACTION_INVENTORY, Array.empty[FastObject]) )
+
+    // Move actions, based on current location
+    if (this.agentLocation.locationNorth != null) actionsOut.append( ("move north", ACTION_MOVE, Array(this.agentLocation.doorNorth, this.agentLocation.locationNorth)) )
+    if (this.agentLocation.locationSouth != null) actionsOut.append( ("move south", ACTION_MOVE, Array(this.agentLocation.doorSouth, this.agentLocation.locationSouth)) )
+    if (this.agentLocation.locationEast != null) actionsOut.append( ("move east", ACTION_MOVE, Array(this.agentLocation.doorEast, this.agentLocation.locationEast)) )
+    if (this.agentLocation.locationWest != null) actionsOut.append( ("move west", ACTION_MOVE, Array(this.agentLocation.doorWest, this.agentLocation.locationWest)) )
+
+    // Door-related actions
+    if (this.agentLocation.doorNorth != null) {
+      actionsOut.append( ("open door to north", ACTION_OPENDOOR, Array(this.agentLocation.doorNorth, this.agentLocation.locationNorth)) )
+      actionsOut.append( ("close door to north", ACTION_CLOSEDOOR, Array(this.agentLocation.doorNorth, this.agentLocation.locationNorth)) )
+    }
+    if (this.agentLocation.doorSouth != null) {
+      actionsOut.append( ("open door to south", ACTION_OPENDOOR, Array(this.agentLocation.doorSouth, this.agentLocation.locationSouth)) )
+      actionsOut.append( ("close door to south", ACTION_CLOSEDOOR, Array(this.agentLocation.doorSouth, this.agentLocation.locationSouth)) )
+    }
+    if (this.agentLocation.doorEast != null) {
+      actionsOut.append( ("open door to east", ACTION_OPENDOOR, Array(this.agentLocation.doorEast, this.agentLocation.locationEast)) )
+      actionsOut.append( ("close door to east", ACTION_CLOSEDOOR, Array(this.agentLocation.doorEast, this.agentLocation.locationEast)) )
+    }
+    if (this.agentLocation.doorWest != null) {
+      actionsOut.append( ("open door to west", ACTION_OPENDOOR, Array(this.agentLocation.doorWest, this.agentLocation.locationWest)) )
+      actionsOut.append( ("close door to west", ACTION_CLOSEDOOR, Array(this.agentLocation.doorWest, this.agentLocation.locationWest)) )
+    }
+
+    // Object-centered actions
+    for (obj <- visibleObjects) {
+      // Check if it can be picked up
+      if (obj.isMovable) {
+        actionsOut.append(("take " + obj.name, ACTION_TAKE, Array(obj)))
+      }
+
+      // Readable
+      // NOTE: Unlike other tasks, for this task, the instructions can be read without picking them up
+      if (obj.isReadable) {
+        actionsOut.append( ("read " + obj.name, ACTION_READ, Array(obj)) )
+      }
+    }
+
+    // Inventory objects
+    for (iObj <- this.agentInventory.contents) {
+
+      // For each environment object
+      for (eObj <- visibleObjects) {
+        if ((eObj.isContainer) && (eObj.isOpen)) {
+          actionsOut.append( ("put " + iObj.name + " in " + eObj.name, ACTION_PUTIN, Array(iObj, eObj)) )
+        }
+      }
+
+      // Readable
+      if (iObj.isReadable) {
+        actionsOut.append( ("read " + iObj.name, ACTION_READ, Array(iObj)) )
+      }
+
+    }
+
+
+    return actionsOut
+  }
+
+
+  /*
+   * Update the instruction book
+   */
+
+  def updateInstructionBook(): Unit = {
+    val nextObjToPickUpIdx = this.getScore().scoreRaw.toInt
+    if ((nextObjToPickUpIdx >= 0) && (nextObjToPickUpIdx < this.objectOrder.length)) {
+      val nextObjToPickUp = this.objectOrder(nextObjToPickUpIdx)
+      this.instructionBook.readText = "The current task is to take the " + nextObjToPickUp.name + "."
+    } else {
+      this.instructionBook.readText = "The task is completed."
+    }
+
+  }
+
+  /*
+   * Step
+   */
+
+  def initalStep():StepResult = {
+    return this.step("look around", ACTION_LOOKAROUND, Array.empty[FastObject])
+  }
+
+  def step(actionStr:String):StepResult = {
+    for (action <- lastValidActions) {
+      if (action._1 == actionStr) {
+        return this.step(actionStr, action._2, action._3)
+      }
+    }
+    // If we reach here, the action was invalid
+    return this.step(actionStr, ACTION_INVALID, Array.empty[FastObject])
+  }
+
+  def step(validActionIdx:Int):StepResult = {
+    val action = this.lastValidActions(validActionIdx)
+    return this.step(action._1, action._2, action._3)
+  }
+
+  def step(actionStr:String, actionNumber:Int, actionParams:Array[FastObject]):StepResult = {
+    // Special to this task: Update the instruction book based on the current task progress
+    this.updateInstructionBook()
+
+    // Run action
+    val observationStr = this.runAction(actionNumber, actionParams)
+    val wasValidAction = if (actionNumber == ACTION_INVALID) { false } else { true }    // If the action is valid, true, otherwise, false
+
+    // Do scoring
+    scorer.doScoring()
+
+    // Get current score
+    val curScores = this.getScore()
+
+    // Generate next valid actions
+    val visibleObjects = this.getVisibleObjects()
+    val validActions = this.mkActions(visibleObjects)
+    lastValidActions = validActions
+    var validActionStrs = new ArrayBuffer[String](validActions.length)
+    for (i <- 0 until validActions.length) validActionStrs.append(validActions(i)._1)
+    validActionStrs = random.shuffle(validActionStrs)
+
+    // Generate free-look and inventory strings
+    val freeLookStr = this.agentLocation.getDescription()
+    val inventoryStr = this.actionInventory()
+
+    // Add to action history
+    this.history.append( new ActionHistory(actionStr, observationStr, curScores) )
+
+    // Return
+    val result = new StepResult(observationStr=observationStr, freeLookStr=freeLookStr, inventoryStr=inventoryStr, validActions = validActionStrs.toArray, scoreRaw=curScores.scoreRaw, scoreNormalized=curScores.scoreNormalized, taskSuccess=curScores.taskSuccess, taskFailure=curScores.taskFailure, wasValidAction = wasValidAction)
+    return result
+  }
+
+
+}
+
+
+class PeckingOrderGameGenerator {
+  val doorMaker = new DoorMaker()
+
+
+  def mkEnvironment(r:Random, seed:Int, fold:String):(ArrayBuffer[Room], Array[FastObject], Instructions) = {
+    val locations = new ArrayBuffer[Room]()
+
+    // Add only a single location for this game (no map/multiple locations).
+    val randLocationIdx = r.nextInt(11)
+    if (randLocationIdx == 0) locations.append(new Kitchen(r, addKnife = false))
+    if (randLocationIdx == 1) locations.append(new Pantry(r))
+    if (randLocationIdx == 2) locations.append(new Corridor(r))
+    if (randLocationIdx == 3) locations.append(new Bedroom(r))
+    if (randLocationIdx == 4) locations.append(new Backyard(r))
+    if (randLocationIdx == 5) locations.append(new LivingRoom(r))
+    if (randLocationIdx == 6) locations.append(new Bathroom(r))
+    if (randLocationIdx == 7) locations.append(new LaundryRoom(r))
+    if (randLocationIdx == 8) locations.append(new Driveway(r))
+    if (randLocationIdx == 9) locations.append(new Street(r))
+    if (randLocationIdx == 10) locations.append(new Supermarket(r))
+
+    // Generate the arithmetic problem (abstract), and the math problem object.
+    val instructionBook = new Instructions()
+    instructionBook.isMovable = false
+
+    // Add the instructions to the location
+    locations(0).addObject(instructionBook)
+
+    // Make task objects
+    val taskObjects = this.mkTaskObjects(r, fold)
+
+    // Place objects
+    val objectsToPlace = r.shuffle(taskObjects.toList).toArray
+    // Find possible containers for objects
+    val visibleObjects = locations(0).collectVisibleObjects()
+    val validContainers = new ArrayBuffer[FastObject]
+    for (obj <- visibleObjects) {
+      if (obj.isContainer && obj.isOpen) validContainers.append(obj)
+    }
+
+    // Add the objects to the containers around this location
+    for (obj <- objectsToPlace) {
+      if (validContainers.length > 0) {
+        // Add to a container
+        val randContainer = validContainers( r.nextInt(validContainers.length) )
+        randContainer.addObject(obj)
+      } else {
+        // Add to the environment (i.e. on the floor)
+        locations(0).addObject(obj)
+      }
+    }
+
+    return (locations, taskObjects, instructionBook)
+  }
+
+
+  // Make task objects
+  def mkTaskObjects(r:Random, gameFold:String):Array[FastObject] = {
+    val objectNamesTrain  = Array(("apple", "apples"), ("orange", "oranges"), ("grape", "grapes"), ("tangerine", "tangerines"), ("banana", "bananas"), ("pineapple", "pineapples"), ("papaya", "papayas"), ("peach", "peaches"), ("strawberry", "strawberries"), ("grapefruit", "grapefruits") )
+    val objectNamesDev    = Array(("broccoli", "brocollis"), ("onion", "onions"), ("cucumber", "cucumbers"), ("potato", "potatoes"), ("cucumber", "cucumbers"), ("coconut", "coconuts"), ("watermelon", "watermelons"), ("mango", "mangos"), ("olive", "olives"), ("lime", "limes"), ("pear", "pears") )
+    val objectNamesTest   = Array(("pepper", "peppers"), ("tomato", "tomatoes"), ("eggplant", "eggplants"), ("squash", "squashes"), ("pumpkin", "pumpkins"), ("pea", "peas"), ("avocado", "avocados"), ("cabbage", "cabbages"), ("prune", "prunes"), ("blueberry", "blueberries") )
+
+    val numObjects:Int = 4
+
+    // Find appropriate set, and shuffle order
+    val objectNames:Array[(String, String)] = if (gameFold == "train") { objectNamesTrain } else if (gameFold == "dev") { objectNamesDev } else if (gameFold == "test") { objectNamesTest } else { Array.empty[(String, String)] }
+    val shuffled = r.shuffle(r.shuffle(objectNames.toList)).toArray   // Double shuffle, so the order is different than other games
+
+    // Create task objects, in order
+    val objectsOut = new ArrayBuffer[FastObject]
+    for (i <- 0 until numObjects) {
+      val objName = shuffled(i)._1
+      val obj = new FastObject(name = objName)
+      obj.isMovable = true
+
+      objectsOut.append(obj)
+    }
+
+    // Return
+    return objectsOut.toArray
+  }
+
+
+  //def mkGame(seed:Long, numLocations:Int = 12, numDistractorItems:Int = 10, includeDoors:Boolean = true, limitInventorySize:Boolean = true, fold:String = "train"):CoinGame = {
+  def mkGame(seed:Long, fold:String = "train"):PeckingOrderGame = {
+    // Store properties in a form that are user accessible later on
+    val props = mutable.Map[String, Int]()
+    props("seed") = seed.toInt
+    props("gameSet") = if (fold == "train") { 1 } else if (fold == "dev") { 2 } else if (fold == "test") { 3 } else -1
+
+    // Generate Game
+    val r = new Random(seed)
+    val (locations, taskObjects, instructionBook) = mkEnvironment(r, seed.toInt, fold)
+    val game = new PeckingOrderGame( locations.toArray, taskObjects, instructionBook, seed, generationProperties = props.toMap )
+
+    return game
+  }
+
+
+  def mkGameWithGoldPath(seed:Long, fold:String = "train"):(PeckingOrderGame, Array[String]) = {
+    val MAX_ATTEMPTS:Int = 50
+    val rg = new Random()
+
+    var attempts: Int = 0
+    var goldPath = Array.empty[String]
+    breakable {
+      while (attempts < MAX_ATTEMPTS) {
+        val game = this.mkGame(seed, fold)
+        val goldAgent = new PeckingOrderGoldAgent(game)
+        val (success, _goldPath) = goldAgent.mkGoldPath(rg)
+        if (success) goldPath = _goldPath
+
+        if (success) break()
+        attempts += 1
+      }
+
+      // If we reach here, for some reason no gold path could be generated.
+      println ("ERROR: Unknown error: Gold path could not be generated after maximum number of attempts (" + MAX_ATTEMPTS + ").")
+    }
+
+    // Create fresh copy of game
+    val game = this.mkGame(seed, fold)
+    return (game, goldPath)
+  }
+
+
+}
+
+
+
diff --git a/simulator/src/main/scala/textworldexpress/games/SimonSaysGame.scala b/simulator/src/main/scala/textworldexpress/games/SimonSaysGame.scala
new file mode 100644
index 0000000..7de7abe
--- /dev/null
+++ b/simulator/src/main/scala/textworldexpress/games/SimonSaysGame.scala
@@ -0,0 +1,360 @@
+package textworldexpress.games
+
+import textworldexpress.goldagent.SimonSaysGoldAgent
+import textworldexpress.objects.FastObject
+import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
+
+import scala.collection.mutable
+import scala.collection.mutable.{ArrayBuffer, ListBuffer}
+import scala.util.Random
+import scala.util.control.Breaks.{break, breakable}
+
+// GoldActionSequences contains the list of action sequences that the agent should repeat.
+class SimonSaysGameScoring(val goldActionSequence:Array[String], history:ArrayBuffer[ActionHistory]) extends Scorer {
+
+  def doScoring(): Unit = {
+    var curScore:Double = 0
+    var taskFailure:Boolean = false
+    var taskSuccess:Boolean = false
+
+    // Check: Make sure the number of actions taken is less than or equal to the number of gold actions requested
+    if (history.length-1 > goldActionSequence.length) {
+      val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+      this.curScore = scores
+      return
+    }
+
+    // Check each action taken by the agent, to make sure it was the correct (gold) action
+    if (history.length > 1) {
+      for (i <- 1 until history.length) {
+        val actionStr = history(i).actionStr
+        //println("history(" + i + "): " + actionStr + "    " + history(i).scores.scoreNormalized)
+
+        if (goldActionSequence(i-1) == actionStr) {
+          // Chose the right option here
+          curScore += 1
+        } else {
+          // Task failure
+          taskFailure = true
+        }
+
+      }
+    }
+
+    // If one action was incorrect, then task failure
+    if (taskFailure) {
+      val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+      this.curScore = scores
+      return
+    }
+
+    // If all actions have been taken, and all were correct, then task success
+    if (goldActionSequence.length == history.length-1) {
+      taskSuccess = true
+    }
+
+
+    // Store current scores in 'this.curScore', for the accessor in the base class
+    val normalizedScore = curScore/maxScore
+    val scores = new GameScore(scoreRaw = curScore, scoreNormalized = normalizedScore, taskSuccess = taskSuccess, taskFailure = taskFailure)
+    this.curScore = scores
+  }
+
+  // Calculate the maximum possible score for this task (the number of actions in the gold sequence)
+  def calculateMaxScore():Double = {
+    var maxScore:Double = this.goldActionSequence.size.toDouble
+    return maxScore
+  }
+
+}
+
+
+
+class SimonSaysGame(val goldActionSequence:Array[String], val possibleActions:Array[String], val seed:Long = 0, val generationProperties:Map[String, Int]) extends TextGame {
+
+  // A list of the most recently generated valid actions (for step() )
+  var lastValidActions = ListBuffer.empty[(String, Int, Array[FastObject])]
+
+  // The action/observation history
+  var history = new ArrayBuffer[ActionHistory]
+
+  // Scorer
+  val scorer:Scorer = new SimonSaysGameScoring(goldActionSequence, history)
+
+  // Internal game random number generator -- primarily for randomizing valid action list.
+  val random = new Random(seed)
+
+  // Current step
+  var currentStep:Int = 0
+
+  /*
+   * Cloning
+   */
+
+  // TODO: Not implemented
+  def deepCopy():SimonSaysGame = {
+    println ("NOTE: deepCopy() not implemented -- returning a shallow copy")
+    // Return
+    return new SimonSaysGame(goldActionSequence, possibleActions, seed, generationProperties)
+  }
+
+
+
+  /*
+   * Generation Properties
+   */
+
+  def getGenerationProperties():Map[String, Int] = return this.generationProperties
+
+  /*
+   * Task Description
+   */
+  def getTaskDescription():String = {
+    return "Your task is to do exactly what Simon says."
+  }
+
+  /*
+   * History
+   */
+  def getHistory():ArrayBuffer[ActionHistory] = {
+    return this.history
+  }
+
+  /*
+   * Score
+   */
+  // Returns (score, taskSuccess, taskFailure)
+  def getScore():GameScore = this.scorer.getCurrentScore()
+
+  /*
+   * Actions
+   */
+
+  def actionSimonSays(params:Array[FastObject]):String = this.actionSimonSays()
+  def actionSimonSays():String = {
+    return ""
+  }
+
+  val ACTION_SIMONSAYS    = 18
+
+  val ACTION_INVALID      = 0
+
+
+  /*
+   * Action runner/interpreter
+   */
+  def runAction(actionIdx:Int, params:Array[FastObject]):String = {
+
+    actionIdx match {
+      case ACTION_SIMONSAYS => return this.actionSimonSays(params)
+
+      case _ => return "That is not a command that I recognize."
+    }
+
+  }
+
+  /*
+   * Action generation
+   */
+  def mkActions(visibleObjects:ListBuffer[FastObject]): ListBuffer[ (String, Int, Array[FastObject]) ] = {
+    val actionsOut = new ListBuffer[(String, Int, Array[FastObject])]    // (action, callback function, parameters)
+
+    // Generic simon says action
+    for (i <- 0 until this.possibleActions.length) {
+      val possibleActionStr = this.possibleActions(i)
+      actionsOut.append( (possibleActionStr, ACTION_SIMONSAYS, Array.empty[FastObject]) )
+    }
+
+    return actionsOut
+  }
+
+  /*
+   * Make current observation for Simon Says
+   */
+  def mkObservation(curStage:Int):String = {
+    // Check for task completion
+    if (this.getScore().taskFailure) {
+      return "Task failure."
+    }
+
+    if (curStage >= this.goldActionSequence.length) {
+      return "Task Completed."
+    }
+
+    val os = "Simon says, '" + this.goldActionSequence(curStage) + "'."
+    return os
+  }
+
+
+  /*
+   * Step
+   */
+
+  def initalStep():StepResult = {
+    // By giving '-1' as an actionNumber, 'step()' will generate the observation without running the action or including it in the history.
+    return this.step("", -1, Array.empty[FastObject])
+  }
+
+  def step(actionStr:String):StepResult = {
+    for (action <- lastValidActions) {
+      if (action._1 == actionStr) {
+        return this.step(actionStr, action._2, action._3)
+      }
+    }
+    // If we reach here, the action was invalid
+    return this.step(actionStr, ACTION_INVALID, Array.empty[FastObject])
+  }
+
+  def step(validActionIdx:Int):StepResult = {
+    val action = this.lastValidActions(validActionIdx)
+    return this.step(action._1, action._2, action._3)
+  }
+
+  def step(actionStr:String, actionNumber:Int, actionParams:Array[FastObject]):StepResult = {
+
+    // Run action
+    if (actionNumber >= 0) {
+      // Only run the action if actionNumber is positive -- this is used as a way of generating the initial observation by calling this method with actionNumber = -1.
+      val unused = this.runAction(actionNumber, actionParams)
+    }
+    val wasValidAction = if (actionNumber == ACTION_INVALID) { false } else { true }    // If the action is valid, true, otherwise, false
+
+
+    // Generate next valid actions
+    val validActions = this.mkActions(ListBuffer.empty[FastObject])
+    lastValidActions = validActions
+    var validActionStrs = new ArrayBuffer[String](validActions.length)
+    for (i <- 0 until validActions.length) validActionStrs.append(validActions(i)._1)
+    validActionStrs = random.shuffle(validActionStrs)
+
+
+    // Generate observation, free-look, and inventory strings
+    val freeLookStr = ""
+    val inventoryStr = ""
+
+    val observationStr = this.mkObservation(curStage = this.currentStep)                       //## Special to this task: Observation is generated, rather than coming from environment
+
+    // This part is a bit weird -- the scorer uses the history to generate the score.  But the history stores the score, so there's a circular reference.
+    // So, we add a faux entry to the history -- calculate the score, then pop it off and put the real one on.
+    // Add to action history
+    this.history.append(new ActionHistory(actionStr, observationStr, this.getScore()))
+
+    // Do scoring
+    scorer.doScoring()
+
+    // Get current score
+    val curScores = this.getScore()
+
+    // The strange part
+    this.history.remove(this.history.size-1)
+    this.history.append(new ActionHistory(actionStr, observationStr, curScores))
+
+    this.currentStep += 1
+
+    // Return
+    val result = new StepResult(observationStr=observationStr, freeLookStr=freeLookStr, inventoryStr=inventoryStr, validActions = validActionStrs.toArray, scoreRaw=curScores.scoreRaw, scoreNormalized=curScores.scoreNormalized, taskSuccess=curScores.taskSuccess, taskFailure=curScores.taskFailure, wasValidAction = wasValidAction)
+    return result
+  }
+
+
+}
+
+
+class SimonSaysGameGenerator {
+
+  // Make a random sequence of actions
+  def mkActionSequence(r:Random, gameFold:String, length:Int):Array[String] = {
+    // Step 1: Object actions
+    val objectNamesTrain  = Array(("apple", "apples"), ("orange", "oranges"), ("grape", "grapes"), ("tangerine", "tangerines"), ("banana", "bananas"), ("pineapple", "pineapples"), ("papaya", "papayas"), ("peach", "peaches"), ("strawberry", "strawberries"), ("grapefruit", "grapefruits") )
+    val objectNamesDev    = Array(("broccoli", "brocollis"), ("onion", "onions"), ("cucumber", "cucumbers"), ("potato", "potatoes"), ("cucumber", "cucumbers"), ("coconut", "coconuts"), ("watermelon", "watermelons"), ("mango", "mangos"), ("olive", "olives"), ("lime", "limes"), ("pear", "pears") )
+    val objectNamesTest   = Array(("pepper", "peppers"), ("tomato", "tomatoes"), ("eggplant", "eggplants"), ("squash", "squashes"), ("pumpkin", "pumpkins"), ("pea", "peas"), ("avocado", "avocados"), ("cabbage", "cabbages"), ("prune", "prunes"), ("blueberry", "blueberries") )
+
+    val numObjects:Int = 4
+
+    // Find appropriate set, and shuffle order
+    val objectNames:Array[(String, String)] = if (gameFold == "train") { objectNamesTrain } else if (gameFold == "dev") { objectNamesDev } else if (gameFold == "test") { objectNamesTest } else { Array.empty[(String, String)] }
+    val shuffled = r.shuffle(r.shuffle(objectNames.toList)).toArray   // Double shuffle, so the order is different than other games
+
+    // Create task objects, in order
+    val actionsOut = new ArrayBuffer[String]
+    for (i <- 0 until numObjects) {
+      val objName = shuffled(i)._1
+      var indefinite = if (Array("a", "e", "i", "o", "u").contains(objName.charAt(0).toString)) { "an" } else { "a" }
+      actionsOut.append("take " + indefinite + " " + objName)
+      actionsOut.append("eat " + indefinite + " " + objName)
+    }
+
+    // Step 2: Action actions (from canonical simon says games)
+    val actionsTrain = Array("sit down", "turn around in a circle", "jump up and down", "hop on your right foot", "meow like a cat")
+    val actionsDev = Array("hop on your left foot", "clap your hands", "touch your nose", "wiggle your fingers", "quack like a duck")
+    val actionsTest = Array("hop to the left", "touch your ears", "do a dance", "wave hello", "wiggle your toes")
+
+    if (gameFold == "train") {
+      actionsOut.insertAll(actionsOut.length, actionsTrain)
+    } else if (gameFold == "dev") {
+      actionsOut.insertAll(actionsOut.length, actionsDev)
+    } else if (gameFold == "test") {
+      actionsOut.insertAll(actionsOut.length, actionsTest)
+    }
+
+    // Shuffle final actions
+    val shuffledOut = r.shuffle(actionsOut).toArray
+    val out = shuffledOut.slice(0, length)
+
+    // Return
+    return shuffledOut.toArray
+  }
+
+
+  def mkGame(seed:Long, gameLength:Int = 5, numDistractors:Int = 3, fold:String = "train"):SimonSaysGame = {
+    val r = new Random(seed)
+
+    // Store properties in a form that are user accessible later on
+    val props = mutable.Map[String, Int]()
+    props("seed") = seed.toInt
+    props("gameLength") = gameLength
+    props("numDistractors") = numDistractors
+    props("gameSet") = if (fold == "train") { 1 } else if (fold == "dev") { 2 } else if (fold == "test") { 3 } else -1
+
+    // Generate Game
+    val actionSequence = this.mkActionSequence(r, gameFold = fold, length = gameLength+numDistractors)   // Overgenerate by numDistractors.
+    val goldSequence = actionSequence.slice(0, gameLength)
+    val possibleActions = actionSequence
+
+    val game = new SimonSaysGame(goldSequence, possibleActions, seed, generationProperties = props.toMap )
+
+    return game
+  }
+
+
+  def mkGameWithGoldPath(seed:Long, gameLength:Int = 5, numDistractors:Int = 3, fold:String = "train"):(SimonSaysGame, Array[String]) = {
+    val MAX_ATTEMPTS:Int = 50
+    val rg = new Random()
+
+    var attempts: Int = 0
+    var goldPath = Array.empty[String]
+    breakable {
+      while (attempts < MAX_ATTEMPTS) {
+        val game = this.mkGame(seed, gameLength, numDistractors, fold)
+        val goldAgent = new SimonSaysGoldAgent(game)
+        val (success, _goldPath) = goldAgent.mkGoldPath(rg)
+        if (success) goldPath = _goldPath
+
+        if (success) break()
+        attempts += 1
+      }
+
+      // If we reach here, for some reason no gold path could be generated.
+      println ("ERROR: Unknown error: Gold path could not be generated after maximum number of attempts (" + MAX_ATTEMPTS + ").")
+    }
+
+    // Create fresh copy of game
+    val game = this.mkGame(seed, gameLength, numDistractors, fold)
+    return (game, goldPath)
+  }
+
+
+}
+
+
+
diff --git a/simulator/src/main/scala/textworldexpress/games/SimonSaysMemoryGame.scala b/simulator/src/main/scala/textworldexpress/games/SimonSaysMemoryGame.scala
new file mode 100644
index 0000000..db363de
--- /dev/null
+++ b/simulator/src/main/scala/textworldexpress/games/SimonSaysMemoryGame.scala
@@ -0,0 +1,366 @@
+package textworldexpress.games
+
+import textworldexpress.goldagent.SimonSaysMemoryGoldAgent
+import textworldexpress.objects.FastObject
+import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
+
+import scala.collection.mutable
+import scala.collection.mutable.{ArrayBuffer, ListBuffer}
+import scala.util.Random
+import scala.util.control.Breaks.{break, breakable}
+
+// GoldActionSequences contains the list of action sequences that the agent should repeat.
+class SimonSaysMemoryGameScoring(val goldActionSequence:Array[String], history:ArrayBuffer[ActionHistory]) extends Scorer {
+
+  def doScoring(): Unit = {
+    var curScore:Double = 0
+    var taskFailure:Boolean = false
+    var taskSuccess:Boolean = false
+
+    // Check: Make sure the number of actions taken is less than or equal to the number of gold actions requested
+    if (history.length-1 > goldActionSequence.length) {
+      val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+      this.curScore = scores
+      return
+    }
+
+    // Check each action taken by the agent, to make sure it was the correct (gold) action
+    if (history.length > 1) {
+      for (i <- 1 until history.length) {
+        val actionStr = history(i).actionStr
+        //println("history(" + i + "): " + actionStr + "    " + history(i).scores.scoreNormalized)
+
+        if (goldActionSequence(i-1) == actionStr) {
+          // Chose the right option here
+          curScore += 1
+        } else {
+          // Task failure
+          taskFailure = true
+        }
+
+      }
+    }
+
+    // If one action was incorrect, then task failure
+    if (taskFailure) {
+      val scores = new GameScore(scoreRaw = -1, scoreNormalized = -1, taskSuccess = false, taskFailure = true)
+      this.curScore = scores
+      return
+    }
+
+    // If all actions have been taken, and all were correct, then task success
+    if (goldActionSequence.length == history.length-1) {
+      taskSuccess = true
+    }
+
+
+    // Store current scores in 'this.curScore', for the accessor in the base class
+    val normalizedScore = curScore/maxScore
+    val scores = new GameScore(scoreRaw = curScore, scoreNormalized = normalizedScore, taskSuccess = taskSuccess, taskFailure = taskFailure)
+    this.curScore = scores
+  }
+
+  // Calculate the maximum possible score for this task (the number of actions in the gold sequence)
+  def calculateMaxScore():Double = {
+    var maxScore:Double = this.goldActionSequence.size.toDouble
+    return maxScore
+  }
+
+}
+
+
+
+class SimonSaysMemoryGame(val goldActionSequence:Array[String], val possibleActions:Array[String], val seed:Long = 0, val generationProperties:Map[String, Int]) extends TextGame {
+
+  // A list of the most recently generated valid actions (for step() )
+  var lastValidActions = ListBuffer.empty[(String, Int, Array[FastObject])]
+
+  // The action/observation history
+  var history = new ArrayBuffer[ActionHistory]
+
+  // Scorer
+  val scorer:Scorer = new SimonSaysGameScoring(goldActionSequence, history)
+
+  // Internal game random number generator -- primarily for randomizing valid action list.
+  val random = new Random(seed)
+
+  // Current step
+  var currentStep:Int = 0
+
+  /*
+   * Cloning
+   */
+
+  // TODO: Not implemented
+  def deepCopy():SimonSaysMemoryGame = {
+    println ("NOTE: deepCopy() not implemented -- returning a shallow copy")
+    // Return
+    return new SimonSaysMemoryGame(goldActionSequence, possibleActions, seed, generationProperties)
+  }
+
+
+
+  /*
+   * Generation Properties
+   */
+
+  def getGenerationProperties():Map[String, Int] = return this.generationProperties
+
+  /*
+   * Task Description
+   */
+  def getTaskDescription():String = {
+    return "Your task is to do exactly what Simon says."
+  }
+
+  /*
+   * History
+   */
+  def getHistory():ArrayBuffer[ActionHistory] = {
+    return this.history
+  }
+
+  /*
+   * Score
+   */
+  // Returns (score, taskSuccess, taskFailure)
+  def getScore():GameScore = this.scorer.getCurrentScore()
+
+  /*
+   * Actions
+   */
+
+  def actionSimonSays(params:Array[FastObject]):String = this.actionSimonSays()
+  def actionSimonSays():String = {
+    return ""
+  }
+
+  val ACTION_SIMONSAYS    = 18
+
+  val ACTION_INVALID      = 0
+
+
+  /*
+   * Action runner/interpreter
+   */
+  def runAction(actionIdx:Int, params:Array[FastObject]):String = {
+
+    actionIdx match {
+      case ACTION_SIMONSAYS => return this.actionSimonSays(params)
+
+      case _ => return "That is not a command that I recognize."
+    }
+
+  }
+
+  /*
+   * Action generation
+   */
+  def mkActions(visibleObjects:ListBuffer[FastObject]): ListBuffer[ (String, Int, Array[FastObject]) ] = {
+    val actionsOut = new ListBuffer[(String, Int, Array[FastObject])]    // (action, callback function, parameters)
+
+    // Generic simon says action
+    for (i <- 0 until this.possibleActions.length) {
+      val possibleActionStr = this.possibleActions(i)
+      actionsOut.append( (possibleActionStr, ACTION_SIMONSAYS, Array.empty[FastObject]) )
+    }
+
+    return actionsOut
+  }
+
+  /*
+   * Make current observation for Simon Says
+   */
+  def mkObservation(curStage:Int):String = {
+    // Check for task completion
+    if (this.getScore().taskFailure) {
+      return "Task failure."
+    }
+
+    if (curStage >= this.goldActionSequence.length) {
+      return "Task Completed."
+    }
+
+    if (curStage == 0) {
+      // First step
+      return "Simon says, take these actions in order: " + this.goldActionSequence.mkString(", ") + "."
+    } else {
+      // Subsequent steps -- if we reach here, they are making progress.
+      return "Good work!"
+    }
+
+  }
+
+
+  /*
+   * Step
+   */
+
+  def initalStep():StepResult = {
+    // By giving '-1' as an actionNumber, 'step()' will generate the observation without running the action or including it in the history.
+    return this.step("", -1, Array.empty[FastObject])
+  }
+
+  def step(actionStr:String):StepResult = {
+    for (action <- lastValidActions) {
+      if (action._1 == actionStr) {
+        return this.step(actionStr, action._2, action._3)
+      }
+    }
+    // If we reach here, the action was invalid
+    return this.step(actionStr, ACTION_INVALID, Array.empty[FastObject])
+  }
+
+  def step(validActionIdx:Int):StepResult = {
+    val action = this.lastValidActions(validActionIdx)
+    return this.step(action._1, action._2, action._3)
+  }
+
+  def step(actionStr:String, actionNumber:Int, actionParams:Array[FastObject]):StepResult = {
+
+    // Run action
+    if (actionNumber >= 0) {
+      // Only run the action if actionNumber is positive -- this is used as a way of generating the initial observation by calling this method with actionNumber = -1.
+      val unused = this.runAction(actionNumber, actionParams)
+    }
+    val wasValidAction = if (actionNumber == ACTION_INVALID) { false } else { true }    // If the action is valid, true, otherwise, false
+
+
+    // Generate next valid actions
+    val validActions = this.mkActions(ListBuffer.empty[FastObject])
+    lastValidActions = validActions
+    var validActionStrs = new ArrayBuffer[String](validActions.length)
+    for (i <- 0 until validActions.length) validActionStrs.append(validActions(i)._1)
+    validActionStrs = random.shuffle(validActionStrs)
+
+
+    // Generate observation, free-look, and inventory strings
+    val freeLookStr = ""
+    val inventoryStr = ""
+
+    val observationStr = this.mkObservation(curStage = this.currentStep)                       //## Special to this task: Observation is generated, rather than coming from environment
+
+    // This part is a bit weird -- the scorer uses the history to generate the score.  But the history stores the score, so there's a circular reference.
+    // So, we add a faux entry to the history -- calculate the score, then pop it off and put the real one on.
+    // Add to action history
+    this.history.append(new ActionHistory(actionStr, observationStr, this.getScore()))
+
+    // Do scoring
+    scorer.doScoring()
+
+    // Get current score
+    val curScores = this.getScore()
+
+    // The strange part
+    this.history.remove(this.history.size-1)
+    this.history.append(new ActionHistory(actionStr, observationStr, curScores))
+
+    this.currentStep += 1
+
+    // Return
+    val result = new StepResult(observationStr=observationStr, freeLookStr=freeLookStr, inventoryStr=inventoryStr, validActions = validActionStrs.toArray, scoreRaw=curScores.scoreRaw, scoreNormalized=curScores.scoreNormalized, taskSuccess=curScores.taskSuccess, taskFailure=curScores.taskFailure, wasValidAction = wasValidAction)
+    return result
+  }
+
+
+}
+
+
+class SimonSaysMemoryGameGenerator {
+
+  // Make a random sequence of actions
+  def mkActionSequence(r:Random, gameFold:String, length:Int):Array[String] = {
+    // Step 1: Object actions
+    val objectNamesTrain  = Array(("apple", "apples"), ("orange", "oranges"), ("grape", "grapes"), ("tangerine", "tangerines"), ("banana", "bananas"), ("pineapple", "pineapples"), ("papaya", "papayas"), ("peach", "peaches"), ("strawberry", "strawberries"), ("grapefruit", "grapefruits") )
+    val objectNamesDev    = Array(("broccoli", "brocollis"), ("onion", "onions"), ("cucumber", "cucumbers"), ("potato", "potatoes"), ("cucumber", "cucumbers"), ("coconut", "coconuts"), ("watermelon", "watermelons"), ("mango", "mangos"), ("olive", "olives"), ("lime", "limes"), ("pear", "pears") )
+    val objectNamesTest   = Array(("pepper", "peppers"), ("tomato", "tomatoes"), ("eggplant", "eggplants"), ("squash", "squashes"), ("pumpkin", "pumpkins"), ("pea", "peas"), ("avocado", "avocados"), ("cabbage", "cabbages"), ("prune", "prunes"), ("blueberry", "blueberries") )
+
+    val numObjects:Int = 4
+
+    // Find appropriate set, and shuffle order
+    val objectNames:Array[(String, String)] = if (gameFold == "train") { objectNamesTrain } else if (gameFold == "dev") { objectNamesDev } else if (gameFold == "test") { objectNamesTest } else { Array.empty[(String, String)] }
+    val shuffled = r.shuffle(r.shuffle(objectNames.toList)).toArray   // Double shuffle, so the order is different than other games
+
+    // Create task objects, in order
+    val actionsOut = new ArrayBuffer[String]
+    for (i <- 0 until numObjects) {
+      val objName = shuffled(i)._1
+      var indefinite = if (Array("a", "e", "i", "o", "u").contains(objName.charAt(0).toString)) { "an" } else { "a" }
+      actionsOut.append("take " + indefinite + " " + objName)
+      actionsOut.append("eat " + indefinite + " " + objName)
+    }
+
+    // Step 2: Action actions (from canonical simon says games)
+    val actionsTrain = Array("sit down", "turn around in a circle", "jump up and down", "hop on your right foot", "meow like a cat")
+    val actionsDev = Array("hop on your left foot", "clap your hands", "touch your nose", "wiggle your fingers", "quack like a duck")
+    val actionsTest = Array("hop to the left", "touch your ears", "do a dance", "wave hello", "wiggle your toes")
+
+    if (gameFold == "train") {
+      actionsOut.insertAll(actionsOut.length, actionsTrain)
+    } else if (gameFold == "dev") {
+      actionsOut.insertAll(actionsOut.length, actionsDev)
+    } else if (gameFold == "test") {
+      actionsOut.insertAll(actionsOut.length, actionsTest)
+    }
+
+    // Shuffle final actions
+    val shuffledOut = r.shuffle(actionsOut).toArray
+    val out = shuffledOut.slice(0, length)
+
+    // Return
+    return shuffledOut.toArray
+  }
+
+
+  def mkGame(seed:Long, gameLength:Int = 5, numDistractors:Int = 3, fold:String = "train"):SimonSaysMemoryGame = {
+    val r = new Random(seed)
+
+    // Store properties in a form that are user accessible later on
+    val props = mutable.Map[String, Int]()
+    props("seed") = seed.toInt
+    props("gameLength") = gameLength
+    props("numDistractors") = numDistractors
+    props("gameSet") = if (fold == "train") { 1 } else if (fold == "dev") { 2 } else if (fold == "test") { 3 } else -1
+
+    // Generate Game
+    val actionSequence = this.mkActionSequence(r, gameFold = fold, length = gameLength+numDistractors)  // Overgenerate by numDistractors.
+    val goldSequence = actionSequence.slice(0, gameLength)
+    val possibleActions = actionSequence
+
+    val game = new SimonSaysMemoryGame(goldSequence, possibleActions, seed, generationProperties = props.toMap )
+
+    return game
+  }
+
+
+  def mkGameWithGoldPath(seed:Long, gameLength:Int = 5, numDistractors:Int = 3, fold:String = "train"):(SimonSaysMemoryGame, Array[String]) = {
+    val MAX_ATTEMPTS:Int = 50
+    val rg = new Random()
+
+    var attempts: Int = 0
+    var goldPath = Array.empty[String]
+    breakable {
+      while (attempts < MAX_ATTEMPTS) {
+        val game = this.mkGame(seed, gameLength, numDistractors, fold)
+        val goldAgent = new SimonSaysMemoryGoldAgent(game)
+        val (success, _goldPath) = goldAgent.mkGoldPath(rg)
+        if (success) goldPath = _goldPath
+
+        if (success) break()
+        attempts += 1
+      }
+
+      // If we reach here, for some reason no gold path could be generated.
+      println ("ERROR: Unknown error: Gold path could not be generated after maximum number of attempts (" + MAX_ATTEMPTS + ").")
+    }
+
+    // Create fresh copy of game
+    val game = this.mkGame(seed, gameLength, numDistractors, fold)
+    return (game, goldPath)
+  }
+
+
+}
+
+
+
diff --git a/simulator/src/main/scala/textworldexpress/games/SortingGame.scala b/simulator/src/main/scala/textworldexpress/games/SortingGame.scala
index baebd5c..cea253e 100644
--- a/simulator/src/main/scala/textworldexpress/games/SortingGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/SortingGame.scala
@@ -1,6 +1,6 @@
 package textworldexpress.games
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON, MathProblemGenerator, SortingProblemGenerator}
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON, MathProblemGenerator, SortingProblemGenerator}
 import textworldexpress.goldagent.{ArithmeticGoldAgent, CoinGoldAgent, SortingGoldAgent}
 import textworldexpress.objects.{Backyard, Bathroom, Bedroom, Box, BundleOfObjects, Coin, Corridor, DoorMaker, Driveway, FastObject, Kitchen, LaundryRoom, LivingRoom, MathProblem, Pantry, Room, Street, Supermarket}
 import textworldexpress.preprocessing.ArithmeticProblem
diff --git a/simulator/src/main/scala/textworldexpress/games/TWCGame.scala b/simulator/src/main/scala/textworldexpress/games/TWCGame.scala
index 2e12a4c..1e13260 100644
--- a/simulator/src/main/scala/textworldexpress/games/TWCGame.scala
+++ b/simulator/src/main/scala/textworldexpress/games/TWCGame.scala
@@ -1,6 +1,6 @@
 package textworldexpress.games
 
-import textworldexpress.data.{LoadTWCDataJSON, LoadTWKitchenDataJSON}
+import textworldexpress.data.{LoadTWCDataJSON, LoadCookingWorldDataJSON}
 import textworldexpress.goldagent.TWCGoldAgent
 import textworldexpress.objects.{Backyard, Bathroom, Bedroom, Coin, Corridor, DoorMaker, Driveway, FastObject, Kitchen, LaundryRoom, LivingRoom, Pantry, Room, Street, Supermarket}
 import textworldexpress.struct.{ActionHistory, GameScore, Scorer, StepResult, TextGame}
@@ -563,7 +563,7 @@ class TWCGame(val locations:Array[Room], val taskObjects:ArrayBuffer[FastObject]
 
 class TWCGameGenerator {
   val TWCObjectDatabase = new LoadTWCDataJSON()
-  //val TWKitchenObjectDatabase = new LoadTWKitchenDataJSON()
+  //val CookingWorldObjectDatabase = new LoadCookingWorldDataJSON()
   val doorMaker = new DoorMaker()
 
 
@@ -713,7 +713,7 @@ class TWCGameGenerator {
         //println("location: " + location.name)
 
         val item = TWCObjectDatabase.mkRandomObjectByLocation(r, container.name, fold)
-        //val distractorItem = TWKitchenObjectDatabase.mkRandomObjectByLocation(r, container.name)
+        //val distractorItem = CookingWorldObjectDatabase.mkRandomObjectByLocation(r, container.name)
         if (item.isDefined) {
           //println ("Item: " + item.get.name)
 
diff --git a/simulator/src/main/scala/textworldexpress/generator/GameGenerator.scala b/simulator/src/main/scala/textworldexpress/generator/GameGenerator.scala
index 8f14ca7..6728e83 100644
--- a/simulator/src/main/scala/textworldexpress/generator/GameGenerator.scala
+++ b/simulator/src/main/scala/textworldexpress/generator/GameGenerator.scala
@@ -1,6 +1,6 @@
 package textworldexpress.generator
 
-import textworldexpress.games.{SortingGameGenerator, ArithmeticGameGenerator, CoinGameGenerator, KitchenGameGenerator, MapReaderGameGenerator, TWCGameGenerator}
+import textworldexpress.games.{SortingGameGenerator, ArithmeticGameGenerator, CoinGameGenerator, CookingWorldGameGenerator, MapReaderGameGenerator, TWCGameGenerator, SimonSaysGameGenerator, SimonSaysMemoryGameGenerator, PeckingOrderGameGenerator}
 import textworldexpress.struct.TextGame
 
 /*
@@ -28,8 +28,8 @@ abstract class GameGenerator {
 /*
  * Kitchen
  */
-class GameGeneratorKitchen(val numLocations:Int=11, val numDistractorItems:Int = 10, val numIngredients:Int=3, val includeDoors:Boolean=true, val limitInventorySize:Boolean=true) extends GameGenerator {
-  val generator = new KitchenGameGenerator()
+class GameGeneratorCookingWorld(val numLocations:Int=11, val numDistractorItems:Int = 10, val numIngredients:Int=3, val includeDoors:Boolean=true, val limitInventorySize:Boolean=true) extends GameGenerator {
+  val generator = new CookingWorldGameGenerator()
   this.errorStr = this.checkValidConfiguration()
 
   /*
@@ -277,7 +277,6 @@ class GameGeneratorArithmetic() extends GameGenerator {
 
 }
 
-
 /*
  * Sorting Game
  */
@@ -320,15 +319,143 @@ class GameGeneratorSorting() extends GameGenerator {
 }
 
 
+/*
+ * Simon Says Game
+ */
+class GameGeneratorSimonSays(gameLength:Int = 5, numDistractors:Int = 3) extends GameGenerator {
+  val generator = new SimonSaysGameGenerator()
+  this.errorStr = this.checkValidConfiguration()
+
+  /*
+   * Error checking
+   */
+  private def checkValidConfiguration():String = {
+    val os = new StringBuilder
+    return os.toString()
+  }
+
+  def isInvalid():Boolean = {
+    if (errorStr.length > 0) return true
+    // Otherwise
+    return false
+  }
+
+  def getConfigStr():String = {
+    val os = new StringBuilder()
+    os.append("Game: Simon Says\n")
+    os.append("gameLength: " + gameLength + "\n")
+    os.append("numDistractors: " + numDistractors + "\n")
+    return os.toString()
+  }
+
+  /*
+   * Game generation
+   */
+  def mkGame(seed:Long, fold:String):TextGame = {
+    return generator.mkGame(seed=seed, gameLength=gameLength, numDistractors=numDistractors, fold=fold)
+  }
+
+  def mkGameWithGoldPath(seed:Long, fold:String):(TextGame, Array[String]) = {
+    return generator.mkGameWithGoldPath(seed=seed, gameLength=gameLength, numDistractors=numDistractors, fold=fold)
+  }
+
+}
+
+
+/*
+ * Simon Says (long memory version) Game
+ */
+class GameGeneratorSimonSaysMemory(gameLength:Int = 5, numDistractors:Int = 3) extends GameGenerator {
+  val generator = new SimonSaysMemoryGameGenerator()
+  this.errorStr = this.checkValidConfiguration()
+
+  /*
+   * Error checking
+   */
+  private def checkValidConfiguration():String = {
+    val os = new StringBuilder
+    return os.toString()
+  }
+
+  def isInvalid():Boolean = {
+    if (errorStr.length > 0) return true
+    // Otherwise
+    return false
+  }
+
+  def getConfigStr():String = {
+    val os = new StringBuilder()
+    os.append("Game: Simon Says (memory)\n")
+    os.append("gameLength: " + gameLength + "\n")
+    os.append("numDistractors: " + numDistractors + "\n")
+    return os.toString()
+  }
+
+  /*
+   * Game generation
+   */
+  def mkGame(seed:Long, fold:String):TextGame = {
+    return generator.mkGame(seed=seed, gameLength=gameLength, numDistractors=numDistractors, fold=fold)
+  }
+
+  def mkGameWithGoldPath(seed:Long, fold:String):(TextGame, Array[String]) = {
+    return generator.mkGameWithGoldPath(seed=seed, gameLength=gameLength, numDistractors=numDistractors, fold=fold)
+  }
+
+}
+
+
+/*
+ * Pecking Order Game
+ */
+class GameGeneratorPeckingOrder() extends GameGenerator {
+  val generator = new PeckingOrderGameGenerator()
+  this.errorStr = this.checkValidConfiguration()
+
+  /*
+   * Error checking
+   */
+  private def checkValidConfiguration():String = {
+    val os = new StringBuilder
+    return os.toString()
+  }
+
+  def isInvalid():Boolean = {
+    if (errorStr.length > 0) return true
+    // Otherwise
+    return false
+  }
+
+  def getConfigStr():String = {
+    val os = new StringBuilder()
+    os.append("Game: Pecking Order\n")
+    os.append("This game has no parameters other than seed, and game fold (train/dev/test).\n")
+    return os.toString()
+  }
+
+  /*
+   * Game generation
+   */
+  def mkGame(seed:Long, fold:String):TextGame = {
+    return generator.mkGame(seed=seed, fold=fold)
+  }
+
+  def mkGameWithGoldPath(seed:Long, fold:String):(TextGame, Array[String]) = {
+    return generator.mkGameWithGoldPath(seed=seed, fold=fold)
+  }
+
+}
+
+
 
 /*
  * Generic generator
  */
 object GameGenerator {
-  val VALID_GAME_NAMES = Array("cookingworld", "twc", "coin", "mapreader", "arithmetic", "sorting")
+  val VALID_GAME_NAMES = Array("cookingworld", "twc", "coin", "mapreader", "arithmetic", "sorting", "simonsays", "peckingorder")
 
-  // Make the kitchen game
-  private def mkKitchen(properties:Map[String, Int]):GameGenerator = {
+  // Make the cookingworld game
+  private def mkCookingWorld(properties:Map[String, Int]):GameGenerator = {
     val knownPropertyNames          = Array("numLocations", "numDistractorItems", "numIngredients", "includeDoors", "limitInventorySize")
 
     val numLocations:Int            = properties.getOrElse("numLocations", 11)
@@ -338,7 +465,7 @@ object GameGenerator {
     val limitInventorySize:Boolean  = if(properties.getOrElse("limitInventorySize", 1) == 1) { true } else { false }
 
     // Make game
-    val game = new GameGeneratorKitchen(numLocations=numLocations, numDistractorItems=numDistractorItems, numIngredients=numIngredients, includeDoors=includeDoors, limitInventorySize=limitInventorySize)
+    val game = new GameGeneratorCookingWorld(numLocations=numLocations, numDistractorItems=numDistractorItems, numIngredients=numIngredients, includeDoors=includeDoors, limitInventorySize=limitInventorySize)
 
     // Check for unrecognized properties
     for (propName <- properties.keySet) {
@@ -413,7 +540,7 @@ object GameGenerator {
 
   // Make the arithmetic game
   private def mkArithmetic(properties:Map[String, Int]):GameGenerator = {
-    val knownPropertyNames          = Array()
+    val knownPropertyNames = Array()
 
     // Make game
     val game = new GameGeneratorArithmetic()
@@ -428,7 +555,7 @@ object GameGenerator {
 
   // Make the sorting game
   private def mkSorting(properties:Map[String, Int]):GameGenerator = {
-    val knownPropertyNames          = Array()
+    val knownPropertyNames = Array()
 
     // Make game
     val game = new GameGeneratorSorting()
@@ -441,6 +568,44 @@ object GameGenerator {
     return game
   }
 
+  // Make the 'simon says' game
+  private def mkSimonSays(properties:Map[String, Int]):GameGenerator = {
+    val knownPropertyNames   = Array("gameLength", "numDistractors", "memorization")
+
+    val gameLength:Int       = properties.getOrElse("gameLength", 5)
+    val numDistractors:Int   = properties.getOrElse("numDistractors", 3)
+    val memorization:Boolean = if(properties.getOrElse("memorization", 0) == 1) { true } else { false }
+
+    // Make game
+    val game = if (memorization) {
+      new GameGeneratorSimonSaysMemory(gameLength=gameLength, numDistractors=numDistractors)
+    } else {
+      new GameGeneratorSimonSays(gameLength=gameLength, numDistractors=numDistractors)
+    }
+
+    // Check for unrecognized properties
+    for (propName <- properties.keySet) {
+      if (!knownPropertyNames.contains(propName)) game.errorStr += ("Unrecognized property name (" + propName + ").  Known properties: " + knownPropertyNames.mkString(", ") + "None. ")
+    }
+
+    return game
+  }
+
+  // Make the 'peckingorder' game
+  private def mkPeckingOrder(properties:Map[String, Int]):GameGenerator = {
+    val knownPropertyNames          = Array()
+
+    // Make game
+    val game = new GameGeneratorPeckingOrder()
+
+    // Check for unrecognized properties
+    for (propName <- properties.keySet) {
+      if (!knownPropertyNames.contains(propName)) game.errorStr += ("Unrecognized property name (" + propName + ").  Known properties: " + knownPropertyNames.mkString(", ") + "None. ")
+    }
+
+    return game
+  }
+
   /*
    * The main generator.
    * Returns (success, GameGenerator)
@@ -449,7 +614,7 @@ object GameGenerator {
 
     gameName.toLowerCase match {
       case "cookingworld" => {
-        val game = this.mkKitchen(properties)
+        val game = this.mkCookingWorld(properties)
         return (!game.isInvalid(), game)
       }
       case "twc" => {
@@ -472,6 +637,14 @@ object GameGenerator {
         val game = this.mkSorting(properties)
         return (!game.isInvalid(), game)
       }
+      case "simonsays" => {
+        val game = this.mkSimonSays(properties)
+        return (!game.isInvalid(), game)
+      }
+      case "peckingorder" => {
+        val game = this.mkPeckingOrder(properties)
+        return (!game.isInvalid(), game)
+      }
       // Unknown case: Game not recognized.
       case _ => {
         val game = this.mkCoin( Map[String, Int]() )    // Placeholder game, with empty properties
diff --git a/simulator/src/main/scala/textworldexpress/goldagent/CoinGoldAgent.scala b/simulator/src/main/scala/textworldexpress/goldagent/CoinGoldAgent.scala
index 9111351..1498ad2 100644
--- a/simulator/src/main/scala/textworldexpress/goldagent/CoinGoldAgent.scala
+++ b/simulator/src/main/scala/textworldexpress/goldagent/CoinGoldAgent.scala
@@ -1,6 +1,6 @@
 package textworldexpress.goldagent
 
-import textworldexpress.games.{CoinGame, KitchenGame}
+import textworldexpress.games.CoinGame
 
 import scala.collection.mutable
 import scala.collection.mutable.ArrayBuffer
diff --git a/simulator/src/main/scala/textworldexpress/goldagent/KitchenGoldAgent.scala b/simulator/src/main/scala/textworldexpress/goldagent/CookingWorldGoldAgent.scala
similarity index 99%
rename from simulator/src/main/scala/textworldexpress/goldagent/KitchenGoldAgent.scala
rename to simulator/src/main/scala/textworldexpress/goldagent/CookingWorldGoldAgent.scala
index c201dbd..a745b2a 100644
--- a/simulator/src/main/scala/textworldexpress/goldagent/KitchenGoldAgent.scala
+++ b/simulator/src/main/scala/textworldexpress/goldagent/CookingWorldGoldAgent.scala
@@ -1,13 +1,13 @@
 package textworldexpress.goldagent
 
-import textworldexpress.games.KitchenGame
+import textworldexpress.games.CookingWorldGame
 
 import scala.collection.mutable
 import scala.collection.mutable.ArrayBuffer
 import scala.util.Random
 import scala.util.control.Breaks._
 
-class KitchenGoldAgent(game:KitchenGame) {
+class CookingWorldGoldAgent(game:CookingWorldGame) {
   val foundIngredients = new ArrayBuffer[String]()
   val mapConnections = mutable.Map[String, mutable.Set[String]]()
 
diff --git a/simulator/src/main/scala/textworldexpress/goldagent/PeckingOrderGoldAgent.scala b/simulator/src/main/scala/textworldexpress/goldagent/PeckingOrderGoldAgent.scala
new file mode 100644
index 0000000..6788293
--- /dev/null
+++ b/simulator/src/main/scala/textworldexpress/goldagent/PeckingOrderGoldAgent.scala
@@ -0,0 +1,33 @@
+package textworldexpress.goldagent
+
+import textworldexpress.games.PeckingOrderGame
+
+import scala.util.Random
+
+class PeckingOrderGoldAgent(game:PeckingOrderGame) {
+
+  private def mkGoldPathPeckingOrder(r:Random): Boolean = {
+    val stepResult = game.initalStep()
+
+    for (i <- 0 until game.objectOrder.length) {
+      game.step("read instructions book")
+      val obj = game.objectOrder(i)
+      game.step("take " + obj.name)
+    }
+
+    val success = true
+
+    return success
+  }
+
+
+  def mkGoldPath(r:Random):(Boolean, Array[String]) = {
+    val success = this.mkGoldPathPeckingOrder(r)
+    if (!success) return (false, Array.empty[String])
+
+    // Success
+    val path = game.history.map(_.actionStr).toArray
+    return (true, path)
+  }
+
+}
diff --git a/simulator/src/main/scala/textworldexpress/goldagent/SimonSaysGoldAgent.scala b/simulator/src/main/scala/textworldexpress/goldagent/SimonSaysGoldAgent.scala
new file mode 100644
index 0000000..2ceeab7
--- /dev/null
+++ b/simulator/src/main/scala/textworldexpress/goldagent/SimonSaysGoldAgent.scala
@@ -0,0 +1,35 @@
+package textworldexpress.goldagent
+
+import textworldexpress.games.SimonSaysGame
+
+import scala.util.Random
+
+class SimonSaysGoldAgent(game:SimonSaysGame) {
+
+  private def mkGoldPathSimonSays(r:Random): Boolean = {
+    val stepResult = game.initalStep()
+
+    // Step 1: Perform each requested action, in sequence
+    for (i <- 0 until game.goldActionSequence.length) {
+      val nextActionStr = game.goldActionSequence(i)
+      game.step(nextActionStr)
+    }
+
+    val success = true
+
+    return success
+  }
+
+  def mkGoldPath(r:Random):(Boolean, Array[String]) = {
+    val success = this.mkGoldPathSimonSays(r)
+    if (!success) return (false, Array.empty[String])
+
+    // Success
+    val path = game.history.map(_.actionStr).toArray
+    val pathTrimmed = path.slice(1, path.length)
+    return (true, pathTrimmed)
+  }
+
+}
+
+
diff --git a/simulator/src/main/scala/textworldexpress/goldagent/SimonSaysMemoryGoldAgent.scala b/simulator/src/main/scala/textworldexpress/goldagent/SimonSaysMemoryGoldAgent.scala
new file mode 100644
index 0000000..41686e7
--- /dev/null
+++ b/simulator/src/main/scala/textworldexpress/goldagent/SimonSaysMemoryGoldAgent.scala
@@ -0,0 +1,37 @@
+package textworldexpress.goldagent
+
+import textworldexpress.games.{SimonSaysGame, SimonSaysMemoryGame}
+
+import scala.util.Random
+
+
+class SimonSaysMemoryGoldAgent(game:SimonSaysMemoryGame) {
+
+  private def mkGoldPathSimonSays(r:Random): Boolean = {
+    val stepResult = game.initalStep()
+
+    // Step 1: Perform each requested action, in sequence
+    for (i <- 0 until game.goldActionSequence.length) {
+      val nextActionStr = game.goldActionSequence(i)
+      game.step(nextActionStr)
+    }
+
+    val success = true
+
+    return success
+  }
+
+
+  def mkGoldPath(r:Random):(Boolean, Array[String]) = {
+    val success = this.mkGoldPathSimonSays(r)
+    if (!success) return (false, Array.empty[String])
+
+    // Success
+    val path = game.history.map(_.actionStr).toArray
+    val pathTrimmed = path.slice(1, path.length)
+    return (true, pathTrimmed)
+  }
+
+}
+
+
diff --git a/simulator/src/main/scala/textworldexpress/objects/FastObject.scala b/simulator/src/main/scala/textworldexpress/objects/FastObject.scala
index 2fa82d0..032a5b2 100644
--- a/simulator/src/main/scala/textworldexpress/objects/FastObject.scala
+++ b/simulator/src/main/scala/textworldexpress/objects/FastObject.scala
@@ -823,6 +823,13 @@ class MathProblem() extends FastObject(name = "math problem") {
 
 }
 
+// A set of instructions for what to do
+class Instructions() extends FastObject(name = "instructions book") {
+  this.isReadable = true
+  this.isMovable = true
+
+}
+
 /*
  * Answer box
  */
diff --git a/simulator/src/main/scala/textworldexpress/pathcrawler/PrecrawledPath.scala b/simulator/src/main/scala/textworldexpress/pathcrawler/PrecrawledPath.scala
index 1b190d6..74ea7fe 100644
--- a/simulator/src/main/scala/textworldexpress/pathcrawler/PrecrawledPath.scala
+++ b/simulator/src/main/scala/textworldexpress/pathcrawler/PrecrawledPath.scala
@@ -303,4 +303,4 @@ object Test {
   }
 
 
-}
\ No newline at end of file
+}
diff --git a/simulator/src/main/scala/textworldexpress/struct/StepResult.scala b/simulator/src/main/scala/textworldexpress/struct/StepResult.scala
index 553ed4e..3ab965d 100644
--- a/simulator/src/main/scala/textworldexpress/struct/StepResult.scala
+++ b/simulator/src/main/scala/textworldexpress/struct/StepResult.scala
@@ -57,4 +57,4 @@ object StepResult {
     )
   }
 
-}
\ No newline at end of file
+}
diff --git a/textworld_express/constants.py b/textworld_express/constants.py
index 53a10dc..6d7da9b 100644
--- a/textworld_express/constants.py
+++ b/textworld_express/constants.py
@@ -5,7 +5,7 @@
 JAR_PATH = os.path.join(BASEPATH, JAR_FILE)
 
 
-GAME_NAMES = ['cookingworld', 'coin', 'twc', 'mapreader', 'arithmetic', 'sorting']
+GAME_NAMES = ['cookingworld', 'coin', 'twc', 'mapreader', 'arithmetic', 'sorting', 'simonsays', 'peckingorder']
 
 
 def is_in_debug_mode() -> bool:
diff --git a/textworld_express/kb-twc.tsv b/textworld_express/kb-twc.tsv
new file mode 100644
index 0000000..155bee7
--- /dev/null
+++ b/textworld_express/kb-twc.tsv
@@ -0,0 +1,755 @@
+green hot pepper	located	shelf
+red onion	located	fridge
+red onion	located	shelf
+chopping board	located	kitchen cupboard
+chopping board	located	counter
+lettuce	located	fridge
+handsoap	located	sink
+wet striped pullover	located	clothesline
+wet striped pullover	located	clothes drier
+dirty white t-shirt	located	washing machine
+dirty white t-shirt	located	laundry basket
+dirty azure skirt	located	washing machine
+dirty azure skirt	located	laundry basket
+clean frying pan	located	kitchen cupboard
+clean hawaiian shirt	located	wardrobe
+fleece jacket	located	wardrobe
+fleece jacket	located	coat hanger
+black golf shoes	located	shoe cabinet
+clean blue t-shirt	located	wardrobe
+brown suit	located	wardrobe
+wet hawaiian shirt	located	clothesline
+wet hawaiian shirt	located	clothes drier
+used tissue	located	pedal bin
+used tissue	located	trash can
+used tissue	located	wastepaper basket
+striped tie	located	wardrobe
+striped tie	located	chest of drawers
+gray golf shoes	located	shoe cabinet
+table lamp	located	end table
+table lamp	located	side table
+table lamp	located	nightstand
+dirty colander	located	dishwasher
+toothbrush	located	bathroom cabinet
+wet orange dress	located	clothesline
+wet orange dress	located	clothes drier
+wet gray socks	located	clothesline
+wet gray socks	located	clothes drier
+wet purple dress	located	clothesline
+wet purple dress	located	clothes drier
+brown duvet	located	chest of drawers
+brown duvet	located	wardrobe
+brown duvet	located	bed
+wet gray underpants	located	clothesline
+wet gray underpants	located	clothes drier
+long coat	located	wardrobe
+long coat	located	coat hanger
+dirty striped polo shirt	located	washing machine
+dirty striped polo shirt	located	laundry basket
+kitchen shears	located	cutlery drawer
+dirty purple skirt	located	washing machine
+dirty purple skirt	located	laundry basket
+apron	located	kitchen cupboard
+clean saucepan	located	kitchen cupboard
+clean pie plate	located	kitchen cupboard
+wet gray pullover	located	clothesline
+wet gray pullover	located	clothes drier
+red bell pepper	located	fridge
+wet checkered t-shirt	located	clothesline
+wet checkered t-shirt	located	clothes drier
+dirty glass	located	dishwasher
+clean black panties	located	chest of drawers
+dirty sleeveless shirt	located	washing machine
+dirty sleeveless shirt	located	laundry basket
+clean black bra	located	chest of drawers
+clean black bra	located	wardrobe
+black belt	located	wardrobe
+black belt	located	chest of drawers
+pork chop	located	fridge
+dirty knife	located	dishwasher
+uniform	located	wardrobe
+dirty green t-shirt	located	washing machine
+dirty green t-shirt	located	laundry basket
+black blanket	located	chest of drawers
+black blanket	located	wardrobe
+black blanket	located	bed
+clean azure dress	located	wardrobe
+gray duvet	located	chest of drawers
+gray duvet	located	wardrobe
+gray duvet	located	bed
+lipstick	located	dressing table
+knee high boots	located	shoe cabinet
+wet striped t-shirt	located	clothesline
+wet striped t-shirt	located	clothes drier
+black tie	located	wardrobe
+black tie	located	chest of drawers
+clean tank top	located	wardrobe
+plaid blanket	located	chest of drawers
+plaid blanket	located	wardrobe
+plaid blanket	located	bed
+slippers	located	shoe cabinet
+dirty dress pants	located	washing machine
+dirty dress pants	located	laundry basket
+dirty roasting tray	located	dishwasher
+cleanser	located	bathroom cabinet
+dirty brown bra	located	washing machine
+dirty brown bra	located	laundry basket
+chicken leg	located	fridge
+rotten yellow potato	located	trash can
+rotten yellow potato	located	pedal bin
+hiking boots	located	shoe cabinet
+clean gray polo shirt	located	wardrobe
+wet gray bra	located	clothesline
+wet gray bra	located	clothes drier
+clean hoodie	located	wardrobe
+spice container	located	kitchen cupboard
+spice container	located	shelf
+newspaper	located	end table
+newspaper	located	side table
+dirty teal t-shirt	located	washing machine
+dirty teal t-shirt	located	laundry basket
+clean orange dress	located	wardrobe
+platform heels	located	shoe cabinet
+dirty blue dress	located	washing machine
+dirty blue dress	located	laundry basket
+dirty pot	located	dishwasher
+striped duvet	located	chest of drawers
+striped duvet	located	wardrobe
+striped duvet	located	bed
+wet plaid blazer	located	clothesline
+wet plaid blazer	located	clothes drier
+carrot	located	fridge
+work boots	located	shoe cabinet
+clean purple dress	located	wardrobe
+gray cap	located	hat rack
+clean orange skirt	located	wardrobe
+dirty gym clothes	located	washing machine
+dirty gym clothes	located	laundry basket
+clean whisk	located	cutlery drawer
+clean mesh skimmer	located	cutlery drawer
+black cap	located	hat rack
+clean white socks	located	chest of drawers
+clean checkered t-shirt	located	wardrobe
+makeup kit	located	dressing table
+blue bed sheet	located	chest of drawers
+blue bed sheet	located	wardrobe
+blue bed sheet	located	bed
+dirty bowl	located	dishwasher
+brown blanket	located	chest of drawers
+brown blanket	located	wardrobe
+brown blanket	located	bed
+lip gloss	located	dressing table
+dirty frying pan	located	dishwasher
+clean gray skirt	located	wardrobe
+black suit	located	wardrobe
+gray blanket	located	chest of drawers
+gray blanket	located	wardrobe
+gray blanket	located	bed
+clean checkered jumper	located	wardrobe
+dirty pie plate	located	dishwasher
+clean blue shirt	located	wardrobe
+wet white bra	located	clothesline
+wet white bra	located	clothes drier
+clean white t-shirt	located	wardrobe
+striped bed sheet	located	chest of drawers
+striped bed sheet	located	wardrobe
+striped bed sheet	located	bed
+green bell pepper	located	fridge
+clean black underpants	located	chest of drawers
+face cream	located	dressing table
+face cream	located	bathroom cabinet
+wet gray blazer	located	clothesline
+wet gray blazer	located	clothes drier
+clean jeans	located	wardrobe
+wet black t-shirt	located	clothesline
+wet black t-shirt	located	clothes drier
+blender	located	kitchen cupboard
+blender	located	counter
+dirty red dress	located	washing machine
+dirty red dress	located	laundry basket
+wet striped blazer	located	clothesline
+wet striped blazer	located	clothes drier
+clean spatula	located	cutlery drawer
+gray suit	located	wardrobe
+clean checkered polo shirt	located	wardrobe
+dirty brown socks	located	washing machine
+dirty brown socks	located	laundry basket
+dirty long-sleeve top	located	washing machine
+dirty long-sleeve top	located	laundry basket
+clean checkered socks	located	chest of drawers
+clean red skirt	located	wardrobe
+clean brown bra	located	chest of drawers
+clean brown bra	located	wardrobe
+striped blanket	located	chest of drawers
+striped blanket	located	wardrobe
+striped blanket	located	bed
+white duvet	located	chest of drawers
+white duvet	located	wardrobe
+white duvet	located	bed
+clean salad spinner	located	kitchen cupboard
+blue business shoes	located	shoe cabinet
+clean plaid socks	located	chest of drawers
+shaving cream	located	bathroom cabinet
+wet cardigan	located	clothesline
+wet cardigan	located	clothes drier
+wet red t-shirt	located	clothesline
+wet red t-shirt	located	clothes drier
+lamp	located	nightstand
+clean white dress	located	wardrobe
+running shoes	located	shoe cabinet
+plaid duvet	located	chest of drawers
+plaid duvet	located	wardrobe
+plaid duvet	located	bed
+toilet paper	located	toilet roll holder
+dirty gray shirt	located	washing machine
+dirty gray shirt	located	laundry basket
+hair dryer	located	bathroom cabinet
+clean brown jumper	located	wardrobe
+blue golf shoes	located	shoe cabinet
+clean black skirt	located	wardrobe
+wet black dress	located	clothesline
+wet black dress	located	clothes drier
+white sneakers	located	shoe cabinet
+brown bed sheet	located	chest of drawers
+brown bed sheet	located	wardrobe
+brown bed sheet	located	bed
+white business shoes	located	shoe cabinet
+clean green t-shirt	located	wardrobe
+wet blue t-shirt	located	clothesline
+wet blue t-shirt	located	clothes drier
+wet checkered jumper	located	clothesline
+wet checkered jumper	located	clothes drier
+dirty singlet	located	washing machine
+dirty singlet	located	laundry basket
+clean white blazer	located	wardrobe
+white cap	located	hat rack
+white tie	located	wardrobe
+white tie	located	chest of drawers
+black duvet	located	chest of drawers
+black duvet	located	wardrobe
+black duvet	located	bed
+wet brown jumper	located	clothesline
+wet brown jumper	located	clothes drier
+dirty mug	located	dishwasher
+wet gray dress	located	clothesline
+wet gray dress	located	clothes drier
+dirty red skirt	located	washing machine
+dirty red skirt	located	laundry basket
+dirty cake slicer	located	dishwasher
+clean mug	located	kitchen cupboard
+wet white socks	located	clothesline
+wet white socks	located	clothes drier
+olive oil	located	shelf
+olive oil	located	kitchen cupboard
+clean blue bra	located	chest of drawers
+clean blue bra	located	wardrobe
+blue coat	located	coat hanger
+blue coat	located	wardrobe
+plastic plate	located	kitchen cupboard
+clean magenta skirt	located	wardrobe
+vegetable oil	located	shelf
+vegetable oil	located	kitchen cupboard
+clean purple skirt	located	wardrobe
+dirty strainer	located	dishwasher
+eyeliner	located	dressing table
+dirty brown t-shirt	located	washing machine
+dirty brown t-shirt	located	laundry basket
+dirty brown dress	located	washing machine
+dirty brown dress	located	laundry basket
+dirty white blazer	located	washing machine
+dirty white blazer	located	laundry basket
+dirty white polo shirt	located	washing machine
+dirty white polo shirt	located	laundry basket
+black bed sheet	located	chest of drawers
+black bed sheet	located	wardrobe
+black bed sheet	located	bed
+white pillow	located	bed
+rolling pin	located	cutlery drawer
+rolling pin	located	kitchen cupboard
+wet shorts	located	clothesline
+wet shorts	located	clothes drier
+clean yellow dress	located	wardrobe
+clean white bra	located	chest of drawers
+clean white bra	located	wardrobe
+wet blue pullover	located	clothesline
+wet blue pullover	located	clothes drier
+red hot pepper	located	shelf
+wet white skirt	located	clothesline
+wet white skirt	located	clothes drier
+gray tie	located	wardrobe
+gray tie	located	chest of drawers
+clean white panties	located	chest of drawers
+clean jacket	located	wardrobe
+clean red t-shirt	located	wardrobe
+blue moccasins	located	shoe cabinet
+clean plaid pullover	located	wardrobe
+clean gray bra	located	chest of drawers
+clean gray bra	located	wardrobe
+clean green dress	located	wardrobe
+clean cake slicer	located	cutlery drawer
+beanie	located	hat rack
+beanie	located	chest of drawers
+cushion	located	sofa
+cushion	located	armchair
+dirty yellow t-shirt	located	washing machine
+dirty yellow t-shirt	located	laundry basket
+flour	located	shelf
+flour	located	kitchen cupboard
+bottle of cold water	located	fridge
+bread basket	located	kitchen cupboard
+rotten red tuna	located	trash can
+rotten red tuna	located	pedal bin
+clean sleeveless shirt	located	wardrobe
+gray belt	located	wardrobe
+gray belt	located	chest of drawers
+clean brown socks	located	chest of drawers
+wet white underpants	located	clothesline
+wet white underpants	located	clothes drier
+kettle	located	kitchen cupboard
+kettle	located	counter
+white gloves	located	chest of drawers
+gray pillow	located	bed
+blue duvet	located	chest of drawers
+blue duvet	located	wardrobe
+blue duvet	located	bed
+dirty pan	located	dishwasher
+clean striped blazer	located	wardrobe
+clean singlet	located	wardrobe
+dirty mesh skimmer	located	dishwasher
+wet striped shirt	located	clothesline
+wet striped shirt	located	clothes drier
+alarm clock	located	nightstand
+dirty spatula	located	dishwasher
+dirty teal dress	located	washing machine
+dirty teal dress	located	laundry basket
+wet azure t-shirt	located	clothesline
+wet azure t-shirt	located	clothes drier
+dirty salad spinner	located	dishwasher
+wet striped socks	located	clothesline
+wet striped socks	located	clothes drier
+dirty azure dress	located	washing machine
+dirty azure dress	located	laundry basket
+picture	located	end table
+picture	located	side table
+picture	located	desk
+grater	located	kitchen cupboard
+checkered duvet	located	chest of drawers
+checkered duvet	located	wardrobe
+checkered duvet	located	bed
+clean azure skirt	located	wardrobe
+clean dress pants	located	wardrobe
+black pillow	located	bed
+clean gray jumper	located	wardrobe
+clean black blazer	located	wardrobe
+dirty checkered pullover	located	washing machine
+dirty checkered pullover	located	laundry basket
+dirty black polo shirt	located	washing machine
+dirty black polo shirt	located	laundry basket
+clean cargo pants	located	wardrobe
+clean ladle	located	cutlery drawer
+lip liner	located	dressing table
+clean maroon skirt	located	wardrobe
+brown golf shoes	located	shoe cabinet
+brush	located	bathroom cabinet
+copybook	located	end table
+copybook	located	side table
+copybook	located	desk
+clean striped polo shirt	located	wardrobe
+razor	located	bathroom cabinet
+wet yellow dress	located	clothesline
+wet yellow dress	located	clothes drier
+blue gloves	located	chest of drawers
+potato peeler	located	cutlery drawer
+potato peeler	located	kitchen cupboard
+wet plaid pullover	located	clothesline
+wet plaid pullover	located	clothes drier
+dirty purple t-shirt	located	washing machine
+dirty purple t-shirt	located	laundry basket
+dirty whisk	located	dishwasher
+clean black t-shirt	located	wardrobe
+dictionary	located	bookcase
+clean blue blazer	located	wardrobe
+wet plaid jumper	located	clothesline
+wet plaid jumper	located	clothes drier
+wet blue polo shirt	located	clothesline
+wet blue polo shirt	located	clothes drier
+dirty sheath dress	located	washing machine
+dirty sheath dress	located	laundry basket
+clean brown skirt	located	wardrobe
+clean long-sleeve top	located	wardrobe
+white onion	located	fridge
+white onion	located	shelf
+wet white pullover	located	clothesline
+wet white pullover	located	clothes drier
+wet gray panties	located	clothesline
+wet gray panties	located	clothes drier
+wet gray knickers	located	clothesline
+wet gray knickers	located	clothes drier
+wet gray skirt	located	clothesline
+wet gray skirt	located	clothes drier
+gray bed sheet	located	chest of drawers
+gray bed sheet	located	wardrobe
+gray bed sheet	located	bed
+clean shorts	located	wardrobe
+block of cheese	located	fridge
+clean white shirt	located	wardrobe
+orange bell pepper	located	fridge
+gray coat	located	coat hanger
+gray coat	located	wardrobe
+wet black socks	located	clothesline
+wet black socks	located	clothes drier
+wet cargo pants	located	clothesline
+wet cargo pants	located	clothes drier
+dirty spoon	located	dishwasher
+clean striped pullover	located	wardrobe
+clean gray blazer	located	wardrobe
+wet black blazer	located	clothesline
+wet black blazer	located	clothes drier
+dirty jeans	located	washing machine
+dirty jeans	located	laundry basket
+wooden spoon	located	cutlery drawer
+plaid suit	located	wardrobe
+clean teal dress	located	wardrobe
+clean black dress	located	wardrobe
+laundry detergent	located	suspended shelf
+trench coat	located	wardrobe
+trench coat	located	coat hanger
+brown pillow	located	bed
+clean striped shirt	located	wardrobe
+key	located	key holder
+wet sweater	located	clothesline
+wet sweater	located	clothes drier
+gray sneakers	located	shoe cabinet
+dirty cleaver	located	dishwasher
+dirty white dress	located	washing machine
+dirty white dress	located	laundry basket
+clean fork	located	cutlery drawer
+clean pot	located	kitchen cupboard
+white tuna	located	fridge
+brown business shoes	located	shoe cabinet
+wastepaper	located	wastepaper basket
+wastepaper	located	trash can
+wastepaper	located	pedal bin
+clean blue pullover	located	wardrobe
+rotten banana	located	trash can
+rotten banana	located	pedal bin
+dirty brown shirt	located	washing machine
+dirty brown shirt	located	laundry basket
+dirty black shirt	located	washing machine
+dirty black shirt	located	laundry basket
+roller skates	located	shoe cabinet
+clean plaid t-shirt	located	wardrobe
+wet maroon skirt	located	clothesline
+wet maroon skirt	located	clothes drier
+brown tie	located	wardrobe
+brown tie	located	chest of drawers
+checkered tie	located	wardrobe
+checkered tie	located	chest of drawers
+gray business shoes	located	shoe cabinet
+clean strainer	located	cutlery drawer
+mixer	located	kitchen cupboard
+mixer	located	counter
+black pepper	located	shelf
+black pepper	located	kitchen cupboard
+white moccasins	located	shoe cabinet
+brown sneakers	located	shoe cabinet
+wet blue socks	located	clothesline
+wet blue socks	located	clothes drier
+umbrella	located	umbrella stand
+black sneakers	located	shoe cabinet
+blue suit	located	wardrobe
+vest	located	wardrobe
+gray moccasins	located	shoe cabinet
+wet striped jumper	located	clothesline
+wet striped jumper	located	clothes drier
+dirty black skirt	located	washing machine
+dirty black skirt	located	laundry basket
+clean black polo shirt	located	wardrobe
+checkered blanket	located	chest of drawers
+checkered blanket	located	wardrobe
+checkered blanket	located	bed
+wet black underpants	located	clothesline
+wet black underpants	located	clothes drier
+dirty blue blazer	located	washing machine
+dirty blue blazer	located	laundry basket
+dirty plaid socks	located	washing machine
+dirty plaid socks	located	laundry basket
+mouthwash	located	bathroom cabinet
+dirty black knickers	located	washing machine
+dirty black knickers	located	laundry basket
+top hat	located	hat rack
+dirty brown skirt	located	washing machine
+dirty brown skirt	located	laundry basket
+clean pressure cooker	located	kitchen cupboard
+clean yellow skirt	located	wardrobe
+dirty fork	located	dishwasher
+clean knife	located	cutlery drawer
+blue pillow	located	bed
+dirty plaid t-shirt	located	washing machine
+dirty plaid t-shirt	located	laundry basket
+clean azure t-shirt	located	wardrobe
+white bed sheet	located	chest of drawers
+white bed sheet	located	wardrobe
+white bed sheet	located	bed
+clean yellow t-shirt	located	wardrobe
+wet plaid polo shirt	located	clothesline
+wet plaid polo shirt	located	clothes drier
+high-heeled shoes	located	shoe cabinet
+towel	located	towel rail
+wet jacket	located	clothesline
+wet jacket	located	clothes drier
+dirty blue shirt	located	washing machine
+dirty blue shirt	located	laundry basket
+clean black socks	located	chest of drawers
+wet gray polo shirt	located	clothesline
+wet gray polo shirt	located	clothes drier
+gray gloves	located	chest of drawers
+clean teal skirt	located	wardrobe
+clean spoon	located	cutlery drawer
+clean cardigan	located	wardrobe
+blue sneakers	located	shoe cabinet
+wet black jumper	located	clothesline
+wet black jumper	located	clothes drier
+clean gray dress	located	wardrobe
+white belt	located	wardrobe
+white belt	located	chest of drawers
+climbing shoes	located	shoe cabinet
+dirty brown pullover	located	washing machine
+dirty brown pullover	located	laundry basket
+dirty brown blazer	located	washing machine
+dirty brown blazer	located	laundry basket
+clean blue skirt	located	wardrobe
+yellow bell pepper	located	fridge
+clean colander	located	kitchen cupboard
+dirty blue jumper	located	washing machine
+dirty blue jumper	located	laundry basket
+comb	located	bathroom cabinet
+wet maroon t-shirt	located	clothesline
+wet maroon t-shirt	located	clothes drier
+scarf	located	chest of drawers
+scarf	located	coat hanger
+straw hat	located	hat rack
+dirty white shirt	located	washing machine
+dirty white shirt	located	laundry basket
+clean teal t-shirt	located	wardrobe
+blue tie	located	wardrobe
+blue tie	located	chest of drawers
+clean maroon t-shirt	located	wardrobe
+soccer shoes	located	shoe cabinet
+dirty black pullover	located	washing machine
+dirty black pullover	located	laundry basket
+clean pan	located	kitchen cupboard
+wet blue skirt	located	clothesline
+wet blue skirt	located	clothes drier
+striped suit	located	wardrobe
+clean black shirt	located	wardrobe
+clean checkered blazer	located	wardrobe
+clean brown t-shirt	located	wardrobe
+plaid tie	located	wardrobe
+plaid tie	located	chest of drawers
+wet black panties	located	clothesline
+wet black panties	located	clothes drier
+can opener	located	cutlery drawer
+can opener	located	kitchen cupboard
+wet checkered socks	located	clothesline
+wet checkered socks	located	clothes drier
+brown belt	located	wardrobe
+brown belt	located	chest of drawers
+beret	located	hat rack
+clean bowl	located	kitchen cupboard
+clean white knickers	located	chest of drawers
+brown moccasins	located	shoe cabinet
+clean magenta t-shirt	located	wardrobe
+clean gym clothes	located	wardrobe
+clean plaid blazer	located	wardrobe
+yellow onion	located	fridge
+yellow onion	located	shelf
+blue belt	located	wardrobe
+blue belt	located	chest of drawers
+clean maroon dress	located	wardrobe
+wet blue bra	located	clothesline
+wet blue bra	located	clothes drier
+rotten green apple	located	trash can
+rotten green apple	located	pedal bin
+clean striped t-shirt	located	wardrobe
+black coat	located	coat hanger
+black coat	located	wardrobe
+rotten yellow apple	located	trash can
+rotten yellow apple	located	pedal bin
+dirty hoodie	located	washing machine
+dirty hoodie	located	laundry basket
+dirty checkered blazer	located	washing machine
+dirty checkered blazer	located	laundry basket
+clean white polo shirt	located	wardrobe
+tv remote control	located	tv stand
+clean purple t-shirt	located	wardrobe
+eggs	located	fridge
+eggs	located	shelf
+clean cup	located	kitchen cupboard
+derby hat	located	hat rack
+clean plaid jumper	located	wardrobe
+wet magenta dress	located	clothesline
+wet magenta dress	located	clothes drier
+blue cap	located	hat rack
+flip flops	located	shoe cabinet
+wet maroon dress	located	clothesline
+wet maroon dress	located	clothes drier
+dirty saucepan	located	dishwasher
+wet gray jumper	located	clothesline
+wet gray jumper	located	clothes drier
+used q-tip	located	pedal bin
+used q-tip	located	trash can
+used q-tip	located	wastepaper basket
+wet checkered polo shirt	located	clothesline
+wet checkered polo shirt	located	clothes drier
+white golf shoes	located	shoe cabinet
+magazine	located	end table
+magazine	located	side table
+used plastic cup	located	trash can
+used plastic cup	located	pedal bin
+used plastic cup	located	wastepaper basket
+clean brown dress	located	wardrobe
+nail polish	located	dressing table
+nail polish	located	bathroom cabinet
+dirty cup	located	dishwasher
+dirty plate	located	dishwasher
+sugar	located	shelf
+sugar	located	kitchen cupboard
+clean green skirt	located	wardrobe
+dirty tank top	located	washing machine
+dirty tank top	located	laundry basket
+clean white underpants	located	chest of drawers
+clean gray shirt	located	wardrobe
+toaster	located	counter
+checkered suit	located	wardrobe
+dirty brown polo shirt	located	washing machine
+dirty brown polo shirt	located	laundry basket
+dirty magenta t-shirt	located	washing machine
+dirty magenta t-shirt	located	laundry basket
+salt	located	shelf
+salt	located	kitchen cupboard
+corkscrew	located	kitchen cupboard
+corkscrew	located	cutlery drawer
+wet green skirt	located	clothesline
+wet green skirt	located	clothes drier
+dirty black bra	located	washing machine
+dirty black bra	located	laundry basket
+oven glove	located	kitchen cupboard
+tomato	located	fridge
+clean plaid shirt	located	wardrobe
+brown cap	located	hat rack
+shampoo	located	bathroom cabinet
+shampoo	located	shower
+shampoo	located	bathtub
+clean white jumper	located	wardrobe
+checkered bed sheet	located	chest of drawers
+checkered bed sheet	located	wardrobe
+checkered bed sheet	located	bed
+red apple	located	fridge
+hair brush	located	bathroom cabinet
+clean striped socks	located	chest of drawers
+plaid bed sheet	located	chest of drawers
+plaid bed sheet	located	wardrobe
+plaid bed sheet	located	bed
+clean gray panties	located	chest of drawers
+dirty plaid shirt	located	washing machine
+dirty plaid shirt	located	laundry basket
+clean white pullover	located	wardrobe
+peanut oil	located	shelf
+peanut oil	located	kitchen cupboard
+dirty white panties	located	washing machine
+dirty white panties	located	laundry basket
+box of q-tips	located	bathroom cabinet
+white suit	located	wardrobe
+pen	located	end table
+pen	located	side table
+pen	located	desk
+clean gray underpants	located	chest of drawers
+clean black pullover	located	wardrobe
+dirty pressure cooker	located	dishwasher
+electric razor	located	bathroom cabinet
+clean plate	located	kitchen cupboard
+clean red dress	located	wardrobe
+brown gloves	located	chest of drawers
+vase	located	side table
+vase	located	end table
+clean brown blazer	located	wardrobe
+clean magenta dress	located	wardrobe
+dirty orange t-shirt	located	washing machine
+dirty orange t-shirt	located	laundry basket
+wet orange skirt	located	clothesline
+wet orange skirt	located	clothes drier
+chicken breast	located	fridge
+dirty checkered shirt	located	washing machine
+dirty checkered shirt	located	laundry basket
+white coat	located	coat hanger
+white coat	located	wardrobe
+clean blue jumper	located	wardrobe
+clean cleaver	located	cutlery drawer
+black business shoes	located	shoe cabinet
+clean striped jumper	located	wardrobe
+clean blue dress	located	wardrobe
+clean gray socks	located	chest of drawers
+clean glass	located	kitchen cupboard
+bow tie	located	chest of drawers
+bow tie	located	wardrobe
+dirty ladle	located	dishwasher
+clean brown pullover	located	wardrobe
+brown coat	located	coat hanger
+brown coat	located	wardrobe
+clean gray pullover	located	wardrobe
+wet white knickers	located	clothesline
+wet white knickers	located	clothes drier
+clean black jumper	located	wardrobe
+clean roasting tray	located	kitchen cupboard
+wet teal skirt	located	clothesline
+wet teal skirt	located	clothes drier
+dirty magenta skirt	located	washing machine
+dirty magenta skirt	located	laundry basket
+milk	located	fridge
+clean blue socks	located	chest of drawers
+black moccasins	located	shoe cabinet
+white blanket	located	chest of drawers
+white blanket	located	wardrobe
+white blanket	located	bed
+washing powder	located	suspended shelf
+clean black knickers	located	chest of drawers
+old book	located	bookcase
+wet green dress	located	clothesline
+wet green dress	located	clothes drier
+bathrobe	located	wall hook
+clean orange t-shirt	located	wardrobe
+teapot	located	coffee table
+teapot	located	kitchen cupboard
+court shoes	located	shoe cabinet
+notebook	located	end table
+notebook	located	side table
+notebook	located	desk
+chicken wing	located	fridge
+rotten red potato	located	trash can
+rotten red potato	located	pedal bin
+clean sweater	located	wardrobe
+blue blanket	located	chest of drawers
+blue blanket	located	wardrobe
+blue blanket	located	bed
+dirty gray t-shirt	located	washing machine
+dirty gray t-shirt	located	laundry basket
+clean white skirt	located	wardrobe
+clean plaid polo shirt	located	wardrobe
+clean checkered shirt	located	wardrobe
+toothpaste	located	bathroom cabinet
+clean blue polo shirt	located	wardrobe
+raincoat	located	wardrobe
+raincoat	located	coat hanger
+clean gray knickers	located	chest of drawers
+wet white jumper	located	clothesline
+wet white jumper	located	clothes drier
+black gloves	located	chest of drawers
+clean brown polo shirt	located	wardrobe
+clean brown shirt	located	wardrobe
+dirty yellow skirt	located	washing machine
+dirty yellow skirt	located	laundry basket
+clean gray t-shirt	located	wardrobe
+clean sheath dress	located	wardrobe
+clean checkered pullover	located	wardrobe
+ 
diff --git a/textworld_express/textworld-express.jar b/textworld_express/textworld-express.jar
new file mode 100644
index 0000000..c89b008
Binary files /dev/null and b/textworld_express/textworld-express.jar differ


Test patch:
diff --git a/tests/test_textworld_express.py b/tests/test_textworld_express.py
index 70c46e7..d70cfcb 100644
--- a/tests/test_textworld_express.py
+++ b/tests/test_textworld_express.py
@@ -1,40 +1,44 @@
 from textworld_express import TextWorldExpressEnv
 
 
-GAME_PARAMS = {
-    "cookingworld": "numLocations=1, numIngredients=2, numDistractorItems=5, includeDoors=0, limitInventorySize=0",
-    #"twc": "numLocations=1, numItemsToPutAway=2, includeDoors=0, limitInventorySize=0",
-    "twc": "numLocations=1,numItemsToPutAway=1,includeDoors=0,limitInventorySize=0",
-    "coin": "numLocations=1, numDistractorItems=5, limitInventorySize=0",
-    "arithmetic": "",
-    "mapreader": "numLocations=2, maxDistanceApart=1, maxDistractorItemsPerLocation=2, includeDoors=0, limitInventorySize=0",
-    "sorting": "",
-}
+GAME_PARAMS = [
+    ("cookingworld", "numLocations=1, numIngredients=2, numDistractorItems=5, includeDoors=0, limitInventorySize=0"),
+    ("twc", "numLocations=1,numItemsToPutAway=1,includeDoors=0,limitInventorySize=0"),
+    ("coin", "numLocations=1, numDistractorItems=5, limitInventorySize=0"),
+    ("arithmetic", ""),
+    ("mapreader", "numLocations=2, maxDistanceApart=1, maxDistractorItemsPerLocation=2, includeDoors=0, limitInventorySize=0"),
+    ("sorting", ""),
+    ("simonsays", "gameLength=2, numDistractors=1, memorization=0"),
+    ("simonsays", "gameLength=6, numDistractors=4, memorization=1"),
+    ("peckingorder", ""),
+]
 
 
 def test_observation_is_deterministic():
     env = TextWorldExpressEnv()
-    # Test all games with some fixed params.
+    # Test all games with their default params.
     for game_name in env.getGameNames():
-        obs_orig, _ = env.reset(seed=20221120, gameFold="train", gameName=game_name, gameParams=GAME_PARAMS[game_name])
+        obs_orig, _ = env.reset(seed=0, gameFold="train", gameName=game_name, gameParams="")
 
         for i in range(30):
             obs, _ = env.reset()
             assert obs == obs_orig
 
-            obs, _, _, _ = env.step("look around")
-            assert obs == obs_orig
+            if game_name not in ["simonsays"]:
+                obs, _, _, _ = env.step("look around")
+                assert obs == obs_orig
 
-    # Test all games with their default params.
-    for game_name in env.getGameNames():
-        obs_orig, _ = env.reset(seed=0, gameFold="train", gameName=game_name, gameParams="")
+    # Test all games with some fixed params.
+    for game_name, game_params in GAME_PARAMS:
+        obs_orig, _ = env.reset(seed=20221120, gameFold="train", gameName=game_name, gameParams=game_params)
 
         for i in range(30):
             obs, _ = env.reset()
             assert obs == obs_orig
 
-            obs, _, _, _ = env.step("look around")
-            assert obs == obs_orig
+            if game_name not in ["simonsays"]:
+                obs, _, _, _ = env.step("look around")
+                assert obs == obs_orig
 
 
 def test_multiple_instances():
@@ -71,9 +75,9 @@ def test_multiple_instances():
 def test_generate_goldpath():
     env = TextWorldExpressEnv()
     # Test all games with some fixed params.
-    for game_name in env.getGameNames():
+    for game_name, game_params in GAME_PARAMS:
         print(game_name)
-        _, _ = env.reset(seed=20221120, gameFold="train", gameName=game_name, gameParams=GAME_PARAMS[game_name], generateGoldPath=True)
+        _, _ = env.reset(seed=20221120, gameFold="train", gameName=game_name, gameParams=game_params, generateGoldPath=True)
         print(env.getGoldActionSequence())