A Tetris example project needs to draw 200 board cells and scan rows for a full line. Neither is expressible without iteration, so the node system gets a Repeat node plus the missing text side: Substring, Find text and To number. The interpreter drops its data cache once per round -- otherwise every round would recompute with the first round's values. Object by name takes its name as an input now, not just as a setting; addressing "c" + index is impossible otherwise. An unconnected input reads the stored setting, so saved projects are untouched. The board itself is a 200-character text, "." for empty. That makes both operations of Tetris a single expression instead of a loop, and each frame attempts exactly one move -- rotate beats sideways beats fall -- so the fit check exists once in the whole graph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96 lines
5.9 KiB
Markdown
96 lines
5.9 KiB
Markdown
## Nächstes spiel: Tetris
|
||
|
||
Ganz simpel halten, also wirklich nur das nötigste:
|
||
- die möglichen shapes die es halt so gibt
|
||
- ein zufallspawn eines dieser shapes das langsam nach unten geht
|
||
- links/rechts pfeilstasten um zu bewegen, pfeil rauf um im uhrzeigsinn zu drehen, pfeil runter gedrückt halten um nach unten zu beschleunigen
|
||
- eine line gefüllte line solved
|
||
- obere grenze = game over, ansonsten ist es einfach ein endlosspiel
|
||
- counter ui der einfach nur die gesolved lines zählt
|
||
|
||
was wir nicht brauchen:
|
||
- "levels", sprich dass shapes schneller werden beim runterfallen
|
||
- titlescreen oder gameover screen, wenn gameover einfach spiel stoppen
|
||
- animationen, spezielle texturen, oder sonst irgendwelchen schnick schnack
|
||
- e2e erweiterung zu dem projekt => dieses projekt braucht keine speziellen e2e tests die man immer wieder durchlaufen muss
|
||
|
||
habe ich irgendwas vergessen? wenn ja, rückfragen, ansonsten einfach project seed anlegen und projekt generieren
|
||
|
||
falls nodes fehlen, oder praktische nodes sinnvoll wären die noch fehlen, dürfen diese natürlich erstellt werden
|
||
|
||
---
|
||
|
||
**Umgesetzt (2026-09-09).** Projekt *Tetris* (`2072b56583af`), Seed `tools/seed-tetris.mjs`,
|
||
gespielt von `tools/smoke-tetris.mjs`. Alles aus deiner Liste ist drin, nichts davon, was du
|
||
ausgeschlossen hast: keine Level, kein Titel- oder Game-Over-Screen (Game Over stoppt das Spiel
|
||
per `Stop game`), keine Animationen, keine Texturen, und **keine Erweiterung der E2E-Suite** —
|
||
der Smoke-Lauf ist wie beim Jump-and-Run ein eigenes Skript, das nur laeuft, wenn dieses Projekt
|
||
oder die Engine darunter sich aendert.
|
||
|
||
**Das Brett ist ein Text.** 200 Zeichen, eins pro Zelle: `.` fuer leer, sonst die Ziffer der Form,
|
||
die dort liegengeblieben ist. Damit sind die beiden Operationen, auf denen Tetris steht, je ein
|
||
einziger Ausdruck statt einer Schleife: eine Zelle ist `Substring(grid, zeile·10 + spalte, 1)`,
|
||
und eine volle Reihe zu loesen ist eine leere Reihe, vor alles darueber und darunter gejoint.
|
||
Gezeichnet wird aus einem **Vorrat benannter Objekte** — `c0…c199` fuer das Brett, `p0…p3` fuer
|
||
den fallenden Stein. Waehrend des Spielens wird nichts gespawnt und nichts zerstoert.
|
||
|
||
**Vier neue Nodes, alle allgemein und nicht Tetris-eigen** (danach gefragt, du hast (a) gewaehlt):
|
||
|
||
| | |
|
||
|---|---|
|
||
| `Repeat` (flow) | `times` → `body` / `index` / `done`. Der Interpreter kann seitdem Schleifen: er verwirft den Datencache **pro Runde**, sonst rechnete jede Runde mit den Werten der ersten. |
|
||
| `Substring` | `count` Zeichen ab `start`; negatives `count` reicht bis zum Ende |
|
||
| `Find text` | wo ein Text zuerst vorkommt, `−1` wenn nicht — damit ist "Reihe voll" = *kein Punkt darin* |
|
||
| `To number` | die fehlende Gegenrichtung zu `To text` |
|
||
|
||
Dazu eine Aenderung an einem vorhandenen Node: **`Object by name` nimmt den Namen jetzt als
|
||
Eingang** statt nur als Einstellung. Ohne das waeren 200 Zellen nicht adressierbar — die Zeichen-
|
||
schleife baut `"c" + index`. Alte Projekte sind unberuehrt: ein gespeicherter Name liegt in
|
||
`params.name`, und genau von dort liest ein unverbundener Eingang (eigener Test dafuer).
|
||
|
||
**Der Kunstgriff, der den Graphen klein haelt:** jeder Frame versucht **genau einen** Zug —
|
||
Drehen schlaegt Seitwaerts schlaegt Fallen. Die Tasten bewegen nichts selbst, sie hinterlassen
|
||
einen Wunsch (`wantDx`, `wantRot`), den der naechste Frame einloest. Dadurch gibt es die
|
||
Pruefung "passen die vier Zellen dort hin?" **einmal** im ganzen Graphen statt einmal pro Taste.
|
||
Ein um einen Frame verzoegerter Seitwaertsschritt ist bei 60 fps unsichtbar; drei Kopien
|
||
derselben 30 Nodes waeren es nicht.
|
||
|
||
**Wie das Landen ins Brett kommt:** der gelandete Stein wird nicht noch einmal aus der Formtabelle
|
||
ausgerechnet, sondern **von seinen vier Zellobjekten abgelesen** (`Get position` → Spalte/Zeile).
|
||
Sie stehen genau dort, wo der Stein ist — das Zeichnen ist die einzige Stelle, die sie anfasst.
|
||
|
||
**Belegt** — `tools/smoke-tetris.mjs`, remote im echten Browser: **ALL PASSED, 17 Checks.**
|
||
|
||
```
|
||
PASS the piece falls on its own – row 1 → 4
|
||
PASS the left arrow moves the piece one column – 4,5,4,5 → 3,4,3,4
|
||
PASS the up arrow turns the piece – rot 0 → 1
|
||
PASS the board draws exactly the cells the board text names – {"lit":8,"inText":8}
|
||
PASS a full line disappears and is counted – lines 1, bottom row "....44...."
|
||
PASS a piece that cannot fall at the very top stops the game – stopped
|
||
```
|
||
|
||
Der Brett-Check ist der wichtigste: er haelt den **Text** gegen die **Bildschirmzellen** — ohne ihn
|
||
koennte die Zeichenschleife irgendetwas anzeigen und alles andere trotzdem gruen sein. Fuer die
|
||
volle Reihe wird die unterste Reihe vorher gefuellt, damit jeder Stein sie vollmacht, egal welcher
|
||
kommt. Der Game-Over-Check wartet auf die Bedingung statt auf die Uhr — mit fester Wartezeit war
|
||
er in einem von zwei Laeufen rot, und ein Check, der wuerfelt, ist keiner.
|
||
|
||
**Was ich selbst entschieden habe:**
|
||
|
||
- **Kein Wall-Kick.** Eine Drehung, die nicht passt, findet nicht statt — sie wird nicht an der
|
||
Wand vorbeigeschoben. Das ist die simple Regel, und "ganz simpel" war die Vorgabe.
|
||
- **Farbe pro Form**, wie im Original. Kostet drei Nodes: die Ziffer im Bretttext sucht ihre
|
||
Farbe aus einem zweiten Text (`colors`), acht Ziffern je Farbe.
|
||
- **Drehachse** ist die Box der Form (I in 4×4, O in 2×2, Rest 3×3), nicht der Schwerpunkt. Ohne
|
||
Wall-Kick ist das das Verhalten, das am wenigsten ueberrascht.
|
||
- **Game Over = ein Stein kann ganz oben (`py ≤ 0`) nicht einmal fallen.** Kein zweiter Test beim
|
||
Erscheinen, damit die Pruefung an der einen Stelle bleibt.
|
||
- **Tempo:** eine Reihe pro halber Sekunde, gehaltene Pfeil-runter-Taste zehnmal so schnell. Beides
|
||
steht als Variable `stepTime` im Inspector.
|
||
|
||
**Was du wissen solltest:** der Board-Container traegt **205 Objekte** (Wanne + 200 Zellen + 4
|
||
Steinzellen). Im Editor sieht man davon nichts — die Szene zeigt acht Objekte, der Rest liegt
|
||
im Container. Wer ihn aber oeffnet, bekommt eine lange Liste. Das ist der Preis dafuer, dass
|
||
nichts gespawnt wird.
|