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