Chessbot
A real wooden-piece chessboard that knows what's on it. Magnetic sensors under the squares feed a Raspberry Pi, which works out each move and checks it against the rules of chess. It talks to an Arduino that drives a two-motor parallel arm with an electromagnet to move pieces back.
The problem
A sensor under each square can only tell you whether that square is occupied. It can't tell a knight from a queen, or which side a piece belongs to. A capture leaves the occupancy grid looking almost the same as before, and castling changes four squares at once.
So the board has to reason. It starts from a known legal position, watches the order in which squares empty and fill, and asks the chess engine which legal move would produce exactly the board it now sees.
System
Sensing 64 squares with 16 pins
Each square has a reed switch that closes when a magnet in the base of a piece sits on it. The switches are wired as a matrix: the Pi drives one row pin high at a time and reads all eight column pins. A full scan takes eight row strobes, and the whole board costs only 16 GPIO pins.
def sense(self):
for i, row in enumerate(self.rows):
row.on()
for j, column in enumerate(self.columns):
self.matrix[i][j] = int(column.is_pressed)
row.off()
return self.matrix
From occupancy to moves
The game keeps two histories:
- HOLM (history of legal matrices): every confirmed position.
- HOTM (history of temporary matrices): every raw sensor frame since the last confirmed move.
When a player presses their confirm button, detect_move compares the last legal matrix with the latest frame. It uses the frames in between to settle the ambiguous cases:
Quiet move
One square empties and one fills. That gives the source and destination directly.
Capture
Only the source empties, because the destination was occupied before and still is. The detector finds the square that went empty before the moving piece was lifted, which is where the captured piece was removed.
Castling
Two squares empty and two fill. If one pair is the king moving two files along a rank, that's the move, and python-chess moves the rook too.
En passant
Two squares empty and one fills. The square lifted later is the moving pawn and the earlier one is the captured pawn.
The candidate is then checked against board.legal_moves. If it isn't legal, the LCD shows INVALID MOVE / ADJUST BOARD and nothing is committed. A pawn reaching the last rank pauses the game until the player picks Q, R, B or N with the four menu buttons.
Example: the capture exd5 after 1.e4 d5, as the sensors see it.
The arm: a 5-bar parallel linkage
Instead of a gantry, pieces are moved by a planar five-bar linkage. Two stepper motors sit 26 cm apart, each driving a 22 cm link, and two 27 cm links meet at the end effector. Both motors stay on the base, so the moving parts are light. The trade-off is that the inverse kinematics is a pair of triangles rather than a straight line.
def calc_angles(x, y):
beta1 = atan2(y, l0 + x)
beta2 = atan2(y, l0 - x)
alpha1 = acos((l1**2 + (l0+x)**2 + y**2 - l2**2) / (2*l1*hypot(l0+x, y)))
alpha2 = acos((l1**2 + (l0-x)**2 + y**2 - l2**2) / (2*l1*hypot(l0-x, y)))
return beta1 + alpha1, pi - beta2 - alpha2 # left, right shoulder
Tap or drag over the board below. The arm is solved live with the same equations and link lengths as the code. The step counts use the real 200-step motor and 17.52:1 gearing.
To pick up a piece, the Arduino lowers a servo-mounted electromagnet to a height set by the piece type (pawns, minor pieces and king/queen each have their own), switches it on and lifts. Squares close to the motors fall into "under the line" zones that need a different approach path, and the arm handler classifies them from a lookup grid.
Pi ↔ Arduino protocol
Commands are newline-terminated text at 9600 baud, and each command gets an explicit acknowledgement. That makes it easy to drive the hardware by hand from the debugger's serial console.
| Command | Reply | Effect |
|---|---|---|
PING | PONG | Heartbeat, polled every 2 s |
LCD a, b | LCD SUCCESS | Write two lines to the 16×2 display |
MOVE s1 s2 | STEPPERS MOVED | Relative move of both steppers, run together |
HOME | – | Slow approach to each limit switch, back off, zero |
EM_D n / EM_R n | EM_dropped / EM_rose | Ease the magnet servo to angle n |
EM_ON / EM_OFF | EM_on / EM_off | Switch the electromagnet |
Until the Pi connects, the LCD cycles through status lines every five seconds, such as Consulting / Stockfish, Hating / London and Forking / knights.
Tooling
Debugger app
A Tkinter window that mirrors the board's live occupancy grid and button states. It also has a raw serial console, a trajectory canvas for sending the arm to a clicked square, and a per-task timing view of each pass through the main loop (lattice, buttons, chessboard, gameplay, arm, serial, GUI).
Self-updating boot
On power-up, a script runs diagnostics and makes sure SSH is enabled. It then pulls the latest code, installs dependencies, and compiles and flashes the Arduino sketch with arduino-cli before launching the game. Each stage reports its log to a remote endpoint, so the board can be debugged without a screen.