{"action":"created","comment":{"url":"https://api.github.com/repos/gpoesia/comp4kids/pulls/comments/88460431","pull_request_review_id":9024130,"id":88460431,"diff_hunk":"@@ -0,0 +1,1173 @@\n+/*\n+ * comp4kids programming 101 lesson 4.\n+ */\n+\n+var React = require(\"react\");\n+var Lesson = require(\"./lesson\");\n+var Interpreter = require(\"../language/interpreter\")\n+var Animator = require(\"../util/animator\");\n+var ResourceLoader = require(\"../util/resource_loader\");\n+var AnimationFactories = require(\"../util/animator/animation_factories\");\n+var ElementFactories = require(\"../util/animator/element_factories\");\n+var Robolang = require(\"../language/robolang/robolang\");\n+var Constants = require(\"../constants\");\n+var Icons = require(\"../view/icons\");\n+var Grid = require(\"./utils/grid\");\n+\n+// Objects of this type indicate that some source is not producing any\n+// more items.\n+var SourceEnd = function() {};\n+\n+// Sources can be used to collect items from. They should have a `position`,\n+// and the list of items to be produced.\n+// Parameters:\n+//  `position`: Integer indicating the source's position.\n+//  `item_list`: The list of items to be produced by this source.\n+var Source = function(position, item_list) {\n+  this.position = position;\n+  this._initial_item_list = item_list;\n+  this.reset();\n+};\n+\n+Source.prototype = {\n+  // This function will push some item to the back of a source (FIFO).\n+  push: function(item) {\n+    return this.item_list.push(item);\n+  },\n+  // This function returns the item that is returned upon calling\n+  // this.generator_fxn, or an object of type SourceEnd if this.limit is\n+  // reached.\n+  pop: function() {\n+    if (this.item_list.length === 0)\n+      return new SourceEnd();\n+    return this.item_list.splice(0, 1)[0];\n+  },\n+  // Returns the item that would be popped from this source, without actually\n+  // removing it.\n+  peek: function() {\n+    if (this.item_list.length === 0)\n+      return new SourceEnd();\n+    return this.item_list[0];\n+  },\n+  // Resets the source to its initial state.\n+  reset: function() {\n+    this.item_list = this._initial_item_list.slice();\n+  }\n+};\n+\n+// A factory for predefined Source behaviors.\n+// Parameters:\n+//  `position`: The position of the source\n+//  `limit`: The production limit of the source\n+//  `type`: The type of the source; It can be either\n+//    \"random_from_set\": Will return an element, chosen at random, from a set\n+//      of item types. For this type of Souce, `parameters` is expected to\n+//      be an object containing the `item_set` property, with a list of items\n+//      to sample from.\n+//    \"from_list\": Will return elements from a list. If the list is shorter\n+//      than limit, the items in the list will be returned again in a\n+//      round-robin fashion. `parameters` is expected be an object containing\n+//      the `item_list` property.\n+//  `parameter`: The set of parameters required by the chosen type. This will\n+//    vary accordingly with the `type`, and therefore more usage details can\n+//    be found for each type of source.\n+var SourceFactory = function(position, limit, type, parameters) {\n+  switch (type) {\n+    case \"random_from_set\":\n+      var items = new Array(limit);\n+      for (var i = 0; i < limit; ++i) {\n+        items[i] = parameters.item_set[Math.floor(Math.random() *\n+                                       parameters.item_set.length)]\n+      }\n+      return new Source(position, items);\n+    case \"from_list\":\n+    default:\n+      var items = new Array(limit);\n+      for (var i = 0; i < limit; ++i) {\n+        items[i] = parameters.item_list[i % parameters.item_list.length];\n+      }\n+      return new Source(position, items);\n+  };\n+};\n+\n+// A Deposit is a place where items can be deposited. It should be passed\n+// a position and a set of accepted types.\n+// Parameters:\n+//  `position`: Integer indicating where the deposit should be placed.\n+//  `accepted_types`: Object containing each accepted type followed by a limit.\n+//    Example: { IRON: 10 }. In this example, this source (only) accepts IRON\n+//    items, with quantity up to 10 units.\n+var Deposit = function(position, accepted_types) {\n+  this.position = position;\n+  this.accepted_types = accepted_types;\n+  this.capacity = Object.assign({}, this.accepted_types);\n+};\n+\n+Deposit.Error = function(code) {\n+  this.code = code;\n+};\n+\n+Object.assign(Deposit.Error, {\n+  ITEM_TYPE_NOT_ACCEPTED: 1,\n+  CAPACITY_EXCEEDED: 2,\n+  NOTHING_TO_DEPOSIT: 3,\n+});\n+\n+Deposit.prototype = {\n+  // Pushes some item to the deposit, returning null if successful or some\n+  // error otherwise.\n+  push: function(item) {\n+    if (item === null)\n+      return new Deposit.Error(Deposit.Error.NOTHING_TO_DEPOSIT);\n+    if (!this.accepted_types.hasOwnProperty(item))\n+      return new Deposit.Error(Deposit.Error.ITEM_TYPE_NOT_ACCEPTED);\n+    if (this.capacity[item] == 0)\n+      return new Deposit.Error(Deposit.Error.CAPACITY_EXCEEDED);\n+    --this.capacity[item];\n+    return null;\n+  },\n+  // Returns true if the deposit is with its capacity full. This the deposit\n+  // cannot accomodate any more items. It will be true iff, for every item in\n+  // this.accepted_types, the corresponding item's limit for this deposit has\n+  // been reached.\n+  is_full: function() {\n+    for (var item in this.capacity) {\n+      if (this.capacity[item] > 0)\n+        return false;\n+    }\n+    return true;\n+  },\n+  // Restores this deposit to its full capacity\n+  reset: function() {\n+    this.capacity = Object.assign({}, this.accepted_types);\n+  },\n+};\n+\n+// A Machine turns a set of objects of some types into one object of another\n+// type, generally not the same as the deposited objects. The behavior is\n+// similar to that of a deposit - when depositing the items that will be\n+// transformed - and to that of a source - when collecting the produced item.\n+// Parameters:\n+//  `deposit_list`: a list of objects of type Deposit. The limits of the\n+//    accepted_items will determine the mixture (ratio of how much of some item\n+//    should be mixed with the other items in order to produce the final item)\n+//  `source_position`: The position of the source of the machine (where the\n+//    product is collected from).\n+//  `product_type`: The type of the item produced by the machine.\n+var Machine = function(deposit_list, source_position, product_type) {\n+  this.deposit_list = deposit_list;\n+  this.deposit_inverted_index = {};\n+  this.product_type = product_type;\n+  this.source = new Source(source_position, new Array());\n+  for (var i = 0; i < this.deposit_list.length; ++i) {\n+    this.deposit_inverted_index[this.deposit_list[i].position] = i;\n+  }\n+};\n+\n+Machine.Error = function(code) {\n+  this.code = code;\n+};\n+\n+Object.assign(Machine.Error, {\n+  INVALID_DEPOSIT_POSITION: 1,\n+});\n+\n+Machine.prototype = {\n+  // Behavior is similar to that of Deposit.push (unless there is no deposit at\n+  // deposit_position). The same Deposit.Errors that would be expected from\n+  // Deposit.push will be returned in case of errors. If the push is successful,\n+  // null is returned.\n+  push: function (deposit_position, item) {\n+    if (!this.deposit_inverted_index.hasOwnProperty(deposit_position))\n+      return new Machine.Error(Machine.Error.INVALID_DEPOSIT_POSITION);\n+    var deposit_idx = this.deposit_inverted_index[deposit_position];\n+    var result = this.deposit_list[deposit_idx].push(item)\n+    if (result !== null)\n+      return result;\n+    var every_deposit_full = true;\n+    for (var i = 0; i < this.deposit_list.length; ++i) {\n+      if (!this.deposit_list[i].is_full()) {\n+        every_deposit_full = false;\n+        break;\n+      }\n+    }\n+    if (every_deposit_full) {\n+      for (var i = 0; i < this.deposit_list.length; ++i) {\n+        this.deposit_list[i].reset();\n+      }\n+      this.source.push(this.product_type);\n+    }\n+    return result;\n+  },\n+  // The behavior is identical to that of Source.pop.\n+  pop: function() {\n+    var ret = this.source.pop();\n+    return ret;\n+  },\n+  // The behavior is identical to that of Source.peek.\n+  peek: function() {\n+    var ret = this.source.peek();\n+    return ret;\n+  },\n+  // Resets the machine to its initial state.\n+  reset: function() {\n+    for (var i = 0; i < this.deposit_list.length; ++i) {\n+      this.deposit_list[i].reset();\n+    }\n+    this.source.reset();\n+    this.product_list = new Array();\n+  },\n+};\n+\n+// In this game, a mechanical arm will have to detect the presence of a set of\n+// collectable items that come from a source. Those items will need to be\n+// deposited in item deposits, or will be converted into other items using\n+// machines.\n+//\n+// Parameters:\n+//  `size`: Size of the 1d space where the arm can move around collecting and\n+//    depositing items. Example: 10.\n+//  `arm_pos`: Initial position of the arm. Example: 0.\n+//  `sources`: List of Source objects from which the collectables can be\n+//    extracted.\n+//  `machines`: List of Machine objects that transform an item or set of items\n+//    into another set of items. Example:\n+//  `deposits`: List of Deposit objects to some item.\n+//  `goal`: The main goal of the game. If none specified, the goal is to make\n+//    every deposit full (deposits inside machines are NOT taken into account).\n+var Lesson04Game = function(size, arm_pos, sources, machines, deposits, goal) {\n+  this._size = size;\n+  this._initial_arm_pos = arm_pos;\n+  this.sources = sources;\n+  this.machines = machines;\n+  this.deposits = deposits;\n+  this.inverted_object_index = new Array(this._size);\n+  this.goal = goal || Lesson04Game.Goals.FILL_EVERY_DEPOSIT;\n+  this.holding_item = null;\n+  var initialize = function (){\n+    this.reset();\n+    for (var i = 0; i < this.sources.length; ++i) {\n+      if (this.sources[i].position >= this._size)\n+        throw new Error(\"Trying to put object at position greater than size\");\n+      if (!this.inverted_object_index[this.sources[i].position] === undefined)\n+        throw new Error(\"Trying to put more than one object at the same \"+\n+                        \"position\");\n+      this.inverted_object_index[this.sources[i].position] = this.sources[i];\n+    }\n+    for (var i = 0; i < this.deposits.length; ++i) {\n+      if (this.deposits[i].position >= this._size)\n+        throw new Error(\"Trying to put object at position greater than size\");\n+      if (!this.inverted_object_index[this.deposits[i].position] === undefined)\n+        throw new Error(\"Trying to put more than one object at the same \"+\n+                        \"position\");\n+      this.inverted_object_index[this.deposits[i].position] = this.deposits[i];\n+    }\n+    for (var i = 0; i < this.machines.length; ++i) {\n+      var object = this.machines[i].source;\n+      if (object.position >= this._size)\n+        throw new Error(\"Trying to put object at position greater than size\");\n+      if (!this.inverted_object_index[object.position] === undefined)\n+        throw new Error(\"Trying to put more than one object at the same \"+\n+                        \"position\");\n+      this.inverted_object_index[object.position] = this.machines[i];\n+      for (var j = 0; j < this.machines[i].deposit_list.length; ++j) {\n+        var object = this.machines[i].deposit_list[j];\n+        if (object.position >= this._size)\n+          throw new Error(\"Trying to put object at position greater than size\");\n+        if (!this.inverted_object_index[object.position] === undefined)\n+          throw new Error(\"Trying to put more than one object at the same \"+\n+                          \"position\");\n+        this.inverted_object_index[object.position] = this.machines[i];\n+      }\n+    }\n+  }\n+  initialize.call(this);\n+};\n+\n+Lesson04Game.Goals = {\n+  FILL_EVERY_DEPOSIT: 0,\n+  EVERY_MACHINE_SOURCE_NOT_EMPTY: 1,\n+};\n+\n+Lesson04Game.Error = function(code) {\n+  this.code = code;\n+};\n+\n+Object.assign(Lesson04Game.Error, {\n+  MOVED_OUT_OF_LIMITS: 1,\n+  COLLECTED_FROM_EMPTY_SOURCE: 2,\n+  COLLECTED_FROM_NO_SOURCE:3,\n+  COLLECTED_WHILE_HOLDING_ITEM: 4,\n+  DEPOSITED_ON_INVALID_LOCATION: 5,\n+});\n+\n+Lesson04Game.prototype = {\n+  // Moves the arm to the left, returning null if successful or some error\n+  // otherwise.\n+  moveLeft: function() {\n+    if (this.arm_pos - 1 < 0)\n+      return new Lesson04Game.Error(Lesson04Game.Error.MOVED_OUT_OF_LIMITS);\n+    --this.arm_pos;\n+    return null;\n+  },\n+  // Moves the arm to the right, returning null if successful or some error\n+  // otherwise.\n+  moveRight: function() {\n+    if (this.arm_pos + 1 >= this._size)\n+      return new Lesson04Game.Error(Lesson04Game.Error.MOVED_OUT_OF_LIMITS);\n+    ++this.arm_pos;\n+    return null;\n+  },\n+  // Collect from the position the arm is currently standing by, returning\n+  // null if successful (and transferring the item to the arm) of some error\n+  // otherwise.\n+  collect: function() {\n+    var source = this.inverted_object_index[this.arm_pos];\n+    if (!(source instanceof Source || source instanceof Machine))\n+      return new Lesson04Game.Error(\n+        Lesson04Game.Error.COLLECTED_FROM_NO_SOURCE);\n+    if (this.holding_item !== null)\n+      return new Lesson04Game.Error(\n+        Lesson04Game.Error.COLLECTED_WHILE_HOLDING_ITEM);\n+    var item = source.pop();\n+    if (item instanceof SourceEnd)\n+      return new Lesson04Game.Error(\n+        Lesson04Game.Error.COLLECTED_FROM_EMPTY_SOURCE);\n+    if (/SHIP/.test(item))\n+      return new Lesson04Game.Error(\n+        Lesson04Game.Error.COLLECTED_FROM_NO_SOURCE);\n+    this.holding_item = item;\n+    return null;\n+  },\n+  // If arm is currently standing by some source, return what would be\n+  // collected from it. Return null otherwise.\n+  peek: function() {\n+    var source = this.inverted_object_index[this.arm_pos];\n+    if (!(source instanceof Source || source instanceof Machine))\n+      return null;\n+    return source.peek();\n+  },\n+  // Deposit item into the position the arm is currently standing by, returning\n+  // null if successful (and transferring the item to the arm) of some error\n+  // otherwise.\n+  deposit: function() {\n+    var deposit = this.inverted_object_index[this.arm_pos];\n+    if (!(deposit instanceof Deposit || deposit instanceof Machine))\n+      return new Lesson04Game.Error(\n+        Lesson04Game.Error.DEPOSITED_ON_INVALID_LOCATION);\n+    var result = undefined;\n+    if (deposit instanceof Deposit)\n+      result = deposit.push(this.holding_item);\n+    else\n+      result = deposit.push(this.arm_pos, this.holding_item);\n+    if (result === null)\n+      this.holding_item = null;\n+    return result;\n+  },\n+  // Returns true if the game goal has been reached.\n+  reached_goal: function() {\n+    switch (this.goal) {\n+      case Lesson04Game.Goals.EVERY_MACHINE_SOURCE_NOT_EMPTY:\n+        for (var i = 0; i < this.machines.length; ++i)\n+          if (this.machines[i].peek() instanceof SourceEnd)\n+            return false;\n+        return true;\n+      case Lesson04Game.Goals.FILL_EVERY_DEPOSIT:\n+      default:\n+        for (var i = 0; i < this.deposits.length; ++i)\n+          if (!this.deposits[i].is_full())\n+            return false;\n+        return true;\n+    }\n+  },\n+  // Resets the game to its initial state.\n+  reset: function() {\n+    this.arm_pos = this._initial_arm_pos;\n+    this.holding_item = null;\n+    for (var i = 0; i < this.deposits.length; ++i)\n+      this.deposits[i].reset();\n+    for (var i = 0; i < this.sources.length; ++i)\n+      this.sources[i].reset()\n+    for (var i = 0; i < this.machines.length; ++i)\n+      this.machines[i].reset();\n+  },\n+\n+  // Returns the size of the game board.\n+  size: function() { return this._size; },\n+};\n+\n+var MAX_GRID_CELL_SIZE = Constants.RUN_VIEW_SQUARE_DIMENSION / 8;\n+\n+var Sensors = {\n+  PEEK: {\n+    IRON: {\n+      item_name: \"IRON\",\n+      variable_name: \"ferro\",\n+    },\n+  },\n+  SOLID: {\n+    accept_list: [\"IRON\", \"GLASS\"],\n+    variable_name: \"solido\"\n+  },\n+};\n+\n+// Actions supported in this lesson.\n+var Actions = {\n+  MOVE_LEFT: \"L\",\n+  MOVE_RIGHT: \"R\",\n+  GET_ITEM: \"G\",\n+  PUT_ITEM: \"P\",\n+};\n+\n+// Possible runtine errors.\n+var FailureReasons = {\n+  MISSION_UNFINISHED: 1,\n+};\n+\n+function Lesson04ExerciseStepPlayer(size, arm_pos, sources, machines,\n+                                    deposits, goal) {\n+  Lesson.LessonStepPlayer.call(this);\n+  this._game = new Lesson04Game(size, arm_pos, sources, machines, deposits,\n+                                goal);\n+  this._solved = false;\n+};\n+\n+Lesson04ExerciseStepPlayer.prototype = {\n+  reset: function(canvas) {\n+    this._animator = new Animator.Animator();\n+    this._game.reset();\n+    this._initializeElements()\n+\n+    if (canvas) {\n+      this._animator.render(canvas, 0);\n+    }\n+  },\n+  play: function(sourceCode) {\n+    this.reset();\n+\n+    var interpreter = new Robolang.Interpreter();\n+    var compilation_errors = interpreter.parse(sourceCode);\n+\n+    if (compilation_errors) {\n+      return {compilation_errors: compilation_errors};\n+    }\n+\n+    var runtime_errors = this._render(interpreter);\n+\n+    if (!runtime_errors.length) {\n+      this._solved = true;\n+    }\n+\n+    return {\n+      runtime_errors: runtime_errors,\n+      animator: this._animator,\n+    };\n+  },\n+  isInAcceptingState: function() {\n+    return this._solved;\n+  },\n+  _initializeElements: function() {\n+    this.grid_cell_size = Math.min(MAX_GRID_CELL_SIZE,\n+      Constants.RUN_VIEW_SQUARE_DIMENSION / this._game.size());\n+    var grid_cell_size = this.grid_cell_size;\n+\n+    var background = ElementFactories.createSpaceshipFactoryBackground(\n+        \"background\",\n+        Constants.RUN_VIEW_SQUARE_DIMENSION,\n+        Constants.RUN_VIEW_SQUARE_DIMENSION\n+        );\n+    background.x = Constants.RUN_VIEW_SQUARE_DIMENSION / 2;\n+    background.y = Constants.RUN_VIEW_SQUARE_DIMENSION / 2;\n+    this._animator.addElement(background);\n+\n+    var grid = new Animator.SimpleGridElement(\n+        \"grid\", Constants.RUN_VIEW_SQUARE_DIMENSION, 1,\n+         grid_cell_size, this._game.size());\n+    this._animator.addElement(grid);\n+    this._arm = ElementFactories.createRoboticArm(\n+        \"arm\",\n+        grid_cell_size, 3 * grid_cell_size);\n+    this._arm.x = (0.5 + this._game.arm_pos) * grid_cell_size;\n+    this._arm.y = 7 * grid_cell_size;\n+    this._animator.addElement(this._arm);\n+    this._animator.addAnimation(this._arm.createAnimation(\n+                                    \"move_right\", 0, 0, 1));\n+    for (var i = 0; i < this._game.deposits.length; ++i) {\n+      var component = this._game.deposits[i];\n+      var item_type = Object.keys(component.accepted_types)[0];\n+      var element = ElementFactories.future_createDeposit(\n+            \"d_\" + component.position,\n+            Object.keys(component.accepted_types)\n+              .map(function(key) { return component.accepted_types[key] })\n+              .reduce(function(a, b){ return a + b; }),\n+            item_type,\n+            grid_cell_size, 5*grid_cell_size);\n+      element.x = (0.5 + component.position) * grid_cell_size;\n+      element.y = 4 * grid_cell_size;\n+      this._animator.addElement(element);\n+      this._animator.addAnimation(element.changeStyle(item_type, 0));\n+      this._animator.addAnimation(element.createAnimation(\n+            \"fill_0\",\n+            0, 0, 1));\n+    }\n+    for (var i = 0; i < this._game.sources.length; ++i) {\n+      var component = this._game.sources[i];\n+      var element = ElementFactories.createSource(\n+            \"s_\" + component.position,\n+            grid_cell_size, 3 * grid_cell_size);\n+      element.x = (0.5 + component.position) * grid_cell_size;\n+      element.y = 5 * grid_cell_size;\n+      this._animator.addElement(element);\n+      if (component.peek() instanceof SourceEnd)\n+        this._animator.addAnimation(element.createAnimation(\n+          \"source_of_nothing\", 0, 0, 1));\n+      else\n+        this._animator.addAnimation(element.createAnimation(\n+          \"source_of_\" + component.peek().toLowerCase(), 0, 0, 1));\n+    }\n+    for (var i = 0; i < this._game.machines.length; ++i) {\n+      for (var j = 0; j < this._game.machines[i].deposit_list.length; ++j) {\n+        var component = this._game.machines[i].deposit_list[j];\n+        var item_type = Object.keys(component.accepted_types)[0];\n+        var element = ElementFactories.future_createDeposit(\n+              \"d_\" + component.position,\n+              Object.keys(component.accepted_types)\n+                .map(function(key) { return component.accepted_types[key] })\n+                .reduce(function(a, b){ return a + b; }),\n+              item_type,\n+              grid_cell_size, 5*grid_cell_size);\n+        element.x = (0.5 + component.position) * grid_cell_size;\n+        element.y = 4 * grid_cell_size;\n+        this._animator.addElement(element);\n+        this._animator.addAnimation(element.changeStyle(item_type, 0));\n+        this._animator.addAnimation(element.createAnimation(\n+              \"fill_0\",\n+              0, 0, 1));\n+      }\n+      var element = ElementFactories.createSource(\n+            \"s_\" + this._game.machines[i].source.position,\n+            grid_cell_size, 3 * grid_cell_size);\n+      element.x = (0.5 + this._game.machines[i].source.position) *\n+                   grid_cell_size;\n+      element.y = 5 * grid_cell_size;\n+      this._animator.addElement(element);\n+      if (this._game.machines[i].source.peek() instanceof SourceEnd)\n+        this._animator.addAnimation(element.createAnimation(\n+          \"source_of_nothing\", 0, 0, 1));\n+      else\n+        this._animator.addAnimation(element.createAnimation(\n+          \"source_of_\" + this._game.machines[i].source.peek().toLowerCase(), 0, 0, 1));\n+    }\n+  },\n+  // Updates the values of the robot sensor variables to reflect the current\n+  // state of the game.\n+  _updateSensors: function(scope) {\n+    for (var sensor in Sensors.PEEK) {\n+      scope.set(Sensors.PEEK[sensor].variable_name,\n+                this._game.peek() === Sensors.PEEK[sensor].item_name);\n+    }\n+    scope.set(\n+      Sensors.SOLID.variable_name,\n+      Sensors.SOLID.accept_list.indexOf(this._game.peek()) !== -1);\n+  },\n+\n+  // Parses an action literal into a value from Actions.\n+  // Returns null if the action is unknown.\n+  _parseAction: function(literal) {\n+    for (var action in Actions) {\n+      if (Actions.hasOwnProperty(action)) {\n+        if (Actions[action] == literal) {\n+          return action;\n+        }\n+      }\n+    }\n+    return null;\n+  },\n+  _render: function(interpreter) {\n+    var grid_cell_size = this.grid_cell_size;\n+    var failure_reason = null;\n+\n+    this._updateSensors(interpreter.getGlobalScope());\n+\n+    for (var action_literal = interpreter.runUntilNextAction(), i = 0;\n+         !!action_literal;\n+         action_literal = interpreter.runUntilNextAction(), i++) {\n+      var player_action = action_literal;\n+\n+      // Processes action.\n+      switch (player_action) {\n+        case Actions.MOVE_LEFT:\n+          failure_reason = this._game.moveLeft();\n+          break;\n+\n+        case Actions.MOVE_RIGHT:\n+          failure_reason = this._game.moveRight();\n+          break;\n+\n+        case Actions.GET_ITEM:\n+          failure_reason = this._game.collect();\n+          break;\n+\n+        case Actions.PUT_ITEM:\n+          failure_reason = this._game.deposit();\n+          break;\n+\n+        default:\n+          failure_reason = \"Unknown action \" + action_literal;\n+      }\n+\n+      // Create animations corresponding to the executed action.\n+      switch (player_action) {\n+        // Make robot turn left/right.\n+        case Actions.MOVE_LEFT:\n+          this._animator.addAnimation(AnimationFactories.straightMove(\n+                'arm',\n+                i,\n+                i + 1,\n+                -grid_cell_size, 0));\n+\n+          this._animator.addAnimation(this._arm.createAnimation(\n+                                          \"move_left\", i, i + 1, 1));\n+          break;\n+        case Actions.MOVE_RIGHT:\n+          this._animator.addAnimation(AnimationFactories.straightMove(\n+                'arm',\n+                i,\n+                i + 1,\n+                grid_cell_size, 0));\n+\n+          this._animator.addAnimation(this._arm.createAnimation(\n+                                          \"move_right\", i, i + 1, 1));\n+          break;\n+        case Actions.GET_ITEM:\n+          this._animator.addAnimation(this._arm.createAnimation(\n+                                          \"move_up\", i, i + 0.5 , 0.5));\n+\n+          if (this._game.holding_item) {\n+            var item_to_style = {\n+                \"IRON\": ElementFactories.ROBOTIC_ARM_HOLDING_IRON_STYLE,\n+                \"GLASS\": ElementFactories.ROBOTIC_ARM_HOLDING_GLASS_STYLE,\n+                \"FUEL\": ElementFactories.ROBOTIC_ARM_HOLDING_FUEL_STYLE\n+                };\n+            var style = item_to_style[this._game.holding_item];\n+            this._animator.addAnimation(this._arm.changeStyle(\n+                                            style,\n+                                            i + 0.5));\n+          }\n+\n+          this._animator.addAnimation(this._arm.createAnimation(\n+                                          \"move_down\", i + 0.5, i + 1, 0.5));\n+\n+          if (this._animator.elements.hasOwnProperty(\"s_\" + this._game.arm_pos)) {\n+            var element = this._animator.elements[\"s_\" + this._game.arm_pos];\n+            var component = this._game.inverted_object_index[this._game.arm_pos];\n+            if (component.peek() instanceof SourceEnd)\n+              this._animator.addAnimation(\n+                element.createAnimation(\n+                  \"source_of_nothing\", i + 0.75, i + 0.75, 1));\n+            else\n+              this._animator.addAnimation(\n+                element.createAnimation(\n+                  \"source_of_\" + component.peek().toLowerCase(), i + 0.75, i + 0.75, 1));\n+          }\n+          break;\n+        case Actions.PUT_ITEM:\n+          this._animator.addAnimation(this._arm.createAnimation(\n+                                          \"move_up\", i, i + 0.5, 0.5));\n+\n+          if (!failure_reason) {\n+            this._animator.addAnimation(this._arm.changeStyle(\n+                                            \"default\",\n+                                            i + 0.5));\n+          }\n+\n+          this._animator.addAnimation(this._arm.createAnimation(\n+                                          \"move_down\", i + 0.5, i + 1, 0.5));\n+          console.log(\"arm_pos: \", this._game.arm_pos);\n+          console.log(Object.keys(this._animator.elements));\n+          if (this._animator.elements.hasOwnProperty('d_' + this._game.arm_pos)) {\n+            var component = this._game.inverted_object_index[this._game.arm_pos];\n+            if (component instanceof Machine) {\n+              for (var j = 0; j < component.deposit_list.length; ++j) {\n+                var sub_deposit = component.deposit_list[j];\n+                var item_type = Object.keys(sub_deposit.accepted_types)[0];\n+                this._animator.addAnimation(\n+                  this._animator.elements[\"d_\" + sub_deposit.position].createAnimation(\n+                    'fill_' + (sub_deposit.accepted_types[item_type] - sub_deposit.capacity[item_type]),\n+                    i + 0.75, i + 0.75, 1\n+                  )\n+                );\n+              }\n+              var element = this._animator.elements[\"s_\" + component.source.position];\n+              if (component.source.peek() instanceof SourceEnd)\n+                this._animator.addAnimation(\n+                  element.createAnimation(\n+                    \"source_of_nothing\", i + 0.75, i + 0.75, 1));\n+              else\n+                this._animator.addAnimation(\n+                  element.createAnimation(\n+                    \"source_of_\" + component.peek().toLowerCase(), i + 0.75, i + 0.75, 1));\n+            } else {\n+              var deposit = component;\n+              var item_type = Object.keys(deposit.accepted_types)[0];\n+              console.log(\"item_type: \", item_type);\n+              this._animator.addAnimation(\n+                this._animator.elements[\"d_\" + this._game.arm_pos].createAnimation(\n+                  'fill_' + (deposit.accepted_types[item_type] - deposit.capacity[item_type]),\n+                  i + 0.75, i + 0.75, 1\n+                )\n+              );\n+            }\n+          }\n+          break;\n+      }\n+\n+      if (!!failure_reason){\n+        i++;\n+        break;\n+      }\n+\n+      this._updateSensors(interpreter.getGlobalScope());\n+    }\n+\n+    this._animator.addAnimation(this._arm.createAnimation(\n+                                    \"move_left\", i, i, 1));\n+\n+    if (!failure_reason && !this._game.reached_goal()) {\n+      failure_reason = FailureReasons.MISSION_UNFINISHED;\n+    }\n+\n+    if (!failure_reason)\n+      return [];\n+\n+    if (failure_reason instanceof Lesson04Game.Error) {\n+      switch (failure_reason.code) {\n+        case Lesson04Game.Error.MOVED_OUT_OF_LIMITS:\n+          return [Constants.Lesson04.MOVED_OUT_OF_LIMITS];\n+        case Lesson04Game.Error.COLLECTED_FROM_EMPTY_SOURCE:\n+          return [Constants.Lesson04.COLLECTED_FROM_EMPTY_SOURCE];\n+        case Lesson04Game.Error.COLLECTED_FROM_NO_SOURCE:\n+          return [Constants.Lesson04.COLLECTED_FROM_NO_SOURCE];\n+        case Lesson04Game.Error.COLLECTED_WHILE_HOLDING_ITEM:\n+          return [Constants.Lesson04.COLLECTED_WHILE_HOLDING_ITEM];\n+        case Lesson04Game.Error.DEPOSITED_ON_INVALID_LOCATION:\n+          return [Constants.Lesson04.DEPOSITED_ON_INVALID_LOCATION];\n+        default:\n+          throw \"Unknown failure reason \" + failure_reason;\n+      }\n+    } else if (failure_reason instanceof Deposit.Error) {\n+      switch (failure_reason.code) {\n+        case Deposit.Error.ITEM_TYPE_NOT_ACCEPTED:\n+          return [Constants.Lesson04.ITEM_TYPE_NOT_ACCEPTED];\n+        case Deposit.Error.CAPACITY_EXCEEDED:\n+          return [Constants.Lesson04.CAPACITY_EXCEEDED];\n+        case Deposit.Error.NOTHING_TO_DEPOSIT:\n+          return [Constants.Lesson04.NOTHING_TO_DEPOSIT];\n+        default:\n+          throw \"Unknown failure reason \" + failure_reason;\n+      }\n+    } else if (failure_reason instanceof Machine.Error) {\n+      switch (failure_reason.code) {\n+        case INVALID_DEPOSIT_POSITION:\n+          return [Constants.Lesson04.INVALID_DEPOSIT_POSITION];\n+        default:\n+          throw \"Unknown failure reason \" + failure_reason;\n+      }\n+    } else if (failure_reason === FailureReasons.MISSION_UNFINISHED) {\n+      return [Constants.Lesson04.MISSION_UNFINISHED];\n+    } else {\n+      throw \"Unknown failure reason \" + failure_reason;\n+    }\n+  },\n+}\n+\n+\n+function Lesson04() {\n+  Lesson.Lesson.call(this);\n+\n+  var commandsReference = [Constants.References.MOVE_ARM_LEFT,\n+                           Constants.References.MOVE_ARM_RIGHT,\n+                           Constants.References.GET_MATERIAL,\n+                           Constants.References.PUT_MATERIAL];\n+\n+  // Step 1\n+  this.addStep(\n+    new Lesson.LessonStep(\n+      <p>\n+        Movimente o braço mecânico para levar a placa de vidro em seu\n+        respectivo depósito.\n+      </p>,\n+      <div>\n+        <p>\n+          Agora que todas as máquinas na nossa base estão funcionando\n+          novamente, podemos usá-las para construir uma nave espacial para o\n+          robô escapar do planeta antes da próxima chuva de meteoros, que\n+          poderá destruir nossa base.\n+        </p>\n+        <p>\n+          A missão do robô é operar as máquinas\n+          da base para construir peças que serão utilizadas para montar a\n+          nave. Porém, antes de construir as peças, é preciso coletar a\n+          matéria prima necessária.\n+        </p>\n+        <p>\n+          Nesta primeira etapa, existe uma placa de\n+          vidro que deve ser colocada no depósito de vidro para ser usada\n+          posteriormente. Para realizar essa tarefa, você deve escrever um\n+          programa que controle o braço robótico.\n+        </p>\n+        <p>\n+          O braço robótico se movimenta apenas horizontalmente.\n+          Os comandos <b>L</b> e <b>R</b> fazem\n+          com que o braço se mova para a esquerda e para a direita,\n+          respectivamente. O comando <b>W</b> faz com que o braço fique parado.\n+          Os comandos <b>G</b> e <b>P</b> fazem com que o braço pegue o que estiver\n+          na sua frente ou largue o que estiver carregando, respectivamente.\n+        </p>\n+      </div>,\n+      commandsReference,\n+      new Lesson04ExerciseStepPlayer(\n+        5,\n+        2,\n+        [SourceFactory(4, 1, \"random_from_set\", {item_set: [\"GLASS\"]})],","path":"src/lesson/lesson04.js","position":832,"original_position":832,"commit_id":"5e05e7c1d70e779317c9549a43d092a64bbb72e2","original_commit_id":"5e05e7c1d70e779317c9549a43d092a64bbb72e2","user":{"login":"gpoesia","id":1392193,"avatar_url":"https://avatars.githubusercontent.com/u/1392193?v=3","gravatar_id":"","url":"https://api.github.com/users/gpoesia","html_url":"https://github.com/gpoesia","followers_url":"https://api.github.com/users/gpoesia/followers","following_url":"https://api.github.com/users/gpoesia/following{/other_user}","gists_url":"https://api.github.com/users/gpoesia/gists{/gist_id}","starred_url":"https://api.github.com/users/gpoesia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gpoesia/subscriptions","organizations_url":"https://api.github.com/users/gpoesia/orgs","repos_url":"https://api.github.com/users/gpoesia/repos","events_url":"https://api.github.com/users/gpoesia/events{/privacy}","received_events_url":"https://api.github.com/users/gpoesia/received_events","type":"User","site_admin":false},"body":"These item names can also be in an enum.","created_at":"2016-11-17T14:19:09Z","updated_at":"2016-11-17T14:19:10Z","html_url":"https://github.com/gpoesia/comp4kids/pull/58#discussion_r88460431","pull_request_url":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58","_links":{"self":{"href":"https://api.github.com/repos/gpoesia/comp4kids/pulls/comments/88460431"},"html":{"href":"https://github.com/gpoesia/comp4kids/pull/58#discussion_r88460431"},"pull_request":{"href":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58"}}},"pull_request":{"url":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58","id":92897411,"html_url":"https://github.com/gpoesia/comp4kids/pull/58","diff_url":"https://github.com/gpoesia/comp4kids/pull/58.diff","patch_url":"https://github.com/gpoesia/comp4kids/pull/58.patch","issue_url":"https://api.github.com/repos/gpoesia/comp4kids/issues/58","number":58,"state":"open","locked":false,"title":"Add Lesson04.","user":{"login":"vrod","id":6827321,"avatar_url":"https://avatars.githubusercontent.com/u/6827321?v=3","gravatar_id":"","url":"https://api.github.com/users/vrod","html_url":"https://github.com/vrod","followers_url":"https://api.github.com/users/vrod/followers","following_url":"https://api.github.com/users/vrod/following{/other_user}","gists_url":"https://api.github.com/users/vrod/gists{/gist_id}","starred_url":"https://api.github.com/users/vrod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vrod/subscriptions","organizations_url":"https://api.github.com/users/vrod/orgs","repos_url":"https://api.github.com/users/vrod/repos","events_url":"https://api.github.com/users/vrod/events{/privacy}","received_events_url":"https://api.github.com/users/vrod/received_events","type":"User","site_admin":false},"body":"Add game in which a mechanical arm has to collect material from sources\r\nand deposit them into their correct deposits.\r\n\r\nIt is possible to use machines to transform a bundle of items into\r\nanother item, that can further be collected and deposited to another\r\ndeposit or machine that accepts that same item.","created_at":"2016-11-09T02:38:03Z","updated_at":"2016-11-17T14:21:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9ba6e4242091466eb032cc7ae0f7668438507f59","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58/commits","review_comments_url":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58/comments","review_comment_url":"https://api.github.com/repos/gpoesia/comp4kids/pulls/comments{/number}","comments_url":"https://api.github.com/repos/gpoesia/comp4kids/issues/58/comments","statuses_url":"https://api.github.com/repos/gpoesia/comp4kids/statuses/5e05e7c1d70e779317c9549a43d092a64bbb72e2","head":{"label":"gpoesia:add-lesson04","ref":"add-lesson04","sha":"5e05e7c1d70e779317c9549a43d092a64bbb72e2","user":{"login":"gpoesia","id":1392193,"avatar_url":"https://avatars.githubusercontent.com/u/1392193?v=3","gravatar_id":"","url":"https://api.github.com/users/gpoesia","html_url":"https://github.com/gpoesia","followers_url":"https://api.github.com/users/gpoesia/followers","following_url":"https://api.github.com/users/gpoesia/following{/other_user}","gists_url":"https://api.github.com/users/gpoesia/gists{/gist_id}","starred_url":"https://api.github.com/users/gpoesia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gpoesia/subscriptions","organizations_url":"https://api.github.com/users/gpoesia/orgs","repos_url":"https://api.github.com/users/gpoesia/repos","events_url":"https://api.github.com/users/gpoesia/events{/privacy}","received_events_url":"https://api.github.com/users/gpoesia/received_events","type":"User","site_admin":false},"repo":{"id":58094737,"name":"comp4kids","full_name":"gpoesia/comp4kids","owner":{"login":"gpoesia","id":1392193,"avatar_url":"https://avatars.githubusercontent.com/u/1392193?v=3","gravatar_id":"","url":"https://api.github.com/users/gpoesia","html_url":"https://github.com/gpoesia","followers_url":"https://api.github.com/users/gpoesia/followers","following_url":"https://api.github.com/users/gpoesia/following{/other_user}","gists_url":"https://api.github.com/users/gpoesia/gists{/gist_id}","starred_url":"https://api.github.com/users/gpoesia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gpoesia/subscriptions","organizations_url":"https://api.github.com/users/gpoesia/orgs","repos_url":"https://api.github.com/users/gpoesia/repos","events_url":"https://api.github.com/users/gpoesia/events{/privacy}","received_events_url":"https://api.github.com/users/gpoesia/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/gpoesia/comp4kids","description":"A playful CS 101 course designed for children with a novel approach","fork":false,"url":"https://api.github.com/repos/gpoesia/comp4kids","forks_url":"https://api.github.com/repos/gpoesia/comp4kids/forks","keys_url":"https://api.github.com/repos/gpoesia/comp4kids/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gpoesia/comp4kids/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gpoesia/comp4kids/teams","hooks_url":"https://api.github.com/repos/gpoesia/comp4kids/hooks","issue_events_url":"https://api.github.com/repos/gpoesia/comp4kids/issues/events{/number}","events_url":"https://api.github.com/repos/gpoesia/comp4kids/events","assignees_url":"https://api.github.com/repos/gpoesia/comp4kids/assignees{/user}","branches_url":"https://api.github.com/repos/gpoesia/comp4kids/branches{/branch}","tags_url":"https://api.github.com/repos/gpoesia/comp4kids/tags","blobs_url":"https://api.github.com/repos/gpoesia/comp4kids/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gpoesia/comp4kids/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gpoesia/comp4kids/git/refs{/sha}","trees_url":"https://api.github.com/repos/gpoesia/comp4kids/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gpoesia/comp4kids/statuses/{sha}","languages_url":"https://api.github.com/repos/gpoesia/comp4kids/languages","stargazers_url":"https://api.github.com/repos/gpoesia/comp4kids/stargazers","contributors_url":"https://api.github.com/repos/gpoesia/comp4kids/contributors","subscribers_url":"https://api.github.com/repos/gpoesia/comp4kids/subscribers","subscription_url":"https://api.github.com/repos/gpoesia/comp4kids/subscription","commits_url":"https://api.github.com/repos/gpoesia/comp4kids/commits{/sha}","git_commits_url":"https://api.github.com/repos/gpoesia/comp4kids/git/commits{/sha}","comments_url":"https://api.github.com/repos/gpoesia/comp4kids/comments{/number}","issue_comment_url":"https://api.github.com/repos/gpoesia/comp4kids/issues/comments{/number}","contents_url":"https://api.github.com/repos/gpoesia/comp4kids/contents/{+path}","compare_url":"https://api.github.com/repos/gpoesia/comp4kids/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gpoesia/comp4kids/merges","archive_url":"https://api.github.com/repos/gpoesia/comp4kids/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gpoesia/comp4kids/downloads","issues_url":"https://api.github.com/repos/gpoesia/comp4kids/issues{/number}","pulls_url":"https://api.github.com/repos/gpoesia/comp4kids/pulls{/number}","milestones_url":"https://api.github.com/repos/gpoesia/comp4kids/milestones{/number}","notifications_url":"https://api.github.com/repos/gpoesia/comp4kids/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gpoesia/comp4kids/labels{/name}","releases_url":"https://api.github.com/repos/gpoesia/comp4kids/releases{/id}","deployments_url":"https://api.github.com/repos/gpoesia/comp4kids/deployments","created_at":"2016-05-05T01:19:36Z","updated_at":"2016-11-05T19:05:26Z","pushed_at":"2016-11-17T13:33:09Z","git_url":"git://github.com/gpoesia/comp4kids.git","ssh_url":"git@github.com:gpoesia/comp4kids.git","clone_url":"https://github.com/gpoesia/comp4kids.git","svn_url":"https://github.com/gpoesia/comp4kids","homepage":null,"size":1791,"stargazers_count":12,"watchers_count":12,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":12,"forks":1,"open_issues":12,"watchers":12,"default_branch":"master"}},"base":{"label":"gpoesia:master","ref":"master","sha":"996e292a139b2633b60e7f8b7add20e07d3f25ef","user":{"login":"gpoesia","id":1392193,"avatar_url":"https://avatars.githubusercontent.com/u/1392193?v=3","gravatar_id":"","url":"https://api.github.com/users/gpoesia","html_url":"https://github.com/gpoesia","followers_url":"https://api.github.com/users/gpoesia/followers","following_url":"https://api.github.com/users/gpoesia/following{/other_user}","gists_url":"https://api.github.com/users/gpoesia/gists{/gist_id}","starred_url":"https://api.github.com/users/gpoesia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gpoesia/subscriptions","organizations_url":"https://api.github.com/users/gpoesia/orgs","repos_url":"https://api.github.com/users/gpoesia/repos","events_url":"https://api.github.com/users/gpoesia/events{/privacy}","received_events_url":"https://api.github.com/users/gpoesia/received_events","type":"User","site_admin":false},"repo":{"id":58094737,"name":"comp4kids","full_name":"gpoesia/comp4kids","owner":{"login":"gpoesia","id":1392193,"avatar_url":"https://avatars.githubusercontent.com/u/1392193?v=3","gravatar_id":"","url":"https://api.github.com/users/gpoesia","html_url":"https://github.com/gpoesia","followers_url":"https://api.github.com/users/gpoesia/followers","following_url":"https://api.github.com/users/gpoesia/following{/other_user}","gists_url":"https://api.github.com/users/gpoesia/gists{/gist_id}","starred_url":"https://api.github.com/users/gpoesia/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gpoesia/subscriptions","organizations_url":"https://api.github.com/users/gpoesia/orgs","repos_url":"https://api.github.com/users/gpoesia/repos","events_url":"https://api.github.com/users/gpoesia/events{/privacy}","received_events_url":"https://api.github.com/users/gpoesia/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/gpoesia/comp4kids","description":"A playful CS 101 course designed for children with a novel approach","fork":false,"url":"https://api.github.com/repos/gpoesia/comp4kids","forks_url":"https://api.github.com/repos/gpoesia/comp4kids/forks","keys_url":"https://api.github.com/repos/gpoesia/comp4kids/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gpoesia/comp4kids/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gpoesia/comp4kids/teams","hooks_url":"https://api.github.com/repos/gpoesia/comp4kids/hooks","issue_events_url":"https://api.github.com/repos/gpoesia/comp4kids/issues/events{/number}","events_url":"https://api.github.com/repos/gpoesia/comp4kids/events","assignees_url":"https://api.github.com/repos/gpoesia/comp4kids/assignees{/user}","branches_url":"https://api.github.com/repos/gpoesia/comp4kids/branches{/branch}","tags_url":"https://api.github.com/repos/gpoesia/comp4kids/tags","blobs_url":"https://api.github.com/repos/gpoesia/comp4kids/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gpoesia/comp4kids/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gpoesia/comp4kids/git/refs{/sha}","trees_url":"https://api.github.com/repos/gpoesia/comp4kids/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gpoesia/comp4kids/statuses/{sha}","languages_url":"https://api.github.com/repos/gpoesia/comp4kids/languages","stargazers_url":"https://api.github.com/repos/gpoesia/comp4kids/stargazers","contributors_url":"https://api.github.com/repos/gpoesia/comp4kids/contributors","subscribers_url":"https://api.github.com/repos/gpoesia/comp4kids/subscribers","subscription_url":"https://api.github.com/repos/gpoesia/comp4kids/subscription","commits_url":"https://api.github.com/repos/gpoesia/comp4kids/commits{/sha}","git_commits_url":"https://api.github.com/repos/gpoesia/comp4kids/git/commits{/sha}","comments_url":"https://api.github.com/repos/gpoesia/comp4kids/comments{/number}","issue_comment_url":"https://api.github.com/repos/gpoesia/comp4kids/issues/comments{/number}","contents_url":"https://api.github.com/repos/gpoesia/comp4kids/contents/{+path}","compare_url":"https://api.github.com/repos/gpoesia/comp4kids/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gpoesia/comp4kids/merges","archive_url":"https://api.github.com/repos/gpoesia/comp4kids/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gpoesia/comp4kids/downloads","issues_url":"https://api.github.com/repos/gpoesia/comp4kids/issues{/number}","pulls_url":"https://api.github.com/repos/gpoesia/comp4kids/pulls{/number}","milestones_url":"https://api.github.com/repos/gpoesia/comp4kids/milestones{/number}","notifications_url":"https://api.github.com/repos/gpoesia/comp4kids/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gpoesia/comp4kids/labels{/name}","releases_url":"https://api.github.com/repos/gpoesia/comp4kids/releases{/id}","deployments_url":"https://api.github.com/repos/gpoesia/comp4kids/deployments","created_at":"2016-05-05T01:19:36Z","updated_at":"2016-11-05T19:05:26Z","pushed_at":"2016-11-17T13:33:09Z","git_url":"git://github.com/gpoesia/comp4kids.git","ssh_url":"git@github.com:gpoesia/comp4kids.git","clone_url":"https://github.com/gpoesia/comp4kids.git","svn_url":"https://github.com/gpoesia/comp4kids","homepage":null,"size":1791,"stargazers_count":12,"watchers_count":12,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":12,"forks":1,"open_issues":12,"watchers":12,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58"},"html":{"href":"https://github.com/gpoesia/comp4kids/pull/58"},"issue":{"href":"https://api.github.com/repos/gpoesia/comp4kids/issues/58"},"comments":{"href":"https://api.github.com/repos/gpoesia/comp4kids/issues/58/comments"},"review_comments":{"href":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58/comments"},"review_comment":{"href":"https://api.github.com/repos/gpoesia/comp4kids/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/gpoesia/comp4kids/pulls/58/commits"},"statuses":{"href":"https://api.github.com/repos/gpoesia/comp4kids/statuses/5e05e7c1d70e779317c9549a43d092a64bbb72e2"}}}}
{"actor":{"display_login":"gpoesia"}}