Skip to content

Commit ad0025e

Browse files
Web 0.14.17: Hinweis auf fehlenden Zeilenumbruch bei print (#227)
Stimmt die Ausgabe einer Übung bis auf fehlende Zeilenumbrüche (z. B. zweimal System.out.print statt println), zeigt die Prüfung einen freundlichen Hinweis auf println. \n in einer Ausgabe beginnt jetzt eine neue Zeile, print("…\n") zählt wie println. Claude-Session: https://claude.ai/code/session_01Ugz28WidPQ6TKvwxgRTMM9 Co-authored-by: Claude <noreply@anthropic.com>
1 parent b8d0d7b commit ad0025e

19 files changed

Lines changed: 1280 additions & 1240 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is loosely inspired by Keep a Changelog.
88

99
### Changed
1010

11+
- **Web 0.14.17, hint on a missing line break:** when a Java exercise's output is right except that lines run together (e.g. `System.out.print` twice instead of `println`), the check now says „Fast! Es fehlt nur ein Zeilenumbruch …“ and explains print vs. println, instead of only showing a mismatch. `\n` inside an output now starts a new line, so `System.out.print("…\n")` counts like `println` (de/en/pt; all exercises).
1112
- **Web 0.14.16, „Im Editor öffnen“ under the Java examples in lessons 4–19:** every runnable Java example in the lesson text of lessons 4–19 now has the button „Im Editor öffnen“ (like the Mia example in lesson 4), so students can run and change it in Programmieren. Not in lesson 18, whose examples are a method and its call on their own, and not in cloze texts or games (de/en/pt).
1213
- **Web 0.14.15, three kinds of errors, red line numbers and a light bulb with suggestions:** lesson 7 gets the new step „Drei Arten von Fehlern“ (syntax error, runtime error, logic error, with the app's own messages), a Kompetenz item and two quiz questions; lesson 20 explains the red line number and the light bulb and calls the debugger the tool for logic errors (plus Kompetenz and quiz question); lesson 22 calls an exception a runtime error; glossary entries Syntaxfehler, Laufzeitfehler, Logikfehler (de/en/pt). In the Code area the line number of an error turns red (syntax problems and the line where the run stopped), the console names the line of a runtime error (e.g. „Zeile 3: Division durch null.“), and a 💡 at the end of the line offers a fix when VisuStruct knows it (missing `import`, missing `Scanner … = new Scanner(System.in);`, missing `;` or `)`, misspelled data type such as `Strng` → `String`); hover shows it, a click applies it. The round red dot stays the breakpoint.
1314
- **Web 0.13.37, no coffee cup and a better camel in lesson 4:** the coffee cup drawing in step „Jetzt in Java“ is removed again (Holger's decision). The camel for camelCase is redrawn to look more like a camel: long legs with knees and broad feet, U-shaped neck, longer snout, tail with tuft.

‎web/package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎web/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "visustruct-web",
33
"private": true,
4-
"version": "0.14.16",
4+
"version": "0.14.17",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

‎web/src/core/exerciseCheck.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ export function sameOutput(actual: string[], expected: string[]): boolean {
125125
return actual.every((line, i) => normalize(line) === normalize(expected[i]))
126126
}
127127

128+
/** Stimmt die Ausgabe bis auf fehlende Zeilenumbrüche, z. B. zweimal `print` statt `println`? */
129+
export function onlyLineBreaksMissing(actual: string[], expected: string[]): boolean {
130+
const joined = (lines: string[]) => lines.join('').replace(/\s+/g, '')
131+
return actual.length > 0 && actual.length < expected.length && joined(actual) === joined(expected)
132+
}
133+
128134
function normalize(line: string): string {
129135
const t = line.trim().replace(/\s+/g, ' ')
130136
return /^-?\d+\.0+$/.test(t) ? t.replace(/\.0+$/, '') : t

‎web/src/core/simulation.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,9 +1406,14 @@ export class SimulationEngine {
14061406
const rendered = this.peek(() => this.renderOutputExpression(expression))
14071407
// `println()` ohne Text: nur eine neue Zeile.
14081408
const output = expression.trim() === '' ? '' : this.evaluateOutputExpression(expression)
1409-
if (state.outputLineOpen && state.outputLines.length) state.outputLines[state.outputLines.length - 1] += output
1410-
else state.outputLines.push(output)
1411-
state.outputLineOpen = javaOut?.[1] === 'print'
1409+
// `\n` im Text beginnt eine neue Zeile; `print("…\n")` endet wie `println("…")`.
1410+
const print = javaOut?.[1] === 'print'
1411+
const endsLine = print && output.endsWith('\n')
1412+
const [first, ...rest] = (endsLine ? output.slice(0, -1) : output).split('\n')
1413+
if (state.outputLineOpen && state.outputLines.length) state.outputLines[state.outputLines.length - 1] += first
1414+
else state.outputLines.push(first)
1415+
state.outputLines.push(...rest)
1416+
state.outputLineOpen = print && !endsLine
14121417
this.afterOutput = true
14131418
state.lastTrace = `${rendered} -> ${output}`
14141419
return

‎web/src/i18n/de.strings‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"web_learn_actual" = "Deine Ausgabe";
9999
"web_learn_missing_input" = "Dein Struktogramm fragt nach mehr Eingaben als vorgesehen.";
100100
"web_learn_unused_input" = "Dein Struktogramm fragt nicht alle Eingaben ab.";
101+
"web_learn_missing_line_break" = "Fast! Es fehlt nur ein Zeilenumbruch. System.out.print schreibt weiter in derselben Zeile, System.out.println beginnt danach eine neue.";
101102
"web_learn_too_many_steps" = "Abbruch nach 100 000 Schritten. Läuft eine Schleife endlos?";
102103
"web_learn_score_fmt" = "%d von %d richtig";
103104
"web_learn_cloze_hint" = "Tippe die fehlenden Wörter in die Lücken und drücke „Prüfen“.";

‎web/src/i18n/en.strings‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"web_learn_actual" = "Your output";
9999
"web_learn_missing_input" = "Your structure chart asks for more input than planned.";
100100
"web_learn_unused_input" = "Your structure chart does not ask for all inputs.";
101+
"web_learn_missing_line_break" = "Almost! Only a line break is missing. System.out.print keeps writing on the same line, System.out.println starts a new line afterwards.";
101102
"web_learn_too_many_steps" = "Stopped after 100,000 steps. Is a loop running forever?";
102103
"web_learn_score_fmt" = "%d of %d correct";
103104
"web_learn_cloze_hint" = "Type the missing words into the gaps and press “Check”.";

‎web/src/i18n/pt-PT.strings‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"web_learn_actual" = "A tua saída";
9797
"web_learn_missing_input" = "O teu estrutograma pede mais entradas do que o previsto.";
9898
"web_learn_unused_input" = "O teu estrutograma não pede todas as entradas.";
99+
"web_learn_missing_line_break" = "Quase! Só falta uma quebra de linha. System.out.print continua a escrever na mesma linha, System.out.println começa depois uma linha nova.";
99100
"web_learn_too_many_steps" = "Parado após 100 000 passos. Há um ciclo que nunca termina?";
100101
"web_learn_score_fmt" = "%d de %d certas";
101102
"web_learn_cloze_hint" = "Escreve as palavras que faltam nos espaços e carrega em “Verificar”.";

‎web/src/ui/learnPanel.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { checkExercise, fileLines, loopDepth, usedElementTypes, type TestOutcome } from '../core/exerciseCheck'
1+
import { checkExercise, fileLines, loopDepth, onlyLineBreaksMissing, usedElementTypes, type TestOutcome } from '../core/exerciseCheck'
22
import { compressDrive } from '../core/mbot'
33
import { isPlaceholderElement } from '../core/editing'
44
import { ElementType, makeElement, type VSDocument } from '../core/model'
@@ -778,6 +778,8 @@ export class LearnPanel {
778778
h('span', {}, `📄 ${f.name}, ${t('web_learn_actual')}: `, h('code', {}, fileLines(o.result.files?.get(f.name)).join(' | ') || '–')),
779779
)
780780
}
781+
if (!o.passed && !o.result.error && onlyLineBreaksMissing(o.result.outputs, o.test.expected))
782+
detail.append(h('span', { class: 'learn-error' }, t('web_learn_missing_line_break')))
781783
if (o.result.error) detail.append(h('span', { class: 'learn-error' }, noticeText(o.result.error)))
782784
if (o.result.missingInput) detail.append(h('span', { class: 'learn-error' }, t('web_learn_missing_input')))
783785
if (o.result.tooManySteps) detail.append(h('span', { class: 'learn-error' }, t('web_learn_too_many_steps')))

‎web/test/javaParser.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { documentFromJava, mergeLiveJava, plainLiveJava, type JavaForm } from '.
88
import { ElementType, makeElement, type VSDocument } from '../src/core/model'
99
import { decodeDocument } from '../src/core/xmlCodec'
1010
import { SimulationEngine } from '../src/core/simulation'
11+
import { onlyLineBreaksMissing, sameOutput } from '../src/core/exerciseCheck'
1112
import { generateCode } from '../src/core/codeGenerator'
1213
import { indentJava } from '../src/core/javaIndent'
1314

@@ -154,6 +155,23 @@ describe('Java tippen → Struktogramm', () => {
154155
expect(keys('System.out.println("Scanner");')).toEqual([])
155156
})
156157

158+
it('\\n in einer Ausgabe beginnt eine neue Zeile; fehlende Zeilenumbrüche werden erkannt', () => {
159+
const run = (src: string) => {
160+
const engine = new SimulationEngine(doc(src))
161+
let steps = 0
162+
while (engine.canStep && steps++ < 1000) engine.step()
163+
return engine.state.outputLines
164+
}
165+
const expected = ['Ludwig ist 7 Jahre alt.', 'Größe: 1.2 m']
166+
expect(run('System.out.print("Ludwig ist 7 Jahre alt.\\n");\nSystem.out.print("Größe: 1.2 m");')).toEqual(expected)
167+
expect(run('System.out.println("A\\nB");\nSystem.out.print("C");')).toEqual(['A', 'B', 'C'])
168+
const joined = run('System.out.print("Ludwig ist 7 Jahre alt.");\nSystem.out.print("Größe: 1.2 m");')
169+
expect(sameOutput(joined, expected)).toBe(false)
170+
expect(onlyLineBreaksMissing(joined, expected)).toBe(true)
171+
expect(onlyLineBreaksMissing(['Ludwig ist 7 Jahre alt. Größe: 1.3 m'], expected)).toBe(false)
172+
expect(onlyLineBreaksMissing(expected, expected)).toBe(false)
173+
})
174+
157175
it('System.out.print schreibt ohne neue Zeile, auch mit Array-Fach', () => {
158176
const d = doc(
159177
'int[] intAllgemeinFach = {3, 5};\n' +

0 commit comments

Comments
 (0)