ocadotechnology/rapid-router

View on GitHub
game/static/game/js/blocklyCustomBlocks.js

Summary

Maintainability
F
1 wk
Test Coverage
"use strict";

var ocargo = ocargo || {};
var Blockly = Blockly || {};

function initCustomBlocks() {
  initCustomBlocksDescription();
  initCustomBlocksPython();
}

function initCustomBlocksDescription() {
  Blockly.Blocks["start"] = {
    // Beginning block - identifies the start of the program
    init: function () {
      ocargo.blocklyControl.numStartBlocks++;
      this.setColour(50);
      this.appendDummyInput()
        .appendField(gettext("Start"))
        .appendField(new Blockly.FieldImage(
          new Date().getMonth() === 11 && CHARACTER_NAME === "Van"
            ? ocargo.Drawing.imageDir + "characters/top_view/Sleigh.svg"
            : ocargo.Drawing.imageDir + CHARACTER_EN_FACE_URL,
          ocargo.BlocklyControl.BLOCK_CHARACTER_HEIGHT,
          ocargo.BlocklyControl.BLOCK_CHARACTER_WIDTH
        )
      );
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("The beginning of the program"));
      this.setDeletable(false);
    },
  };

  /*****************/
  /* Action Blocks */
  /*****************/

  Blockly.Blocks["move_forwards"] = {
    // Block for moving forward
    init: function () {
      this.setColour(160);
      this.appendDummyInput()
        .appendField(gettext("move forwards"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "actions/forward.svg",
            ocargo.BlocklyControl.IMAGE_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Move the van forwards"));
    },
  };

  Blockly.Blocks["turn_left"] = {
    // Block for turning left
    init: function () {
      this.setColour(160);
      this.appendDummyInput()
        .appendField(gettext("turn left"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            38,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        )
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "actions/left.svg",
            ocargo.BlocklyControl.IMAGE_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Turn the van left"));
    },
  };

  Blockly.Blocks["turn_right"] = {
    // Block for turning right
    init: function () {
      this.setColour(160);
      this.appendDummyInput()
        .appendField(gettext("turn right"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            29,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        )
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "actions/right.svg",
            ocargo.BlocklyControl.IMAGE_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Turn the van right"));
    },
  };

  Blockly.Blocks["turn_around"] = {
    // Block for turning around
    init: function () {
      this.setColour(160);
      this.appendDummyInput()
        .appendField(gettext("turn around"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            12,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        )
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "actions/turn_around.svg",
            ocargo.BlocklyControl.IMAGE_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Turn the van around"));
    },
  };

  Blockly.Blocks["wait"] = {
    // Block for not moving the van for a time
    init: function () {
      this.setColour(160);
      this.appendDummyInput()
        .appendField(gettext("wait"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            60,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        )
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "actions/wait.svg",
            ocargo.BlocklyControl.IMAGE_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Keep the van stationary"));
    },
  };

  Blockly.Blocks["deliver"] = {
    // Block for delivering (only on levels with multiple destinations)
    init: function () {
      this.setColour(160);
      this.appendDummyInput()
        .appendField(gettext("deliver"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            43,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        )
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "actions/deliver.svg",
            ocargo.BlocklyControl.IMAGE_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Deliver the goods from the van"));
    },
  };

    Blockly.Blocks['sound_horn'] = {
        init: function() {
            this.setColour(160);
            this.appendDummyInput()
                .appendField(gettext('sound horn'))
                .appendField(new Blockly.FieldImage(ocargo.Drawing.imageDir + 'empty.svg',
                    43,
                    ocargo.BlocklyControl.BLOCK_HEIGHT))
                .appendField(new Blockly.FieldImage(ocargo.Drawing.imageDir + 'empty.svg',
                    ocargo.BlocklyControl.IMAGE_WIDTH,
                    ocargo.BlocklyControl.BLOCK_HEIGHT));
            this.setPreviousStatement(true, 'Action');
            this.setNextStatement(true, 'Action');
            this.setTooltip(gettext('Sound the horn to scare away the cows'));
        }
    };

  /*****************/
  /*   Conditions  */
  /*****************/

  Blockly.Blocks["road_exists"] = {
    init: function () {
      var BOOLEANS = [
        [gettext("road exists forward"), "FORWARD"],
        [gettext("road exists left"), "LEFT"],
        [gettext("road exists right"), "RIGHT"],
      ];
      this.setColour(210);
      this.setOutput(true, "Boolean");
      this.appendDummyInput()
        .appendField(new Blockly.FieldDropdown(BOOLEANS), "CHOICE")
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            ocargo.BlocklyControl.EXTRA_BLOCK_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
    },
  };

  Blockly.Blocks["traffic_light"] = {
    init: function () {
      var BOOLEANS = [
        [gettext("traffic light red"), ocargo.TrafficLight.RED],
        [gettext("traffic light green"), ocargo.TrafficLight.GREEN],
      ];
      this.setColour(210);
      this.setOutput(true, "Boolean");
      this.appendDummyInput()
        .appendField(new Blockly.FieldDropdown(BOOLEANS), "CHOICE")
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            ocargo.BlocklyControl.EXTRA_BLOCK_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
    },
  };

  Blockly.Blocks["dead_end"] = {
    init: function () {
      this.setColour(210);
      this.setOutput(true, "Boolean");
      this.appendDummyInput()
        .appendField(gettext("is dead end"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            ocargo.BlocklyControl.EXTRA_BLOCK_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
    },
  };

  Blockly.Blocks["at_destination"] = {
    init: function () {
      this.setColour(210);
      this.setOutput(true, "Boolean");
      this.appendDummyInput()
        .appendField(gettext("at destination"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            ocargo.BlocklyControl.EXTRA_BLOCK_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        );
    },
  };

    Blockly.Blocks['cow_crossing'] = {
        init: function() {
            this.setColour(210);
            this.setOutput(true, 'Boolean');
            this.appendDummyInput()
                .appendField(gettext('cows'))
                .appendField(new Blockly.FieldImage(ocargo.Drawing.imageDir + ocargo.Drawing.whiteCowUrl,
                    ocargo.BlocklyControl.COW_WIDTH,
                    ocargo.BlocklyControl.BLOCK_HEIGHT), 'IMAGE');
        }
    };
    /****************/
    /*  Procedures  */
    /****************/

  Blockly.Blocks["call_proc"] = {
    // Block for calling a defined procedure
    init: function () {
      var name = "";
      this.setColour(260);
      this.appendDummyInput()
        .appendField(gettext("Call"))
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + "empty.svg",
            7,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          )
        )
        .appendField(new Blockly.FieldTextInput(name), "NAME");
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Call"));
    },
  };

  Blockly.Blocks["declare_proc"] = {
    // Block for declaring a procedure
    init: function () {
      var name = "";
      this.setColour(260);
      this.appendDummyInput()
        .appendField(gettext("Define"))
        .appendField(new Blockly.FieldTextInput(name), "NAME");
      this.appendStatementInput("DO")
        .setCheck("Action")
        .appendField(gettext("Do"));
      this.setTooltip(gettext("Declares the procedure"));
      this.statementConnection_ = null;
    },
  };

  /****************/
  /*    Events    */
  /****************/

  Blockly.Blocks["declare_event"] = {
    // Block for declaring an event handler
    init: function () {
      this.setColour(260);
      var dropdown = new Blockly.FieldDropdown(
        [
          [gettext("white"), ocargo.Cow.WHITE],
          [gettext("brown"), ocargo.Cow.BROWN],
        ],
        function (option) {
          var imageUrl =
            ocargo.Drawing.imageDir + ocargo.Drawing.cowUrl(option);
          this.sourceBlock_.getField("IMAGE").setValue(imageUrl);
        }
      );
      this.appendDummyInput("Event")
        .appendField(gettext("On "))
        .appendField(dropdown, "TYPE")
        .appendField(
          new Blockly.FieldImage(
            ocargo.Drawing.imageDir + ocargo.Drawing.whiteCowUrl,
            ocargo.BlocklyControl.COW_WIDTH,
            ocargo.BlocklyControl.BLOCK_HEIGHT
          ),
          "IMAGE"
        );
      this.getField("IMAGE").EDITABLE = true; //saves the image path as well in the XML
      this.appendStatementInput("DO")
        .setCheck("EventAction")
        .appendField(gettext("Do"));
      this.setTooltip(gettext("Declares the event handler"));
      this.statementConnection_ = null;
    },
  };

  /*******************/
  /*  Control Flows  */
  /*******************/

  Blockly.Blocks["controls_repeat_while"] = {
    // Block for repeat while
    init: function () {
      this.setColour(120);
      this.appendValueInput("condition")
        .setCheck("Boolean")
        .appendField(gettext("repeat while"));
      this.appendStatementInput("body")
        .setCheck("Action")
        .appendField(gettext("do"));
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("While a value is true, do some statements"));
    },
  };

  Blockly.Blocks["controls_repeat_until"] = {
    // Block for repeat until
    init: function () {
      this.setColour(120);
      this.appendValueInput("condition")
        .setCheck("Boolean")
        .appendField(gettext("repeat until"));
      this.appendStatementInput("body")
        .setCheck("Action")
        .appendField(gettext("do"));
      this.setPreviousStatement(true, "Action");
      this.setNextStatement(true, "Action");
      this.setTooltip(gettext("Until a value is true, do some statements"));
    },
  };

  /*****************/
  /*   Variables   */
  /*****************/

  Blockly.Blocks["variables_get"] = {
    init: function () {
      this.appendDummyInput().appendField(
        new Blockly.FieldTextInput(""),
        "NAME"
      );
      this.setOutput(true, null);
      this.setColour(330);
      this.setTooltip(gettext("A variable"));
    },
  };

  Blockly.Blocks["variables_set"] = {
    init: function () {
      this.appendValueInput("VALUE")
        .setCheck(null)
        .appendField("set")
        .appendField(new Blockly.FieldTextInput(""), "VAR")
        .appendField("to");
      this.setPreviousStatement(true, null);
      this.setNextStatement(true, null);
      this.setColour(330);
      this.setTooltip(gettext("Set a variable"));
    },
  };

  Blockly.Blocks["variables_numeric_set"] = {
    init: function () {
      this.appendDummyInput()
        .appendField("set")
        .appendField(new Blockly.FieldTextInput(""), "NAME")
        .appendField("to")
        .appendField(new Blockly.FieldNumber(0), "VALUE");
      this.setPreviousStatement(true, null);
      this.setNextStatement(true, null);
      this.setColour(330);
      this.setTooltip(gettext("Set a variable to a number"));
    },
  };

  Blockly.Blocks["variables_increment"] = {
    init: function () {
      this.appendDummyInput()
        .appendField("increment")
        .appendField(new Blockly.FieldTextInput(""), "NAME")
        .appendField("by")
        .appendField(new Blockly.FieldNumber(0), "VALUE");
      this.setPreviousStatement(true, null);
      this.setNextStatement(true, null);
      this.setColour(330);
      this.setTooltip(gettext("Increment a variable"));
    },
  };

  /***************/
  /*   Numbers   */
  /***************/

  Blockly.Blocks["math_number"] = {
    init: function () {
      this.appendDummyInput()
        .appendField("number")
        .appendField(new Blockly.FieldNumber(0), "NUM");
      this.setOutput(true, null);
      this.setColour(230);
      this.setTooltip(gettext("A number"));
    },
  };

  // Set text colour to red
  var textBlock = Blockly.Blocks["text"];
  var originalTextInit = textBlock.init;
  textBlock.init = function () {
    originalTextInit.call(this);
    this.setColour(260);
  };

  //Customise controls_repeat block to not allow more than a sensible number of repetitions
  var controlsRepeatBlock = Blockly.Blocks["controls_repeat"];
  var originalInit = controlsRepeatBlock.init;
  controlsRepeatBlock.init = function () {
    originalInit.call(this);

    this.setPreviousStatement(!0, "Action");
    this.setNextStatement(!0, "Action");
    this.inputList[1].setCheck("Action"); //Disallow event action blocks to be in body

    var input = this.inputList[0];
    var field = input.fieldRow[1];
    field.changeHandler_ = function (text) {
      var n = Blockly.FieldTextInput.numberValidator(text);
      if (n) {
        n = String(Math.min(Math.max(0, Math.floor(n)), 20));
      }
      return n;
    };
  };

  // Make 'not' taller
  var notBlock = Blockly.Blocks["logic_negate"];
  var originalNotInit = notBlock.init;
  notBlock.init = function () {
    originalNotInit.call(this);
    this.inputList[0].appendField(
      new Blockly.FieldImage(
        ocargo.Drawing.imageDir + "empty.svg",
        ocargo.BlocklyControl.EXTRA_BLOCK_WIDTH,
        ocargo.BlocklyControl.BLOCK_HEIGHT
      )
    );
  };
}

function initCustomBlocksPython() {
  Blockly.Python["start"] = function (block) {
    return "";
  };

  Blockly.Python["move_forwards"] = function (block) {
    return "my_van.move_forwards()\n";
  };

  Blockly.Python["turn_left"] = function (block) {
    return "my_van.turn_left()\n";
  };

  Blockly.Python["turn_right"] = function (block) {
    return "my_van.turn_right()\n";
  };

  Blockly.Python["turn_around"] = function (block) {
    return "my_van.turn_around()\n";
  };

  Blockly.Python["wait"] = function (block) {
    return "my_van.wait()\n";
  };

    Blockly.Python['deliver'] = function(block) {
        return 'my_van.deliver()\n';
    };

    Blockly.Python['sound_horn'] = function(block) {
        return 'my_van.sound_horn()\n';
    };


  Blockly.Python["road_exists"] = function (block) {
    if (block.inputList[0].fieldRow[1].value_ === "FORWARD") {
      var python = "my_van.is_road('FORWARD')";
    } else if (block.inputList[0].fieldRow[1].value_ === "LEFT") {
      var python = "my_van.is_road('LEFT')";
    } else {
      var python = "my_van.is_road('RIGHT')";
    }

    return [python, Blockly.Python.ORDER_NONE];
    // TODO: figure out what this ordering relates to
  };

  Blockly.Python["traffic_light"] = function (block) {
    var python;
    if (block.inputList[0].fieldRow[1].value_ === ocargo.TrafficLight.RED) {
      python = "my_van.at_traffic_light('RED')";
    } else {
      python = "my_van.at_traffic_light('GREEN')";
    }

    return [python, Blockly.Python.ORDER_NONE]; //TODO: figure out what this ordering relates to
  };

  Blockly.Python["dead_end"] = function (block) {
    return ["my_van.at_dead_end()", Blockly.Python.ORDER_NONE];
    // TODO: figure out what this ordering relates to
  };

  Blockly.Python["cow_crossing"] = function (block) {
    return ["my_van.cow_crossing()", Blockly.Python.ORDER_NONE];
    // TODO: figure out what this ordering relates to
  };

  Blockly.Python["at_destination"] = function (block) {
    return ["my_van.at_destination()", Blockly.Python.ORDER_NONE];
    // TODO: figure out what this ordering relates to;
  };

  Blockly.Python["call_proc"] = function (block) {
    return block.inputList[0].fieldRow[2].text_ + "()\n";
  };

  Blockly.Python["declare_proc"] = function (block) {
    var branch = Blockly.Python.statementToCode(block, "DO");
    return "def " + block.inputList[0].fieldRow[1].text_ + "():\n" + branch;
    // TODO: get code out of sub-blocks (there's a Blockly function for it)
  };

  Blockly.Python["declare_event"] = function (block) {
    // TODO support events in python
    throw "events not supported in python";
  };

  Blockly.Python["controls_repeat_while"] = function (block) {
    var condition = Blockly.Python.valueToCode(
      block,
      "condition",
      Blockly.Python.ORDER_ATOMIC
    );
    var subBlock = Blockly.Python.statementToCode(block, "body");
    var code = "while " + condition + ":\n" + subBlock;
    return code;
  };

  Blockly.Python["controls_repeat_until"] = function (block) {
    var condition = Blockly.Python.valueToCode(
      block,
      "condition",
      Blockly.Python.ORDER_ATOMIC
    );
    var subBlock = Blockly.Python.statementToCode(block, "body");
    var code = "while not " + condition + ":\n" + subBlock;
    return code;
  };

  Blockly.Python["variables_get"] = function (block) {
    var variableName = block.getFieldValue("NAME");
    return [variableName, Blockly.Python.ORDER_ATOMIC];
  };

  Blockly.Python["variables_set"] = function (block) {
    var variableName = block.getFieldValue("VAR");
    var value = Blockly.Python.valueToCode(
      block,
      "VALUE",
      Blockly.Python.ORDER_NONE
    );
    var code = `${variableName} = ${value}\n`;
    return code;
  };

  Blockly.Python["variables_numeric_set"] = function (block) {
    var variableName = block.getFieldValue("NAME");
    var numberValue = block.getFieldValue("VALUE");
    var code = `${variableName} = ${numberValue}\n`;
    return code;
  };

  Blockly.Python["variables_increment"] = function (block) {
    var variableName = block.getFieldValue("NAME");
    var numberValue = block.getFieldValue("VALUE");
    var code = `${variableName} = ${variableName} + ${numberValue}\n`;
    return code;
  };
}