<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Artem Lytkin</title>
    <description>The latest articles on DEV Community by Artem Lytkin (@4rh1t3ct0r).</description>
    <link>https://dev.to/4rh1t3ct0r</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3849306%2F3a4f47f9-b03c-4d01-9a41-1e0eaaf2658e.jpg</url>
      <title>DEV Community: Artem Lytkin</title>
      <link>https://dev.to/4rh1t3ct0r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/4rh1t3ct0r"/>
    <language>en</language>
    <item>
      <title>How I Reimplemented LÖVE2D in Rust to Play Balatro in a Terminal</title>
      <dc:creator>Artem Lytkin</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:57:02 +0000</pubDate>
      <link>https://dev.to/4rh1t3ct0r/how-i-reimplemented-love2d-in-rust-to-play-balatro-in-a-terminal-2ag2</link>
      <guid>https://dev.to/4rh1t3ct0r/how-i-reimplemented-love2d-in-rust-to-play-balatro-in-a-terminal-2ag2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvu98zdy6b8r1yg433ls9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvu98zdy6b8r1yg433ls9.gif" alt=" " width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8odxhu4p4sr5xobs1aut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8odxhu4p4sr5xobs1aut.png" alt=" " width="800" height="485"&gt;&lt;/a&gt;&lt;br&gt;
A few weeks ago, I wondered: what would it take to run a full commercial game in a terminal? Not a text-based approximation — the actual game, with real graphics, shaders,&lt;br&gt;
and UI.&lt;br&gt;
The answer turned out to be ~9,800 lines of Rust.&lt;br&gt;
—&lt;/p&gt;

&lt;p&gt;The idea&lt;br&gt;
Balatro is built on LÖVE2D, a Lua game framework. The game’s logic is entirely in Lua; LÖVE2D provides the graphics, audio, and input APIs. My idea: reimplement those APIs in&lt;br&gt;
Rust, but instead of rendering to a GPU-backed window, render to a terminal.&lt;br&gt;
—&lt;/p&gt;

&lt;p&gt;Architecture&lt;br&gt;
The project has three crates:&lt;br&gt;
love-terminal: The binary — CLI parsing, terminal setup, game loop.&lt;br&gt;
love-api: The core — implements ˷80 LÖVE2D API functions. graphics.rs alone is 3,400+&lt;br&gt;
lines covering a full software rasterizer: anti-aliased rectangles, ellipses, polygons, thick&lt;br&gt;
lines, sprite rendering with bilinear filtering, TTF text via fontdue, transform stack, canvas&lt;br&gt;
system, stencil buffer, and blend modes.&lt;br&gt;
sprite-to-text: The renderer — takes the RGBA pixel buffer and converts it to terminal&lt;br&gt;
output.&lt;br&gt;
—&lt;/p&gt;

&lt;p&gt;Three ways to render pixels in a terminal&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sixel graphics (best quality)
The Sixel protocol, dating back to DEC terminals in the 1980s, encodes actual pixel data inline in the terminal stream. Modern terminals (Windows Terminal 1.22+, WezTerm, foot,
kitty, mlterm) support it.
The internal canvas can be 700×350+ pixels. I control the resolution via TUI_PIXELBUDGET — the canvas auto-scales to stay within a pixel budget. At 250K pixels (default), I get
~707×354 at 50-60 FPS on CPU. Each frame is quantized to 256 colors.&lt;/li&gt;
&lt;li&gt;Unicode octant characters
Unicode 13.0 added octant characters (U+1FB00–U+1FB3B) that divide each cell into a 2×4 grid. Each of the 8 sub-cells can be on or off, giving 256 possible patterns per cell.
I pair each octant pattern with a foreground and background color, choosing the combination that minimizes error via gamma-correct downsampling. The result: 2×4 sub-
pixel resolution per cell, which is dramatically better than half-blocks.
Requires Cascadia Code 2404.23+ (or another font with octant support).&lt;/li&gt;
&lt;li&gt;Half-block fallback
The classic approach: ▀ (U+2580) with the top pixel as foreground, bottom pixel as background. 2× vertical resolution. Works on any terminal with 24-bit color.
—&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shader emulation&lt;br&gt;
Balatro uses several GLSL shaders for visual effects. Since there’s no GPU, every shader is emulated per-pixel in Rust. Some highlights:&lt;br&gt;
The CRT shader extracts bright pixels, applies a 5-tap Gaussian blur for bloom, then adjusts github.com/4RH1T3CT0R7/balatro-port-tui contrast and adds a vignette.&lt;br&gt;
The holographic shader combines an HSL rainbow shift with a hexagonal grid pattern and a noise field.&lt;br&gt;
The polychrome shader uses animated noise to rotate hues in HSL space with boosted saturation.&lt;br&gt;
There are 11 shaders total, all ported from the original GLSL.&lt;br&gt;
—&lt;/p&gt;

&lt;p&gt;Compatibility tricks&lt;br&gt;
A few things needed special handling:&lt;br&gt;
love.system.getOS() returns “Linux” to skip Steam initialization. A require "luasteam" stub returns a dummy module. The bit library (present in LuaJIT but missing in Lua 5.1) is implemented in Rust. Balatro’s custom love.run() returns a per-frame closure instead of using standard callbacks.&lt;br&gt;
And keyboard events are mapped to gamepad events, since Balatro’s UI is designed around gamepad input.&lt;br&gt;
—&lt;/p&gt;

&lt;p&gt;Try it yourself&lt;br&gt;
You need a copy of Balatro. The engine reads game files from Balatro.exe (which is a zip archive).&lt;br&gt;
cargo build --release &amp;amp;&amp;amp; cargo run --release -- "path/to/Balatro.exe"&lt;br&gt;
GitHub: &lt;a href="https://github.com/4RH1T3CT0R7/balatro-port-tui" rel="noopener noreferrer"&gt;https://github.com/4RH1T3CT0R7/balatro-port-tui&lt;/a&gt;&lt;br&gt;
Free and open-source under Apache 2.0. Feedback welcome!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>rust</category>
      <category>gamedev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
