Solving "Lights Out" Puzzles
Update: I did some research, found the game’s Wikipedia page and a similar solution, so I won’t take the credit of the solution even though I came up with it independently.
The Racket programming language has a dozen of simple bundled games, one of which is the “Lights Out” puzzles. Here is the screenshot of the game and its instructions:

It’s supposed to be a hard game if you are using your human heuristics or logic (or luck?), but this game can actually be solved using a dumb polynomial algorithm, and you probably have learned it in your linear algebra class.
The Problem
Let’s consider a generalized problem: we are playing “Lights out” on an undirected simple graph (i.e. there is at most one edge between two nodes), and clicking a node also toggles lights of its neighbors. Our goal is to turn off all the lights in the graph.
Here’s an example of 6 nodes :

If you want to play the generalized game and gain some intuition, here’s an app written specifically for this blog post. Roughly 90% of the code and every line of the document is written by a coding agent. See Appendix A for its technical details.
The Solution
Suppose that we have known the solution to the above example. Let be the number of clicks on node , be the number of clicks on node , etc. Then we can have a set of equations by examine each node and its neighbors:
The first equation can be obtained by looking at the node . It should remain off, so it should be toggled by even number of times. Its neighbors are node , and any click to itself and its neighbors will toggle it once, so it will be toggled times. The same is true for the node , except that it should be turned off by toggling odd number of times, this gives us the in the right-hand side of the second equation.
Note that all the parameters are in (because of the simple graph constraint), therefore we can get rid of the annoying by solving the equations (1) in the field :
In fact, (as shown in Appendix B) we won’t miss any solution by limiting the scope: if there is no solution on , neither will there be a solution on .
So it’s just a set of linear equations which can be solved by Gaussian elimination. It’s even simpler since we are doing Gaussian elimination on .
Here’s the algorithm that can solves any light-out puzzle:
- Get the adjacent matrix and the vector , where if the i-th node is on, otherwise .
- Solve the set of equations on . If it’s unsolvable, then the puzzle is not solvable.
I implemented the algorithm in the app so that the app can show you the solution if there is any. The algorithm is boring, so I won’t paste it here to waste your time (although it’s written by hand, and it’s about 80 lines of code).
Conclusion
Perhaps the game is too simple to write a blog post to solve it, but it’s a interesting game anyway. I thought that a puzzle game must be NP-hard to be hard enough for humans, but a game that has a simple but unintuitive solution can also be hard.
Appendix A: Implementation Details of the App
The app is written in Rust using raylib’s Rust binding. I chose raylib because I can really understand the code and fix it when something went wrong.
At the time this post is written, opencode is still providing free models, so I’m using them for this app.
The project is more challenging than expected as
- It’s a GUI app and is hard to test.
- I decided to start with something small and gradually adding features to it.
- The agent tends to patch things up instead of refactor the code when the old design is not suitable for a new feature.
- As a result, technical debt accumulates, and you will get an unmaintainable, buggy app.
- This gets worse when the agent has to compress the context and most of its experience are lost.
Maybe I should add something like “Refactor the code when it smells” to the agent’s skills, but I doubt that it has the correct judgment. I’ll try that in later projects.
Appendix B: Why Solving in
Let’s proof its converse: let be an matrix on and be a vector in , then if has solution , the equation has solution in .
Let . Because for every , , and , therefore is a homomorphism between the ring and .
Let , , then can be expanded as:
Because is homomorphism, we have
Since , therefore . So is a solution to .
Here’s an interpretation of this proposition: you don’t need to click a node more than once, since clicking a node twice does nothing.