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%的功能。重点不是要构建可用于生产的软件(尽管确实有些最优秀的生产级软件最初就是作为玩具项目诞生的)。要极力避免过度设计,只写实现目标所必需的代码。让每一条代码路径都先触发 panic 或崩溃,直到你不得不实现它才能继续推进。你可能会惊讶地发现,构建那些你曾认为难以企及的软件的玩具版本其实非常容易。
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)
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.