A minimal terminal Tetris

A minimal terminal Tetris. I needed a quick project to get me back into writing Zig and had never implemented Tetris before.

Both fun to write and to play.

demo

11 Likes

Your build.zig.zon uses a branch specific url, this mean that the upstream project breaks your build when it updates its branch (because then the hashes no longer match).

To avoid that use the git+https protocol which automatically creates commit specific urls because it automatically adds the commit hash as the fragment/hash/# part of the url, or manually get a commit specific url like described here: Best way to release packages for libraries? - #2 by Sze, the changing the ending to tar is no longer necessary.

Thanks. Done

1 Like

This is great! I canā€™t wait to take a deeper look and get it running on my local machine.

Iā€™m curious, were there any hurdles that you overcame when implementing this? Any gotchas a newbie should be aware of?

1 Like

Things I had to puzzle over:

  • Iā€™d never used Zigā€™s package system before. The key thing I needed was to do zig fetch --save git+https://github.com/xyaman/mibu.git and then GitHub - xyaman/mibu: Pure Zig library for low-level terminal manipulation.
  • Initially, I forgot that terminals have non-square characters. To print something on a square grid, each cell needs to be two characters wide (see stage.zig)
  • Also, writing the entire game to the terminal for every redrawn frame would be quite inefficient, so I wanted to do double buffering, a comparison then only send the changes (see display.zig). This could be done much more neatly and with less cursor movement
  • I forgot how strict Zig is about casting (compared to C) and the syntax for @intCast() might have changed since I remember last using it. E.g. const xo = px + @as(isize, @intCast(x)) in player.zig
  • AFAICT, Zigā€™s range .. operator only supports positive and increasing integers, so I ended up using while in some cases which I had to look up the syntax for

Everything else I spent time on was to do with how Tetris works. Probably the thing which took the longest was remembering how to rotate a bitmap in 2D. Initially I went with a ā€œproperā€ trigonometry type solution, until I realised I could just write out the mapping Simplify rotation Ā· ringtailsoftware/zigtris@408fd24 Ā· GitHub

4 Likes

Very fun project, I look forward to trying it out!

Itā€™s probably true that this technique is more efficient, and itā€™s definitely the classic thing to do.

I havenā€™t bothered in quite a long time, though. Computers, and modern terminal emulators, are ludicrously fast in comparison to human reflexes, so it just wonā€™t happen that painting one screenā€™s worth of terminal, no matter how intricate, will visibly drop the frame rate.

What can happen is tearing, if the terminal renders a frame in a partial state then it doesnā€™t matter how fast the render is going, users are going to notice.

But we have mode 2026 now, which prevents that from happening on supported terminals. Iā€™ve found that toggling the mode and using a BufferedWriter to minimize syscalls is more than enough to get flicker-free updates, despite repainting on each update.

I think itā€™s cool that you implemented a double-buffer, great way to stretch your Zig muscles. Mainly I donā€™t want people to think that theyā€™d have to do that to get a good result while working with the terminal, in my experience this is no longer necessary.

Itā€™s still neat that this would probably work on a real glass tty, and not every terminal emulator out there supports 2026, although most do by now.

4 Likes

Fascinating. I didnā€™t know such a mode existed! That would be very useful if I go back to my Zig ā€œDoom in a terminalā€ port Toby Jaffey šŸ³ļøā€šŸŒˆ: "If you'd ever wondered how #Doom looks in a 160x5ā€¦" - mastodon.me.uk which sheared very badly (via zig-wasm-audio-framebuffer/src/doom/doom.zig at master Ā· ringtailsoftware/zig-wasm-audio-framebuffer Ā· GitHub)

I think my mental model of terminals is very much from the 1970sā€¦

3 Likes

Itā€™s a very nice little project! I love tetris and zigtris feels like the real thing even though itā€™s so minimalistic. Nicely done!

1 Like

Btw, the readme says itā€™s MIT licensed, but it would be good to add a standard LICENSE file so that tooling like github and zigistry identify it as MIT licensed.

3 Likes

Thanks. Done

This looks fun but I was unable to get it to build. I suspect itā€™s my fault, being on a dev version rather than 0.13, but I am very new to zig so I cannot be sure.

What I see is:
$ zig build
install
ā””ā”€ install zigtris
ā””ā”€ zig build-exe zigtris Debug native 1 errors
/Users/xxxxx/.cache/zig/p/12205e93ed17ab3081a9d5289c2665e67873501f069a6210e02f3d8178a0c6e3156a/src/term.zig:22:5: error: expected statement, found ā€˜invalid tokenā€™
// t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: the following command failed with 1 compilation errors:
/Users/xxxxx/zig-macos-x86_64-0.14.0-dev.2198+e5f5229fd/zig build-exe -ODebug --dep mibu -Mroot=/Users/xxxxx/zig/zigtris/src/main.zig -Mmibu=/Users/xxxxx/.cache/zig/p/12205e93ed17ab3081a9d5289c2665e67873501f069a6210e02f3d8178a0c6e3156a/src/main.zig --cache-dir /Users/xxxxx/zig/zigtris/.zig-cache --global-cache-dir /Users/xxxxx/.cache/zig --name zigtris --zig-lib-dir /Users/xxxxx/zig-macos-x86_64-0.14.0-dev.2198+e5f5229fd/lib/ --listen=-
Build Summary: 0/3 steps succeeded; 1 failed

1 Like

If youā€™re new to zig, Iā€™d definitely stick with 0.13 as itā€™s a stable release. Things change pretty quickly and can be confusing.

Zigtris has only been tested against 0.13.

It looks like itā€™s choking on term.zig in the mibu library Iā€™m using for terminal control. Seems odd though as it looks as if itā€™s trying to parse inside a line comment.

2 Likes