Dev.to · 7 min read

I made a terminal-based tetris game as my first Rust project!

I made a terminal-based tetris game as my first Rust project!

Hi! I made tetris with powerups that runs fully in your terminal. To try it out, just generate an SSH key ssh-keygen if you don't already have one, then ssh into the server. Your SSH key is your identity; no account is required. ssh play@cursedtetris.orangishcat.dev If you want to play offline, you can also download from GitHub Releases. It is recommended to use a Nerd Font so that all text renders correctly. Star my repo if you liked the game :D My friends challenged me to make a "text-based game". I assume they meant an RPG, but surely a working tetris game in the terminal counts as text-based, right? It's drawing text, except some of that text happens to be full unicode characters and look like tetris pieces. I have no idea why I chose tetris as the game I want to make. I barely even know how to play! Besides, this is a great excuse to learn Rust. I've always been a learning by doing person, and just reading about ownership and borrowing doesn't really give me a great understanding of it. Actually making a game with it would help significantly. As with any project, even a "simple" game ended up being quite the challenge. Next are my devlogs where I wrote one pretty much every day I was working on the project until its eventual completion. Hope you enjoy! 2026-8-1 Now coming into this project, I have some basic knowledge of Rust but no real experience. I know some concepts like ownership and borrowing, but I've heard there's a lot more, like macros for example. Reading through the macros documentation has kinda confused me though, so I'm counting on an actual project to teach me the real applications of these. And for ownership and borrowing, I'll just let the compiler teach me the rules. Got some basic setup code working. The project builds and runs, plus it loads a basic counter in the terminal. It's just a ratatui template created with some AI help. Through each step of the way, I read and reviewed the code and learned Rust, asking AI for help whenever I ran into Rust syntax I didn't really understand. Today I learned: Rust closures. They're pretty familiar to me as they're quite similar to Java, JS, and Python lambdas. terminal.draw(|frame| self.draw(frame))?; Now I am aware that they probably operate pretty differently and closures follow ownership and borrowing rules, but I use them similarly to lambdas. If the compiler doesn't complain, then I'm probably doing it correctly. The project isn't complicated enough for me to worry about design decisions yet I think. Total time spent today: ~1.5 hours I'm not really tracking my time too closely though, so this is just a very rough estimate. 2026-8-6 I broke my computer 5 days ago and didn't really code. Right now I'm using my old MSI gaming laptop with Debian installed on it. It's supposed to be a home server and my external keyboard has a Mac keyboard layout, but it'll have to do for the time being. The board and pieces now render, with some basic controls and some minor issues. After I fixed those, next I had to make full rows disappear. However, pieces appearing totally at random didn't make sense much, so I made a queue for the pieces. The "queue" is really just a vector with a counter pointing to an index within the vector. Within the vector is two copies of all the board pieces, then shuffled. This ensures that no piece can appear more than twice in a row. When the counter reaches near the end of the vector, the vector is then shuffled and the counter/index resets to zero. I know that everyone has different opinions over AI, but I think it's great and it's working out pretty well for me. Last devlog I was still asking AI a lot of questions about Rust and the most concise/idiomatic syntax to do things, but now I am coding entire features with minimal AI assistance. The ability for AI to deliver personalized results helps me learn basic syntax a lot faster, and I can use this to piece together a big feature. 2026-8-7 I added leveling to the game. The formula for this is pretty arbitrary as I decided to create some custom formulas for this. The total number of pieces that have to be played to level up (this number does not reset to zero when you level up) is as follows: floor(16 * level ^ 1.25) So to level up from level 1 to 2, 16 pieces have to be played, then to level up from 2 to 3, 38 pieces have to be played, then 63, 90, 119, etc. For each level up, the gravity duration (the amount of time between two gravity ticks, where the piece currently being played moves down one tile) decreases, with this formula: 750ms * level^(-0.68144) So level 1 -> 750ms, level 2 -> 467ms, then 354ms, 291ms, 250ms, etc. Both formulas are kinda arbitrary as I fitted them using some target values and a regression curve on Desmos, but they work well for the gameplay. I also added a display in the UI for the level. But so far, the game isn't really cursed. Just doing normal tetris seems kinda boring. I need something that is unusual, something that stands out, even if it's simple. So I added powerups! Mainly it's just the bomb powerup for now. Oh, and I changed the text displays on the left and right as well. Total time: 2 hours 2026-8-8 Today is the end of the challenge. I demo'd what I had to my friends :) I'll take a break from this project for now. Maybe in the future I can maybe polish and ship it? Probably no one would play it, but might as well. I don't like leaving my projects half finished; I've got way too many half-finished projects already... 2026-8-26 It totally hasn't been 19 days since the last devlog... I'm planning to add more powerups. One of them includes a roller powerup, which erases all tiles of the same color touching the powerup's collision point. I've already added a paintball powerup that turns nearby tiles into the same color as the one the powerup is touching: And also, powerups are now stored globally; you get exactly 5 powerups per game. The code was already designed like this, I just made the UI reflect that too. But for the roller, I must create a smooth animation of the tiles slowly being deleted. This involves recursively calling a neighbor search with a certain delay between each call, essentially doing BFS with a task scheduler. Only thing is... I never wrote a task scheduler, and was trying to avoid it as it would add complexity to the project. And it makes sense, as I usually use the hardcoded 24fps frame rate as a scheduler by waiting for the next frame, and have my own basic delay checker for gravity. But for any complicated animations, a scheduler is very necessary and will make my life much easier in the future. pub struct Task { time: Instant, run: Box, } impl PartialEq for Task { fn eq(&self, other: &Self) -> bool { self.time == other.time } } // more comparison trait implementations pub fn update_tasks(state: &mut State) { let task_is_due = state .task_queue .peek() .is_some_and(|Reverse(task)| task.time

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More AI & Machine Learning News