PROJECT 09 // FALLING-BLOCK PUZZLE & COMPILER PROVING GROUND
Tetris
The obligatory Tetris clone -- a complete, playable game written entirely in ArcoBASIC and compiled by ArcoFission straight to a self-contained WebAssembly page, with a matching native desktop build from the exact same source.
The obligatory first game
Every new game engine, every new language, every new graphics stack eventually has to prove it can do the ordinary thing: draw a board, move a shape, clear a line. Tetris is that proof for ArcoBASIC and ArcoFission -- a real, complete, playable game with nothing borrowed from an existing engine.
The whole thing is one ArcoBASIC source file. ArcoFission compiles it two ways from that same file: a native Linux binary linking GLFW and Cairo for a real desktop window, and a WebAssembly capsule that runs the identical bytecode behind an HTML5 canvas, requiring nothing but a browser -- not even a server, since the wasm module is embedded directly in the page rather than fetched separately.
Rotation without a rotation matrix
Each of the seven pieces is stored as four rotation states, each state a list of four cell offsets inside a 4x4 box. Turning a piece is nothing more elaborate than switching which offset list is active -- no trigonometry, no matrix multiplication, just a different row of a lookup table.
' Every piece is 4 rotation states, each a list of 4 [x, y] cell offsets inside a
' 4x4 box -- turning a piece is just switching which offset list is active.
I_PIECE_ROTATIONS = [
[[0, 1], [1, 1], [2, 1], [3, 1]],
[[2, 0], [2, 1], [2, 2], [2, 3]],
[[0, 2], [1, 2], [2, 2], [3, 2]],
[[1, 0], [1, 1], [1, 2], [1, 3]]
]
FUNCTION AbsoluteCells(piece, rotations)
cells = rotations[piece.Rotation]
result = []
FOR i = 0 TO LEN(cells) - 1
cell = cells[i]
ignored = Array.Add(result, [piece.X + cell[0], piece.Y + cell[1]])
NEXT i
RETURN result
END FUNCTION
piece = {X: 3, Y: 5, Rotation: 0}
PRINT "Flat I-piece cells: " + STRING(AbsoluteCells(piece, I_PIECE_ROTATIONS))
piece.Rotation = 1
PRINT "Standing I-piece cells: " + STRING(AbsoluteCells(piece, I_PIECE_ROTATIONS))What counts as a valid move
A falling piece is allowed to have cells above the visible board -- that is just a piece still emerging at spawn. Anything at or past the floor, off either side, or already occupied by a locked piece is not. That one rule, checked against every cell of a proposed position, is the entire physics engine: movement, rotation, soft drop, and hard drop are all just "try a new position, keep it only if it is valid."
' A cell above the board (y < 0) is fine -- that is just a piece still emerging
' at spawn. Anything at or past the floor, off either side, or already occupied
' is not.
BOARD_W = 10
BOARD_H = 20
FUNCTION CellsValid(cells, board)
FOR i = 0 TO LEN(cells) - 1
cell = cells[i]
cx = cell[0]
cy = cell[1]
IF cx < 0 OR cx >= BOARD_W THEN RETURN FALSE
IF cy >= BOARD_H THEN RETURN FALSE
IF cy >= 0 THEN
row = board[cy]
IF row[cx] <> 0 THEN RETURN FALSE
END IF
NEXT i
RETURN TRUE
END FUNCTION
board = []
FOR y = 0 TO BOARD_H - 1
row = []
FOR x = 0 TO BOARD_W - 1
ignored = Array.Add(row, 0)
NEXT x
ignored = Array.Add(board, row)
NEXT y
board[19][4] = 3
PRINT "Still emerging above the board: " + STRING(CellsValid([[3, -2], [4, -1]], board))
PRINT "Landing on an empty cell: " + STRING(CellsValid([[5, 19]], board))
PRINT "Landing on an occupied cell: " + STRING(CellsValid([[4, 19]], board))
PRINT "Past the floor: " + STRING(CellsValid([[4, 20]], board))A real bug the game found
Building an actual playable thing surfaces problems a synthetic test suite does not. The browser build's hard-drop key silently did nothing until it became clear the shared web GUI backend was mapping the spacebar's key-name incorrectly -- a length check ran before the lookup table that would have caught it, so the one-character space character never reached the branch that renames it "space". Fixed once, at the source, for every future ArcoBASIC browser game that binds Space to anything.
A lookup table beats a formula you have to trust
The rotation system above is the clearest example of a bigger habit: reach for a lookup table over a computed formula whenever the space of valid states is small and fixed. Four rotation states times seven pieces is twenty-eight known-good offset lists, checked once, then never wrong again. That trade generalizes well past falling blocks.
- When the set of valid configurations is small and enumerable, store them rather than deriving them at runtime. A lookup table cannot introduce a trigonometry rounding error or a sign mistake, because there is no computation left to get wrong at the point of use.
- Reduce a whole feature to one predicate wherever possible, the way movement, rotation, soft drop, and hard drop here are all just "try a new position, keep it only if CellsValid says yes." One well-tested rule is easier to trust than four separately-implemented ones that are each supposed to agree with it.
- Trust a synthetic test suite for logic, but still put the real thing in front of a real user before calling it done. The spacebar bug was invisible to unit tests and only surfaced because someone actually tried to hard-drop a piece in a browser; playing the thing is still a test synthetic coverage cannot replace.