Rustris
A guideline-compliant terminal Tetris with LAN multiplayer support.
Install
Or build from source:
Requires Rust 2024 edition (1.85+). On Linux,
libasound2-dev(or equivalent) is needed for audio support.
Recommended Terminal Settings
The game renders blocks as ██ (two full-block characters). For correct appearance, the terminal should use:
- Font: Monospace with full Unicode support (JetBrains Mono, Cascadia Code, Fira Code, etc.)
- Line height: 1.0 (extra line spacing causes horizontal gaps between blocks)
- Letter spacing: 0 (extra spacing breaks block and border alignment)
| Terminal | Where to check |
|---|---|
| Kitty | modify_font cell_height 0px |
| Windows Terminal | Settings > Profile > Appearance |
| iTerm2 | Profiles > Text > Character/Line spacing: 100% |
Game Modes
| Mode | Objective |
|---|---|
| Marathon | Clear a target number of lines (default 150) |
| Sprint | Clear lines (default 40) as fast as possible |
| Ultra | Score as high as possible within a time limit (default 120s) |
| Endless | Play with no goal until game over |
| Versus | LAN 1v1 - send garbage lines to your opponent |
Versus Mode (LAN Multiplayer)
Play 1v1 over a local network. One player hosts, the other joins.
Quick Start
Start the game and select Versus mode from the menu. One player selects Host (enter a port), the other selects Join (enter <host-ip>:<port>). The host's LAN IP is displayed on the lobby screen.
Garbage System
Clearing lines sends garbage to your opponent:
| Clear Type | Attack |
|---|---|
| Single | 0 |
| Double | 1 |
| Triple | 2 |
| Tetris | 4 |
| T-Spin Single | 2 |
| T-Spin Double | 4 |
| T-Spin Triple | 6 |
| T-Spin Mini Single | 0 |
| T-Spin Mini Double | 1 |
| Back-to-Back | +1 |
| Perfect Clear | 10 |
Combo bonus (added on top): 0-1 combo = +0, 2-3 = +1, 4-5 = +2, 6-7 = +3, 8-10 = +4, 11+ = +5.
Pending garbage is absorbed when you clear lines (cancel before send). Uncleared garbage is applied to your board on lock. A red bar between the two boards shows the amount of pending garbage.
Versus Rules
- Level is fixed (no level-up during a match)
- Game does not pause; Esc opens a non-blocking Forfeit menu (gravity and network continue)
- No records are saved for Versus games
Menu Navigation
All menus use Up/Down to navigate, Enter to select, and Left/Right to change mode or toggle values.
Main Menu
├── < Mode > ← Left/Right to switch (works on any item)
├── Start → Start game
├── Settings → Settings (mode-specific + audio)
├── Records → Leaderboard (Left/Right to switch mode)
├── Help → Controls reference
└── Quit → Exit
Versus Menu
├── Host Game → Port Input
│ ├── Confirm → Host Lobby
│ ├── Back → Versus Menu
│ └── Menu → Main Menu
├── Join Game → IP Input → Port Input
│ ├── Confirm → Client Lobby
│ ├── Back → Previous step (Port→IP, IP→Versus Menu)
│ └── Menu → Main Menu
└── Back → Main Menu
Host Lobby (waiting for connection)
├── Back → Versus Menu
└── Menu → Main Menu
Client Lobby (connection failed)
├── Retry → Retry connection
├── Back → Versus Menu
└── Menu → Main Menu
Pause (single-player, Esc/P to open)
├── Resume → Resume game (Esc also resumes)
├── Settings → BGM/SFX toggles
├── Help → Controls reference
├── Retry → Restart game
└── Menu → Main Menu
Game Over (single-player)
├── Retry → Restart game
└── Menu → Main Menu
Forfeit (versus, Esc to open, non-blocking)
├── Continue → Resume (Esc also resumes)
├── Forfeit → Lose and end match
├── BGM ← Left/Right/Enter to toggle
└── SFX ← Left/Right/Enter to toggle
Versus Result
├── Rematch → Request rematch (waiting screen)
│ ├── Back → Result screen
│ └── Menu → Disconnect, Main Menu
└── Menu → Disconnect, Main Menu
Controls
| Key | Action |
|---|---|
| Left / Right | Move piece |
| Down | Soft drop (+1 per cell) |
| Space | Hard drop (+2 per cell) |
| Up / X | Rotate clockwise |
| Z | Rotate counter-clockwise |
| C | Hold piece |
| Esc / P | Pause (Forfeit in Versus) |
| Ctrl+C | Force quit |
Features
- Super Rotation System (SRS) with full wall kick tables (toggleable)
- 7-bag randomizer (or pure random)
- Hold piece (toggleable)
- Next queue preview (0-6 pieces, configurable)
- Ghost piece (toggleable)
- Lock delay (0-2s, configurable) with move/rotate reset (0-30 or unlimited)
- DAS/ARR input handling
- Line clear animation (toggleable)
- Guideline scoring - T-Spin (Mini/Full), Back-to-Back, Combo, All Clear
- Guideline gravity with level cap setting
- BGM & SFX with polyphonic playback
- Leaderboard - top 10 per mode, recorded only under default settings
- LAN Versus - P2P TCP multiplayer with protocol handshake, garbage system, dual-board rendering, rematch support
Settings
| Setting | Modes | Range | Default | Description |
|---|---|---|---|---|
| Level | Marathon, Endless, Versus | 1-20 | 1 | Starting level |
| Goal | Marathon | 10-300 (step 10) | 150 | Lines to clear |
| Goal | Sprint | 10-100 (step 10) | 40 | Lines to clear |
| Time | Ultra | 30-300s (step 10) | 120s | Time limit |
| Cap | Marathon, Endless | 1-20 / INF | 15 | Maximum level |
| Next | All | 0-6 | 6 | Next queue preview count |
| Lock | All | 0.0-2.0s (step 0.1) | 0.5s | Lock delay before piece locks |
| Reset | All | 0-30 / INF | 15 | Move reset limit during lock delay |
| Ghost | All | ON / OFF | ON | Ghost piece visibility |
| Anim | All | ON / OFF | ON | Line clear animation |
| Bag | All | ON / OFF | ON | 7-bag randomizer (OFF = pure random) |
| SRS | All | ON / OFF | ON | Super Rotation System with wall kicks |
| Hold | All | ON / OFF | ON | Hold piece |
| BGM | All | ON / OFF | ON | Background music |
| SFX | All | ON / OFF | ON | Sound effects |
Troubleshooting
If the client gets TimedOut when joining, the host's firewall is likely blocking incoming TCP connections.
Linux
# firewalld
# ufw
# iptables
Windows
Windows Firewall usually prompts automatically when hosting. If not:
netsh advfirewall firewall add rule name="Rustris" dir=in action=allow protocol=tcp localport=21711
Or go to Windows Defender Firewall > Advanced Settings > Inbound Rules > New Rule, select Port, enter TCP / 21711, and allow the connection.
macOS
macOS usually prompts automatically when hosting. If not:
Or go to System Settings > Network > Firewall > Options, click +, and add Rustris to the allowed list.
Replace 21711 with the actual port if you changed it.
Project Structure
src/
├── main.rs Entry point, terminal init/cleanup
├── audio/
│ ├── mod.rs Audio constants, module exports
│ ├── bgm.rs BGM note/melody data, cycle assembly
│ ├── sfx.rs Sfx enum, note sequences per sound effect
│ ├── synth.rs Polyphonic synthesis (PolySource, SfxSource)
│ └── player.rs MusicPlayer: BGM/SFX playback via rodio
├── game/
│ ├── mod.rs Game struct definition
│ ├── board.rs Construction, board queries, hold, ghost, timing
│ ├── movement.rs Piece movement, rotation (SRS), gravity, drop
│ ├── scoring.rs T-Spin detection, line clear, scoring
│ ├── animation.rs Line clear animation, ARE, garbage rise animation
│ ├── types.rs GameMode, LastMove, ClearAction, timing constants
│ ├── piece.rs Piece/Bag structs, SRS data (rotation states, kick tables)
│ ├── settings.rs Settings struct (shared by solo and versus)
│ ├── records.rs Leaderboard persistence (JSON via serde)
│ ├── garbage.rs Attack calculation, garbage queue, cancel logic
│ └── tests.rs Unit tests (board, piece, garbage, scoring)
├── net/
│ ├── mod.rs Network module exports
│ ├── protocol.rs NetMessage enum, protocol version, BoardSnapshot, GarbageAttack
│ ├── transport.rs Connection: frame encoding/decoding, non-blocking TCP I/O, timeout/length guard
│ ├── host.rs LAN IP detection, TCP listener (non-blocking accept)
│ └── client.rs TCP connect with timeout
├── render/
│ ├── mod.rs Render module exports
│ ├── common.rs Shared render utilities, title, piece preview
│ ├── board.rs Single-player board rendering
│ ├── menus.rs Menu/overlay rendering (pause, game over, settings, etc.)
│ └── versus.rs Dual-board rendering, lobby/countdown/result screens
└── ui/
├── mod.rs UI module exports
├── app.rs Application loop, versus flow dispatch
├── input.rs Key handling, DAS/ARR, gravity, lock delay, menu helpers
├── session.rs Single-player game loop, pause, game over, records
├── versus.rs Versus game loop, lobby, handshake, countdown, garbage, rematch
└── menus/
├── mod.rs Menu module exports
├── modes.rs Mode select screen, records viewer
├── settings.rs Settings menu (in-game and full)
└── versus.rs Versus Host/Join sub-menus with port/address input
Dependencies
- crossterm - Terminal manipulation
- rand - Bag shuffling and random generation
- rodio - Audio playback
- serde / serde_json - Record and network message serialization
- dirs - Platform data directory resolution