KO
|
EN
gitlite β search
Search
#python
#android
#javascript
#machine-learning
#deep-learning
#pytorch
#golang
#python3
#react
#java
#php
#tensorflow
balatro-port-tui
β 60
Open GitHub β
Balatro in your terminal!
Download README (.md)
Explore Similar Repositories
git-time-machine
:
π°οΈ Browse Git reflog visually and recover reachable local history with a TUI
rustyline-async
:
Rustyline, but with async support
widgetui
:
A bevy systems like widget system for ratatui and crossterm
// repository documentation
Was this content helpful?
β 0
(0 ratings)
Select Rating:
β
β
β
β
β
Submit Feedback
Recent Feedback
×
Download README
Do you want to download the
README.md
file for
balatro-port-tui
?
Download (.md)
<p align="center"> <pre> βββββββ ββββββ βββ ββββββ ββββββββββββββββ βββββββ ββββββββββββ ββββββ βββββββββββββββββββ ββββββββββββββββββββββββββββββββββ ββββββββββββ ββββββ βββββββββββββββββββ ββββββββ βββ βββββββββββ βββ βββ βββ ββββββ βββββββββββββββββββ ββββββββ βββ βββββββββββ βββ βββ βββ ββββββ βββββββββββ ββββββββββββββ βββ βββ βββ ββββββββββββ βββ ββββββββββββ βββββββ βββ ββββββββββββββ βββ βββ βββ βββ βββββββ βββ βββββββ βββ </pre> </p> <p align="center"> <b>Play <a href="https://www.playbalatro.com/">Balatro</a> in your terminal. No modifications to game code.</b> </p> <p align="center"> <a href="https://github.com/4RH1T3CT0R7/balatro-port-tui/releases"><img src="https://img.shields.io/github/v/release/4RH1T3CT0R7/balatro-port-tui?style=flat-square&color=blue" alt="Release"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License"></a> <img src="https://img.shields.io/badge/rust-1.75%2B-orange?style=flat-square&logo=rust" alt="Rust"> <img src="https://img.shields.io/badge/lines-~9800-informational?style=flat-square" alt="Lines of Code"> </p> --- <p align="center"><img src="assets/demo.gif" alt="Demo" width="800"></p> <p align="center"> <img src="assets/screenshot-title-menu.png" alt="Title screen" width="800"> </p> <p align="center"> <img src="assets/screenshot-sixel.png" alt="Gameplay (Sixel mode)" width="700"> </p> --- ## What is this? A Rust reimplementation of the **LOVE2D game engine** that renders entirely in a terminal. It runs **unmodified Balatro Lua code** β the game thinks it's talking to a real LOVE2D runtime, but all graphics go through a software pixel buffer and are displayed via the Sixel graphics protocol (true pixels), Unicode octant characters (2Γ4 sub-pixels per cell), or half-block characters. The result: a fully playable Balatro in your terminal. ## Quick Start ### Prerequisites - **Rust 1.75+** with a C compiler (for vendored Lua 5.1) - **A copy of [Balatro](https://store.steampowered.com/app/2379780/Balatro/)** (`Balatro.exe`) - **A modern terminal** with 24-bit color support (Windows Terminal 1.22+, WezTerm, iTerm2, kitty, Alacritty) ### Build ```bash cargo build --release ``` > **Windows tip:** If your project is on OneDrive, use a separate build directory to avoid sync lag: > ```bash > CARGO_TARGET_DIR="C:/tmp/balatro_build" cargo build --release # Git Bash > ``` > ```powershell > $env:CARGO_TARGET_DIR="C:/tmp/balatro_build"; cargo build --release # PowerShell > ``` ### Run **Linux / macOS / Git Bash:** ```bash cargo run --release -- "path/to/Balatro.exe" ``` **Windows PowerShell:** ```powershell cargo run --release -- "C:/Program Files (x86)/Steam/steamapps/common/Balatro/Balatro.exe" ``` > Sixel mode is used by default. Set `TUI_RENDER=octant` or `TUI_RENDER=halfblock` to switch (see [Configuration](#configuration)). ### Download Pre-built Binaries Pre-built binaries for Windows, Linux, and macOS are available on the [Releases](https://github.com/4RH1T3CT0R7/balatro-port-tui/releases) page. ```bash ./love-terminal "path/to/Balatro.exe" # Linux / macOS ``` ```powershell .\love-terminal.exe "path\to\Balatro.exe" # Windows PowerShell ``` ## Controls | Key | Action | |-----|--------| | **Arrow keys** | Navigate menus | | **Enter** | Confirm / Play hand | | **Escape** | Back / Cancel | | **E** | Select / deselect cards | | **Tab** | Switch between hand and jokers | | **Space** | Sort hand | > Balatro's UI is designed for gamepad input. Keyboard keys are automatically mapped to `gamepadpressed`/`gamepadreleased` events for full compatibility. ## How It Works ### Rendering Pipeline ``` Balatro Lua code β βΌ love.graphics.* API calls (Rust) β βΌ Software pixel buffer + shader emulation β βΌ Terminal output ββββββββββββββββββββββββββββββββββββββββ β Sixel: true pixel graphics β β best quality β Octant: 2Γ4 sub-pixels/cell β β default β Half-block: β fg/bg per cell β β fallback ββββββββββββββββββββββββββββββββββββββββ ``` The project embeds a **Lua 5.1 VM** via [mlua](https://github.com/khvzak/mlua) and implements ~80 `love.*` API functions. All graphics pass through a software rasterizer β rectangles, ellipses, polygons, sprites, text β with anti-aliasing, bilinear filtering, and proper alpha blending. **Sixel mode** renders actual pixels via the [Sixel graphics protocol](https://en.wikipedia.org/wiki/Sixel). The internal canvas matches the terminal's pixel area (e.g. 1080Γ600 on a 120Γ30 terminal), giving true pixel-level fidelity with 256-color quantization per frame. **Octant mode** (default) uses Unicode octant characters (π¬βπ¬») to achieve 2Γ4 sub-pixel resolution per cell with gamma-correct downsampling. **Half-block mode** uses `β` (U+2580) with fg=top/bg=bottom as a universal fallback. ### Architecture ``` balatro_port_tui/ βββ love-terminal/ # Binary crate β entry point + game loop β βββ src/ β βββ main.rs # CLI parsing, path resolution β βββ runner.rs # Terminal setup, frame loop, auto-input βββ love-api/ # Library crate β LOVE2D API implementation β βββ src/ β βββ lib.rs # LoveRuntime: Lua VM + module registration β βββ state.rs # SharedState (Arc<Mutex> fields) β βββ graphics.rs # love.graphics.* (3,400+ lines) β βββ filesystem.rs # love.filesystem + zip loader + require β βββ event.rs # love.event (keyboard/mouse β events) β βββ keyboard.rs # love.keyboard.isDown β βββ window.rs # love.window (terminal as display) β βββ timer.rs # love.timer (delta, FPS, sleep) β βββ system.rs # love.system (getOS β "Linux") β βββ stubs.rs # audio, thread, mouse, joystick stubs β βββ lua_util.rs # Color parsing helpers βββ sprite-to-text/ # Library crate β pixel buffer + renderer βββ src/ βββ pixel_buffer.rs # RGBA framebuffer (2,000+ lines) βββ renderer.rs # Downsampling β ratatui terminal output ``` **~9,800 lines of Rust** across 16 source files. ## Features ### Graphics Engine - **Software rasterizer** β rectangles, rounded rects, ellipses, polygons, thick lines, all anti-aliased - **Sprite rendering** β `love.graphics.draw` with images, quads, sprite batches, bilinear filtering - **Text rendering** β TTF via [fontdue](https://github.com/mooman219/fontdue), colored segments, word-wrapped `printf` - **Transform stack** β translate, scale, rotate with matrix composition - **Canvas system** β offscreen render targets with premultiplied alpha - **Stencil buffer** β write + test operations for masked rendering - **Blend modes** β alpha, replace, additive, multiply, premultiplied ### Shader Emulation Since there's no GPU, all shaders are emulated per-pixel in Rust: | Shader | Effect | |--------|--------| | **Background** | Procedural swirl β 60Γ45 grid, paint distortion, 3-color mixing | | **CRT** | Bloom extraction + Gaussian blur + contrast + vignette | | **Dissolve** | Per-pixel noise threshold with burn-edge coloring | | **Foil** | Radial + angular shimmer, silvery-blue metallic (from GLSL) | | **Holographic** | HSL rainbow shift + hexagonal grid + noise field (from GLSL) | | **Polychrome** | HSL hue rotation via animated noise, boosted saturation (from GLSL) | | **Negative Shine** | 5-component sine wave shimmer with blue tint (from GLSL) | | **Gold Seal** | Animated golden sine-wave highlight sweep (from GLSL) | | **Debuff** | Desaturation + reddish tint + diagonal stripes (from GLSL) | | **Played** | HSL desaturation + darkening | | **Flash / Shadow / Flame** | Overlay effects (white flash, dark shadow, gradient flame) | ### Compatibility Balatro runs unmodified thanks to several compatibility tricks: - **`love.system.getOS()` β `"Linux"`** β skips Steam initialization - **`require "luasteam"` stub** β returns `{ init = function() return false end }` - **`require "bit"` in Rust** β Lua 5.1 lacks the `bit` library that LuaJIT provides; implemented as `bxor/band/bor/bnot/lshift/rshift` - **Custom `love.run()`** β Balatro returns a per-frame closure instead of using standard callbacks - **Keyboard β Gamepad mapping** β keys automatically fire `gamepadpressed`/`gamepadreleased` events ## Configuration All settings are controlled via environment variables. None are required β defaults work well for most setups. ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `TUI_RENDER` | `sixel` | Rendering mode: `sixel`, `octant`, or `halfblock` | | `TUI_PIXELBUDGET` | `250000` | Max canvas pixels for Sixel mode. **Main FPS vs quality knob.** Lower = faster, higher = sharper. See details below | | `TUI_SCALE` | `2` | Canvas divider. For octant/halfblock: canvas = terminal_cells Γ scale. For Sixel: overrides PIXELBUDGET, canvas = sixel_target / scale | | `TUI_CELL` | auto-detect | Cell pixel size for Sixel, format `WxH` (e.g. `9x20`, `11x24`). Use if the image doesn't fill the terminal or overflows | | `TUI_COLS` | auto | Override terminal column count | | `TUI_ROWS` | auto | Override terminal row count | | `TUI_DEBUG` | off | Enable timing logs every 120 frames (set to any value to activate) | | `TUI_AUTOPLAY` | off | Auto-play mode: progresses through title β blind select β play hand β shop automatically | | `TUI_SNAPSHOT` | off | Save PPM screenshots of the pixel buffer every 200 frames (frames 200β12000) | ### How TUI_PIXELBUDGET works This is the most important performance setting for Sixel mode. It controls how many pixels the internal canvas has. In Sixel mode, the **Sixel target** = terminal columns Γ 10 Γ terminal rows Γ 20 (VT340 virtual cell size). On a large terminal (e.g. 280Γ70) that's 2800Γ1400 = **3.92 million pixels** β far too many for CPU rendering at 60 FPS. The canvas is automatically downscaled so its total pixel count stays within the budget: ``` scale = sqrt(sixel_target_pixels / budget) canvas = sixel_target / scale ``` | Budget | Canvas (at 280Γ70 term) | Quality | FPS (typical) | |--------|------------------------|---------|---------------| | `100000` | ~450Γ225 | Low | 60+ | | `250000` (default) | ~707Γ354 | Good | 50-60 | | `500000` | ~1000Γ500 | High | 30-45 | | `1000000` | ~1414Γ707 | Very high | 15-25 | The canvas is drawn by Lua/shaders at this reduced resolution, then the Sixel encoder scales it up to the full terminal pixel area using nearest-neighbor interpolation. Lower budget = fewer pixels to shade = faster frames. ### How TUI_SCALE works **Octant/Half-block mode:** canvas width = terminal columns Γ 2 Γ scale, canvas height = terminal rows Γ 4 Γ scale (octant) or rows Γ 2 Γ scale (halfblock). Default `2` gives good text readability. `3` is smoother but smaller text. **Sixel mode:** if set, overrides PIXELBUDGET. Canvas = sixel_target / scale. E.g. `TUI_SCALE=4` on a 2800Γ1400 target gives a 700Γ350 canvas. ### How TUI_CELL works Sixel mode needs to know how many pixels each terminal cell occupies. Auto-detection chain: 1. `TUI_CELL` env var (if set, uses this directly) 2. Window pixel area via process tree walk (Windows Terminal) 3. CSI 14 t terminal query (Linux/macOS) 4. GetConsoleWindow + ConsoleFontEx (Windows legacy console) 5. crossterm terminal size query 6. DPI-based estimation (Windows β assumes Cascadia Mono at 9.6Γ20 per 96 DPI) 7. Fallback: 9Γ20 If the rendered image is too small or too large for your terminal, set `TUI_CELL` manually. To find your cell size: divide your terminal window's pixel dimensions by column Γ row count. ### Rendering Modes | Mode | Resolution | Quality | Requirement | |------|-----------|---------|-------------| | **`sixel`** (default) | Terminal pixel area (e.g. 1080Γ600) | Excellent | Terminal with Sixel support | | **`octant`** | 2Γ4 sub-pixels/cell (~560Γ280) | Good | Cascadia Code 2404.23+ | | **`halfblock`** | β top/bottom per cell | Fair | Any terminal | **Sixel mode** renders actual pixels via the Sixel graphics protocol, bypassing the 2-color-per-cell limitation. 256-color quantization per frame. Requires: Windows Terminal 1.22+, WezTerm, foot, mlterm, xterm. **Octant mode** uses Unicode octant characters (π¬βπ¬») for 2Γ4 sub-pixel resolution per cell with gamma-correct downsampling. Requires Cascadia Code 2404.23+ font. **Half-block mode** uses `β` (U+2580) with fg=top pixel, bg=bottom pixel. Works everywhere. ### Examples **Linux / macOS / Git Bash:** ```bash cargo run --release -- "path/to/Balatro.exe" # Sixel (default) TUI_CELL=11x24 cargo run --release -- "path/to/Balatro.exe" # Sixel + manual cell size TUI_PIXELBUDGET=500000 cargo run --release -- "path/to/Balatro.exe" # Sixel + higher quality TUI_PIXELBUDGET=100000 cargo run --release -- "path/to/Balatro.exe" # Sixel + max FPS TUI_RENDER=octant cargo run --release -- "path/to/Balatro.exe" # Octant mode TUI_RENDER=halfblock cargo run --release -- "path/to/Balatro.exe" # Half-block fallback TUI_DEBUG=1 cargo run --release -- "path/to/Balatro.exe" # Show frame timing TUI_AUTOPLAY=1 cargo run --release -- "path/to/Balatro.exe" # Auto-play demo ``` **Windows PowerShell:** ```powershell cargo run --release -- "path/to/Balatro.exe" # Sixel (default) $env:TUI_CELL="11x24"; cargo run --release -- "path/to/Balatro.exe" # Sixel + manual cell size $env:TUI_PIXELBUDGET="500000"; cargo run --release -- "path/to/Balatro.exe" # Sixel + higher quality $env:TUI_PIXELBUDGET="100000"; cargo run --release -- "path/to/Balatro.exe" # Sixel + max FPS $env:TUI_RENDER="octant"; cargo run --release -- "path/to/Balatro.exe" # Octant mode $env:TUI_RENDER="halfblock"; cargo run --release -- "path/to/Balatro.exe" # Half-block fallback $env:TUI_DEBUG="1"; cargo run --release -- "path/to/Balatro.exe" # Show frame timing $env:TUI_AUTOPLAY="1"; cargo run --release -- "path/to/Balatro.exe" # Auto-play demo ``` ## Known Limitations - **No audio** β `love.audio` is fully stubbed (play/stop/setVolume are no-ops) - **No threading** β `love.thread` is stubbed - **Mouse precision** β mouse events map terminal coordinates to canvas, but precision is limited by cell size - **Shader approximations** β CPU-based shader emulation is faithful but not pixel-identical to GPU GLSL - **Sixel color limit** β Sixel mode quantizes each frame to 256 colors, which is sufficient for Balatro's art style but not lossless - **Cell size detection on Windows** β Windows Terminal doesn't expose pixel dimensions; use `TUI_CELL` env var if the image doesn't fill the screen ## Dependencies | Crate | Purpose | |-------|---------| | [mlua](https://crates.io/crates/mlua) | Lua 5.1 VM embedding | | [ratatui](https://crates.io/crates/ratatui) | Terminal UI framework | | [crossterm](https://crates.io/crates/crossterm) | Cross-platform terminal I/O | | [fontdue](https://crates.io/crates/fontdue) | TTF font rasterization | | [zip](https://crates.io/crates/zip) | Reading game files from exe | | [flate2](https://crates.io/crates/flate2) | Deflate compress/decompress | | [parking_lot](https://crates.io/crates/parking_lot) | Fast mutexes for shared state | | [anyhow](https://crates.io/crates/anyhow) | Error handling | ## License This project is licensed under the [Apache License 2.0](LICENSE). **Disclaimer:** [Balatro](https://www.playbalatro.com/) is a game by [LocalThunk](https://x.com/LocalThunk). This project is an independent engine reimplementation for educational purposes β it does not include any game assets. You must own a legitimate copy of Balatro to use this software.