Writing Toy Software Is A Joy
编写玩具软件是一种乐趣
Why you should write more toy programs
为什么你应该多写玩具程序
I am a huge fan of Richard Feyman’s famous quote:
我是理查德·费曼著名名言的忠实粉丝:
“What I cannot create, I do not understand”
“我无法创造的东西,我就无法理解”
I think it’s brilliant, and it remains true across many fields (if you’re willing to be a little creative with the
definition of ‘create’). It is to this principle that I believe I owe everything I’m truly good at. Some will tell you
to avoid reinventing the wheel, but they’re wrong: you should build your own wheel, because it’ll teach you more about
how they work than reading a thousand books on them ever will.
我认为这句话非常精彩,而且在许多领域都适用(前提是你愿意对“创造”的定义稍作发挥)。正是基于这个原则,我相信我所有真正擅长的东西都源于此。有人会告诉你不要重复发明轮子,但他们错了:你应该自己造轮子,因为这比读一千本关于轮子的书更能教会你它们是如何工作的。
In 2025, the beauty and craft of writing software is being eroded. AI is threatening to replace us (or, at least, the
most joyful aspects of our craft) and software development is being increasingly commodified, measured, packaged, and
industrialised. Software development needs more simple joy, and I’ve found that creating toy programs is a great way to
remember why I started working with computers again.
到了 2025 年,编写软件的美感和工艺正在被侵蚀。人工智能正威胁着取代我们(或者至少是我们工艺中最令人愉悦的部分),软件开发正日益被商品化、量化、打包和工业化。软件开发需要更多简单的快乐,而我发现编写玩具程序是一个很好的方式,让我重新记起了当初为什么开始与计算机打交道。
Keep it simple 保持简单
Toy programs follow the 80:20 rule: 20% of the work, 80% of the functionality. The point is not to build
production-worthy software (although it is true that some of the best production software began life as a toy).
Aggressively avoid over-engineering, restrict yourself to only whatever code is necessary to achieve your goal. Have
every code path panic/crash until you’re forced to implement it to make progress. You might be surprised by just how
easy it is to build toy versions of software you might previously have considered to be insummountably difficult to
create.
玩具程序遵循 80:20 法则:20%的工作,80%的功能。重点不是构建可用于生产的软件(尽管确实有一些最好的生产软件最初就是作为玩具开始的)。要积极避免过度设计,只编写实现目标所必需的代码。让每条代码路径都崩溃/出错,直到你被迫实现它以取得进展。你可能会惊讶于构建你之前可能认为难以逾越的软件玩具版本竟然如此简单。
Other benefits 其他好处
I’ve been consistently surprised by just how often some arcane nugget of knowledge I’ve acquired when working on a toy
project has turned out to be immensely valuable in my day job, either by giving me a head-start on tracking down a
problem in a tool or library, or by recognising mistakes before they’re made.
我一直很惊讶,在玩具项目中获得的一些晦涩知识竟然在我的日常工作中极其有价值,无论是帮助我快速定位工具或库中的问题,还是让我在错误发生之前就能识别出来。
Understanding the constraints that define the shape of software is vital for working with it, and there’s no better way
to gain insight into those constraints than by running into them head-first. You might even come up with some novel
solutions!
理解定义软件形态的约束对于使用软件至关重要,而没有比直接撞上这些约束更好的方式来深入了解它们了。你甚至可能会想出一些新颖的解决方案!
The list 列表
Here is a list of toy programs I’ve attempted over the past 15 years, rated by difficulty and time required. These
ratings are estimates and assume that you’re already comfortable with at least one general-purpose programming language
and that, like me, you tend to only have an hour or two per day free to write code. Also included are some suggested
resources that I found useful.
以下是我在过去 15 年中尝试过的一些玩具程序列表,按难度和所需时间进行评级。这些评级是估算值,假设你已经熟悉至少一种通用编程语言,并且像我一样,每天只有一两个小时的空闲时间来写代码。还包括一些我觉得有用的推荐资源。
Regex engine (difficulty = 4/10, time = 5 days)
正则表达式引擎(难度 = 4/10,时间 = 5 天)
A regex engine that can read a POSIX-style regex program and recognise strings that match it. Regex is simple yet
shockingly expressive, and writing a competent regex engine will teach you everything you need to know about using the
language too.
一个能够读取 POSIX 风格正则表达式程序并识别匹配字符串的正则表达式引擎。正则表达式简单却极具表现力,编写一个合格的正则表达式引擎将教会你使用该语言所需了解的一切。
x86 OS kernel (difficulty = 7/10, time = 2 months)
x86 操作系统内核(难度 = 7/10,时间 = 2 个月)
A multiboot-compatible OS kernel with a simple CLI, keyboard/mouse driver, ANSI escape sequence support, memory manager,
scheduler, etc. Additional challenges include writing an in-memory filesystem, user mode and process isolation, loading
ELF executables, and supporting enough video hardware to render a GUI.
一个多重引导兼容的操作系统内核,带有简单的命令行界面、键盘/鼠标驱动、ANSI 转义序列支持、内存管理器、调度器等。额外的挑战包括编写内存文件系统、用户模式和进程隔离、加载 ELF 可执行文件,以及支持足够的视频硬件以渲染图形用户界面。
GameBoy/NES emulator (difficulty = 6/10, time = 3 weeks)
GameBoy/NES 模拟器(难度 = 6/10,时间 = 3 周)
A crude emulator for the simplest GameBoy or NES games. The GB and the NES are classics, and both have relatively simple
instruction sets and peripheral hardware. Additional challenges include writing competent PPU (video) and PSG (audio)
implementations, along with dealing with some of the more exotic cartridge formats.
一个为最简单的 GameBoy 或 NES 游戏制作的粗糙模拟器。GB 和 NES 都是经典机型,且它们都有相对简单的指令集和外围硬件。额外的挑战包括编写合格的 PPU(视频)和 PSG(音频)实现,以及处理一些较为特殊的卡带格式。
GameBoy Advance game (difficulty = 3/10, time = 2 weeks)
GameBoy Advance 游戏(难度 = 3/10,时间 = 2 周)
A sprite-based game (top-down or side-on platform). The GBA is a beautiful little console to write code for and there’s
an active and dedicated development community for the console. I truly believe that the GBA is one of the last game
consoles that can be fully and completely understood by a single developer, right down to instruction timings.
一个基于精灵的游戏(俯视或横向平台)。GBA 是一个非常适合编写代码的小巧精美的主机,并且有一个活跃且专注的开发社区。我真心相信,GBA 是最后几个可以被单个开发者完全理解的游戏主机之一,甚至包括指令时序。
Physics engine (difficulty = 5/10, time = 1 week)
物理引擎(难度 = 5/10,时间 = 1 周)
A 2D rigid body physics engine that implements Newtonian physics with support for rectangles, circles, etc. On the
simplest end, just spheres that push away from one-another is quite simple to implement. Things start to get complex
when you introduce more complex shapes, angular momentum, and the like. Additional challenges include making collision
resolution fast and scaleable, having complex interactions move toward a steady state over time, soft-body interactions,
etc.
一个二维刚体物理引擎,实现牛顿物理,支持矩形、圆形等。在最简单的情况下,仅实现相互推开的球体是相当容易的。当引入更复杂的形状、角动量等时,事情开始变得复杂。额外的挑战包括使碰撞分辨率快速且可扩展,使复杂的交互随时间趋于稳定状态,软体交互等。
Dynamic interpreter (difficulty = 4/10, time = 1-2 weeks)
动态解释器(难度 = 4/10,时间 = 1-2 周)
A tree-walking interpreter for a JavaScript-like language with basic flow control. There’s an unbounded list of extra
things to add to this one, but being able to write programs in my own language still gives me child-like elation. It
feels like a sort of techno-genesis: once you’ve got your own language, you can start building the universe within it.
一个用于类似 JavaScript 语言的树遍历解释器,具备基本的流程控制。这个项目有无穷无尽的额外功能可以添加,但能够用我自己的语言编写程序仍让我感到孩童般的兴奋。这感觉像是一种技术创世:一旦拥有了自己的语言,就可以开始在其中构建宇宙。
Compiler for a C-like (difficulty = 8/10, time = 3 months)
类似 C 语言的编译器(难度 = 8/10,时间 = 3 个月)
A compiler for a simply-typed C-like programming language with support for at least one target archtecture. Extra
challenges include implementing some of the most common optimisations (inlining, const folding, loop-invariant code
motion, etc.) and designing an intermediate representation (IR) that’s general enough to support multiple backends.
一个针对简单类型的类似 C 语言编程语言的编译器,至少支持一个目标架构。额外的挑战包括实现一些最常见的优化(内联、常量折叠、循环不变代码移动等)以及设计一个足够通用的中间表示(IR),以支持多个后端。
Text editor (difficulty = 5/10, time = 2-4 weeks)
文本编辑器(难度 = 5/10,时间 = 2-4 周)
This one has a lot of variability. At the blunt end, simply reading and writing a file can be done in a few lines of
Python. But building something that’s closer to a daily driver gets more complex. You could choose to implement the UI
using a toolkit like QT or GTK, but I personally favour an editor that works in the console. Properly handling unicode,
syntax highlighting, cursor movement, multi-buffer support, panes/windows, tabs, search/find functionality, LSP support,
etc. can all add between a week or a month to the project. But if you persist, you might join the elite company of those
developers who use an editor of their own creation.
这个有很大的变数。在最简单的层面上,读写一个文件用几行 Python 代码就能完成。但要构建一个更接近日常使用的软件,复杂度就会增加。你可以选择使用像 QT 或 GTK 这样的工具包来实现界面,但我个人更喜欢在控制台中运行的编辑器。正确处理 Unicode、语法高亮、光标移动、多缓冲区支持、窗格/窗口、标签页、搜索/查找功能、LSP 支持等,都可能让项目增加一周到一个月的开发时间。但如果你坚持下去,或许你能加入那些使用自己创建的编辑器的开发者精英行列。
Async runtime (difficulty = 6/10, time = 1 week)
异步运行时(难度 = 6/10,时间 = 1 周)
There’s a lot of language-specific variability as to what ‘async’ actually means. In Rust, at least, this means a
library that can ingest impl Future
tasks and poll them concurrently until completion. Adding support for I/O waking
makes for a fun challenge.
Hash map (difficulty = 4/10, time = 3-5 days)
Hash maps (or sets/dictionaries, as a higher-level language might call them) are a programmer’s bread & butter. And yet, surprisingly few of us understand how they really work under the bonnet. There are a plethora of techniques to throw into the mix too: closed or open addressing, tombstones, the robin hood rule, etc. You’ll gain an appreciation for when and why they’re fast, and also when you should just use a vector + linear search.
Rasteriser / texture-mapper (difficulty = 6/10, time = 2 weeks)
Most of us have played with simple 3D graphics at some point, but how many of us truly understand how the graphics pipeline works and, more to the point, how to fix it when it doesn’t work? Writing your own software rasteriser will give you that knowledge, along with a new-found appreciation for the beauty of vector maths and half-spaces that have applications across many other fields. Additional complexity involves properly implementing clipping, a Z-buffer, N-gon rasterisation, perspective-correct texture-mapping, Phong or Gouraud shading, shadow-mapping, etc.
SDF Rendering (difficulty = 5/10, time = 3 days)
Signed Distance Fields are a beautifully simple way to render 3D spaces defined through mathematics, and are perfectly suited to demoscene shaders. With relatively little work you can build yourself a cute little visualisation or some moving shapes like the graphics demos of the 80s. You’ll also gain an appreciation for shader languages and vector maths.
Voxel engine (difficulty = 5/10, time = 2 weeks)
I doubt there are many reading this that haven’t played Minecraft. It’s surprisingly easy to build your own toy voxel engine cut from a similar cloth, especially if you’ve got some knowledge of 3D graphics or game development already. The simplicity of a voxel engine, combined with the near-limitless creativity that can be expressed with them, never ceases to fill me with joy. Additional complexity can be added by tackling textures, more complex procedural generation, floodfill lighting, collisions, dynamic fluids, sending voxel data over the network, etc.
Threaded Virtual Machine (difficulty = 6/10, time = 1 week)
Writing interpreters is great fun. What’s more fun? Faster interpreters. If you keep pushing interpreters as far as they can go without doing architecture-specific codegen (like AOT or JIT), you’ll eventually wind up (re)discovering threaded code (not to be confused with multi-threading, which is a very different beast). It’s a beautiful way of weaving programs together out highly-optimised miniature programs, and a decent implementation can even give an AOT compiler a run for its money in the performance department.
GUI Toolkit (difficulty = 6/10, time = 2-3 weeks)
Most of us have probably cobbled together a GUI program using tkinter, GTK, QT, or WinForms. But why not try writing your GUI toolkit? Additional complexity involves implementing a competent layout engine, good text shaping (inc. unicode support), accessibility support, and more. Fair warning: do not encourage people to use your tool unless it’s battle-tested - the world has enough GUIs with little-to-no accessibility or localisation support.
Orbital Mechanics Sim (difficulty = 6/10, time = 1 week)
A simple simulation of Newtonian gravity can be cobbled together in a fairly short time. Infamously, gravitational systems with more than two bodies cannot be solved analytically, so you’ll have to get familiar with iterative integration methods. Additional complexity comes with implementing more precise and faster integration methods, accounting for relativistic effects, and writing a visualiser. If you’ve got the maths right, you can even try plugging in real numbers from NASA to predict the next high tide or full moon.
Bitwise Challenge (difficulty = 3/10, time = 2-3 days)
Here’s one I came up with for myself, but I think it would make for a great game jam: write a game that only persists 64 bits of state between subsequent frames. That’s 64 bits for everything: the entire frame-for-frame game state should be reproducible using only 64 bits of data. It sounds simple, but it forces you to get incredibly creative with your game state management. Details about the rules can be found on the GitHub page below.
An ECS Framework (difficulty = 4/10, time = 1-2 weeks)
For all those game devs out there: try building your own ECS framework. It’s not as hard as you might think (you might have accidentally done it already!). Extra points if you can build in safety and correctness features, as well as good integration with your programming language of choice’s type system features.
I built a custom ECS for my Super Mario 64 on the GBA project due to the unique performance and memory constraints of the platform, and enjoyed it a lot.
CHIP-8 Emulator (difficulty = 3/10, time = 3-6 days)
The CHIP-8 is a beautifully simple virtual machine from the 70s. You can write a fully compliant emulator in a day or two, and there are an enormous plethora of fan-made games that run on it. Here’s a game I made for it.
Chess engine (difficulty = 5/10, time = 2-5 days)
Writing a chess engine is great fun. You’ll start off with every move it makes being illegal, but over time it’ll get smart and smarter. Experiencing a loss to your own chess engine really is a rite of passage, and it feels magical.
POSIX shell (difficulty = 4/10, time = 3-5 days)
We interact with shells every day, and building one will teach you can incredible amount about POSIX - how it works, and how it doesn’t. A simple one can be built in a day, but compliance with an existing shell language will take time and teach you more than you ever wanted to know about its quirks.
A note on learning and LLMs
Perhaps you’re a user of LLMs. I get it, they’re neat tools. They’re useful for certain kinds of learning. But I might suggest resisting the temptation to use them for projects like this. Knowledge is not supposed to be fed to you on a plate. If you want that sort of learning, read a book - the joy in building toy projects like this comes from an exploration of the unknown, without polluting one’s mind with an existing solution. If you’ve been using LLMs for a while, this cold-turkey approach might even be painful at first, but persist. There is no joy without pain.
The runner’s high doesn’t come to those that take the bus.