--{{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,
printandinputwork 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.
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.
--{{0}}--
The blocks lie on top of the code block. What is stored, however, is always the code in the code block.
- 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.
- 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.
- Press ▶ – the program runs; the block that is running lights up. The slider between 🐢 and 🐇 sets the speed, ⏹ stops the program.
- 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
--{{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 |
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)
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)
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)
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)
--{{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,copyand a few more are available. - JavaScript runs natively in the browser.
prompt()reads from the LiaScript terminal,console.log()writes to it.
--{{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() |
|
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
--{{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.
--{{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.
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)
<!--
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 |
--{{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).
--{{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,jaorzh. - 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ößeorсчёт.
An example in German is in examples/deutsch.md.
--{{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.
--{{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
globalline 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.
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 reloadStructure 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.tsplaces 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.jsis 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>
@endThe 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.