hardware/game_controller.h

Integration layer between engine and physical board.

This module bridges the chess engine’s internal representation with the physical board interpreter and gantry controller. It handles:

  • Converting engine move encoding to physical moves

  • Converting engine piece types to physical piece types

  • Orchestrating move execution on the physical board

  • Special move handling (castling, en passant, promotion)

  • Detecting human moves via hall effect sensor scanning

IMPORTANT: This header is designed to be included AFTER board_interpreter.h but BEFORE any engine headers to avoid macro conflicts.

Enums

enum class TrackerValidity

Values:

enumerator Exact
enumerator Unknown

Functions

PieceType engineToPhysicalPiece(int enginePiece)

Convert engine piece enum to physical PieceType

Engine pieces: P=0, N=1, B=2, R=3, Q=4, K=5, p=6, n=7, b=8, r=9, q=10, k=11 Physical pieces: PAWN=0, KNIGHT=1, BISHOP=2, ROOK=3, QUEEN=4, KING=5, UNKNOWN=6

Parameters:

enginePiece – The engine’s piece enum value (0-11)

Returns:

The corresponding PieceType for path generation

int physicalToEnginePiece(PieceType pt, bool isWhite)

Convert physical PieceType back to engine piece enum

Parameters:
  • pt – The physical piece type

  • isWhite – True if the piece is white

Returns:

The engine’s piece enum value (0-11)

inline bool isWhitePiece(int enginePiece)

Check if an engine piece is white

Parameters:

enginePiece – The engine’s piece enum value (0-11)

Returns:

True if the piece is white (0-5), false if black (6-11)

PhysicalMove engineToPhysicalMove(int engineMove)

Convert engine move encoding to PhysicalMove struct

Extracts source/target squares, piece type, and capture flag from the engine’s compact move encoding.

Parameters:

engineMove – The engine’s encoded move (see movegen.h for format)

Returns:

PhysicalMove struct ready for path planning

inline BoardCoord squareToBoardCoord(int square)

Convert a square index to BoardCoord

Engine squares: a8=0, b8=1, …, h1=63 BoardCoord: row 0 = rank 8, col 0 = file a

Parameters:

square – The engine’s square index (0-63)

Returns:

BoardCoord for the physical board

inline int boardCoordToSquare(const BoardCoord &coord)

Convert BoardCoord to engine square index

Parameters:

coord – The board coordinate

Returns:

Engine square index (0-63)

int parseBoardMove(const char *moveStr)

Parse a move string (e.g., “e2e4”) and find the matching move in the move list

Parameters:

moveStr – The move string to parse

Returns:

The encoded move if found, 0 otherwise

void printPhysicalBoard(const PhysicalBoard &board)

Print the current physical board state (for debugging)

class GameController
#include <game_controller.h>

Public Functions

GameController()
~GameController()
bool connectHardware(const char *port)

Connect to the gantry controller hardware

Parameters:

port – Serial port path (e.g., “/dev/ttyUSB0”)

Returns:

True if connection successful

bool initScanner()

Initialize the board scanner hardware

Returns:

True if scanner initialization succeeded

bool homeGantry()

Home the gantry (move to reference position)

Returns:

True if homing successful

bool setupNewGame()

Set up a new game - move all pieces from capture zones to starting positions Also resets the physical board state tracking

Returns:

True if setup successful

void syncWithEngine()

Synchronize physical board state with engine state without changing per-piece identity tracking.

This updates occupancy and scanner baselines only. PieceTracker must be initialized or updated separately so move-history-dependent identity information is preserved across normal gameplay.

void initTrackerForStartingPosition()

Reset piece identity tracking for a standard new game starting position. This should only be used when the engine position is the normal start.

void invalidateTracker()

Mark tracker identity as unavailable for slot-based move disambiguation. Use this after loading arbitrary positions or making manual identity- destroying edits.

void initTrackerFromSnapshot(const BoardIdentity &snapshot)

Seed piece identity from a known past snapshot (review-docs §6.5), so a review or a “play from here” game starts with exact identity instead of the standard start. Marks the tracker Exact.

bool executeSetupPlan(const SetupPlan &plan, const BoardIdentity &target)

Physically realize a setup plan (review-docs §6.3): drive the gantry through the plan’s ordered Move / Store / Retrieve steps, then adopt target as the exact identity. Path collisions per step are handled by planMove() as usual; the plan only guarantees ordering. Returns false on an invalid plan or a hardware error.

bool setUpPosition(const BoardIdentity &target)

Physically drive the pieces from wherever they stand now to target.

Plans from the tracked identity, so it is safe from any known board state: an already-correct board plans to an empty plan and executes as a no-op, which is what makes it usable when the caller cannot know whether the pieces are already in place (see HardwareBackend::prepareNewGameFromSnapshot — a review followed on the board has already built the position; one reviewed silently on screen has not).

Requires exact identity — where a piece must go is its slot, which the type-only engine state cannot tell us. Returns false if identity is Unknown (a cold board the machine has nothing to plan from) or on a hardware error.

bool resetToStartingPosition()

Physically restore the standard starting position from wherever the pieces are now — the end of a game, with most pieces still on the board and the captured ones in storage. A setUpPosition() to the standard start.

This is NOT setupNewGame(). That routine blindly retrieves all 32 pieces from their storage slots to their starting squares, which is only correct for a cold board with every piece parked in the rack; run it on a finished game and it flies to empty slots and drops pieces onto occupied squares. Here we plan from the tracked identity instead, so the ordering is safe.

Requires exact identity — see setUpPosition(). Returns false if identity is Unknown (caller should fall back to a hand reset) or on a hardware error.

bool executeEngineMove(int engineMove)

Execute a move from the engine on the physical board Handles normal moves, captures, and special moves (castling, en passant, promotion)

Parameters:

engineMove – The engine’s encoded move

Returns:

True if move executed successfully

bool undoMove(int engineMove, int capturedSlot)

Physically reverse a previously-executed move (physical review, §6.2). Backward stepping in a ReviewSession is one gantry operation, not a replay: the review session knows exactly what the move did, so it supplies the captured piece’s storage slot (SLOT_NONE for a non-capture) to retrieve it. Updates the physical board and the piece tracker in step, so the tracker stays exact as the reviewer scrubs backward and forward.

Parameters:
  • engineMove – The engine move to undo (the one that produced the current position)

  • capturedSlot – The StartingSlot the captured piece is standing in, or SLOT_NONE for a non-capture

Returns:

True if the undo executed successfully

bool executeHumanMove(const char *moveStr)

Execute a human move given in algebraic notation (typed input) Parses the move string and converts to engine format

Parameters:

moveStr – Move in format like “e2e4”, “e7e8q” (promotion)

Returns:

True if move executed successfully

bool waitForHumanMove(int &engineMove, int timeoutMs = 0, const std::atomic<bool> *cancelFlag = nullptr)

Wait for a human player to make a move on the physical board. Uses the hall effect sensors to detect piece movement, then validates the detected move against the engine’s legal move list.

This is the primary interface for sensor-based human input. It handles all move types (normal, capture, castling, en passant, promotion) by waiting until the board state corresponds to exactly one legal move.

The human can take as long as they want — there is no timeout that auto-commits a move. If the board sits in an invalid state for too long (e.g., the player knocked a piece over), the system will alert them via a callback but continue waiting patiently.

Parameters:
  • engineMove[out] Set to the matched engine move on success

  • timeoutMs – Maximum wait time (0 = wait forever)

Returns:

True if a legal move was detected, false on timeout

inline void setScannerObserver(const ScannerObserver &observer)

Install the LED subsystem’s scanner observer (PLAN §6). Passed to the scanner on the next waitForHumanMove so a physical piece lift lights its legal moves. Copied by value; the observer’s context must outlive the controller (the LED subsystem does).

inline void setPlanObserver(const PlanObserver &observer)
bool executeCastling(int engineMove)

Execute a castling move Moves both king and rook in the correct order

Parameters:

engineMove – The castling move from the engine

Returns:

True if successful

bool executeEnPassant(int engineMove)

Execute an en passant capture Handles the capture from the correct square (not destination)

Parameters:

engineMove – The en passant move from the engine

Returns:

True if successful

bool executePromotion(int engineMove)

Execute a pawn promotion For now, just moves the pawn (simplified approach)

Parameters:

engineMove – The promotion move from the engine

Returns:

True if successful

bool verifyBoardState(Bitboard &expectedOcc, Bitboard &actualOcc)

Scan the physical board and verify it matches the engine’s expected state. Call this after gantry moves to catch dropped pieces, missed steps, or failed magnet engagement.

Parameters:
  • expectedOcc[out] Set to the engine’s expected occupancy

  • actualOcc[out] Set to the sensor scan result

Returns:

True if the physical board matches the engine’s expected state

bool verifyBoardState()

Convenience overload: verify and print diagnostics on mismatch.

Returns:

True if board matches engine state

bool isGameOver() const

Check if the game is over (checkmate, stalemate, etc.) Queries the engine for game state

Returns:

True if game has ended

inline const PhysicalBoard &getPhysicalBoard() const

Get the physical board state (for debugging)

inline Gantry::GrblController &getGantry()

Get the gantry controller (for manual operations)

inline BoardScanner &getScanner()

Get the board scanner (for diagnostics and direct access)

inline const PieceTracker &getPieceTracker() const

Get the piece tracker (for diagnostics and direct access)

inline PieceTracker &getPieceTracker()
inline TrackerValidity getTrackerValidity() const

Query whether slot-based piece identity is still exact.

inline bool hasExactTracker() const
void updateTracker(int engineMove)

Update the piece tracker after an engine move has been applied. Call this after makeMove() succeeds so the tracker stays in sync.

Parameters:

engineMove – The engine move that was just applied

inline bool isConnected() const

Check if hardware is connected

inline bool isScannerReady() const

Check if the scanner is ready

inline void setEnginePlaysWhite(bool playsWhite)

Set which side the engine plays

bool isEngineTurn() const

Check if it’s the engine’s turn

inline bool isHumanTurn() const

Check if it’s the human’s turn

void startGame(bool enginePlaysWhite)

Start a new game with the engine playing the specified side

inline void stopGame()

Stop the current game

inline bool isGameInProgress() const

Check if a game is in progress

Public Static Functions

static void printBoardMismatch(Bitboard expected, Bitboard actual)

Print a diagnostic showing which squares differ between expected and actual board states.

Private Functions

inline void notifyPlan(const char *label, const MovePlan &plan)
bool executeCastlingInternal(int engineMove)
bool executeEnPassantInternal(int engineMove)
bool executePromotionInternal(int engineMove)
bool executeNormalMove(int engineMove)
int getCapturedSlotForMove(int engineMove) const
bool undoNormalMove(int engineMove, int capturedSlot)
bool undoCastlingInternal(int engineMove)
bool undoEnPassantInternal(int engineMove, int capturedSlot)
bool retrieveFromStorage(const BoardCoord &square, bool isWhite, int slot)

Private Members

Gantry::GrblController gantry
BoardScanner scanner
PhysicalBoard physicalBoard
PieceTracker pieceTracker
bool gameInProgress
bool waitingForHuman
bool enginePlaysWhite
TrackerValidity trackerValidity
ScannerObserver scannerObserver
bool hasScannerObserver = false
PlanObserver planObserver
bool hasPlanObserver = false

Private Static Functions

static void onIllegalState()
static void onWrongSlot()
namespace EngineMove

Functions

inline int getSource(int move)
inline int getTarget(int move)
inline int getPiece(int move)
inline int getPromoted(int move)
inline bool getCapture(int move)
inline bool getDpp(int move)
inline bool getEnpassant(int move)
inline bool getCastle(int move)
inline int getCapturedPiece(int move)