Skip to Content
🌾 Simulation FeaturesCreating Custom Blocks

Last Updated: 4/7/2026


Creating Custom Blocks

Agricultural Microworlds uses Blockly to provide a visual programming interface for students. Adding a new custom block requires three coordinated steps: defining the block’s visual structure, adding it to the toolbox, and implementing the JavaScript code generator that translates the block into simulation commands.

Prerequisites

Before creating a custom block, you should:

All custom block code lives in agricultural-microworlds.client/src/SetUpCustomBlocks/.

Step 1: Define the Block Structure

Block definitions live in customBlockDefinitions.js. Each block is defined as a JSON object that describes its visual appearance, inputs, and behavior.

Basic Block Definition

Here’s the structure of a simple statement block:

var waitXDays = { type: "wait_x_days", message0: "wait %1 days", args0: [ { type: "input_value", name: "DAYS", check: "Number", }, ], previousStatement: null, nextStatement: null, style: "control_blocks", tooltip: "Wait for a certain number of days", }; Blocks["wait_x_days"] = { init: function () { this.jsonInit(waitXDays); }, };

Block Definition Properties

PropertyPurposeExample Values
typeUnique identifier for the block"wait_x_days", "move_forward"
message0Display text with %1, %2 placeholders for inputs"wait %1 days"
args0Array of input field definitionsSee input types below
previousStatementAllows blocks above (null = yes)null for statement blocks
nextStatementAllows blocks below (null = yes)null for statement blocks
outputType of value returned (for expression blocks)"Number", "Boolean"
styleVisual style reference from theme"control_blocks", "movement_blocks"
tooltipHelp text on hover"Wait for a certain number of days"

Input Types

The args0 array defines what inputs the block accepts:

Number Input:

{ type: "input_value", name: "DAYS", check: "Number", }

Dropdown Menu:

{ type: "field_dropdown", name: "DIRECTION", options: [ ["left", "0"], ["right", "1"], ], }

Number Field (inline):

{ type: "field_number", name: "NUM", value: 1, }

Expression Blocks vs. Statement Blocks

Statement blocks (like move_forward, wait_x_weeks) execute actions and stack vertically. They have previousStatement and nextStatement set to null.

Expression blocks (like math_number, get_current_week) return values and plug into inputs. They have an output property instead:

var getCurrentDay = { type: "get_current_day", message0: "Current Day", output: "Number", style: "variable_blocks", tooltip: "Gets the current day number from the simulation.", }; Blocks["get_current_day"] = { init: function () { this.jsonInit(getCurrentDay); }, };

Step 2: Add to Toolbox

Once defined, add your block to the toolbox in toolboxConfig.js. The toolbox is organized into categories:

export const toolbox = { kind: "categoryToolbox", contents: [ { kind: "category", name: "Control", categorystyle: "control_category", contents: [ { kind: "block", type: "toggle_harvesting" }, { kind: "block", type: "toggle_seeding" }, { kind: "block", type: "wait_x_weeks" }, { kind: "block", type: "wait_x_days" }, // Add your new block here ], }, // ... other categories ], };

Available Categories

The existing categories and their styles:

CategoryStyleColorPurpose
Movementmovement_category300 (purple)Tractor movement commands
Variablesvariable_category330 (pink)Variable operations and simulation state
Numbersnumbers_category230 (blue)Numeric values and operations
Controlcontrol_category190 (teal)Simulation control (harvesting, seeding, waiting)
Logiclogic_category135 (cyan)Conditionals and boolean operations
Loopsloops_category60 (green)Iteration constructs
Eventsevents_category150 (light green)Program entry points

Choose the category that best fits your block’s purpose. The categorystyle determines the category’s color in the toolbox, while the block’s style property determines the individual block color.

Step 3: Implement the Code Generator

The code generator in blocklyJSGenerator.js translates your block into JavaScript code that runs in the Web Worker. This generated code calls simulation methods that send commands to the simulation engine.

Generator Function Structure

javascriptGenerator.forBlock["wait_x_days"] = function (block, generator) { const days = generator.valueToCode(block, "DAYS", generator.ORDER_ATOMIC) || "1"; return `await simulationMethods.waitXDays(${days});\n`; };

Reading Block Values

Input values (from input_value fields):

const duration = generator.valueToCode(block, "DURATION", generator.ORDER_ATOMIC) || "1";

Dropdown selections (from field_dropdown fields):

const direction = block.getFieldValue("DIRECTION");

Number fields (from field_number fields):

const number = block.getFieldValue("NUM");

Generator Return Values

Statement blocks return a string ending with \n:

return `await simulationMethods.waitXDays(${days});\n`;

Expression blocks return an array [code, order]:

return ["simulationMethods.currentWeek", generator.ORDER_ATOMIC];

The order constant (like generator.ORDER_ATOMIC, generator.ORDER_EQUALITY) controls operator precedence for proper parenthesization.

Async vs. Sync Commands

Commands that take time (movement, waiting, turning) use await:

return `await simulationMethods.moveForward(${duration});\n`;

Instant state changes (toggling harvesting, switching crops) don’t:

return `simulationMethods.toggleHarvesting(${inputType});\n`;

Complete Example: Wait X Days Block

Here’s a complete implementation of a hypothetical “wait X days” block:

1. Definition in customBlockDefinitions.js:

var waitXDays = { type: "wait_x_days", message0: "wait %1 days", args0: [ { type: "input_value", name: "DAYS", check: "Number", }, ], previousStatement: null, nextStatement: null, style: "control_blocks", tooltip: "Wait for a certain number of days", }; Blocks["wait_x_days"] = { init: function () { this.jsonInit(waitXDays); }, };

2. Toolbox entry in toolboxConfig.js:

{ kind: "category", name: "Control", categorystyle: "control_category", contents: [ { kind: "block", type: "wait_x_weeks" }, { kind: "block", type: "wait_x_days" }, // Add here // ... other blocks ], }

3. Generator in blocklyJSGenerator.js:

javascriptGenerator.forBlock["wait_x_days"] = function (block, generator) { const days = generator.valueToCode(block, "DAYS", generator.ORDER_ATOMIC) || "1"; // Convert days to hours (24 hours per day) return `await simulationMethods.waitXWeeks(${days} / 7.0);\n`; };

Note: This example reuses the existing waitXWeeks command by converting days to weeks. For a true waitXDays command, you would need to add a new handler in simulationEngine.js (see Worker Command System).

How Generated Code Becomes Worker Commands

When a student runs their Blockly program:

  1. Blockly generates JavaScript from the blocks using your generator functions
  2. The generated code runs in a Web Worker with access to simulationMethods
  3. Each simulationMethods call sends a message to the main thread with a command name and arguments
  4. The simulation engine receives the message via handleWorkerMessage() in simulationEngine.js
  5. The engine executes the command (e.g., moveForward(), waitXWeeks()) which updates simulation state
  6. For async commands, the engine sends a response back to the worker when complete

This architecture keeps the simulation loop running smoothly on the main thread while student code executes in a separate worker thread.

Pattern Reference: Existing Blocks

Study these existing blocks as patterns:

  • Simple statement: turn_left (no inputs, just an action)
  • Statement with number input: move_forward (takes duration)
  • Statement with dropdown: toggle_harvesting (ON/OFF choice)
  • Statement with both: turn_x_degrees (number input + direction dropdown)
  • Expression returning number: get_current_week (no inputs, returns value)
  • Expression with inputs: custom_compare (two number inputs, returns boolean)

All patterns are fully implemented in the three source files mentioned above.

Testing Your Block

After implementing all three steps:

  1. Start the development server: npm run dev
  2. Open the Blockly workspace
  3. Find your block in the appropriate category
  4. Drag it into the workspace and connect it to other blocks
  5. Run the program and verify the simulation behavior matches your expectations
  6. Check the browser console for any JavaScript errors from the generated code

What’s Next