Skip to content

Repository files navigation

BlocklyCode

--{{0}}--

This template turns a LiaScript code block with Python or JavaScript into a Blockly program.

Attach @Blockly.python or @Blockly.js to a code block – and the code is shown as blocks:

  • The code block stays real Python or JavaScript. Blocks and code are synchronized in both directions: change the blocks and the code follows, change the code and the blocks follow.
  • ▶ runs the program step by step, the running block lights up. The slider sets the speed, ⏹ stops the program.
  • Every run with changes creates a new version. Use the arrows below the program to go back and forth between your versions.
  • Turtle graphics, print and input work in both languages.
  • Blocks, menus and texts follow the language of the course.
import turtle

for _ in range(5):
    turtle.forward(100)
    turtle.right(144)

@Blockly.python(level2)

Blockly is a project of the Raspberry Pi Foundation (originally developed by Google). This template is not an official Blockly product; it uses the freely licensed Blockly library.

Import

To use the template in your own course, add one of the following lines to the header of your course.

Fixed version (recommended, will not change anymore):

import: https://raw.githubusercontent.com/LiaTemplates/BlocklyCode/0.1.0/README.md

Latest version (may change at any time):

import: https://raw.githubusercontent.com/LiaTemplates/BlocklyCode/main/README.md

Then attach one of the macros to a code block:

Macro Language Code block
@Blockly.python Python program
@Blockly.js JavaScript program
@Blockly.python(level) Python program with a level
@Blockly.js(level) JavaScript program with a level
@Blockly.python.check(level) Python task with a check
@Blockly.js.check(level) JavaScript task with a check

@Blockly.javascript is the same as @Blockly.js. The level is one of level1 … level4 (German: stufe1 … stufe4), the name of a custom profile, or an inline profile. Without a level, level4 is used.

How it works

--{{0}}--

The blocks lie on top of the code block. What is stored, however, is always the code in the code block.

  1. Drag blocks – every change is written into the code block right away. With the Code button (from level 3 on) you can look at it; in level 4 it is visible from the start.
  2. Edit the code – as soon as you stop typing, the blocks follow. As long as the code contains a syntax error, the blocks keep their last state and the error is shown below them.
  3. Press ▶ – the program runs; the block that is running lights up. The slider between 🐢 and 🐇 sets the speed, ⏹ stops the program.
  4. Versions – with ◀ and ▶ below the program you get earlier versions back. The blocks follow along.

The code is only rewritten when the blocks are changed. As long as nobody touches the blocks, your code stays exactly as you wrote it – with your own formatting. After a change of the blocks, the code is written in a uniform way (for example "…" for texts and four spaces in Python).

let size;

// the side length
size = 60;

for (let count = 0; count < 6; count++) {
  turtle.forward(size);
  turtle.left(60);
}

@Blockly.js

Levels

--{{0}}--

There is a level for every age group. It defines which blocks are offered, how large they are, whether the code can be seen, and how fast programs run.

The levels build on each other: a program from level 1 also runs in level 4, just with more blocks to choose from.

Level for Blocks Code
level1 / stufe1 grades 1–2 turtle (move, turn, pen, colour), repeat; large blocks, slow hidden
level2 / stufe2 grades 3–4 + more turtle, variables, arithmetic, random numbers, if, print hidden
level3 / stufe3 grades 5–7 + all loops, logic, text, input, lists, functions Code button
level4 / stufe4 grade 8 on everything, including blocks with free code visible

Level 1 – grades 1 and 2

Few, large blocks. The program runs slowly, so every step can be followed.

Task: Let the turtle walk a square.

import turtle

turtle.forward(100)
turtle.right(90)

@Blockly.python(level1)

Level 2 – grades 3 and 4

Variables, arithmetic, conditions and random numbers.

Task: Draw ten random steps. Change the colour when the step is long.

import random
import turtle

for _ in range(10):
    step = random.randint(10, 60)
    if step > 40:
        turtle.color("#ff0000")
    turtle.forward(step)
    turtle.right(90)

@Blockly.python(level2)

Level 3 – grades 5 to 7

Functions, lists, text and input. The Code button shows the code.

Task: Draw polygons with different numbers of corners.

import turtle


def polygon(corners, side):
    for _ in range(corners):
        turtle.forward(side)
        turtle.left(360 / corners)


name = input("What is your name?")
print("Hello " + name + "!")
polygon(7, 50)

@Blockly.python(level3)

Level 4 – grade 8 on

All blocks, the code is visible and can be edited directly. Constructs without a block of their own are kept as blocks with free code.

let numbers, total, n;

numbers = [3, 1, 4, 1, 5, 9, 2, 6];
total = 0;
for (n of numbers) {
  total += n;
}
console.log("Sum: " + String(total));
console.log("Mean: " + String(total / numbers.length));

@Blockly.js(level4)

Python and JavaScript

--{{0}}--

The blocks are the same for both languages, only the code differs. That makes it easy to compare both languages – or to switch between them.

count = 0
while count < 5:
    count += 1
    if count % 2 == 0:
        print(str(count) + " is even")
    else:
        print(str(count) + " is odd")

@Blockly.python

let count;

count = 0;
while (count < 5) {
  count += 1;
  if (count % 2 === 0) {
    console.log(String(count) + " is even");
  } else {
    console.log(String(count) + " is odd");
  }
}

@Blockly.js

  • Python runs in the browser with Skulpt (a Python 3 subset). The modules math, random, time, string, collections, itertools, functools, re, copy and a few more are available.
  • JavaScript runs natively in the browser. prompt() reads from the LiaScript terminal, console.log() writes to it.

Turtle

--{{0}}--

The turtle starts in the middle of a 400 × 400 area, looking to the right. The drawing area appears as soon as a program uses the turtle.

The commands are the same in Python (import turtle) and JavaScript (the object turtle is always there), and they are named as in Python's turtle module:

Block Code Aliases
move forward by turtle.forward(50) fd
move backward by turtle.backward(50) bk, back
turn right by turtle.right(90) rt
turn left by turtle.left(90) lt
pen up / down turtle.penup(), turtle.pendown() pu, up, pd, down
set colour to turtle.color("#ff0000") pencolor
set width to turtle.width(3) pensize
go to x: y: turtle.goto(0, 0) setpos, setposition
point in direction turtle.setheading(90) seth
circle with radius turtle.circle(50)
go home turtle.home()
print turtle.write("Hello")
hide / show turtle turtle.hideturtle(), turtle.showturtle() ht, st
clear drawing turtle.clear()

Without a block: turtle.xcor(), turtle.ycor(), turtle.heading(), turtle.isdown(), turtle.reset(); turtle.speed() is accepted and ignored (the speed is set with the slider).

import turtle

turtle.width(3)
for i in range(36):
    turtle.color("#0066cc")
    turtle.circle(80)
    turtle.right(10)

@Blockly.python

Tasks with checks

--{{0}}--

A task consists of two code blocks directly below each other: the program and a hidden check written in JavaScript.

The check starts with a minus in front of its file name (-Check), so it stays collapsed. When ▶ is pressed, the check runs the program (as fast as possible) and then evaluates it. The result appears below the program.

Task: Draw a square with a repeat loop – with at most 3 blocks (numbers do not count).

import turtle

turtle.forward(50)
turtle.right(90)
await run()

expect(turtle.lines.length === 4, "The square needs exactly 4 sides.")
expect(blocks.uses("controls_repeat_ext"), "Use a repeat loop.")
expect(blocks.count() <= 3, "Can you do it with at most 3 blocks?")

@Blockly.python.check(level1)

Task: Write a function double(x) that returns twice the number, and print double(21).

def double(x):
    return x


print(double(21))
await run()

expect(output().includes("42"), "The program should print 42.")
expect(await call("double", 5) === 10, "double(5) should be 10.")
expect(await call("double", -3) === -6, "double(-3) should be -6.")

@Blockly.python.check(level3)

These commands are available in a check (German names in brackets):

Command Meaning
await run() (lauf) runs the program (at most 5 s), returns everything it wrote
await run({ input: ["Ada", 3], timeout: 2 }) … with answers for input() / prompt(), at most 2 s
expect(condition, text) (erwarte) reports text if the condition is not met
output() (ausgabe()) everything the last run wrote
variable(name) value of a global variable after the run
await call(name, ...args) (aufruf) calls a function of the program and returns its result
turtle (schildkroete) x, y, heading, penDown, color, visible, lines, length (drawn length)
blocks.count(type?) (bloecke.anzahl()) number of blocks (of one type)
blocks.uses(type) (bloecke.nutzt()) does the program use this block?
code() the code of the program

The names of the blocks (controls_repeat_ext, turtle_forward …) are listed in Blocks and code.

Custom profiles

--{{0}}--

If the four levels are not enough, you can define your own profiles in the header of your course, or directly at the macro.

Inline profiles

The macro parameter can also contain options, separated by spaces. Lists are separated by |:

@Blockly.python(`level1 blocks=turtle_forward|turtle_right|controls_repeat_ext max=5`)
import turtle

turtle.forward(60)

@Blockly.python(level1 blocks=turtle_forward|turtle_right|controls_repeat_ext max=5)

Profiles in the header

<!--
import: https://raw.githubusercontent.com/LiaTemplates/BlocklyCode/0.1.0/README.md

@onload
window.LiaBlockly.defineProfile("maze", {
  base: "level1",
  blocks: ["turtle_forward", "turtle_right", "turtle_left", "controls_repeat_ext"],
  maxBlocks: 6
})
@end
-->

Use it with @Blockly.python(maze).

Option inline Meaning
base base= or first word level the profile builds on
blocks blocks=a|b allowed blocks, or "all"
add add=a|b blocks added to those of the base
exclude exclude=a|b blocks to remove
maxBlocks max=5 at most this many blocks (0 = unlimited), a counter is shown
maxInstances at most this many blocks of one type, e.g. { turtle_forward: 2 }
zoom zoom=1.2 block size (level 1: 1.2, level 4: 0.75)
text text=toggle code hidden, behind a toggle button, or visible
turtle turtle=no turtle area: yes, no or auto (when used)
speed speed=50 initial speed, 0 (slow) … 100 (as fast as possible)
renderer Blockly renderer: zelos (default), geras, thrasos

Custom blocks

--{{0}}--

You can add your own blocks, for example for a robot, a traffic light or a game. A block is described by the code it stands for – so it works in both directions and in both languages without further work.

<!--
import: https://raw.githubusercontent.com/LiaTemplates/BlocklyCode/0.1.0/README.md

@onload
// the functions, available as `light` in JavaScript and Python
window.LiaBlockly.defineModule("light", {
  set: (color) => { /* switch the lamp on */ },
  wait: (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)),
})

window.LiaBlockly.defineBlock({
  type: "light_set",
  category: { en: "Traffic light", de: "Ampel" },
  colour: 20,
  message: { en: "switch on %1", de: "schalte %1 an" },
  args: [{
    type: "field_dropdown", name: "COLOR",
    options: [[{ en: "red", de: "rot" }, '"red"'], [{ en: "green", de: "grün" }, '"green"']]
  }],
  call: "light.set(%1)"
})

window.LiaBlockly.defineBlock({
  type: "light_wait",
  category: { en: "Traffic light", de: "Ampel" },
  colour: 20,
  message: { en: "wait %1 seconds", de: "warte %1 Sekunden" },
  args: [{ type: "input_value", name: "SECONDS", check: "Number", shadow: { type: "number", value: 1 } }],
  call: "light.wait(%1)"
})
@end
-->

A complete, working example is in examples/custom-blocks.md.

defineModule(name, functions) makes JavaScript functions available as name.function(...) in JavaScript and as module name in Python (with or without import name). Functions may return promises; the program waits for them.

defineBlock(spec):

Option Meaning
type unique name of the block
message text of the block, %1, %2 … mark the arguments; a text or { en: …, de: … }
args the arguments, see below
call the code: name(%1, …) or object.name(%1, …); or { python: …, javascript: … }
output for blocks with a value: its type ("Number", "String", "Boolean") or true
category name of the category in the toolbox (text or translations)
colour colour, a hue (0 … 360) or "#rrggbb"
tooltip text shown when hovering the block
Argument Code
{ type: "input_value", name, check, shadow: { type: "number", value: 1 } } any value; shadow is the default in the toolbox (number, text or colour)
{ type: "field_number", name, value, min, max } a number
{ type: "field_input", name, text } a text
{ type: "field_dropdown", name, options: [[label, code], …] } the code of the option

Custom blocks are offered in every level; with a profile you can choose exactly which blocks are available (blocks=light_set|light_wait|controls_repeat_ext).

Languages

--{{0}}--

The template speaks the language of your course: set language: in the header of your course, and blocks, categories, menus and buttons follow.

  • Blocks and menus exist in all ~125 languages Blockly is translated into, e.g. de, fr, es, et, uk, ar, ja or zh.
  • Categories and turtle blocks come from the translations of Blockly Games (~100 languages).
  • Right-to-left languages (Arabic, Hebrew, Persian …) are mirrored.
  • Page translations are followed live: when the course is translated, e.g. with "Translate with Google" in LiaScript's settings, blocks, categories, menus and buttons switch to the new language right away – with Blockly's own translations, not the machine translation, which leaves the blocks alone. The code in the code block does not change.
  • The few texts of this template itself (e.g. the result of a check) exist in English and German; other languages show them in English.
  • The code is always Python or JavaScript. Names of variables and functions may contain letters of any language, e.g. größe or счёт.

An example in German is in examples/deutsch.md.

Code without blocks

--{{0}}--

Not everything in Python or JavaScript has a block. Such code is not lost: it is shown as a grey block with free code, which can be edited directly and runs like any other block.

import turtle

colors = ["#e53935", "#fb8c00", "#43a047", "#1e88e5"]
for i in range(8):
    turtle.color(colors[i % 4])
    print(f"side {i}")
    turtle.forward(80)
    turtle.left(45)

@Blockly.python

In level 4 there is also a Code category with empty blocks of this kind, for statements and for values.

Blocks and code

--{{0}}--

Every block stands for one piece of code. When code is turned into blocks, exactly these forms are recognized.

Block Python JavaScript
controls_repeat_ext for _ in range(n): for (let count = 0; count < n; count++)
controls_for for i in range(1, 11): for (i = 1; i <= 10; i++)
controls_forEach for x in items: for (x of items)
controls_whileUntil while x: / while not x: while (x) / while (!x)
controls_flow_statements break, continue break;, continue;
controls_if if / elif / else if / else if / else
logic_compare ==, !=, <, <=, >, >= ===, !==, <, <=, >, >=
logic_operation, logic_negate and, or, not &&, ||, !
logic_boolean, logic_null True, False, None true, false, null
logic_ternary a if c else b c ? a : b
math_number, math_arithmetic 1 + 2 * 3 ** 2 1 + 2 * 3 ** 2
math_modulo a % b a % b
math_single math.sqrt(x), abs(x), -x … Math.sqrt(x), Math.abs(x), -x …
math_round round, math.ceil, math.floor Math.round, Math.ceil, Math.floor
math_constant math.pi, math.e, math.inf Math.PI, Math.E, Infinity
math_random_int random.randint(1, 6) Math.floor(Math.random() * (6 - 1 + 1)) + 1
math_random_float random.random() Math.random()
variables_set, variables_get x = 5, x x = 5;, x
math_change x += 1 x += 1;
text, text_join "a" + str(x) "a" + String(x)
text_append s += "a" s += "a";
text_length, lists_length len(x) x.length
text_changeCase, text_trim s.upper(), s.strip() … s.toUpperCase(), s.trim() …
text_print print(x) console.log(x);
text_prompt_ext input("?"), float(input("?")) prompt("?"), Number(prompt("?"))
lists_create_with [1, 2, 3] [1, 2, 3]
lists_getIndex a[0], a[-1], a.pop() … a[0], a.at(-1), a.pop() …
lists_setIndex a[0] = x, a.append(x) … a[0] = x;, a.push(x); …
procedures_defnoreturn def f(a): function f(a) {
procedures_defreturn def f(a): … return x function f(a) { … return x; }
procedures_ifreturn if c: return x if (c) { return x; }
procedures_callnoreturn/…return f(1) f(1)
turtle_… see Turtle see Turtle
raw_statement, raw_expression any other code any other code

Notes:

  • List positions start at 0, as in the code.
  • JavaScript variables are declared once at the top (let a, b;); all variables except function parameters are global, as in Blockly.
  • In Python, functions get a global line for the variables they change.
  • Comments in front of a statement become comments of its block, and back.
  • An empty line starts a new stack of blocks.

Implementation

The template is an npm project. The sources are in src/, Parcel bundles them into dist/index.js.

npm install        # install dependencies
npm run build      # create dist/index.js (also reduces Skulpt's stdlib)
npm test           # round-trip tests: code → blocks → code, both languages
npm run typecheck  # check TypeScript
npm run gen        # regenerate the translations in src/locales/
npm run serve      # open this course locally with live reload

Structure of src/:

File Purpose
element.ts <lia-blockly>: interface, run, synchronization
bridge.ts connection to the LiaScript code block (read, write, observe ACE)
lang/python/parser.ts Python → syntax tree (Lezer)
lang/javascript/parser.ts JavaScript → syntax tree (acorn)
lang/toBlocks.ts syntax tree → blocks, the same for both languages
lang/generators.ts blocks → code, in exactly the form toBlocks.ts recognizes
lang/checks.ts keeps values that do not fit an input as free code
blocks/ turtle, free code, colour, and the API for custom blocks
runtime/python.ts runs Python with Skulpt, pausing at every block
runtime/javascript.ts runs JavaScript natively, made async to pause at every block
runtime/turtle.ts the turtle for both languages
runtime/stepper.ts speed, highlighting, stop
profiles.ts levels, custom profiles, toolbox
check.ts commands for checks
i18n.ts, locales/ languages
dom-guard.ts keeps Blockly's nodes out of <body> (see below)

Notes:

  • LiaScript manages the children of <body> by their index. Blockly adds nodes of its own there (menus, tooltips). dom-guard.ts places them in a container next to <body>.
  • The run controls and versions of LiaScript are shown below the blocks. Both only swap their visual places (position: relative), no node of LiaScript is moved.
  • Blockly loads a few images (e.g. for the zoom buttons) from a fixed version on jsDelivr.
  • dist/index.js is about 3.6 MB (0.9 MB compressed); half of it are the translations.

The macros in the header of this file:

@Blockly.python:       @Blockly._run(@uid,python,@0)
@Blockly.js:           @Blockly._run(@uid,javascript,@0)
@Blockly.javascript:   @Blockly._run(@uid,javascript,@0)
@Blockly.python.check: @Blockly._check(@uid,python,@0)
@Blockly.js.check:     @Blockly._check(@uid,javascript,@0)

@Blockly._run
<script>
window.LiaBlockly.run("@0", send, console, "@'input")
</script>

<lia-blockly id="@0" lang="@1" profile="@2"></lia-blockly>
@end

@Blockly._check
<script>
window.LiaBlockly.check("@0", send, console, "@'input(0)", async function (api) {
  const { run, expect, output, variable, call, turtle, blocks, code, lauf, erwarte, ausgabe, aufruf, schildkroete, bloecke } = api
@input(1)
})
</script>

<lia-blockly id="@0" lang="@1" profile="@2"></lia-blockly>
@end

The element <lia-blockly> finds the code block right before it, hides its editor and keeps blocks and code in sync. New versions are created by LiaScript itself as soon as ▶ is pressed.

License: MIT. The licenses of all bundled components are listed in NOTICE.

About

LiaScript template: Blockly blocks overlay Python and JavaScript code blocks, synchronized in both directions, with turtle graphics, step-by-step execution, checks and ~125 languages

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages