TetriNode
A playable Tetris game that outputs a real IMAGE tensor
- background_image
- matrix
Yes, that's a full Tetris game running inside a ComfyUI node. TetriNode (from the soulctcher/TetriNode pack) puts a playable board right in the node's own UI - arrow keys to move, Space to hard drop, Shift to hold - with hold/next/queue panels, guideline scoring, six bundled Game Boy and NES tracks, and a whole settings screen for block styles, colors, and themes. It's the kind of node you install on a slow upscale and immediately lose twenty minutes to.
But here's the part that keeps it from being a pure toy: the board is a real IMAGE output. Every time the workflow runs, the node hands you the current playfield as an actual image tensor - 600×1200 pixels at the default block_size of 20 (the board is 10 wide by 20 visible rows, rendered at 3px per block unit). Feed that into a Save Image, an img2img pass, an IPAdapter, or a video model and your game board becomes material for a pipeline. It's a fun way to mess around with img2img on a structured, high-contrast source instead of a photo.
How it actually works
The Python side is a game state machine plus a renderer, and it has zero real dependencies - no requirements.txt, just the standard library. The whole game state lives as a serialized JSON string in the state widget, and the node's bundled JavaScript (js/) draws and plays the game live in the UI, syncing that string back and forth with the backend. Each time you run the queue, Python deserializes the state, applies whatever action you picked, renders the board, and returns the image. That's the whole loop: the live play you do in the UI and the matrix output are two views of the same state.
The scoring and fall speeds are straight out of the official 2009 Tetris Design Guideline - T-Spin detection, back-to-back bonuses, leveling that caps at 15 - which tells you the author actually cared about the game, not just the meme.
Inputs and outputs that matter
The schema is small. The ones a beginner actually touches:
seed- the seed for the piece sequence, with a tooltip from the author: "The random seed used for piece generation." Works like every other seed widget. Note it only matters when a new game starts; bumping it mid-game doesn't re-roll the bag until you triggernew.action- the enum that drives the game from the graph:left,right,down,rotate_cw,rotate_ccw,soft_drop,hard_drop,hold,new, plussync(re-render without stepping) andnone. Default isnone, so a plain queue run just re-renders the current board.block_size- 8 to 48, default 20. This is your output resolution knob: crank it if you're feeding the board downstream and want it sharp.background_image(optional) - any IMAGE, scaled to cover and center-cropped behind the playfield. Nice for making the output feel less "raw game render."state- the serialized game JSON. Leave it to the UI in normal use; it's also your save/load format, since the node's toolbar can export and import state as text.
The single output, matrix (IMAGE), is the live board. There's no latent, no conditioning - this is an output node, so don't go hunting for a KSampler hookup.
Installing it
Easiest path: ComfyUI Manager, search "TetriNode", install. It's also on the Comfy Registry. Manual way:
cd ComfyUI/custom_nodes
git clone https://github.com/soulctcher/TetriNode
Restart ComfyUI and it'll be under the games category. No model downloads, no pip install, no conflicting dependencies - a genuinely rare thing in this ecosystem, where most packs drag in a pile of Python packages that fight each other. The music ships as a bundled blob that unpacks itself on import, so it all just works.
Where people get tripped up
The biggest confusion is expecting it to behave like a normal generation node. It's an output node whose image only updates when the workflow actually runs - the live game in the UI is the JavaScript canvas; the matrix output refreshes on the next queue. Also remember each run is one step: if you set action to left, you get exactly one move, then the image. The live UI handles real-time play; the graph drives it action-by-action.
And since the game state lives in that state widget string, don't clear it or swap it for an empty default between runs - you'll just reset the board. If you do lose a game you liked, the Save State button gives you the JSON to paste back in. That, and don't expect your high score to impress anyone. It won't.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| action | COMBO | none | 11 options: none, sync, left, right, down, rotate_cw, +5 |
| state | STRING | — | |
| seed | INT | 00–18446744073709550000 | The random seed used for piece generation. |
| block_size | INT | 208–48 | — |
| background_imageopt | IMAGE | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| matrix | IMAGE | — |