How it's built
Kistris is pure Kiste. It uses only the built-in modules gui (windows,
drawing, keys), klang, liste, zufall,
wert, datei and text — no external game engine.
Here are the most interesting parts.
1 · The playfield is a flat list
Instead of a two-dimensional grid, Kistris keeps the whole field as one long
list of 200 cells. The cell at column x, row y lives at index
y · BREITE + x. Two small functions hide that arithmetic — after that it
feels like a grid.
// The playfield is ONE flat list, not a 2-D grid.
// Cell (x, y) lives at index y * BREITE + x.
funktion feld_hole(x, y) {
gib wert.als_ganz(feld[y * BREITE + x])
}
funktion feld_setze(x, y, w) {
feld[y * BREITE + x] = w
} 2 · Seven pieces from a table
Each of the seven pieces has four orientations. Rather than rotating them at runtime,
every shape sits ready in a table — four rotations, four blocks each as (x, y) pairs
relative to the pivot. form_wert is the lookup service.
// One flat list per piece type: 4 rotations x 4 blocks x (x, y).
// Clockwise rotation is already baked into the table.
funktion form_wert(t, r, k) {
nimm idx = r * 8 + k
wenn t == 1 { gib FORM_I[idx] } // I piece
wenn t == 2 { gib FORM_O[idx] } // O piece
wenn t == 3 { gib FORM_T[idx] } // T piece
// … 4=S · 5=Z · 6=J
gib FORM_L[idx] // L piece
} 3 · One question decides everything: does it fit?
Moving, rotating, dropping, game over — it all hangs on a single check: does the piece
in orientation r at (bx, by) collide with a wall, the floor or
a settled block? Rotation with "wall-kick" simply asks the same question again a few
cells over.
// A single question decides everything: does the piece fit here?
funktion kollidiert(t, r, bx, by) {
für k = 0 bis 3 {
nimm x = bx + form_wert(t, r, k * 2)
nimm y = by + form_wert(t, r, k * 2 + 1)
wenn x < 0 oder x >= BREITE { gib 1 } // wall
wenn y >= HÖHE { gib 1 } // floor
wenn y >= 0 {
wenn feld_hole(x, y) != 0 { gib 1 } // another block
}
}
gib 0
} 4 · Clearing rows without shifting
The nicest trick: a full row isn't laboriously "copied down". Because the field is a flat list, we cut out the row's ten cells and prepend ten zeros — and the whole stack above slides down one line at once.
// Clearing a full row: cut out the 10 cells and PREPEND 10 zeros —
// everything above slides down one line automatically.
für k = 1 bis BREITE { liste.entferne(feld, y * BREITE) }
für k = 1 bis BREITE { liste.stelle_voran(feld, 0) } 5 · Fair randomness: the 7-bag
Real Tetris doesn't roll pieces purely at random. A "bag" holds each of the seven types exactly once in random order; when it's empty, it refills. So you never get the same piece ten times, and never wait forever for the long bar.
// The 7-bag: a "bag" holding types 1-7 in random order.
// Fairer than pure random — never 10x the same piece, never an I-drought.
funktion fülle_beutel() {
nimm kandidaten = [1, 2, 3, 4, 5, 6, 7]
solange liste.länge(kandidaten) > 0 {
nimm z = zufall.ganz(0, liste.länge(kandidaten) - 1)
liste.hänge_an(beutel, wert.als_ganz(kandidaten[z]))
liste.entferne(kandidaten, z)
}
} 6 · High-score list — typed after the game
The best results land in a text file and survive a restart. Like the old arcade machines, the player types their name after the round, straight onto the screen — no text field that steals the keys mid-game. The key handler assembles the name letter by letter itself.
// The name is typed AFTER the game — no text field that would
// steal the keys during play. The handler reads the letters itself:
wenn taste == "eingabe" {
speichern() // Enter saves the score
} sonstwenn taste == "BackSpace" {
eingabe_name = text.erste_n(eingabe_name, länge(eingabe_name) - 1)
} sonstwenn länge(taste) == 1 {
wenn länge(eingabe_name) < 14 { eingabe_name = "{eingabe_name}{taste}" }
} That's the whole recipe: a flat list, a couple of tables, one collision question and a timer. The full source is included in the download above — build it, change the colours, invent your own piece.