Skip to content
This repository was archived by the owner on Nov 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is loosely inspired by Keep a Changelog.

### Added

- **Web 0.13.4, Pseudocode switch in the toolbar and simulation of pseudocode:** the checkbox „Pseudocode“ now sits in the toolbar left of ⇄ (before it was only in the code window). For a diagram marked as pseudocode (e.g. „Tee kochen“ in Lektion 1) the simulation no longer runs the boxes as Java: it only highlights them one after another, and for a condition („Regnet es?“), a loop or a Fallauswahl it asks for the answer (true/false or ja/nein, case name). Programmieren shows no Java errors for pseudocode. Lektion 1 now mentions spaghetti code right where it says a Struktogramm has no jumps.
- **Web 0.13.3, Struktogramm and Elemente in the code block colours:** the Struktogramm uses the same soft colours as the blocks in Programmieren: statements yellow like the method, loops (for, while, do-until, endless) pink, Verzweigung and Fallauswahl blue; elements inside a loop or branch take its colour, so code and diagram side by side show the matching blocks at a glance. The boxes in „Elemente“ get the same colours. The setting is now called „Blöcke farbig“ and switches code, diagram and Elemente together; a colour stored in the file still wins, and exports stay white. Lesson 1 uses the same colours (statements yellow, branch blue, loop pink; the sequence itself has no colour, it is the row of statements).
- **Web 0.13.1, divider between Struktogramm and Programmieren always draggable:** with four open columns (lessons, Struktogramm, Programmieren, „mBot2 live“) the diagram sat at its minimum width and the divider barely moved. Dragging it now first uses the space of the two columns next to it, then takes width from „mBot2 live“, then from the lessons and files columns (down to their minimum widths). Works in both orders after ⇄; all widths are remembered.
- **Web 0.13.0, VisuBot simulation in the „mBot2 live“ window:** in the VisuBot world „Simulation starten“ no longer shows a second 3D world in the simulation column; the mBot2 of the simulation drives in the „mBot2 live“ window instead (title „🤖 Simulation“, world buttons and distance/line values there, its own ▶ rests until the simulation ends). The window opens by itself for the simulation and closes again afterwards if it was closed before; if it is closed during the simulation, the 3D world goes back to the simulation column. Controls, variables, console, sensor sliders and the real mBot2 stay in the simulation column.
Expand Down
2 changes: 1 addition & 1 deletion web/lessons/de/01-was-ist-ein-struktogramm.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Die Darstellung stammt von **Isaac Nassi und Ben Shneiderman** (1973). Deshalb h
Links Isaac „Ike“ Nassi (Foto: [Ed Schipul](https://commons.wikimedia.org/wiki/File:Me_with_Ike_Nassi!!!!!_%286893328729%29.jpg), [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/deed.de), zugeschnitten), rechts Ben Shneiderman (Foto: [Robert Kosara](https://commons.wikimedia.org/wiki/File:Ben_Shneiderman_at_UNCC_%283x4_cropped%29.jpg), CC0).
:::

> Ein Struktogramm hat immer genau einen Anfang (oben) und ein Ende (unten). Es gibt keine Pfeile und keine Sprünge kreuz und quer.
> Ein Struktogramm hat immer genau einen Anfang (oben) und ein Ende (unten). Es gibt keine Pfeile und keine Sprünge kreuz und quer. Programme, die kreuz und quer springen, nennt man **Spaghetticode**, weil sich ihr Ablauf verheddert wie Nudeln auf dem Teller. Mehr dazu in Schritt 8.

::: angeberwissen
Määäh, aufgepasst! 1972 waren Nassi und Shneiderman noch Doktoranden an der Stony Brook University bei New York, Shneiderman etwa 25 und Nassi etwa 23. Also quasi zwei Studis, die nebenbei Informatikgeschichte geschrieben haben.
Expand Down
2 changes: 1 addition & 1 deletion web/lessons/en/01-was-ist-ein-struktogramm.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This way of drawing was invented by **Isaac Nassi and Ben Shneiderman** (1973).
Left: Isaac “Ike” Nassi (photo: [Ed Schipul](https://commons.wikimedia.org/wiki/File:Me_with_Ike_Nassi!!!!!_%286893328729%29.jpg), [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/), cropped), right: Ben Shneiderman (photo: [Robert Kosara](https://commons.wikimedia.org/wiki/File:Ben_Shneiderman_at_UNCC_%283x4_cropped%29.jpg), CC0).
:::

> A structure chart always has exactly one start (at the top) and one end (at the bottom). There are no arrows and no jumps all over the place.
> A structure chart always has exactly one start (at the top) and one end (at the bottom). There are no arrows and no jumps all over the place. Programs that jump all over the place are called **spaghetti code**, because their flow gets tangled like noodles on a plate. More on this in step 8.

::: angeberwissen
Baaah, listen up! In 1972 Nassi and Shneiderman were still PhD students at Stony Brook University near New York. Shneiderman was about 25 and Nassi about 23. So basically two students who wrote computer science history on the side.
Expand Down
2 changes: 1 addition & 1 deletion web/lessons/pt/01-was-ist-ein-struktogramm.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Esta forma de desenhar foi inventada por **Isaac Nassi e Ben Shneiderman** (1973
À esquerda: Isaac “Ike” Nassi (foto: [Ed Schipul](https://commons.wikimedia.org/wiki/File:Me_with_Ike_Nassi!!!!!_%286893328729%29.jpg), [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/), recortada), à direita: Ben Shneiderman (foto: [Robert Kosara](https://commons.wikimedia.org/wiki/File:Ben_Shneiderman_at_UNCC_%283x4_cropped%29.jpg), CC0).
:::

> Um estrutograma tem sempre exatamente um início (em cima) e um fim (em baixo). Não há setas nem saltos para todo o lado.
> Um estrutograma tem sempre exatamente um início (em cima) e um fim (em baixo). Não há setas nem saltos para todo o lado. Programas que saltam para todo o lado chamam-se **código esparguete**, porque o seu fluxo se enrola como massa no prato. Mais sobre isto no passo 8.

::: angeberwissen
Méééé, ouve bem! Em 1972, Nassi e Shneiderman ainda eram doutorandos na Universidade de Stony Brook, perto de Nova Iorque. Shneiderman tinha uns 25 anos e Nassi uns 23. Ou seja, dois estudantes que escreveram história da informática nas horas vagas.
Expand Down
4 changes: 2 additions & 2 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "visustruct-web",
"private": true,
"version": "0.13.3",
"version": "0.13.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
85 changes: 78 additions & 7 deletions web/src/core/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export interface InputRequest {
prompt: string
/** Wie in Java hinter `System.out.print("Note: ")`: Die Eingabe steht in derselben Zeile wie diese Ausgabe. */
continuesLine?: boolean
/** Pseudocode: Die Frage gehört zu einer Bedingung, die Antwort (true/false) entscheidet den Weg. */
decision?: boolean
}

/** Meldungen der Simulation, unabhängig von der Sprache. */
Expand Down Expand Up @@ -961,12 +963,17 @@ export class SimulationEngine {
private peeking = false
/** Messwerte des echten mBot2; fehlt ein Wert, gilt der simulierte aus `MBOT_SENSORS`. */
robotSensors: MBotSensorValues = {}
/** Pseudocode: Kästen werden nur der Reihe nach gezeigt, Bedingungen beantwortet man selbst. */
private pseudocode: boolean
/** Pseudocode: Antwort auf die gerade gestellte Frage (Bedingung: true/false, Fallauswahl: Name des Falls). */
private decided?: boolean | string

constructor(
document: VSDocument,
private resolveSubprogram: SubprogramResolver = () => undefined,
) {
this.steps = buildSteps(document.elements)
this.pseudocode = !!document.pseudocode
}

/** Im Unterprogramm bleibt der Aufruf-Kasten im Hauptprogramm markiert. */
Expand All @@ -985,7 +992,11 @@ export class SimulationEngine {
}

reset(document?: VSDocument): void {
if (document) this.steps = buildSteps(document.elements)
if (document) {
this.steps = buildSteps(document.elements)
this.pseudocode = !!document.pseudocode
}
this.decided = undefined
this.state = freshState()
this.afterOutput = false
this.nextFrame = 1
Expand All @@ -1003,6 +1014,11 @@ export class SimulationEngine {
try {
switch (step.kind) {
case 'statement':
// Pseudocode ist kein Programm: Der Kasten wird nur gezeigt, nicht ausgeführt.
if (this.pseudocode) {
state.lastTrace = step.text
break
}
if (/^return\b/u.test(step.text)) return this.executeReturn(step)
this.executeStatement(step.text)
break
Expand Down Expand Up @@ -1034,6 +1050,7 @@ export class SimulationEngine {
provideInput(rawValue: string): void {
const request = this.state.inputRequest
if (!request) return
if (request.decision) return this.provideDecision(rawValue)
try {
const value = parseInputValue(rawValue, request.valueType)
this.assignValue(value, request.variableName)
Expand All @@ -1048,6 +1065,42 @@ export class SimulationEngine {
this.state.notice = this.finishedIfAtEnd()
}

/** Pseudocode: Antwort auf die Frage zur Bedingung, dann gleich den Weg nehmen (die Bedingung ist der Schritt). */
private provideDecision(rawValue: string): void {
if (this.state.inputRequest?.valueType === 'string') {
this.state.inputRequest = undefined
this.decided = rawValue.trim()
return this.step()
}
const answer = rawValue.trim().toLowerCase()
const yes = ['true', 'wahr', 'ja', 'yes', 'sim', 'verdadeiro'].includes(answer)
const no = ['false', 'falsch', 'nein', 'no', 'não', 'nao', 'falso'].includes(answer)
if (!yes && !no) {
this.state.inputErrorNotice = { kind: 'booleanExpected' }
return
}
this.state.inputRequest = undefined
this.state.inputErrorNotice = undefined
this.decided = yes
this.step()
}

/**
* Wert einer Bedingung. Bei Pseudocode (z. B. „Regnet es?“) fragt die Simulation: `undefined`, bis die Antwort da ist.
*/
private condition(condition: string): boolean | undefined {
if (!this.pseudocode) return this.evaluateBool(condition)
const answer = this.decided
if (typeof answer === 'boolean') {
this.decided = undefined
return answer
}
this.state.inputRequest = { variableName: '', valueType: 'boolean', prompt: `${condition} (true/false)`, decision: true }
this.state.inputErrorNotice = undefined
this.state.notice = undefined
return undefined
}

private finishedIfAtEnd(): SimNotice | undefined {
return this.state.stepIndex >= this.steps.length ? { kind: 'finished' } : undefined
}
Expand All @@ -1065,7 +1118,8 @@ export class SimulationEngine {
const cases = step.cases
if (!cases || cases.length < 2) throw unsupported(step.text)
const condition = stripConditionKeyword(step.text)
const result = this.evaluateBool(condition)
const result = this.condition(condition)
if (result === undefined) return
const selected = result ? 0 : 1
const branchSteps = buildSteps(cases[selected].elements, [...step.path, selected], contextOf(step))
this.splice(branchSteps)
Expand All @@ -1077,15 +1131,29 @@ export class SimulationEngine {
const cases = step.cases
if (!cases || cases.length === 0) throw unsupported(step.text)
const selector = stripConditionKeyword(step.text)
const value = this.evaluateSwitchSelector(selector)
const selected = matchingCaseIndex(value, cases) ?? cases.length - 1
let selected: number
if (this.pseudocode) {
// Pseudocode: Man wählt den Fall selbst; unbekannte Antwort = letzter Fall (sonst).
const answer = this.decided
if (typeof answer !== 'string') {
const names = cases.map((c) => c.name).join(' / ')
this.state.inputRequest = { variableName: '', valueType: 'string', prompt: `${selector} (${names})`, decision: true }
this.state.notice = undefined
return
}
this.decided = undefined
const index = cases.findIndex((c) => c.name.trim().toLowerCase() === answer.toLowerCase())
selected = index >= 0 ? index : cases.length - 1
} else selected = matchingCaseIndex(this.evaluateSwitchSelector(selector), cases) ?? cases.length - 1
const caseSteps = buildSteps(cases[selected].elements, [...step.path, selected], contextOf(step))
this.splice(caseSteps)
this.state.lastTrace = `${selector} -> ${cases[selected].name}`
this.state.notice = this.noticeAfterEntering(caseSteps)
}

private executeForLoopStart(step: SimStep): void {
// Pseudocode wie „für jeden Gast“: eine Frage wie bei der kopfgesteuerten Schleife.
if (this.pseudocode) return this.splice([{ ...step, kind: 'whileLoop' }])
const parts = normalizedForParts(step.forParts ?? [step.text])
if (parts[0] !== '') this.executeStatement(parts[0])
this.splice([{ ...contextOf(step), path: step.path, text: parts[1], kind: 'forLoopCondition', loopBody: step.loopBody, forParts: parts }])
Expand All @@ -1094,7 +1162,8 @@ export class SimulationEngine {
private executeForLoopCondition(step: SimStep): void {
const parts = normalizedForParts(step.forParts ?? [step.text])
const condition = parts[1] === '' ? 'true' : parts[1]
const shouldEnter = this.evaluateBool(condition)
const shouldEnter = this.condition(condition)
if (shouldEnter === undefined) return
this.state.lastTrace = `${condition} -> ${shouldEnter}`
if (shouldEnter) {
const bodySteps = buildSteps(step.loopBody ?? [], [...step.path, 0], contextOf(step))
Expand All @@ -1117,7 +1186,8 @@ export class SimulationEngine {

private executeWhileLoop(step: SimStep): void {
const condition = stripConditionKeyword(step.text)
const shouldEnter = this.evaluateBool(condition)
const shouldEnter = this.condition(condition)
if (shouldEnter === undefined) return
this.state.lastTrace = `${condition} -> ${shouldEnter}`
if (shouldEnter) {
const bodySteps = buildSteps(step.loopBody ?? [], [...step.path, 0], contextOf(step))
Expand All @@ -1137,7 +1207,8 @@ export class SimulationEngine {

private executeDoUntilCondition(step: SimStep): void {
const condition = stripConditionKeyword(step.text)
const shouldRepeat = this.evaluateBool(condition)
const shouldRepeat = this.condition(condition)
if (shouldRepeat === undefined) return
this.state.lastTrace = `${condition} -> ${shouldRepeat}`
if (shouldRepeat) {
const bodySteps = buildSteps(step.loopBody ?? [], [...step.path, 0], contextOf(step))
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/ar.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "إظهار العناصر";
"web_code_scopes" = "كتل الشيفرة ملوّنة";
"web_code_scopes_hint" = "الصنف والدالة والحلقة والتفرّع في مربعات خاصة";
"web_pseudocode" = "شبه كود";
1 change: 1 addition & 0 deletions web/src/i18n/da.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Vis elementerne";
"web_code_scopes" = "Farvede kodeblokke";
"web_code_scopes_hint" = "Klasse, metode, løkke, forgrening i egne felter";
"web_pseudocode" = "Pseudokode";
1 change: 1 addition & 0 deletions web/src/i18n/de.strings
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,4 @@
"web_visuobject_method_ph" = "+ bellen(): void";
"web_code_scopes" = "Blöcke farbig";
"web_code_scopes_hint" = "Code und Struktogramm: Schleife rosa, Verzweigung blau, Methode gelb, Klasse grün";
"web_pseudocode" = "Pseudocode";
1 change: 1 addition & 0 deletions web/src/i18n/el.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Εμφάνιση στοιχείων";
"web_code_scopes" = "Χρωματιστά μπλοκ κώδικα";
"web_code_scopes_hint" = "Κλάση, μέθοδος, βρόχος, διακλάδωση σε δικά τους πλαίσια";
"web_pseudocode" = "Ψευδοκώδικας";
1 change: 1 addition & 0 deletions web/src/i18n/en.strings
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,4 @@
"web_visuobject_method_ph" = "+ bark(): void";
"web_code_scopes" = "Colored blocks";
"web_code_scopes_hint" = "Code and structogram: loop pink, branch blue, method yellow, class green";
"web_pseudocode" = "Pseudocode";
1 change: 1 addition & 0 deletions web/src/i18n/es.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Mostrar elementos";
"web_code_scopes" = "Bloques de código en color";
"web_code_scopes_hint" = "Clase, método, bucle, condición en sus propias cajas";
"web_pseudocode" = "Pseudocódigo";
1 change: 1 addition & 0 deletions web/src/i18n/fr.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Afficher les éléments";
"web_code_scopes" = "Blocs de code en couleur";
"web_code_scopes_hint" = "Classe, méthode, boucle, condition dans leurs propres cadres";
"web_pseudocode" = "Pseudo-code";
1 change: 1 addition & 0 deletions web/src/i18n/he.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "הצגת הרכיבים";
"web_code_scopes" = "בלוקי קוד צבעוניים";
"web_code_scopes_hint" = "מחלקה, מתודה, לולאה, הסתעפות בתיבות משלהן";
"web_pseudocode" = "פסאודו-קוד";
1 change: 1 addition & 0 deletions web/src/i18n/it.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Mostra elementi";
"web_code_scopes" = "Blocchi di codice colorati";
"web_code_scopes_hint" = "Classe, metodo, ciclo, selezione in riquadri propri";
"web_pseudocode" = "Pseudocodice";
1 change: 1 addition & 0 deletions web/src/i18n/ja.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "要素を表示";
"web_code_scopes" = "コードブロックを色分け";
"web_code_scopes_hint" = "クラス、メソッド、ループ、分岐をそれぞれの枠で表示";
"web_pseudocode" = "疑似コード";
1 change: 1 addition & 0 deletions web/src/i18n/ko.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "요소 표시";
"web_code_scopes" = "코드 블록 색상 표시";
"web_code_scopes_hint" = "클래스, 메서드, 반복, 분기를 각각의 상자로 표시";
"web_pseudocode" = "의사 코드";
1 change: 1 addition & 0 deletions web/src/i18n/nb.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Vis elementene";
"web_code_scopes" = "Fargede kodeblokker";
"web_code_scopes_hint" = "Klasse, metode, løkke, forgrening i egne bokser";
"web_pseudocode" = "Pseudokode";
1 change: 1 addition & 0 deletions web/src/i18n/nl.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Elementen tonen";
"web_code_scopes" = "Gekleurde codeblokken";
"web_code_scopes_hint" = "Klasse, methode, lus, keuze in eigen kaders";
"web_pseudocode" = "Pseudocode";
1 change: 1 addition & 0 deletions web/src/i18n/pl.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Pokaż elementy";
"web_code_scopes" = "Kolorowe bloki kodu";
"web_code_scopes_hint" = "Klasa, metoda, pętla, rozgałęzienie we własnych ramkach";
"web_pseudocode" = "Pseudokod";
1 change: 1 addition & 0 deletions web/src/i18n/pt-PT.strings
Original file line number Diff line number Diff line change
Expand Up @@ -663,3 +663,4 @@
"web_visuobject_method_ph" = "+ ladrar(): void";
"web_code_scopes" = "Blocos coloridos";
"web_code_scopes_hint" = "Código e diagrama: ciclo rosa, decisão azul, método amarelo, classe verde";
"web_pseudocode" = "Pseudocódigo";
1 change: 1 addition & 0 deletions web/src/i18n/ru.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Показать элементы";
"web_code_scopes" = "Цветные блоки кода";
"web_code_scopes_hint" = "Класс, метод, цикл, ветвление в отдельных рамках";
"web_pseudocode" = "Псевдокод";
1 change: 1 addition & 0 deletions web/src/i18n/sv.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Visa elementen";
"web_code_scopes" = "Färgade kodblock";
"web_code_scopes_hint" = "Klass, metod, loop, förgrening i egna rutor";
"web_pseudocode" = "Pseudokod";
1 change: 1 addition & 0 deletions web/src/i18n/tr.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Öğeleri göster";
"web_code_scopes" = "Renkli kod blokları";
"web_code_scopes_hint" = "Sınıf, metot, döngü, dallanma kendi kutularında";
"web_pseudocode" = "Sözde kod";
1 change: 1 addition & 0 deletions web/src/i18n/uk.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "Показати елементи";
"web_code_scopes" = "Кольорові блоки коду";
"web_code_scopes_hint" = "Клас, метод, цикл, розгалуження в окремих рамках";
"web_pseudocode" = "Псевдокод";
1 change: 1 addition & 0 deletions web/src/i18n/zh-Hans.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "显示元素";
"web_code_scopes" = "代码块彩色显示";
"web_code_scopes_hint" = "类、方法、循环、分支各有自己的框";
"web_pseudocode" = "伪代码";
1 change: 1 addition & 0 deletions web/src/i18n/zh-Hant.strings
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@
"web_palette_show" = "顯示元素";
"web_code_scopes" = "程式碼區塊彩色顯示";
"web_code_scopes_hint" = "類別、方法、迴圈、分支各有自己的框";
"web_pseudocode" = "虛擬碼";
Loading
Loading