Build a Chess-Playing Robot
with Raspberry Pi
A real AI chess engine inside a servo arm — it thinks, picks its move, and physically slides the piece across the board. You play, it plays back.
How Does a Chess Robot Actually Work?
This robot has four main jobs happening one after another, like a relay race. You make your move and type it in; the AI works out the perfect counter-move; the robot arm swings over the right square; and the gripper picks up the piece and carries it across the board. Let's meet each layer:
You enter your move in standard chess notation (e.g. "e2e4") into the terminal — no camera needed for the main build.
Stockfish — the same AI engine used by World Chess Champions — runs on the Pi and picks the best counter-move in milliseconds.
Python converts the chess square name (like "e7") into exact servo angles for the arm to reach that spot on the board.
Four servos work together — rotating the base, swinging the shoulder, bending the elbow, and opening/closing the gripper — to physically move the chess piece.
Uses the python-chess library to track every legal move, castling, and checkmate.
Stockfish adjusts difficulty from easy (ELO 800) to grandmaster level — set it anywhere you like.
Four SG90 servos form a simple 4-DOF arm that reaches every square on the board.
The Pi speaks the robot's move aloud using espeak so you always know what happened.
Materials You'll Need
🖥️ Computing & Brains
🦾 Robot Arm Electronics
🏗️ Arm Frame & Board
Building the 4-Servo Robot Arm
Our arm has four joints, each controlled by one SG90 servo. Think of it like your own arm: the base rotates left/right (like twisting at the waist), the shoulder swings the whole arm forward/back, the elbow bends up/down, and the gripper squeezes to grab pieces.
Step-by-Step Arm Assembly
Wiring the Circuit
Raspberry Pi → PCA9685 (I²C)
| Raspberry Pi Pin | PCA9685 Pin | Wire Colour |
|---|---|---|
| Pin 1 — 3.3V | VCC | Red |
| Pin 6 — GND | GND | Black |
| Pin 3 — SDA (GPIO 2) | SDA | Blue |
| Pin 5 — SCL (GPIO 3) | SCL | Yellow |
External Power → PCA9685
| Power Supply | PCA9685 Terminal |
|---|---|
| +5V | V+ (the servo power terminal, next to GND) |
| GND | GND (shared with Pi GND) |
Servos → PCA9685 Channels
| Servo | Function | PCA9685 Channel |
|---|---|---|
| S1 | Base Rotate | Channel 0 |
| S2 | Shoulder | Channel 1 |
| S3 | Elbow | Channel 2 |
| S4 | Gripper | Channel 3 |
Quick-Reference Pin Map
Setting Up Your Raspberry Pi
raspberrypi.com/software, choose "Raspberry Pi OS (64-bit)", and flash your microSD card. Set up your Wi-Fi password in the imager's settings.sudo raspi-config → Interface Options → I2C → Enable. Then reboot.sudo i2cdetect -y 1 — you should see a device at address 0x40 (that's the PCA9685).sudo apt update && sudo apt upgrade -yInstalling the Chess Software
Open a terminal on the Pi and run these commands one by one:
# 1. Install Stockfish chess engine sudo apt install stockfish espeak -y # 2. Install Python libraries pip3 install python-chess adafruit-circuitpython-pca9685 adafruit-circuitpython-motor # 3. Confirm Stockfish is installed which stockfish # Should print: /usr/games/stockfish # 4. Test the chess library quickly python3 -c "import chess; print('python-chess OK ✓')" # 5. Test the servo library python3 -c "import board, busio; print('I2C libraries OK ✓')"
pip3 install [module-name] again and check your internet connection.
The Full Python Code
Save this as chess_robot.py on your Pi desktop. Read the comments — every section explains exactly what it does!
#!/usr/bin/env python3 # ═══════════════════════════════════════════════════════════ # ♟ CHESS ROBOT — Raspberry Pi + Stockfish + Servo Arm # Author: Your Name Here! # How it works: # 1. You type your move (e.g. "e2e4") # 2. Stockfish picks the best reply # 3. The servo arm physically moves the piece # ═══════════════════════════════════════════════════════════ import chess import chess.engine import time import os import board import busio from adafruit_pca9685 import PCA9685 from adafruit_motor import servo as Servo # ── SERVO SETUP ────────────────────────────────────────── i2c = busio.I2C(board.SCL, board.SDA) pca = PCA9685(i2c) pca.frequency = 50 # Standard servo frequency: 50Hz # Create 4 servo objects, one per channel servo_base = Servo.Servo(pca.channels[0], min_pulse=500, max_pulse=2400) servo_shoulder = Servo.Servo(pca.channels[1], min_pulse=500, max_pulse=2400) servo_elbow = Servo.Servo(pca.channels[2], min_pulse=500, max_pulse=2400) servo_gripper = Servo.Servo(pca.channels[3], min_pulse=500, max_pulse=2400) # Gripper angles GRIP_OPEN = 30 # degrees — open enough to fit around a piece GRIP_CLOSE = 90 # degrees — closed / holding piece HOVER_ELBOW = 45 # safe height to travel between squares DOWN_ELBOW = 20 # low enough to pick up / drop piece # ── SQUARE → SERVO ANGLE MAP ───────────────────────────── # Each chess square (a1 to h8) maps to (base_angle, shoulder_angle) # These are EXAMPLE values — you MUST calibrate for your board position! # See the Calibration section for how to tune these. FILES = 'abcdefgh' RANKS = '12345678' def square_to_angles(square_name): """Convert a square like 'e4' to (base_angle, shoulder_angle)""" file_index = FILES.index(square_name[0]) # a=0 … h=7 rank_index = int(square_name[1]) - 1 # 1=0 … 8=7 # Linear interpolation across the board # BASE angle sweeps across files (columns): a=20° … h=160° # SHOULDER angle sweeps across ranks (rows): 1=60° … 8=120° base_angle = 20 + file_index * (140 / 7) shoulder_angle = 60 + rank_index * (60 / 7) return round(base_angle), round(shoulder_angle) # ── LOW-LEVEL SERVO HELPERS ────────────────────────────── def move_servo_smooth(servo_obj, target, step=3, delay=0.02): """Smoothly move a servo to target angle to avoid jerking""" current = servo_obj.angle or 90 direction = 1 if target > current else -1 for angle in range(int(current), int(target), direction * step): servo_obj.angle = angle time.sleep(delay) servo_obj.angle = target def arm_home(): """Move arm to safe resting position""" servo_elbow.angle = HOVER_ELBOW servo_base.angle = 90 servo_shoulder.angle = 90 servo_gripper.angle = GRIP_OPEN time.sleep(0.5) def pick_up_piece(square_name): """Move arm over a square, lower, grab, lift back up""" base_a, shoulder_a = square_to_angles(square_name) print(f" → Moving to {square_name} ({base_a}°, {shoulder_a}°)") # 1. Hover above the square servo_elbow.angle = HOVER_ELBOW move_servo_smooth(servo_base, base_a) move_servo_smooth(servo_shoulder, shoulder_a) time.sleep(0.4) # 2. Open gripper and lower servo_gripper.angle = GRIP_OPEN servo_elbow.angle = DOWN_ELBOW time.sleep(0.4) # 3. Close gripper to grab piece servo_gripper.angle = GRIP_CLOSE time.sleep(0.3) # 4. Lift back up servo_elbow.angle = HOVER_ELBOW time.sleep(0.3) def place_piece(square_name): """Move arm over target square and release piece""" base_a, shoulder_a = square_to_angles(square_name) print(f" → Placing on {square_name} ({base_a}°, {shoulder_a}°)") # 1. Hover above target move_servo_smooth(servo_base, base_a) move_servo_smooth(servo_shoulder, shoulder_a) time.sleep(0.4) # 2. Lower servo_elbow.angle = DOWN_ELBOW time.sleep(0.4) # 3. Open gripper — piece drops servo_gripper.angle = GRIP_OPEN time.sleep(0.3) # 4. Lift and go home servo_elbow.angle = HOVER_ELBOW time.sleep(0.3) def execute_arm_move(move_uci): """Full pick-and-place for a UCI move like 'e7e5'""" from_sq = move_uci[:2] # e.g. 'e7' to_sq = move_uci[2:4] # e.g. 'e5' print(f"\n🦾 Arm moving piece: {from_sq} → {to_sq}") pick_up_piece(from_sq) place_piece(to_sq) arm_home() # ── VOICE ANNOUNCEMENT ─────────────────────────────────── def speak(text): os.system(f'espeak "{text}" 2>/dev/null &') # ── MAIN GAME LOOP ─────────────────────────────────────── def main(): board_state = chess.Board() engine = chess.engine.SimpleEngine.popen_uci("/usr/games/stockfish") # Set difficulty (lower = easier for beginners!) engine.configure({"Skill Level": 5}) # 0=beginner, 20=grandmaster print("\n╔══════════════════════════════════╗") print( "║ ♟ CHESS ROBOT — Ready to play! ║") print( "╚══════════════════════════════════╝") print("Enter moves in UCI format, e.g: e2e4") print("Type 'quit' to end the game.\n") speak("Chess robot ready. Make your move.") arm_home() while not board_state.is_game_over(): print(board_state) print(f"\nMove {board_state.fullmove_number} — Your turn (White)") # Get player input user_input = input("Your move: ").strip().lower() if user_input == 'quit': speak("Good game! Goodbye.") break # Validate player move try: player_move = chess.Move.from_uci(user_input) if player_move not in board_state.legal_moves: print("⚠️ Illegal move! Try again.") continue except ValueError: print("⚠️ Invalid format. Use format like: e2e4") continue board_state.push(player_move) print(f"✅ Your move: {user_input}") if board_state.is_game_over(): break # Stockfish calculates best reply result = engine.play(board_state, chess.engine.Limit(time=2.0)) robot_move = result.move board_state.push(robot_move) move_str = robot_move.uci() from_sq = move_str[:2].upper() to_sq = move_str[2:4].upper() print(f"\n🤖 Robot plays: {from_sq} → {to_sq}") speak(f"Robot moves {from_sq} to {to_sq}") # Physically move the piece on the board! execute_arm_move(move_str) # Game over result = board_state.result() print(f"\n🏁 Game over! Result: {result}") if result == "1-0": speak("White wins! Well played human.") elif result == "0-1": speak("Black wins! I am victorious!") else: speak("It is a draw! What a close game.") engine.quit() pca.deinit() if __name__ == '__main__': main()
python3 chess_robot.py
Then type your first move like e2e4 and press Enter. Watch the arm move!
Calibrating the Arm to Your Board
Every build is slightly different in position and size, so you'll need to tune the servo angles for your exact board. Use this calibration helper script:
#!/usr/bin/env python3 # calibrate.py — Move each servo manually to find correct angles import board, busio from adafruit_pca9685 import PCA9685 from adafruit_motor import servo as Servo i2c = busio.I2C(board.SCL, board.SDA) pca = PCA9685(i2c) pca.frequency = 50 servos = [Servo.Servo(pca.channels[i], min_pulse=500, max_pulse=2400) for i in range(4)] names = ["Base", "Shoulder", "Elbow", "Gripper"] print("CALIBRATION MODE — Enter servo number (0-3) and angle (0-180)") print("Examples: '0 90' '1 45' '2 60'") while True: line = input("servo angle > ") try: s, a = map(int, line.split()) servos[s].angle = a print(f" {names[s]} → {a}°") except: print("Try again. Format: servo_num angle")
python3 calibrate.py and type 0 90 to move the base to 90°. Point the arm toward the a1 corner of the board.square_to_angles() function's min/max values.DOWN_ELBOW to that value in the main code.GRIP_OPEN to just wide enough for a pawn, and GRIP_CLOSE to snug — not so tight it cracks the piece!Playing Your First Game
How to enter moves (UCI notation)
UCI (Universal Chess Interface) notation is simply: from-square + to-square. The piece you're moving doesn't matter — you just name the squares.
| Classic Opening Move | What You Type | What It Means |
|---|---|---|
| King's Pawn Opening | e2e4 | Pawn from e2 to e4 |
| Queen's Pawn Opening | d2d4 | Pawn from d2 to d4 |
| Knight to f3 | g1f3 | Knight from g1 to f3 |
| Bishop to c4 | f1c4 | Bishop from f1 to c4 |
| Pawn promotion to queen | e7e8q | Pawn to e8, promote to Queen |
engine.configure({"Skill Level": 5}). Change 5 to anything from 0 (very easy — great for beginners!) up to 20 (grandmaster level — very hard!). Try level 3 for your first game.
⭐ = where e2e4 lands. Every square has a letter (a–h) + number (1–8).
Awesome Upgrades to Try Next
Add a Pi Camera + OpenCV to detect your physical pieces automatically — no typing moves!
Connect a small OLED screen to show the robot's last move and the current game score.
Use speech_recognition and a USB mic to say "e2 to e4" instead of typing.
Put NeoPixel LEDs under each square — light up the piece the robot just moved!
Add a 7-segment display as a real chess clock counting down time for each player.
Build a simple Flask web server on the Pi so you can enter moves from a phone browser.

Comments
Post a Comment