├── README.md ├── .gitignore ├── arduino_secrets_template.h ├── arduino_secrets.h ├── sensor_test.h ├── wifi_manager_rp2040.cpp ├── wifi_manager_rp2040.h ├── chess_moves.h ├── stockfish_settings.h ├── wifi_manager.h ├── chess_engine.h ├── board_driver.h ├── sensor_test.cpp ├── chess_bot.h ├── Sensor_Test └── Sensor_Test.ino ├── chess_engine.cpp ├── board_driver.cpp ├── OpenChess.ino ├── chess_moves.cpp ├── wifi_manager.cpp ├── Chess └── Chess.ino └── chess_bot.cpp /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/Open-Chess/HEAD/README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/Open-Chess/HEAD/.gitignore -------------------------------------------------------------------------------- /arduino_secrets_template.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Concept-Bytes/Open-Chess/HEAD/arduino_secrets_template.h -------------------------------------------------------------------------------- /arduino_secrets.h: -------------------------------------------------------------------------------- 1 | #ifndef ARDUINO_SECRETS_H 2 | #define ARDUINO_SECRETS_H 3 | 4 | // WiFi Network Credentials 5 | #define SECRET_SSID "Kc iphone" 6 | #define SECRET_PASS "12345678" 7 | 8 | // Stockfish API Settings 9 | #define STOCKFISH_API_URL "stockfish.online" 10 | #define STOCKFISH_API_PATH "/api/s/v2.php" 11 | #define STOCKFISH_API_PORT 443 // HTTPS port 12 | 13 | #endif -------------------------------------------------------------------------------- /sensor_test.h: -------------------------------------------------------------------------------- 1 | #ifndef SENSOR_TEST_H 2 | #define SENSOR_TEST_H 3 | 4 | #include "board_driver.h" 5 | 6 | // --------------------------- 7 | // Sensor Test Mode Class 8 | // --------------------------- 9 | class SensorTest { 10 | private: 11 | BoardDriver* boardDriver; 12 | 13 | // Expected initial configuration for testing 14 | static const char INITIAL_BOARD[8][8]; 15 | 16 | public: 17 | SensorTest(BoardDriver* bd); 18 | void begin(); 19 | void update(); 20 | bool isActive(); 21 | void reset(); 22 | }; 23 | 24 | #endif // SENSOR_TEST_H -------------------------------------------------------------------------------- /wifi_manager_rp2040.cpp: -------------------------------------------------------------------------------- 1 | #include "wifi_manager_rp2040.h" 2 | 3 | WiFiManagerRP2040::WiFiManagerRP2040() { 4 | wifiEnabled = false; 5 | gameMode = "0"; 6 | } 7 | 8 | void WiFiManagerRP2040::begin() { 9 | Serial.println("WiFi Manager for RP2040 - Placeholder Implementation"); 10 | Serial.println("Full WiFi features require external WiFi module"); 11 | Serial.println("Use physical board selectors for game mode selection"); 12 | wifiEnabled = false; 13 | } 14 | 15 | void WiFiManagerRP2040::handleClient() { 16 | // Placeholder - no WiFi functionality 17 | // This prevents compilation errors when WiFi is enabled 18 | // but provides no actual WiFi features 19 | } 20 | 21 | bool WiFiManagerRP2040::isClientConnected() { 22 | return false; 23 | } 24 | 25 | int WiFiManagerRP2040::getSelectedGameMode() { 26 | return 0; // No game mode selected via WiFi 27 | } 28 | 29 | void WiFiManagerRP2040::resetGameSelection() { 30 | gameMode = "0"; 31 | } -------------------------------------------------------------------------------- /wifi_manager_rp2040.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_MANAGER_RP2040_H 2 | #define WIFI_MANAGER_RP2040_H 3 | 4 | // Simple WiFi Manager for RP2040 using built-in WiFi 5 | // This is a simplified version that doesn't require external WiFi modules 6 | 7 | #include 8 | 9 | // --------------------------- 10 | // WiFi Manager Class for RP2040 11 | // --------------------------- 12 | class WiFiManagerRP2040 { 13 | private: 14 | bool wifiEnabled; 15 | String gameMode; 16 | 17 | public: 18 | WiFiManagerRP2040(); 19 | void begin(); 20 | void handleClient(); 21 | bool isClientConnected(); 22 | 23 | // Configuration getters 24 | String getWiFiSSID() { return ""; } 25 | String getWiFiPassword() { return ""; } 26 | String getLichessToken() { return ""; } 27 | String getGameMode() { return gameMode; } 28 | String getStartupType() { return "Local"; } 29 | 30 | // Game selection via web 31 | int getSelectedGameMode(); 32 | void resetGameSelection(); 33 | }; 34 | 35 | #endif // WIFI_MANAGER_RP2040_H -------------------------------------------------------------------------------- /chess_moves.h: -------------------------------------------------------------------------------- 1 | #ifndef CHESS_MOVES_H 2 | #define CHESS_MOVES_H 3 | 4 | #include "board_driver.h" 5 | #include "chess_engine.h" 6 | 7 | // --------------------------- 8 | // Chess Game Mode Class 9 | // --------------------------- 10 | class ChessMoves { 11 | private: 12 | BoardDriver* boardDriver; 13 | ChessEngine* chessEngine; 14 | 15 | // Expected initial configuration 16 | static const char INITIAL_BOARD[8][8]; 17 | 18 | // Internal board state for gameplay 19 | char board[8][8]; 20 | 21 | // Helper functions 22 | void initializeBoard(); 23 | void waitForBoardSetup(); 24 | void processMove(int fromRow, int fromCol, int toRow, int toCol, char piece); 25 | void checkForPromotion(int targetRow, int targetCol, char piece); 26 | void handlePromotion(int targetRow, int targetCol, char piece); 27 | 28 | public: 29 | ChessMoves(BoardDriver* bd, ChessEngine* ce); 30 | void begin(); 31 | void update(); 32 | bool isActive(); 33 | void reset(); 34 | }; 35 | 36 | #endif // CHESS_MOVES_H -------------------------------------------------------------------------------- /stockfish_settings.h: -------------------------------------------------------------------------------- 1 | #ifndef STOCKFISH_SETTINGS_H 2 | #define STOCKFISH_SETTINGS_H 3 | 4 | // Stockfish Engine Settings 5 | struct StockfishSettings { 6 | int depth = 12; // Search depth (1-15, higher = stronger but slower) 7 | int timeoutMs = 30000; // API timeout in milliseconds (30 seconds) 8 | bool useBook = true; // Use opening book for first moves 9 | int maxRetries = 3; // Max API call retries on failure 10 | 11 | // Difficulty presets 12 | static StockfishSettings easy() { 13 | StockfishSettings s; 14 | s.depth = 6; 15 | s.timeoutMs = 15000; 16 | return s; 17 | } 18 | 19 | static StockfishSettings medium() { 20 | StockfishSettings s; 21 | s.depth = 6; 22 | s.timeoutMs = 25000; 23 | return s; 24 | } 25 | 26 | static StockfishSettings hard() { 27 | StockfishSettings s; 28 | s.depth = 14; 29 | s.timeoutMs = 45000; 30 | return s; 31 | } 32 | 33 | static StockfishSettings expert() { 34 | StockfishSettings s; 35 | s.depth = 16; 36 | s.timeoutMs = 60000; 37 | return s; 38 | } 39 | }; 40 | 41 | // Bot difficulty levels 42 | enum BotDifficulty { 43 | BOT_EASY = 1, 44 | BOT_MEDIUM = 2, 45 | BOT_HARD = 3, 46 | BOT_EXPERT = 4 47 | }; 48 | 49 | #endif -------------------------------------------------------------------------------- /wifi_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_MANAGER_H 2 | #define WIFI_MANAGER_H 3 | 4 | #include 5 | #include 6 | 7 | // --------------------------- 8 | // WiFi Configuration 9 | // --------------------------- 10 | #define AP_SSID "OpenChessBoard" 11 | #define AP_PASSWORD "chess123" 12 | #define AP_PORT 80 13 | 14 | // --------------------------- 15 | // WiFi Manager Class 16 | // --------------------------- 17 | class WiFiManager { 18 | private: 19 | WiFiServer server; 20 | bool apMode; 21 | bool clientConnected; 22 | 23 | // Configuration variables 24 | String wifiSSID; 25 | String wifiPassword; 26 | String lichessToken; 27 | String gameMode; 28 | String startupType; 29 | 30 | // Web interface methods 31 | String generateWebPage(); 32 | String generateGameSelectionPage(); 33 | void handleConfigSubmit(WiFiClient& client, String request); 34 | void handleGameSelection(WiFiClient& client, String request); 35 | void sendResponse(WiFiClient& client, String content, String contentType = "text/html"); 36 | void parseFormData(String data); 37 | 38 | public: 39 | WiFiManager(); 40 | void begin(); 41 | void handleClient(); 42 | bool isClientConnected(); 43 | 44 | // Configuration getters 45 | String getWiFiSSID() { return wifiSSID; } 46 | String getWiFiPassword() { return wifiPassword; } 47 | String getLichessToken() { return lichessToken; } 48 | String getGameMode() { return gameMode; } 49 | String getStartupType() { return startupType; } 50 | 51 | // Game selection via web 52 | int getSelectedGameMode(); 53 | void resetGameSelection(); 54 | }; 55 | 56 | #endif // WIFI_MANAGER_H -------------------------------------------------------------------------------- /chess_engine.h: -------------------------------------------------------------------------------- 1 | #ifndef CHESS_ENGINE_H 2 | #define CHESS_ENGINE_H 3 | 4 | // --------------------------- 5 | // Chess Engine Class 6 | // --------------------------- 7 | class ChessEngine { 8 | private: 9 | // Helper functions for move generation 10 | void addPawnMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]); 11 | void addRookMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]); 12 | void addKnightMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]); 13 | void addBishopMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]); 14 | void addQueenMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]); 15 | void addKingMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]); 16 | 17 | bool isSquareOccupiedByOpponent(const char board[8][8], int row, int col, char pieceColor); 18 | bool isSquareEmpty(const char board[8][8], int row, int col); 19 | bool isValidSquare(int row, int col); 20 | char getPieceColor(char piece); 21 | 22 | public: 23 | ChessEngine(); 24 | 25 | // Main move generation function 26 | void getPossibleMoves(const char board[8][8], int row, int col, int &moveCount, int moves[][2]); 27 | 28 | // Move validation 29 | bool isValidMove(const char board[8][8], int fromRow, int fromCol, int toRow, int toCol); 30 | 31 | // Game state checks 32 | bool isPawnPromotion(char piece, int targetRow); 33 | char getPromotedPiece(char piece); 34 | 35 | // Utility functions 36 | void printMove(int fromRow, int fromCol, int toRow, int toCol); 37 | char algebraicToCol(char file); 38 | int algebraicToRow(int rank); 39 | }; 40 | 41 | #endif // CHESS_ENGINE_H -------------------------------------------------------------------------------- /board_driver.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_DRIVER_H 2 | #define BOARD_DRIVER_H 3 | 4 | #include 5 | 6 | // --------------------------- 7 | // Hardware Configuration 8 | // --------------------------- 9 | #define LED_PIN 17 // Pin for NeoPixels 10 | #define NUM_ROWS 8 11 | #define NUM_COLS 8 12 | #define LED_COUNT (NUM_ROWS * NUM_COLS) 13 | #define BRIGHTNESS 100 14 | 15 | // Shift Register (74HC594) Pins 16 | #define SER_PIN 2 // Serial data input (74HC594 pin 14) 17 | #define SRCLK_PIN 3 // Shift register clock (pin 11) 18 | #define RCLK_PIN 4 // Latch clock (pin 12) 19 | 20 | // Column Input Pins (D6..D13) 21 | #define COL_PINS {6, 7, 8, 9, 10, 11, 12, 13} 22 | 23 | // --------------------------- 24 | // Board Driver Class 25 | // --------------------------- 26 | class BoardDriver { 27 | private: 28 | Adafruit_NeoPixel strip; 29 | int colPins[NUM_COLS]; 30 | byte rowPatterns[8]; 31 | bool sensorState[8][8]; 32 | bool sensorPrev[8][8]; 33 | 34 | void loadShiftRegister(byte data); 35 | int getPixelIndex(int row, int col); 36 | 37 | public: 38 | BoardDriver(); 39 | void begin(); 40 | void readSensors(); 41 | bool getSensorState(int row, int col); 42 | bool getSensorPrev(int row, int col); 43 | void updateSensorPrev(); 44 | 45 | // LED Control 46 | void clearAllLEDs(); 47 | void setSquareLED(int row, int col, uint32_t color); 48 | void setSquareLED(int row, int col, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0); 49 | void showLEDs(); 50 | 51 | // Animation Functions 52 | void fireworkAnimation(); 53 | void captureAnimation(); 54 | void promotionAnimation(int col); 55 | void blinkSquare(int row, int col, int times = 3); 56 | void highlightSquare(int row, int col, uint32_t color); 57 | 58 | // Setup Functions 59 | bool checkInitialBoard(const char initialBoard[8][8]); 60 | void updateSetupDisplay(const char initialBoard[8][8]); 61 | void printBoardState(const char initialBoard[8][8]); 62 | }; 63 | 64 | #endif // BOARD_DRIVER_H 65 | -------------------------------------------------------------------------------- /sensor_test.cpp: -------------------------------------------------------------------------------- 1 | #include "sensor_test.h" 2 | #include 3 | 4 | // Expected initial configuration for sensor testing 5 | const char SensorTest::INITIAL_BOARD[8][8] = { 6 | {'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'}, // row 0 (rank 1) 7 | {'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'}, // row 1 (rank 2) 8 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 2 (rank 3) 9 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 3 (rank 4) 10 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 4 (rank 5) 11 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 5 (rank 6) 12 | {'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'}, // row 6 (rank 7) 13 | {'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'} // row 7 (rank 8) 14 | }; 15 | 16 | SensorTest::SensorTest(BoardDriver* bd) : boardDriver(bd) { 17 | } 18 | 19 | void SensorTest::begin() { 20 | Serial.println("Starting Sensor Test Mode..."); 21 | Serial.println("Place pieces on the board to see them light up!"); 22 | Serial.println("This mode continuously displays detected pieces."); 23 | 24 | boardDriver->clearAllLEDs(); 25 | } 26 | 27 | void SensorTest::update() { 28 | // Read current sensor state 29 | boardDriver->readSensors(); 30 | 31 | // Clear all LEDs first 32 | boardDriver->clearAllLEDs(); 33 | 34 | // Light up squares where pieces are detected 35 | for (int row = 0; row < 8; row++) { 36 | for (int col = 0; col < 8; col++) { 37 | if (boardDriver->getSensorState(row, col)) { 38 | // Light up detected pieces in white 39 | boardDriver->setSquareLED(row, col, 0, 0, 0, 255); 40 | } 41 | } 42 | } 43 | 44 | // Show the updated LED state 45 | boardDriver->showLEDs(); 46 | 47 | // Print board state periodically for debugging 48 | static unsigned long lastPrint = 0; 49 | if (millis() - lastPrint > 2000) { // Print every 2 seconds 50 | boardDriver->printBoardState(INITIAL_BOARD); 51 | lastPrint = millis(); 52 | } 53 | 54 | delay(100); // Small delay to prevent overwhelming the system 55 | } 56 | 57 | bool SensorTest::isActive() { 58 | return true; // Always active once started 59 | } 60 | 61 | void SensorTest::reset() { 62 | boardDriver->clearAllLEDs(); 63 | Serial.println("Sensor test reset - ready for testing!"); 64 | } -------------------------------------------------------------------------------- /chess_bot.h: -------------------------------------------------------------------------------- 1 | #ifndef CHESS_BOT_H 2 | #define CHESS_BOT_H 3 | 4 | #include "board_driver.h" 5 | #include "chess_engine.h" 6 | #include "stockfish_settings.h" 7 | #include "arduino_secrets.h" 8 | #include 9 | #include 10 | 11 | class ChessBot { 12 | private: 13 | BoardDriver* _boardDriver; 14 | ChessEngine* _chessEngine; 15 | 16 | char board[8][8]; 17 | const char INITIAL_BOARD[8][8] = { 18 | {'R','N','B','Q','K','B','N','R'}, // row 0 (rank 1) 19 | {'P','P','P','P','P','P','P','P'}, // row 1 (rank 2) 20 | {' ',' ',' ',' ',' ',' ',' ',' '}, // row 2 (rank 3) 21 | {' ',' ',' ',' ',' ',' ',' ',' '}, // row 3 (rank 4) 22 | {' ',' ',' ',' ',' ',' ',' ',' '}, // row 4 (rank 5) 23 | {' ',' ',' ',' ',' ',' ',' ',' '}, // row 5 (rank 6) 24 | {'p','p','p','p','p','p','p','p'}, // row 6 (rank 7) 25 | {'r','n','b','q','k','b','n','r'} // row 7 (rank 8) 26 | }; 27 | 28 | StockfishSettings settings; 29 | BotDifficulty difficulty; 30 | 31 | bool isWhiteTurn; 32 | bool gameStarted; 33 | bool botThinking; 34 | bool wifiConnected; 35 | 36 | // FEN notation handling 37 | String boardToFEN(); 38 | void fenToBoard(String fen); 39 | 40 | // WiFi and API 41 | bool connectToWiFi(); 42 | String makeStockfishRequest(String fen); 43 | bool parseStockfishResponse(String response, String &bestMove); 44 | 45 | // Move handling 46 | bool parseMove(String move, int &fromRow, int &fromCol, int &toRow, int &toCol); 47 | void executeBotMove(int fromRow, int fromCol, int toRow, int toCol); 48 | 49 | // URL encoding helper 50 | String urlEncode(String str); 51 | 52 | // Game flow 53 | void initializeBoard(); 54 | void waitForBoardSetup(); 55 | void processPlayerMove(int fromRow, int fromCol, int toRow, int toCol, char piece); 56 | void makeBotMove(); 57 | void showBotThinking(); 58 | void showConnectionStatus(); 59 | void showBotMoveIndicator(int fromRow, int fromCol, int toRow, int toCol); 60 | void waitForBotMoveCompletion(int fromRow, int fromCol, int toRow, int toCol); 61 | void confirmMoveCompletion(); 62 | void confirmSquareCompletion(int row, int col); 63 | void printCurrentBoard(); 64 | 65 | public: 66 | ChessBot(BoardDriver* boardDriver, ChessEngine* chessEngine, BotDifficulty diff = BOT_MEDIUM); 67 | void begin(); 68 | void update(); 69 | void setDifficulty(BotDifficulty diff); 70 | }; 71 | 72 | #endif -------------------------------------------------------------------------------- /Sensor_Test/Sensor_Test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // --------------------------- 4 | // NeoPixel Setup 5 | // --------------------------- 6 | #define LED_PIN 17 // Pin for NeoPixels 7 | #define NUM_ROWS 8 8 | #define NUM_COLS 8 9 | #define LED_COUNT (NUM_ROWS * NUM_COLS) 10 | #define BRIGHTNESS 100 11 | 12 | Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800); 13 | 14 | // --------------------------- 15 | // Shift Register (74HC594) Pins 16 | // --------------------------- 17 | #define SER_PIN 2 // Serial data input (74HC594 pin 14) 18 | #define SRCLK_PIN 3 // Shift register clock (pin 11) 19 | #define RCLK_PIN 4 // Latch clock (pin 12) 20 | // Pin 13 (OE) on 74HC594 must be tied HIGH (active-high). 21 | // Pin 10 (SRCLR) on 74HC594 must be tied HIGH if not used for clearing. 22 | 23 | // --------------------------- 24 | // Column Input Pins (D6..D13) 25 | // --------------------------- 26 | int colPins[NUM_COLS] = {6, 7, 8, 9, 10, 11, 12, 13}; 27 | 28 | // --------------------------- 29 | // Row Patterns (LSB-first) 30 | // --------------------------- 31 | byte rowPatterns[8] = { 32 | 0x01, // Row 0 33 | 0x02, // Row 1 34 | 0x04, // Row 2 35 | 0x08, // Row 3 36 | 0x10, // Row 4 37 | 0x20, // Row 5 38 | 0x40, // Row 6 39 | 0x80 // Row 7 40 | }; 41 | 42 | // --------------------------------------------------------------------- 43 | // SETUP 44 | // --------------------------------------------------------------------- 45 | void setup() { 46 | Serial.begin(9600); 47 | 48 | // NeoPixel init 49 | strip.begin(); 50 | strip.show(); // Initialize all pixels to 'off' 51 | strip.setBrightness(BRIGHTNESS); 52 | 53 | // Shift register control pins 54 | pinMode(SER_PIN, OUTPUT); 55 | pinMode(SRCLK_PIN, OUTPUT); 56 | pinMode(RCLK_PIN, OUTPUT); 57 | 58 | // Column pins as inputs 59 | for (int c = 0; c < NUM_COLS; c++) { 60 | pinMode(colPins[c], INPUT); 61 | } 62 | 63 | // Initialize shift register to all LOW (no line active) 64 | loadShiftRegister(0x00); 65 | } 66 | 67 | // --------------------------------------------------------------------- 68 | // LOOP 69 | // --------------------------------------------------------------------- 70 | void loop() { 71 | // Clear all LEDs 72 | for(int i = 0; i < LED_COUNT; i++) { 73 | strip.setPixelColor(i, 0); // Off 74 | } 75 | 76 | // Scan each row 77 | for (int row = 0; row < NUM_ROWS; row++) { 78 | 79 | // 1) Enable this row via the shift register 80 | loadShiftRegister(rowPatterns[row]); 81 | 82 | // Small delay to let signals settle 83 | delayMicroseconds(100); 84 | 85 | // 2) Read all columns 86 | for (int c = 0; c < NUM_COLS; c++) { 87 | int sensorVal = digitalRead(colPins[c]); 88 | 89 | // If sensor is active (LOW): 90 | if (sensorVal == LOW) { 91 | // Map row, col to the correct NeoPixel index, no flips: 92 | int pixelIndex = c * NUM_COLS + (7-row); 93 | 94 | // Set that pixel to white for RGBW 95 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 96 | } 97 | } 98 | } 99 | 100 | // Turn off the row lines 101 | loadShiftRegister(0x00); 102 | 103 | // Update NeoPixels 104 | strip.show(); 105 | 106 | // Small pause 107 | delay(100); 108 | } 109 | 110 | // --------------------------------------------------------------------- 111 | // SHIFT OUT (LSB-first to match rowPatterns[]) 112 | // --------------------------------------------------------------------- 113 | void loadShiftRegister(byte data) { 114 | digitalWrite(RCLK_PIN, LOW); 115 | 116 | for (int i = 0; i < 8; i++) { 117 | bool bitVal = (data & (1 << i)) != 0; 118 | digitalWrite(SER_PIN, bitVal ? HIGH : LOW); 119 | 120 | digitalWrite(SRCLK_PIN, HIGH); 121 | delayMicroseconds(10); 122 | digitalWrite(SRCLK_PIN, LOW); 123 | delayMicroseconds(10); 124 | } 125 | 126 | digitalWrite(RCLK_PIN, HIGH); 127 | delayMicroseconds(10); 128 | digitalWrite(RCLK_PIN, LOW); 129 | } -------------------------------------------------------------------------------- /chess_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "chess_engine.h" 2 | #include 3 | 4 | // --------------------------- 5 | // ChessEngine Implementation 6 | // --------------------------- 7 | 8 | ChessEngine::ChessEngine() { 9 | // Constructor - nothing to initialize for now 10 | } 11 | 12 | // Main move generation function 13 | void ChessEngine::getPossibleMoves(const char board[8][8], int row, int col, int &moveCount, int moves[][2]) { 14 | moveCount = 0; 15 | char piece = board[row][col]; 16 | 17 | if (piece == ' ') return; // Empty square 18 | 19 | char pieceColor = getPieceColor(piece); 20 | 21 | // Convert to uppercase for easier comparison 22 | piece = (piece >= 'a' && piece <= 'z') ? piece - 32 : piece; 23 | 24 | switch(piece) { 25 | case 'P': // Pawn 26 | addPawnMoves(board, row, col, pieceColor, moveCount, moves); 27 | break; 28 | case 'R': // Rook 29 | addRookMoves(board, row, col, pieceColor, moveCount, moves); 30 | break; 31 | case 'N': // Knight 32 | addKnightMoves(board, row, col, pieceColor, moveCount, moves); 33 | break; 34 | case 'B': // Bishop 35 | addBishopMoves(board, row, col, pieceColor, moveCount, moves); 36 | break; 37 | case 'Q': // Queen 38 | addQueenMoves(board, row, col, pieceColor, moveCount, moves); 39 | break; 40 | case 'K': // King 41 | addKingMoves(board, row, col, pieceColor, moveCount, moves); 42 | break; 43 | } 44 | } 45 | 46 | // Pawn move generation 47 | void ChessEngine::addPawnMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]) { 48 | int direction = (pieceColor == 'w') ? 1 : -1; 49 | 50 | // One square forward 51 | if (isValidSquare(row + direction, col) && isSquareEmpty(board, row + direction, col)) { 52 | moves[moveCount][0] = row + direction; 53 | moves[moveCount][1] = col; 54 | moveCount++; 55 | 56 | // Initial two-square move 57 | if ((pieceColor == 'w' && row == 1) || (pieceColor == 'b' && row == 6)) { 58 | if (isSquareEmpty(board, row + 2*direction, col)) { 59 | moves[moveCount][0] = row + 2*direction; 60 | moves[moveCount][1] = col; 61 | moveCount++; 62 | } 63 | } 64 | } 65 | 66 | // Diagonal captures 67 | int captureColumns[] = {col-1, col+1}; 68 | for (int i = 0; i < 2; i++) { 69 | int captureRow = row + direction; 70 | int captureCol = captureColumns[i]; 71 | 72 | if (isValidSquare(captureRow, captureCol) && 73 | isSquareOccupiedByOpponent(board, captureRow, captureCol, pieceColor)) { 74 | moves[moveCount][0] = captureRow; 75 | moves[moveCount][1] = captureCol; 76 | moveCount++; 77 | } 78 | } 79 | } 80 | 81 | // Rook move generation 82 | void ChessEngine::addRookMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]) { 83 | int directions[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}}; 84 | 85 | for (int d = 0; d < 4; d++) { 86 | for (int step = 1; step < 8; step++) { 87 | int newRow = row + step * directions[d][0]; 88 | int newCol = col + step * directions[d][1]; 89 | 90 | if (!isValidSquare(newRow, newCol)) break; 91 | 92 | if (isSquareEmpty(board, newRow, newCol)) { 93 | moves[moveCount][0] = newRow; 94 | moves[moveCount][1] = newCol; 95 | moveCount++; 96 | } else { 97 | // Check if it's a capturable piece 98 | if (isSquareOccupiedByOpponent(board, newRow, newCol, pieceColor)) { 99 | moves[moveCount][0] = newRow; 100 | moves[moveCount][1] = newCol; 101 | moveCount++; 102 | } 103 | break; // Can't move past any piece 104 | } 105 | } 106 | } 107 | } 108 | 109 | // Knight move generation 110 | void ChessEngine::addKnightMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]) { 111 | int knightMoves[8][2] = {{2,1}, {1,2}, {-1,2}, {-2,1}, 112 | {-2,-1}, {-1,-2}, {1,-2}, {2,-1}}; 113 | 114 | for (int i = 0; i < 8; i++) { 115 | int newRow = row + knightMoves[i][0]; 116 | int newCol = col + knightMoves[i][1]; 117 | 118 | if (isValidSquare(newRow, newCol)) { 119 | if (isSquareEmpty(board, newRow, newCol) || 120 | isSquareOccupiedByOpponent(board, newRow, newCol, pieceColor)) { 121 | moves[moveCount][0] = newRow; 122 | moves[moveCount][1] = newCol; 123 | moveCount++; 124 | } 125 | } 126 | } 127 | } 128 | 129 | // Bishop move generation 130 | void ChessEngine::addBishopMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]) { 131 | int directions[4][2] = {{1,1}, {1,-1}, {-1,1}, {-1,-1}}; 132 | 133 | for (int d = 0; d < 4; d++) { 134 | for (int step = 1; step < 8; step++) { 135 | int newRow = row + step * directions[d][0]; 136 | int newCol = col + step * directions[d][1]; 137 | 138 | if (!isValidSquare(newRow, newCol)) break; 139 | 140 | if (isSquareEmpty(board, newRow, newCol)) { 141 | moves[moveCount][0] = newRow; 142 | moves[moveCount][1] = newCol; 143 | moveCount++; 144 | } else { 145 | // Check if it's a capturable piece 146 | if (isSquareOccupiedByOpponent(board, newRow, newCol, pieceColor)) { 147 | moves[moveCount][0] = newRow; 148 | moves[moveCount][1] = newCol; 149 | moveCount++; 150 | } 151 | break; // Can't move past any piece 152 | } 153 | } 154 | } 155 | } 156 | 157 | // Queen move generation (combination of rook and bishop) 158 | void ChessEngine::addQueenMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]) { 159 | addRookMoves(board, row, col, pieceColor, moveCount, moves); 160 | addBishopMoves(board, row, col, pieceColor, moveCount, moves); 161 | } 162 | 163 | // King move generation 164 | void ChessEngine::addKingMoves(const char board[8][8], int row, int col, char pieceColor, int &moveCount, int moves[][2]) { 165 | int kingMoves[8][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}, 166 | {1,1}, {1,-1}, {-1,1}, {-1,-1}}; 167 | 168 | for (int i = 0; i < 8; i++) { 169 | int newRow = row + kingMoves[i][0]; 170 | int newCol = col + kingMoves[i][1]; 171 | 172 | if (isValidSquare(newRow, newCol)) { 173 | if (isSquareEmpty(board, newRow, newCol) || 174 | isSquareOccupiedByOpponent(board, newRow, newCol, pieceColor)) { 175 | moves[moveCount][0] = newRow; 176 | moves[moveCount][1] = newCol; 177 | moveCount++; 178 | } 179 | } 180 | } 181 | } 182 | 183 | // Helper function to check if a square is occupied by an opponent piece 184 | bool ChessEngine::isSquareOccupiedByOpponent(const char board[8][8], int row, int col, char pieceColor) { 185 | char targetPiece = board[row][col]; 186 | if (targetPiece == ' ') return false; 187 | 188 | char targetColor = getPieceColor(targetPiece); 189 | return targetColor != pieceColor; 190 | } 191 | 192 | // Helper function to check if a square is empty 193 | bool ChessEngine::isSquareEmpty(const char board[8][8], int row, int col) { 194 | return board[row][col] == ' '; 195 | } 196 | 197 | // Helper function to check if coordinates are within board bounds 198 | bool ChessEngine::isValidSquare(int row, int col) { 199 | return row >= 0 && row < 8 && col >= 0 && col < 8; 200 | } 201 | 202 | // Helper function to get piece color 203 | char ChessEngine::getPieceColor(char piece) { 204 | return (piece >= 'a' && piece <= 'z') ? 'b' : 'w'; 205 | } 206 | 207 | // Move validation 208 | bool ChessEngine::isValidMove(const char board[8][8], int fromRow, int fromCol, int toRow, int toCol) { 209 | int moveCount = 0; 210 | int moves[28][2]; // Maximum possible moves for a queen 211 | 212 | getPossibleMoves(board, fromRow, fromCol, moveCount, moves); 213 | 214 | for (int i = 0; i < moveCount; i++) { 215 | if (moves[i][0] == toRow && moves[i][1] == toCol) { 216 | return true; 217 | } 218 | } 219 | return false; 220 | } 221 | 222 | // Check if a pawn move results in promotion 223 | bool ChessEngine::isPawnPromotion(char piece, int targetRow) { 224 | if (piece == 'P' && targetRow == 7) return true; // White pawn reaches 8th rank 225 | if (piece == 'p' && targetRow == 0) return true; // Black pawn reaches 1st rank 226 | return false; 227 | } 228 | 229 | // Get the promoted piece (always queen for now) 230 | char ChessEngine::getPromotedPiece(char piece) { 231 | return (piece == 'P') ? 'Q' : 'q'; 232 | } 233 | 234 | // Utility function to print a move in readable format 235 | void ChessEngine::printMove(int fromRow, int fromCol, int toRow, int toCol) { 236 | Serial.print((char)('a' + fromCol)); 237 | Serial.print(fromRow + 1); 238 | Serial.print(" to "); 239 | Serial.print((char)('a' + toCol)); 240 | Serial.println(toRow + 1); 241 | } 242 | 243 | // Convert algebraic notation file (a-h) to column index (0-7) 244 | char ChessEngine::algebraicToCol(char file) { 245 | return file - 'a'; 246 | } 247 | 248 | // Convert algebraic notation rank (1-8) to row index (0-7) 249 | int ChessEngine::algebraicToRow(int rank) { 250 | return rank - 1; 251 | } -------------------------------------------------------------------------------- /board_driver.cpp: -------------------------------------------------------------------------------- 1 | #include "board_driver.h" 2 | #include 3 | 4 | // --------------------------- 5 | // BoardDriver Implementation 6 | // --------------------------- 7 | 8 | BoardDriver::BoardDriver() : strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800) { 9 | // Initialize column pins 10 | int tempColPins[NUM_COLS] = COL_PINS; 11 | for (int i = 0; i < NUM_COLS; i++) { 12 | colPins[i] = tempColPins[i]; 13 | } 14 | 15 | // Initialize row patterns (LSB-first for shift register) 16 | rowPatterns[0] = 0x01; // row 0 17 | rowPatterns[1] = 0x02; // row 1 18 | rowPatterns[2] = 0x04; // row 2 19 | rowPatterns[3] = 0x08; // row 3 20 | rowPatterns[4] = 0x10; // row 4 21 | rowPatterns[5] = 0x20; // row 5 22 | rowPatterns[6] = 0x40; // row 6 23 | rowPatterns[7] = 0x80; // row 7 24 | } 25 | 26 | void BoardDriver::begin() { 27 | // Initialize NeoPixel strip 28 | strip.begin(); 29 | strip.show(); // turn off all pixels 30 | strip.setBrightness(BRIGHTNESS); 31 | 32 | // Setup shift register control pins 33 | pinMode(SER_PIN, OUTPUT); 34 | pinMode(SRCLK_PIN, OUTPUT); 35 | pinMode(RCLK_PIN, OUTPUT); 36 | 37 | // Setup column input pins 38 | for (int c = 0; c < NUM_COLS; c++) { 39 | pinMode(colPins[c], INPUT); 40 | } 41 | 42 | // Initialize shift register to no row active 43 | loadShiftRegister(0x00); 44 | 45 | // Initialize sensor arrays 46 | for (int row = 0; row < 8; row++) { 47 | for (int col = 0; col < 8; col++) { 48 | sensorState[row][col] = false; 49 | sensorPrev[row][col] = false; 50 | } 51 | } 52 | } 53 | 54 | void BoardDriver::loadShiftRegister(byte data) { 55 | digitalWrite(RCLK_PIN, LOW); 56 | for (int i = 0; i < 8; i++) { 57 | bool bitVal = (data & (1 << i)) != 0; 58 | digitalWrite(SER_PIN, bitVal ? HIGH : LOW); 59 | digitalWrite(SRCLK_PIN, HIGH); 60 | delayMicroseconds(10); 61 | digitalWrite(SRCLK_PIN, LOW); 62 | delayMicroseconds(10); 63 | } 64 | digitalWrite(RCLK_PIN, HIGH); 65 | delayMicroseconds(10); 66 | digitalWrite(RCLK_PIN, LOW); 67 | } 68 | 69 | void BoardDriver::readSensors() { 70 | for (int row = 0; row < 8; row++) { 71 | loadShiftRegister(rowPatterns[row]); 72 | delayMicroseconds(100); 73 | for (int col = 0; col < NUM_COLS; col++) { 74 | int sensorVal = digitalRead(colPins[col]); 75 | sensorState[row][col] = (sensorVal == LOW); 76 | } 77 | } 78 | loadShiftRegister(0x00); 79 | } 80 | 81 | bool BoardDriver::getSensorState(int row, int col) { 82 | return sensorState[row][col]; 83 | } 84 | 85 | bool BoardDriver::getSensorPrev(int row, int col) { 86 | return sensorPrev[row][col]; 87 | } 88 | 89 | void BoardDriver::updateSensorPrev() { 90 | for (int row = 0; row < 8; row++) { 91 | for (int col = 0; col < 8; col++) { 92 | sensorPrev[row][col] = sensorState[row][col]; 93 | } 94 | } 95 | } 96 | 97 | int BoardDriver::getPixelIndex(int row, int col) { 98 | return col * NUM_COLS + (7 - row); 99 | } 100 | 101 | void BoardDriver::clearAllLEDs() { 102 | for (int i = 0; i < LED_COUNT; i++) { 103 | strip.setPixelColor(i, 0); 104 | } 105 | strip.show(); 106 | } 107 | 108 | void BoardDriver::setSquareLED(int row, int col, uint32_t color) { 109 | int pixelIndex = getPixelIndex(row, col); 110 | strip.setPixelColor(pixelIndex, color); 111 | } 112 | 113 | void BoardDriver::setSquareLED(int row, int col, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { 114 | int pixelIndex = getPixelIndex(row, col); 115 | strip.setPixelColor(pixelIndex, strip.Color(r, g, b, w)); 116 | } 117 | 118 | void BoardDriver::showLEDs() { 119 | strip.show(); 120 | } 121 | 122 | void BoardDriver::highlightSquare(int row, int col, uint32_t color) { 123 | setSquareLED(row, col, color); 124 | showLEDs(); 125 | } 126 | 127 | void BoardDriver::blinkSquare(int row, int col, int times) { 128 | int pixelIndex = getPixelIndex(row, col); 129 | for (int i = 0; i < times; i++) { 130 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 131 | strip.show(); 132 | delay(200); 133 | strip.setPixelColor(pixelIndex, 0); 134 | strip.show(); 135 | delay(200); 136 | } 137 | } 138 | 139 | void BoardDriver::fireworkAnimation() { 140 | float centerX = 3.5; 141 | float centerY = 3.5; 142 | 143 | // Expansion phase: 144 | for (float radius = 0; radius < 6; radius += 0.5) { 145 | for (int row = 0; row < 8; row++) { 146 | for (int col = 0; col < 8; col++) { 147 | float dx = col - centerX; 148 | float dy = row - centerY; 149 | float dist = sqrt(dx * dx + dy * dy); 150 | int pixelIndex = getPixelIndex(row, col); 151 | if (fabs(dist - radius) < 0.5) 152 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 153 | else 154 | strip.setPixelColor(pixelIndex, 0); 155 | } 156 | } 157 | strip.show(); 158 | delay(100); 159 | } 160 | 161 | // Contraction phase: 162 | for (float radius = 6; radius > 0; radius -= 0.5) { 163 | for (int row = 0; row < 8; row++) { 164 | for (int col = 0; col < 8; col++) { 165 | float dx = col - centerX; 166 | float dy = row - centerY; 167 | float dist = sqrt(dx * dx + dy * dy); 168 | int pixelIndex = getPixelIndex(row, col); 169 | if (fabs(dist - radius) < 0.5) 170 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 171 | else 172 | strip.setPixelColor(pixelIndex, 0); 173 | } 174 | } 175 | strip.show(); 176 | delay(100); 177 | } 178 | 179 | // Second expansion phase: 180 | for (float radius = 0; radius < 6; radius += 0.5) { 181 | for (int row = 0; row < 8; row++) { 182 | for (int col = 0; col < 8; col++) { 183 | float dx = col - centerX; 184 | float dy = row - centerY; 185 | float dist = sqrt(dx * dx + dy * dy); 186 | int pixelIndex = getPixelIndex(row, col); 187 | if (fabs(dist - radius) < 0.5) 188 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 189 | else 190 | strip.setPixelColor(pixelIndex, 0); 191 | } 192 | } 193 | strip.show(); 194 | delay(100); 195 | } 196 | 197 | // Clear all LEDs 198 | clearAllLEDs(); 199 | } 200 | 201 | void BoardDriver::captureAnimation() { 202 | float centerX = 3.5; 203 | float centerY = 3.5; 204 | 205 | // Pulsing outward animation 206 | for (int pulse = 0; pulse < 3; pulse++) { 207 | for (int row = 0; row < 8; row++) { 208 | for (int col = 0; col < 8; col++) { 209 | float dx = col - centerX; 210 | float dy = row - centerY; 211 | float dist = sqrt(dx * dx + dy * dy); 212 | 213 | // Create a pulsing effect around the center 214 | float pulseWidth = 1.5 + pulse; 215 | int pixelIndex = getPixelIndex(row, col); 216 | 217 | if (dist >= pulseWidth - 0.5 && dist <= pulseWidth + 0.5) { 218 | // Alternate between red and orange for capture effect 219 | uint32_t color = (pulse % 2 == 0) 220 | ? strip.Color(255, 0, 0, 0) // Red 221 | : strip.Color(255, 165, 0, 0); // Orange 222 | strip.setPixelColor(pixelIndex, color); 223 | } else { 224 | strip.setPixelColor(pixelIndex, 0); 225 | } 226 | } 227 | } 228 | strip.show(); 229 | delay(150); 230 | } 231 | 232 | // Clear LEDs 233 | clearAllLEDs(); 234 | } 235 | 236 | void BoardDriver::promotionAnimation(int col) { 237 | const uint32_t PROMOTION_COLOR = strip.Color(255, 215, 0, 50); // Gold with white 238 | 239 | // Column-based waterfall animation 240 | for (int step = 0; step < 16; step++) { 241 | for (int row = 0; row < 8; row++) { 242 | int pixelIndex = getPixelIndex(row, col); 243 | 244 | // Create a golden wave moving up and down the column 245 | if ((step + row) % 8 < 4) { 246 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 247 | } else { 248 | strip.setPixelColor(pixelIndex, 0); 249 | } 250 | } 251 | strip.show(); 252 | delay(100); 253 | } 254 | 255 | // Clear the animation 256 | for (int row = 0; row < 8; row++) { 257 | int pixelIndex = getPixelIndex(row, col); 258 | strip.setPixelColor(pixelIndex, 0); 259 | } 260 | strip.show(); 261 | } 262 | 263 | bool BoardDriver::checkInitialBoard(const char initialBoard[8][8]) { 264 | readSensors(); 265 | bool allPresent = true; 266 | for (int row = 0; row < 8; row++) { 267 | for (int col = 0; col < 8; col++) { 268 | if (initialBoard[row][col] != ' ' && !sensorState[row][col]) { 269 | allPresent = false; 270 | } 271 | } 272 | } 273 | return allPresent; 274 | } 275 | 276 | void BoardDriver::updateSetupDisplay(const char initialBoard[8][8]) { 277 | for (int row = 0; row < 8; row++) { 278 | for (int col = 0; col < 8; col++) { 279 | int pixelIndex = getPixelIndex(row, col); 280 | if (initialBoard[row][col] != ' ' && sensorState[row][col]) { 281 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 282 | } else { 283 | strip.setPixelColor(pixelIndex, 0); 284 | } 285 | } 286 | } 287 | strip.show(); 288 | } 289 | 290 | void BoardDriver::printBoardState(const char initialBoard[8][8]) { 291 | Serial.println("Current Board:"); 292 | for (int row = 0; row < 8; row++) { 293 | Serial.print("{ "); 294 | for (int col = 0; col < 8; col++) { 295 | char displayChar = ' '; 296 | if (initialBoard[row][col] != ' ') { 297 | displayChar = sensorState[row][col] ? initialBoard[row][col] : '-'; 298 | } 299 | Serial.print("'"); 300 | Serial.print(displayChar); 301 | Serial.print("'"); 302 | if (col < 7) Serial.print(", "); 303 | } 304 | Serial.println(" },"); 305 | } 306 | Serial.println(); 307 | } 308 | -------------------------------------------------------------------------------- /OpenChess.ino: -------------------------------------------------------------------------------- 1 | #include "board_driver.h" 2 | #include "chess_engine.h" 3 | #include "chess_moves.h" 4 | #include "sensor_test.h" 5 | #include "chess_bot.h" 6 | 7 | // Uncomment the next line to enable WiFi features (requires compatible board) 8 | #define ENABLE_WIFI // Currently disabled - RP2040 boards use local mode only 9 | #ifdef ENABLE_WIFI 10 | // Use different WiFi manager based on board type 11 | #if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_SAMD_NANO_33_IOT) || defined(ARDUINO_NANO_RP2040_CONNECT) 12 | #include "wifi_manager.h" // Full WiFi implementation for boards with WiFiNINA 13 | #else 14 | #include "wifi_manager_rp2040.h" // Placeholder for RP2040 and other boards 15 | #define WiFiManager WiFiManagerRP2040 16 | #endif 17 | #endif 18 | 19 | // --------------------------- 20 | // Game State and Configuration 21 | // --------------------------- 22 | 23 | // Game Mode Definitions 24 | enum GameMode { 25 | MODE_SELECTION = 0, 26 | MODE_CHESS_MOVES = 1, 27 | MODE_CHESS_BOT = 2, // Chess vs Bot mode 28 | MODE_GAME_3 = 3, // Reserved for future game mode 29 | MODE_SENSOR_TEST = 4 30 | }; 31 | 32 | // Global instances 33 | BoardDriver boardDriver; 34 | ChessEngine chessEngine; 35 | ChessMoves chessMoves(&boardDriver, &chessEngine); 36 | SensorTest sensorTest(&boardDriver); 37 | ChessBot chessBot(&boardDriver, &chessEngine, BOT_MEDIUM); 38 | 39 | #ifdef ENABLE_WIFI 40 | WiFiManager wifiManager; 41 | #endif 42 | 43 | // Current game state 44 | GameMode currentMode = MODE_SELECTION; 45 | bool modeInitialized = false; 46 | 47 | // --------------------------- 48 | // Function Prototypes 49 | // --------------------------- 50 | void showGameSelection(); 51 | void handleGameSelection(); 52 | void initializeSelectedMode(GameMode mode); 53 | 54 | // --------------------------- 55 | // SETUP 56 | // --------------------------- 57 | void setup() { 58 | // Initialize Serial with extended timeout 59 | Serial.begin(9600); 60 | 61 | // Wait for Serial to be ready (critical for RP2040) 62 | unsigned long startTime = millis(); 63 | while (!Serial && (millis() - startTime < 10000)) { 64 | // Wait up to 10 seconds for Serial connection 65 | delay(100); 66 | } 67 | 68 | // Force a delay to ensure Serial is stable 69 | delay(2000); 70 | 71 | Serial.println(); 72 | Serial.println("================================================"); 73 | Serial.println(" OpenChess Starting Up"); 74 | Serial.println("================================================"); 75 | Serial.println("DEBUG: Serial communication established"); 76 | Serial.print("DEBUG: Millis since boot: "); 77 | Serial.println(millis()); 78 | 79 | // Debug board type detection 80 | Serial.println("DEBUG: Board type detection:"); 81 | #if defined(ARDUINO_SAMD_MKRWIFI1010) 82 | Serial.println(" - Detected: ARDUINO_SAMD_MKRWIFI1010"); 83 | #elif defined(ARDUINO_SAMD_NANO_33_IOT) 84 | Serial.println(" - Detected: ARDUINO_SAMD_NANO_33_IOT"); 85 | #elif defined(ARDUINO_NANO_RP2040_CONNECT) 86 | Serial.println(" - Detected: ARDUINO_NANO_RP2040_CONNECT"); 87 | #else 88 | Serial.println(" - Detected: Unknown/Other board type"); 89 | #endif 90 | 91 | // Check which mode is compiled 92 | #ifdef ENABLE_WIFI 93 | Serial.println("DEBUG: Compiled with ENABLE_WIFI defined"); 94 | #else 95 | Serial.println("DEBUG: Compiled without ENABLE_WIFI (local mode only)"); 96 | #endif 97 | 98 | Serial.println("DEBUG: About to initialize board driver..."); 99 | // Initialize board driver 100 | boardDriver.begin(); 101 | Serial.println("DEBUG: Board driver initialized successfully"); 102 | 103 | #ifdef ENABLE_WIFI 104 | Serial.println(); 105 | Serial.println("=== WiFi Mode Enabled ==="); 106 | Serial.println("DEBUG: About to initialize WiFi Manager..."); 107 | Serial.println("DEBUG: This will attempt to create Access Point"); 108 | 109 | // Initialize WiFi Manager 110 | wifiManager.begin(); 111 | 112 | Serial.println("DEBUG: WiFi Manager initialization completed"); 113 | Serial.println("If WiFi AP was created successfully, you should see:"); 114 | Serial.println("- Network name: OpenChessBoard"); 115 | Serial.println("- Password: chess123"); 116 | Serial.println("- Web interface: http://192.168.4.1"); 117 | Serial.println("Or place a piece on the board for local selection"); 118 | #else 119 | Serial.println(); 120 | Serial.println("=== Local Mode Only ==="); 121 | Serial.println("WiFi features are disabled in this build"); 122 | Serial.println("To enable WiFi: Uncomment #define ENABLE_WIFI and recompile"); 123 | #endif 124 | 125 | Serial.println(); 126 | Serial.println("=== Game Selection Mode ==="); 127 | Serial.println("DEBUG: About to show game selection LEDs..."); 128 | 129 | // Show game selection interface 130 | showGameSelection(); 131 | 132 | Serial.println("DEBUG: Game selection LEDs should now be visible"); 133 | Serial.println("Four white LEDs should be lit in the center of the board:"); 134 | Serial.println("Position 1 (3,3): Chess Moves (Human vs Human)"); 135 | Serial.println("Position 2 (3,4): Chess Bot (Human vs AI)"); 136 | Serial.println("Position 3 (4,3): Game Mode 3 (Coming Soon)"); 137 | Serial.println("Position 4 (4,4): Sensor Test"); 138 | Serial.println(); 139 | Serial.println("Place any chess piece on a white LED to select that mode"); 140 | Serial.println("================================================"); 141 | Serial.println(" Setup Complete - Entering Main Loop"); 142 | Serial.println("================================================"); 143 | } 144 | 145 | // --------------------------- 146 | // MAIN LOOP 147 | // --------------------------- 148 | void loop() { 149 | static unsigned long lastDebugPrint = 0; 150 | static bool firstLoop = true; 151 | 152 | if (firstLoop) { 153 | Serial.println("DEBUG: Entered main loop - system is running"); 154 | firstLoop = false; 155 | } 156 | 157 | // Print periodic status every 10 seconds 158 | if (millis() - lastDebugPrint > 10000) { 159 | Serial.print("DEBUG: Loop running, uptime: "); 160 | Serial.print(millis() / 1000); 161 | Serial.println(" seconds"); 162 | lastDebugPrint = millis(); 163 | } 164 | 165 | #ifdef ENABLE_WIFI 166 | // Handle WiFi clients 167 | wifiManager.handleClient(); 168 | 169 | // Check for WiFi game selection 170 | int selectedMode = wifiManager.getSelectedGameMode(); 171 | if (selectedMode > 0) { 172 | Serial.print("DEBUG: WiFi game selection detected: "); 173 | Serial.println(selectedMode); 174 | 175 | switch (selectedMode) { 176 | case 1: 177 | currentMode = MODE_CHESS_MOVES; 178 | break; 179 | case 4: 180 | currentMode = MODE_SENSOR_TEST; 181 | break; 182 | default: 183 | Serial.println("Invalid game mode selected via WiFi"); 184 | selectedMode = 0; 185 | break; 186 | } 187 | 188 | if (selectedMode > 0) { 189 | modeInitialized = false; 190 | boardDriver.clearAllLEDs(); 191 | wifiManager.resetGameSelection(); 192 | 193 | // Brief confirmation animation 194 | for (int i = 0; i < 3; i++) { 195 | boardDriver.setSquareLED(3, 3, 0, 255, 0, 0); // Green flash 196 | boardDriver.setSquareLED(3, 4, 0, 255, 0, 0); 197 | boardDriver.setSquareLED(4, 3, 0, 255, 0, 0); 198 | boardDriver.setSquareLED(4, 4, 0, 255, 0, 0); 199 | boardDriver.showLEDs(); 200 | delay(200); 201 | boardDriver.clearAllLEDs(); 202 | delay(200); 203 | } 204 | } 205 | } 206 | #endif 207 | 208 | if (currentMode == MODE_SELECTION) { 209 | handleGameSelection(); 210 | } else { 211 | static bool modeChangeLogged = false; 212 | if (!modeChangeLogged) { 213 | Serial.print("DEBUG: Mode changed to: "); 214 | Serial.println(currentMode); 215 | modeChangeLogged = true; 216 | } 217 | // Initialize the selected mode if not already done 218 | if (!modeInitialized) { 219 | initializeSelectedMode(currentMode); 220 | modeInitialized = true; 221 | } 222 | 223 | // Run the current game mode 224 | switch (currentMode) { 225 | case MODE_CHESS_MOVES: 226 | chessMoves.update(); 227 | break; 228 | case MODE_CHESS_BOT: 229 | chessBot.update(); 230 | break; 231 | case MODE_SENSOR_TEST: 232 | sensorTest.update(); 233 | break; 234 | case MODE_GAME_3: 235 | // Future game modes - placeholder 236 | Serial.println("Game mode coming soon!"); 237 | delay(1000); 238 | break; 239 | default: 240 | currentMode = MODE_SELECTION; 241 | modeInitialized = false; 242 | showGameSelection(); 243 | break; 244 | } 245 | } 246 | 247 | delay(50); // Small delay to prevent overwhelming the system 248 | } 249 | 250 | // --------------------------- 251 | // GAME SELECTION FUNCTIONS 252 | // --------------------------- 253 | 254 | void showGameSelection() { 255 | // Clear all LEDs first 256 | boardDriver.clearAllLEDs(); 257 | 258 | // Light up the 4 selector positions in the middle of the board 259 | // All positions now use bright white for better visibility 260 | // Position 1: Chess Moves (row 3, col 3) - White 261 | boardDriver.setSquareLED(3, 3, 0, 0, 0, 255); 262 | 263 | // Position 2: Game Mode 2 (row 3, col 4) - White 264 | boardDriver.setSquareLED(3, 4, 0, 0, 0, 255); 265 | 266 | // Position 3: Game Mode 3 (row 4, col 3) - White 267 | boardDriver.setSquareLED(4, 3, 0, 0, 0, 255); 268 | 269 | // Position 4: Sensor Test (row 4, col 4) - White 270 | boardDriver.setSquareLED(4, 4, 0, 0, 0, 255); 271 | 272 | boardDriver.showLEDs(); 273 | } 274 | 275 | void handleGameSelection() { 276 | boardDriver.readSensors(); 277 | 278 | // Check for piece placement on selector squares 279 | if (boardDriver.getSensorState(3, 3)) { 280 | // Chess Moves selected 281 | Serial.println("Chess Moves mode selected!"); 282 | currentMode = MODE_CHESS_MOVES; 283 | modeInitialized = false; 284 | boardDriver.clearAllLEDs(); 285 | delay(500); // Debounce delay 286 | } 287 | else if (boardDriver.getSensorState(3, 4)) { 288 | // Chess Bot selected 289 | Serial.println("Chess Bot mode selected (Human vs AI)!"); 290 | currentMode = MODE_CHESS_BOT; 291 | modeInitialized = false; 292 | boardDriver.clearAllLEDs(); 293 | delay(500); 294 | } 295 | else if (boardDriver.getSensorState(4, 3)) { 296 | // Game Mode 3 selected 297 | Serial.println("Game Mode 3 selected (Coming Soon)!"); 298 | currentMode = MODE_GAME_3; 299 | modeInitialized = false; 300 | boardDriver.clearAllLEDs(); 301 | delay(500); 302 | } 303 | else if (boardDriver.getSensorState(4, 4)) { 304 | // Sensor Test selected 305 | Serial.println("Sensor Test mode selected!"); 306 | currentMode = MODE_SENSOR_TEST; 307 | modeInitialized = false; 308 | boardDriver.clearAllLEDs(); 309 | delay(500); 310 | } 311 | 312 | delay(100); 313 | } 314 | 315 | void initializeSelectedMode(GameMode mode) { 316 | switch (mode) { 317 | case MODE_CHESS_MOVES: 318 | Serial.println("Starting Chess Moves (Human vs Human)..."); 319 | chessMoves.begin(); 320 | break; 321 | case MODE_CHESS_BOT: 322 | Serial.println("Starting Chess Bot (Human vs AI)..."); 323 | chessBot.begin(); 324 | break; 325 | case MODE_SENSOR_TEST: 326 | Serial.println("Starting Sensor Test..."); 327 | sensorTest.begin(); 328 | break; 329 | case MODE_GAME_3: 330 | Serial.println("This game mode will be available in a future update!"); 331 | Serial.println("Returning to game selection in 3 seconds..."); 332 | delay(3000); 333 | currentMode = MODE_SELECTION; 334 | modeInitialized = false; 335 | showGameSelection(); 336 | break; 337 | default: 338 | currentMode = MODE_SELECTION; 339 | modeInitialized = false; 340 | showGameSelection(); 341 | break; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /chess_moves.cpp: -------------------------------------------------------------------------------- 1 | #include "chess_moves.h" 2 | #include 3 | 4 | // Expected initial configuration (as printed in the grid) 5 | const char ChessMoves::INITIAL_BOARD[8][8] = { 6 | {'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'}, // row 0 (rank 1) 7 | {'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'}, // row 1 (rank 2) 8 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 2 (rank 3) 9 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 3 (rank 4) 10 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 4 (rank 5) 11 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 5 (rank 6) 12 | {'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'}, // row 6 (rank 7) 13 | {'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'} // row 7 (rank 8) 14 | }; 15 | 16 | ChessMoves::ChessMoves(BoardDriver* bd, ChessEngine* ce) : boardDriver(bd), chessEngine(ce) { 17 | // Initialize board state 18 | initializeBoard(); 19 | } 20 | 21 | void ChessMoves::begin() { 22 | Serial.println("Starting Chess Game Mode..."); 23 | 24 | // Copy expected configuration into our board state 25 | initializeBoard(); 26 | 27 | // Wait for board setup 28 | waitForBoardSetup(); 29 | 30 | Serial.println("Chess game ready to start!"); 31 | boardDriver->fireworkAnimation(); 32 | 33 | // Initialize sensor previous state for move detection 34 | boardDriver->readSensors(); 35 | boardDriver->updateSensorPrev(); 36 | } 37 | 38 | void ChessMoves::update() { 39 | boardDriver->readSensors(); 40 | 41 | // Look for a piece pickup 42 | for (int row = 0; row < 8; row++) { 43 | for (int col = 0; col < 8; col++) { 44 | if (boardDriver->getSensorPrev(row, col) && !boardDriver->getSensorState(row, col)) { 45 | char piece = board[row][col]; 46 | 47 | // Skip empty squares 48 | if (piece == ' ') continue; 49 | 50 | Serial.print("Piece lifted from "); 51 | Serial.print((char)('a' + col)); 52 | Serial.println(row + 1); 53 | 54 | // Generate possible moves 55 | int moveCount = 0; 56 | int moves[28][2]; // up to 28 moves (maximum for a queen) 57 | chessEngine->getPossibleMoves(board, row, col, moveCount, moves); 58 | 59 | // Light up current square and possible move squares 60 | boardDriver->setSquareLED(row, col, 0, 0, 0, 100); // Dimmer, but solid 61 | 62 | // Highlight possible move squares (including captures) 63 | for (int i = 0; i < moveCount; i++) { 64 | int r = moves[i][0]; 65 | int c = moves[i][1]; 66 | 67 | // Different highlighting for empty squares vs capture squares 68 | if (board[r][c] == ' ') { 69 | boardDriver->setSquareLED(r, c, 0, 0, 0, 50); // Soft white for moves 70 | } else { 71 | boardDriver->setSquareLED(r, c, 255, 0, 0, 50); // Red tint for captures 72 | } 73 | } 74 | boardDriver->showLEDs(); 75 | 76 | // Wait for piece placement - handle both normal moves and captures 77 | int targetRow = -1, targetCol = -1; 78 | bool piecePlaced = false; 79 | bool captureInProgress = false; 80 | 81 | // Wait for a piece placement on any square 82 | while (!piecePlaced) { 83 | boardDriver->readSensors(); 84 | 85 | // First check if the original piece was placed back 86 | if (boardDriver->getSensorState(row, col)) { 87 | targetRow = row; 88 | targetCol = col; 89 | piecePlaced = true; 90 | break; 91 | } 92 | 93 | // Then check all squares for a regular move or capture initiation 94 | for (int r2 = 0; r2 < 8; r2++) { 95 | for (int c2 = 0; c2 < 8; c2++) { 96 | // Skip the original square which was already checked 97 | if (r2 == row && c2 == col) continue; 98 | 99 | // Check if this would be a legal move 100 | bool isLegalMove = false; 101 | for (int i = 0; i < moveCount; i++) { 102 | if (moves[i][0] == r2 && moves[i][1] == c2) { 103 | isLegalMove = true; 104 | break; 105 | } 106 | } 107 | 108 | // If not a legal move, no need to check further 109 | if (!isLegalMove) continue; 110 | 111 | // For capture moves: detect when the target piece is removed 112 | if (board[r2][c2] != ' ' && !boardDriver->getSensorState(r2, c2) && boardDriver->getSensorPrev(r2, c2)) { 113 | Serial.print("Capture initiated at "); 114 | Serial.print((char)('a' + c2)); 115 | Serial.println(r2 + 1); 116 | 117 | // Store the target square and wait for the capturing piece to be placed there 118 | targetRow = r2; 119 | targetCol = c2; 120 | captureInProgress = true; 121 | 122 | // Flash the capture square to indicate waiting for piece placement 123 | boardDriver->setSquareLED(r2, c2, 255, 0, 0, 100); 124 | boardDriver->showLEDs(); 125 | 126 | // Wait for the capturing piece to be placed 127 | bool capturePiecePlaced = false; 128 | while (!capturePiecePlaced) { 129 | boardDriver->readSensors(); 130 | if (boardDriver->getSensorState(r2, c2)) { 131 | capturePiecePlaced = true; 132 | piecePlaced = true; 133 | break; 134 | } 135 | delay(50); 136 | } 137 | break; 138 | } 139 | 140 | // For normal non-capture moves: detect when a piece is placed on an empty square 141 | else if (board[r2][c2] == ' ' && boardDriver->getSensorState(r2, c2) && !boardDriver->getSensorPrev(r2, c2)) { 142 | targetRow = r2; 143 | targetCol = c2; 144 | piecePlaced = true; 145 | break; 146 | } 147 | } 148 | if (piecePlaced || captureInProgress) break; 149 | } 150 | 151 | delay(50); 152 | } 153 | 154 | // Check if piece is replaced in the original spot 155 | if (targetRow == row && targetCol == col) { 156 | Serial.println("Piece replaced in original spot"); 157 | // Blink once to confirm 158 | boardDriver->setSquareLED(row, col, 0, 0, 0, 255); 159 | boardDriver->showLEDs(); 160 | delay(200); 161 | boardDriver->setSquareLED(row, col, 0, 0, 0, 100); 162 | boardDriver->showLEDs(); 163 | 164 | // Clear all LED effects 165 | boardDriver->clearAllLEDs(); 166 | 167 | continue; // Skip to next iteration 168 | } 169 | 170 | // Check if move is legal 171 | bool legalMove = false; 172 | bool isCapture = false; 173 | for (int i = 0; i < moveCount; i++) { 174 | if (moves[i][0] == targetRow && moves[i][1] == targetCol) { 175 | legalMove = true; 176 | // Check if this is a capture move 177 | if (board[targetRow][targetCol] != ' ') { 178 | isCapture = true; 179 | } 180 | break; 181 | } 182 | } 183 | 184 | if (legalMove) { 185 | Serial.print("Legal move to "); 186 | Serial.print((char)('a' + targetCol)); 187 | Serial.println(targetRow + 1); 188 | 189 | // Play capture animation if needed 190 | if (board[targetRow][targetCol] != ' ') { 191 | Serial.println("Performing capture animation"); 192 | boardDriver->captureAnimation(); 193 | } 194 | 195 | // Process the move 196 | processMove(row, col, targetRow, targetCol, piece); 197 | 198 | // Check for pawn promotion 199 | checkForPromotion(targetRow, targetCol, piece); 200 | 201 | // Confirmation: Double blink destination square 202 | for (int blink = 0; blink < 2; blink++) { 203 | boardDriver->setSquareLED(targetRow, targetCol, 0, 0, 0, 255); 204 | boardDriver->showLEDs(); 205 | delay(200); 206 | boardDriver->setSquareLED(targetRow, targetCol, 0, 0, 0, 50); 207 | boardDriver->showLEDs(); 208 | delay(200); 209 | } 210 | } else { 211 | Serial.println("Illegal move, reverting"); 212 | } 213 | 214 | // Clear any remaining LED effects 215 | boardDriver->clearAllLEDs(); 216 | } 217 | } 218 | } 219 | 220 | // Update previous sensor state 221 | boardDriver->updateSensorPrev(); 222 | } 223 | 224 | void ChessMoves::initializeBoard() { 225 | for (int row = 0; row < 8; row++) { 226 | for (int col = 0; col < 8; col++) { 227 | board[row][col] = INITIAL_BOARD[row][col]; 228 | } 229 | } 230 | } 231 | 232 | void ChessMoves::waitForBoardSetup() { 233 | Serial.println("Waiting for pieces to be placed..."); 234 | while (!boardDriver->checkInitialBoard(INITIAL_BOARD)) { 235 | boardDriver->updateSetupDisplay(INITIAL_BOARD); 236 | boardDriver->printBoardState(INITIAL_BOARD); 237 | delay(500); 238 | } 239 | } 240 | 241 | void ChessMoves::processMove(int fromRow, int fromCol, int toRow, int toCol, char piece) { 242 | // Update board state 243 | board[toRow][toCol] = piece; 244 | board[fromRow][fromCol] = ' '; 245 | } 246 | 247 | void ChessMoves::checkForPromotion(int targetRow, int targetCol, char piece) { 248 | if (chessEngine->isPawnPromotion(piece, targetRow)) { 249 | char promotedPiece = chessEngine->getPromotedPiece(piece); 250 | 251 | Serial.print((piece == 'P' ? "White" : "Black")); 252 | Serial.print(" pawn promoted to Queen at "); 253 | Serial.print((char)('a' + targetCol)); 254 | Serial.println((piece == 'P' ? "8" : "1")); 255 | 256 | // Play promotion animation 257 | boardDriver->promotionAnimation(targetCol); 258 | 259 | // Promote to queen in board state 260 | board[targetRow][targetCol] = promotedPiece; 261 | 262 | // Handle the promotion process 263 | handlePromotion(targetRow, targetCol, piece); 264 | } 265 | } 266 | 267 | void ChessMoves::handlePromotion(int targetRow, int targetCol, char piece) { 268 | Serial.println("Please replace the pawn with a queen piece"); 269 | 270 | // First wait for the pawn to be removed 271 | while (boardDriver->getSensorState(targetRow, targetCol)) { 272 | // Blink the square to indicate action needed 273 | boardDriver->setSquareLED(targetRow, targetCol, 255, 215, 0, 50); 274 | boardDriver->showLEDs(); 275 | delay(250); 276 | boardDriver->setSquareLED(targetRow, targetCol, 0, 0, 0, 0); 277 | boardDriver->showLEDs(); 278 | delay(250); 279 | 280 | // Read sensors 281 | boardDriver->readSensors(); 282 | } 283 | 284 | Serial.println("Pawn removed, please place a queen"); 285 | 286 | // Then wait for the queen to be placed 287 | while (!boardDriver->getSensorState(targetRow, targetCol)) { 288 | // Blink the square to indicate action needed 289 | boardDriver->setSquareLED(targetRow, targetCol, 255, 215, 0, 50); 290 | boardDriver->showLEDs(); 291 | delay(250); 292 | boardDriver->setSquareLED(targetRow, targetCol, 0, 0, 0, 0); 293 | boardDriver->showLEDs(); 294 | delay(250); 295 | 296 | // Read sensors 297 | boardDriver->readSensors(); 298 | } 299 | 300 | Serial.println("Queen placed, promotion complete"); 301 | 302 | // Final confirmation blink 303 | for (int i = 0; i < 3; i++) { 304 | boardDriver->setSquareLED(targetRow, targetCol, 255, 215, 0, 50); 305 | boardDriver->showLEDs(); 306 | delay(100); 307 | boardDriver->setSquareLED(targetRow, targetCol, 0, 0, 0, 0); 308 | boardDriver->showLEDs(); 309 | delay(100); 310 | } 311 | } 312 | 313 | bool ChessMoves::isActive() { 314 | return true; // Simple implementation for now 315 | } 316 | 317 | void ChessMoves::reset() { 318 | boardDriver->clearAllLEDs(); 319 | initializeBoard(); 320 | } -------------------------------------------------------------------------------- /wifi_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "wifi_manager.h" 2 | #include 3 | 4 | WiFiManager::WiFiManager() : server(AP_PORT) { 5 | apMode = true; 6 | clientConnected = false; 7 | wifiSSID = ""; 8 | wifiPassword = ""; 9 | lichessToken = ""; 10 | gameMode = "None"; 11 | startupType = "WiFi"; 12 | } 13 | 14 | void WiFiManager::begin() { 15 | Serial.println("!!! WIFI MANAGER BEGIN FUNCTION CALLED !!!"); 16 | Serial.println("=== Starting OpenChess WiFi Manager ==="); 17 | Serial.println("Debug: WiFi Manager begin() called"); 18 | 19 | // Check if WiFi is available 20 | Serial.println("Debug: Checking WiFi module..."); 21 | 22 | // Try to get WiFi status - this often fails on incompatible boards 23 | Serial.println("Debug: Attempting to get WiFi status..."); 24 | int initialStatus = WiFi.status(); 25 | Serial.print("Debug: Initial WiFi status: "); 26 | Serial.println(initialStatus); 27 | 28 | // Initialize WiFi module 29 | Serial.println("Debug: Checking for WiFi module presence..."); 30 | if (initialStatus == WL_NO_MODULE) { 31 | Serial.println("ERROR: WiFi module not detected!"); 32 | Serial.println("Board type: Arduino Nano RP2040 - WiFi not supported with WiFiNINA"); 33 | Serial.println("This is expected behavior for RP2040 boards."); 34 | Serial.println("Use physical board selectors for game mode selection."); 35 | return; 36 | } 37 | 38 | Serial.println("Debug: WiFi module appears to be present"); 39 | 40 | Serial.println("Debug: WiFi module detected"); 41 | 42 | // Check firmware version 43 | String fv = WiFi.firmwareVersion(); 44 | Serial.print("Debug: WiFi firmware version: "); 45 | Serial.println(fv); 46 | 47 | // Start Access Point 48 | Serial.print("Debug: Creating Access Point with SSID: "); 49 | Serial.println(AP_SSID); 50 | Serial.print("Debug: Using password: "); 51 | Serial.println(AP_PASSWORD); 52 | 53 | Serial.println("Debug: Calling WiFi.beginAP()..."); 54 | 55 | // First, try without channel specification (like Arduino example) 56 | Serial.println("Debug: Attempting AP creation without channel..."); 57 | int status = WiFi.beginAP(AP_SSID, AP_PASSWORD); 58 | 59 | if (status != WL_AP_LISTENING) { 60 | Serial.println("Debug: First attempt failed, trying with channel 6..."); 61 | status = WiFi.beginAP(AP_SSID, AP_PASSWORD, 6); 62 | } 63 | 64 | Serial.print("Debug: WiFi.beginAP() returned: "); 65 | Serial.println(status); 66 | 67 | // Print detailed status explanation 68 | Serial.print("Debug: Status meaning: "); 69 | switch(status) { 70 | case WL_IDLE_STATUS: Serial.println("WL_IDLE_STATUS (0) - Temporary status"); break; 71 | case WL_NO_SSID_AVAIL: Serial.println("WL_NO_SSID_AVAIL (1) - No SSID available"); break; 72 | case WL_SCAN_COMPLETED: Serial.println("WL_SCAN_COMPLETED (2) - Scan completed"); break; 73 | case WL_CONNECTED: Serial.println("WL_CONNECTED (3) - Connected to network"); break; 74 | case WL_CONNECT_FAILED: Serial.println("WL_CONNECT_FAILED (4) - Connection failed"); break; 75 | case WL_CONNECTION_LOST: Serial.println("WL_CONNECTION_LOST (5) - Connection lost"); break; 76 | case WL_DISCONNECTED: Serial.println("WL_DISCONNECTED (6) - Disconnected"); break; 77 | case WL_AP_LISTENING: Serial.println("WL_AP_LISTENING (7) - AP listening (SUCCESS!)"); break; 78 | case WL_AP_CONNECTED: Serial.println("WL_AP_CONNECTED (8) - AP connected"); break; 79 | case WL_AP_FAILED: Serial.println("WL_AP_FAILED (9) - AP failed"); break; 80 | default: Serial.print("UNKNOWN STATUS ("); Serial.print(status); Serial.println(")"); break; 81 | } 82 | 83 | if (status != WL_AP_LISTENING) { 84 | Serial.println("ERROR: Failed to create Access Point!"); 85 | Serial.println("Expected WL_AP_LISTENING (7), but got different status"); 86 | return; 87 | } 88 | 89 | Serial.println("Debug: Access Point creation initiated"); 90 | 91 | // Wait for AP to start and check status 92 | Serial.println("Debug: Waiting for AP to start..."); 93 | for (int i = 0; i < 10; i++) { 94 | delay(1000); 95 | status = WiFi.status(); 96 | Serial.print("Debug: WiFi status check "); 97 | Serial.print(i + 1); 98 | Serial.print("/10 - Status: "); 99 | Serial.println(status); 100 | 101 | if (status == WL_AP_LISTENING) { 102 | Serial.println("Debug: AP is now listening!"); 103 | break; 104 | } 105 | } 106 | 107 | // Print AP information and verify it's actually working 108 | IPAddress ip = WiFi.localIP(); 109 | Serial.println("=== WiFi Access Point Information ==="); 110 | Serial.print("SSID: "); 111 | Serial.println(WiFi.SSID()); // Get actual SSID from WiFi module 112 | Serial.print("Expected SSID: "); 113 | Serial.println(AP_SSID); 114 | Serial.print("Password: "); 115 | Serial.println(AP_PASSWORD); 116 | Serial.print("IP Address: "); 117 | Serial.println(ip); 118 | Serial.print("Web Interface: http://"); 119 | Serial.println(ip); 120 | 121 | // Additional diagnostic information 122 | Serial.print("WiFi Status: "); 123 | Serial.println(WiFi.status()); 124 | Serial.print("WiFi Mode: "); 125 | // Note: WiFiNINA might not have explicit AP mode check 126 | Serial.println("Access Point Mode"); 127 | 128 | // Verify IP is valid 129 | if (ip == IPAddress(0, 0, 0, 0)) { 130 | Serial.println("WARNING: IP address is 0.0.0.0 - AP might not be working!"); 131 | } else { 132 | Serial.println("IP address looks valid"); 133 | } 134 | 135 | Serial.println("====================================="); 136 | 137 | // Start the web server 138 | Serial.println("Debug: Starting web server..."); 139 | server.begin(); 140 | Serial.println("Debug: Web server started on port 80"); 141 | Serial.println("WiFi Manager initialization complete!"); 142 | } 143 | 144 | void WiFiManager::handleClient() { 145 | WiFiClient client = server.available(); 146 | 147 | if (client) { 148 | clientConnected = true; 149 | Serial.println("New client connected"); 150 | 151 | String request = ""; 152 | bool currentLineIsBlank = true; 153 | bool readingBody = false; 154 | String body = ""; 155 | 156 | while (client.connected()) { 157 | if (client.available()) { 158 | char c = client.read(); 159 | 160 | if (!readingBody) { 161 | request += c; 162 | 163 | if (c == '\n' && currentLineIsBlank) { 164 | // Headers ended, now reading body if POST 165 | if (request.indexOf("POST") >= 0) { 166 | readingBody = true; 167 | } else { 168 | break; // GET request, no body 169 | } 170 | } 171 | 172 | if (c == '\n') { 173 | currentLineIsBlank = true; 174 | } else if (c != '\r') { 175 | currentLineIsBlank = false; 176 | } 177 | } else { 178 | // Reading POST body 179 | body += c; 180 | if (body.length() > 1000) break; // Prevent overflow 181 | } 182 | } 183 | } 184 | 185 | // Handle the request 186 | if (request.indexOf("GET / ") >= 0) { 187 | // Main configuration page 188 | String webpage = generateWebPage(); 189 | sendResponse(client, webpage); 190 | } 191 | else if (request.indexOf("GET /game") >= 0) { 192 | // Game selection page 193 | String gameSelectionPage = generateGameSelectionPage(); 194 | sendResponse(client, gameSelectionPage); 195 | } 196 | else if (request.indexOf("POST /submit") >= 0) { 197 | // Configuration form submission 198 | parseFormData(body); 199 | String response = ""; 200 | response += "

Configuration Saved!

"; 201 | response += "

WiFi SSID: " + wifiSSID + "

"; 202 | response += "

Game Mode: " + gameMode + "

"; 203 | response += "

Startup Type: " + startupType + "

"; 204 | response += "

Go to Game Selection

"; 205 | response += ""; 206 | sendResponse(client, response); 207 | } 208 | else if (request.indexOf("POST /gameselect") >= 0) { 209 | // Game selection submission 210 | handleGameSelection(client, body); 211 | } 212 | else { 213 | // 404 Not Found 214 | String response = ""; 215 | response += "

404 - Page Not Found

"; 216 | response += "

Back to Home

"; 217 | response += ""; 218 | sendResponse(client, response, "text/html"); 219 | } 220 | 221 | delay(10); 222 | client.stop(); 223 | Serial.println("Client disconnected"); 224 | clientConnected = false; 225 | } 226 | } 227 | 228 | String WiFiManager::generateWebPage() { 229 | String html = ""; 230 | html += ""; 231 | html += ""; 232 | html += ""; 233 | html += ""; 234 | html += "OPENCHESSBOARD CONFIGURATION"; 235 | html += ""; 246 | html += ""; 247 | html += ""; 248 | html += "
"; 249 | html += "

OPENCHESSBOARD CONFIGURATION

"; 250 | html += "
"; 251 | 252 | html += "
"; 253 | html += ""; 254 | html += ""; 255 | html += "
"; 256 | 257 | html += "
"; 258 | html += ""; 259 | html += ""; 260 | html += "
"; 261 | 262 | html += "
"; 263 | html += ""; 264 | html += ""; 265 | html += "
"; 266 | 267 | html += "
"; 268 | html += ""; 269 | html += ""; 294 | html += ""; 305 | html += "Game Selection Interface"; 306 | html += "
"; 307 | html += "

Configure your OpenChess board settings and WiFi connection.

"; 308 | html += "
"; 309 | html += "
"; 310 | html += ""; 311 | html += ""; 312 | 313 | return html; 314 | } 315 | 316 | String WiFiManager::generateGameSelectionPage() { 317 | String html = ""; 318 | html += ""; 319 | html += ""; 320 | html += ""; 321 | html += ""; 322 | html += "OPENCHESSBOARD GAME SELECTION"; 323 | html += ""; 340 | html += ""; 341 | html += ""; 342 | html += "
"; 343 | html += "

GAME SELECTION

"; 344 | html += "
"; 345 | 346 | html += "
"; 347 | html += "

Chess Moves

"; 348 | html += "

Full chess game with move validation and animations

"; 349 | html += "Available"; 350 | html += "
"; 351 | 352 | html += "
"; 353 | html += "

Game Mode 2

"; 354 | html += "

Future game mode placeholder

"; 355 | html += "Coming Soon"; 356 | html += "
"; 357 | 358 | html += "
"; 359 | html += "

Game Mode 3

"; 360 | html += "

Future game mode placeholder

"; 361 | html += "Coming Soon"; 362 | html += "
"; 363 | 364 | html += "
"; 365 | html += "

Sensor Test

"; 366 | html += "

Test and calibrate board sensors

"; 367 | html += "Available"; 368 | html += "
"; 369 | 370 | html += "
"; 371 | html += "Back to Configuration"; 372 | html += "
"; 373 | 374 | html += ""; 384 | html += ""; 385 | html += ""; 386 | 387 | return html; 388 | } 389 | 390 | void WiFiManager::handleGameSelection(WiFiClient& client, String body) { 391 | // Parse game mode selection 392 | int modeStart = body.indexOf("gamemode="); 393 | if (modeStart >= 0) { 394 | int modeEnd = body.indexOf("&", modeStart); 395 | if (modeEnd < 0) modeEnd = body.length(); 396 | 397 | String selectedMode = body.substring(modeStart + 9, modeEnd); 398 | int mode = selectedMode.toInt(); 399 | 400 | Serial.print("Game mode selected via web: "); 401 | Serial.println(mode); 402 | 403 | // Store the selected game mode (you'll access this from main code) 404 | gameMode = String(mode); 405 | 406 | String response = R"({"status":"success","message":"Game mode selected","mode":)" + String(mode) + "}"; 407 | sendResponse(client, response, "application/json"); 408 | } 409 | } 410 | 411 | void WiFiManager::sendResponse(WiFiClient& client, String content, String contentType) { 412 | client.println("HTTP/1.1 200 OK"); 413 | client.println("Content-Type: " + contentType); 414 | client.println("Connection: close"); 415 | client.println(); 416 | client.println(content); 417 | } 418 | 419 | void WiFiManager::parseFormData(String data) { 420 | // Parse URL-encoded form data 421 | int ssidStart = data.indexOf("ssid="); 422 | if (ssidStart >= 0) { 423 | int ssidEnd = data.indexOf("&", ssidStart); 424 | if (ssidEnd < 0) ssidEnd = data.length(); 425 | wifiSSID = data.substring(ssidStart + 5, ssidEnd); 426 | wifiSSID.replace("+", " "); 427 | } 428 | 429 | int passStart = data.indexOf("password="); 430 | if (passStart >= 0) { 431 | int passEnd = data.indexOf("&", passStart); 432 | if (passEnd < 0) passEnd = data.length(); 433 | wifiPassword = data.substring(passStart + 9, passEnd); 434 | } 435 | 436 | int tokenStart = data.indexOf("token="); 437 | if (tokenStart >= 0) { 438 | int tokenEnd = data.indexOf("&", tokenStart); 439 | if (tokenEnd < 0) tokenEnd = data.length(); 440 | lichessToken = data.substring(tokenStart + 6, tokenEnd); 441 | } 442 | 443 | int gameModeStart = data.indexOf("gameMode="); 444 | if (gameModeStart >= 0) { 445 | int gameModeEnd = data.indexOf("&", gameModeStart); 446 | if (gameModeEnd < 0) gameModeEnd = data.length(); 447 | gameMode = data.substring(gameModeStart + 9, gameModeEnd); 448 | gameMode.replace("+", " "); 449 | } 450 | 451 | int startupStart = data.indexOf("startupType="); 452 | if (startupStart >= 0) { 453 | int startupEnd = data.indexOf("&", startupStart); 454 | if (startupEnd < 0) startupEnd = data.length(); 455 | startupType = data.substring(startupStart + 12, startupEnd); 456 | } 457 | 458 | Serial.println("Configuration updated:"); 459 | Serial.println("SSID: " + wifiSSID); 460 | Serial.println("Game Mode: " + gameMode); 461 | Serial.println("Startup Type: " + startupType); 462 | } 463 | 464 | bool WiFiManager::isClientConnected() { 465 | return clientConnected; 466 | } 467 | 468 | int WiFiManager::getSelectedGameMode() { 469 | return gameMode.toInt(); 470 | } 471 | 472 | void WiFiManager::resetGameSelection() { 473 | gameMode = "0"; 474 | } -------------------------------------------------------------------------------- /Chess/Chess.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // --------------------------- 5 | // NeoPixel Setup 6 | // --------------------------- 7 | #define LED_PIN 17 // Pin for NeoPixels 8 | #define NUM_ROWS 8 9 | #define NUM_COLS 8 10 | #define LED_COUNT (NUM_ROWS * NUM_COLS) 11 | #define BRIGHTNESS 100 12 | 13 | Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800); 14 | 15 | // --------------------------- 16 | // Shift Register (74HC594) Pins 17 | // --------------------------- 18 | #define SER_PIN 2 // Serial data input (74HC594 pin 14) 19 | #define SRCLK_PIN 3 // Shift register clock (pin 11) 20 | #define RCLK_PIN 4 // Latch clock (pin 12) 21 | // (Pin 13 (OE) must be tied HIGH and Pin 10 (SRCLR) tied HIGH if unused) 22 | 23 | // --------------------------- 24 | // Column Input Pins (D6..D13) 25 | // --------------------------- 26 | int colPins[NUM_COLS] = {6, 7, 8, 9, 10, 11, 12, 13}; 27 | 28 | // --------------------------- 29 | // Row Patterns (LSB-first for shift register) 30 | // --------------------------- 31 | byte rowPatterns[8] = { 32 | 0x01, // row 0 33 | 0x02, // row 1 34 | 0x04, // row 2 35 | 0x08, // row 3 36 | 0x10, // row 4 37 | 0x20, // row 5 38 | 0x40, // row 6 39 | 0x80 // row 7 40 | }; 41 | 42 | // --------------------------- 43 | // Global Variables and Board Setup 44 | // --------------------------- 45 | 46 | // sensorState[row][col]: true if a magnet (piece) is detected at board square (row, col) 47 | // Note: row 0 is the top row and row 7 the bottom row as read directly from sensors. 48 | bool sensorState[8][8]; 49 | bool sensorPrev[8][8]; 50 | 51 | // Expected initial configuration (as printed in the grid) 52 | char initialBoard[8][8] = { 53 | {'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R'}, // row 0 (rank 1) 54 | {'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'}, // row 1 (rank 2) 55 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 2 (rank 3) 56 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 3 (rank 4) 57 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 4 (rank 5) 58 | {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}, // row 5 (rank 6) 59 | {'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'}, // row 6 (rank 7) 60 | {'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'} // row 7 (rank 8) 61 | }; 62 | 63 | // Internal board state for gameplay (initialized from initialBoard) 64 | char board[8][8]; 65 | 66 | // For promotion animation 67 | const uint32_t PROMOTION_COLOR = strip.Color(255, 215, 0, 50); // Gold with white 68 | 69 | // --------------------------- 70 | // Function Prototypes 71 | // --------------------------- 72 | void loadShiftRegister(byte data); 73 | void readSensors(); 74 | bool checkInitialBoard(); 75 | void updateSetupDisplay(); 76 | void printBoardState(); 77 | void fireworkAnimation(); 78 | void blinkSquare(int row, int col); 79 | void captureAnimation(); 80 | void promotionAnimation(int col); 81 | void checkForPromotion(int targetRow, int targetCol, char piece); 82 | void getPossibleMoves(int row, int col, int &moveCount, int moves[][2]); 83 | 84 | // --------------------------- 85 | // SETUP 86 | // --------------------------- 87 | void setup() { 88 | Serial.begin(9600); 89 | 90 | // Initialize NeoPixel strip 91 | strip.begin(); 92 | strip.show(); // turn off all pixels 93 | strip.setBrightness(BRIGHTNESS); 94 | 95 | // Setup shift register control pins 96 | pinMode(SER_PIN, OUTPUT); 97 | pinMode(SRCLK_PIN, OUTPUT); 98 | pinMode(RCLK_PIN, OUTPUT); 99 | 100 | // Setup column input pins 101 | for (int c = 0; c < NUM_COLS; c++) { 102 | pinMode(colPins[c], INPUT); 103 | } 104 | 105 | // Initialize shift register to no row active 106 | loadShiftRegister(0x00); 107 | 108 | // Copy expected configuration into our board state 109 | for (int row = 0; row < 8; row++){ 110 | for (int col = 0; col < 8; col++){ 111 | board[row][col] = initialBoard[row][col]; 112 | } 113 | } 114 | 115 | // Wait for board setup: repeatedly check sensors and update display until every expected piece is in place. 116 | Serial.println("Waiting for pieces to be placed..."); 117 | while(!checkInitialBoard()){ 118 | updateSetupDisplay(); 119 | printBoardState(); 120 | delay(500); 121 | } 122 | 123 | Serial.println("Ready to start"); 124 | fireworkAnimation(); 125 | 126 | // Initialize sensorPrev for move detection 127 | readSensors(); 128 | for (int row = 0; row < 8; row++){ 129 | for (int col = 0; col < 8; col++){ 130 | sensorPrev[row][col] = sensorState[row][col]; 131 | } 132 | } 133 | } 134 | 135 | // --------------------------- 136 | // MAIN LOOP 137 | // --------------------------- 138 | void loop() { 139 | readSensors(); 140 | 141 | // Look for a piece pickup 142 | for (int row = 0; row < 8; row++) { 143 | for (int col = 0; col < 8; col++) { 144 | if (sensorPrev[row][col] && !sensorState[row][col]) { 145 | char piece = board[row][col]; 146 | 147 | // Skip empty squares 148 | if (piece == ' ') continue; 149 | 150 | Serial.print("Piece lifted from "); 151 | Serial.print((char)('a' + col)); 152 | Serial.println(row + 1); 153 | 154 | // Generate possible moves 155 | int moveCount = 0; 156 | int moves[20][2]; // up to 20 moves 157 | getPossibleMoves(row, col, moveCount, moves); 158 | 159 | // Light up current square and possible move squares 160 | int currentPixelIndex = col * NUM_COLS + (7 - row); 161 | strip.setPixelColor(currentPixelIndex, strip.Color(0, 0, 0, 100)); // Dimmer, but solid 162 | 163 | // Highlight possible move squares (including captures) 164 | for (int i = 0; i < moveCount; i++) { 165 | int r = moves[i][0]; 166 | int c = moves[i][1]; 167 | int movePixelIndex = c * NUM_COLS + (7 - r); 168 | 169 | // Different highlighting for empty squares vs capture squares 170 | if (board[r][c] == ' ') { 171 | strip.setPixelColor(movePixelIndex, strip.Color(0, 0, 0, 50)); // Soft white for moves 172 | } else { 173 | strip.setPixelColor(movePixelIndex, strip.Color(255, 0, 0, 50)); // Red tint for captures 174 | } 175 | } 176 | strip.show(); 177 | 178 | // Wait for piece placement - handle both normal moves and captures 179 | int targetRow = -1, targetCol = -1; 180 | bool piecePlaced = false; 181 | bool captureInProgress = false; 182 | 183 | // Wait for a piece placement on any square 184 | while (!piecePlaced) { 185 | readSensors(); 186 | 187 | // First check if the original piece was placed back 188 | if (sensorState[row][col]) { 189 | targetRow = row; 190 | targetCol = col; 191 | piecePlaced = true; 192 | break; 193 | } 194 | 195 | // Then check all squares for a regular move or capture initiation 196 | for (int r2 = 0; r2 < 8; r2++) { 197 | for (int c2 = 0; c2 < 8; c2++) { 198 | // Skip the original square which was already checked 199 | if (r2 == row && c2 == col) continue; 200 | 201 | // Check if this would be a legal move 202 | bool isLegalMove = false; 203 | for (int i = 0; i < moveCount; i++) { 204 | if (moves[i][0] == r2 && moves[i][1] == c2) { 205 | isLegalMove = true; 206 | break; 207 | } 208 | } 209 | 210 | // If not a legal move, no need to check further 211 | if (!isLegalMove) continue; 212 | 213 | // For capture moves: detect when the target piece is removed 214 | if (board[r2][c2] != ' ' && !sensorState[r2][c2] && sensorPrev[r2][c2]) { 215 | Serial.print("Capture initiated at "); 216 | Serial.print((char)('a' + c2)); 217 | Serial.println(r2 + 1); 218 | 219 | // Store the target square and wait for the capturing piece to be placed there 220 | targetRow = r2; 221 | targetCol = c2; 222 | captureInProgress = true; 223 | 224 | // Flash the capture square to indicate waiting for piece placement 225 | int capturePixelIndex = c2 * NUM_COLS + (7 - r2); 226 | strip.setPixelColor(capturePixelIndex, strip.Color(255, 0, 0, 100)); 227 | strip.show(); 228 | 229 | // Wait for the capturing piece to be placed 230 | bool capturePiecePlaced = false; 231 | while (!capturePiecePlaced) { 232 | readSensors(); 233 | if (sensorState[r2][c2]) { 234 | capturePiecePlaced = true; 235 | piecePlaced = true; 236 | break; 237 | } 238 | delay(50); 239 | } 240 | break; 241 | } 242 | 243 | // For normal non-capture moves: detect when a piece is placed on an empty square 244 | else if (board[r2][c2] == ' ' && sensorState[r2][c2] && !sensorPrev[r2][c2]) { 245 | targetRow = r2; 246 | targetCol = c2; 247 | piecePlaced = true; 248 | break; 249 | } 250 | } 251 | if (piecePlaced || captureInProgress) break; 252 | } 253 | 254 | delay(50); 255 | } 256 | 257 | // Check if piece is replaced in the original spot 258 | if (targetRow == row && targetCol == col) { 259 | Serial.println("Piece replaced in original spot"); 260 | // Blink once to confirm 261 | strip.setPixelColor(currentPixelIndex, strip.Color(0, 0, 0, 255)); 262 | strip.show(); 263 | delay(200); 264 | strip.setPixelColor(currentPixelIndex, strip.Color(0, 0, 0, 100)); 265 | strip.show(); 266 | 267 | // Clear all LED effects 268 | for (int i = 0; i < LED_COUNT; i++) { 269 | strip.setPixelColor(i, 0); 270 | } 271 | strip.show(); 272 | 273 | continue; // Skip to next iteration 274 | } 275 | 276 | // Check if move is legal 277 | bool legalMove = false; 278 | bool isCapture = false; 279 | for (int i = 0; i < moveCount; i++) { 280 | if (moves[i][0] == targetRow && moves[i][1] == targetCol) { 281 | legalMove = true; 282 | // Check if this is a capture move 283 | if (board[targetRow][targetCol] != ' ') { 284 | isCapture = true; 285 | } 286 | break; 287 | } 288 | } 289 | 290 | if (legalMove) { 291 | Serial.print("Legal move to "); 292 | Serial.print((char)('a' + targetCol)); 293 | Serial.println(targetRow + 1); 294 | 295 | // Play capture animation if needed 296 | if (board[targetRow][targetCol] != ' ') { 297 | Serial.println("Performing capture animation"); 298 | captureAnimation(); 299 | } 300 | 301 | // Update board state 302 | board[targetRow][targetCol] = piece; 303 | board[row][col] = ' '; 304 | 305 | // Check for pawn promotion 306 | checkForPromotion(targetRow, targetCol, piece); 307 | 308 | // Confirmation: Double blink destination square 309 | int newPixelIndex = targetCol * NUM_COLS + (7 - targetRow); 310 | for (int blink = 0; blink < 2; blink++) { 311 | strip.setPixelColor(newPixelIndex, strip.Color(0, 0, 0, 255)); 312 | strip.show(); 313 | delay(200); 314 | strip.setPixelColor(newPixelIndex, strip.Color(0, 0, 0, 50)); 315 | strip.show(); 316 | delay(200); 317 | } 318 | } else { 319 | Serial.println("Illegal move, reverting"); 320 | } 321 | 322 | // Clear any remaining LED effects 323 | for (int i = 0; i < LED_COUNT; i++) { 324 | strip.setPixelColor(i, 0); 325 | } 326 | strip.show(); 327 | } 328 | } 329 | } 330 | 331 | // Update previous sensor state 332 | for (int row = 0; row < 8; row++) { 333 | for (int col = 0; col < 8; col++) { 334 | sensorPrev[row][col] = sensorState[row][col]; 335 | } 336 | } 337 | 338 | delay(100); 339 | } 340 | 341 | // --------------------------- 342 | // FUNCTIONS 343 | // --------------------------- 344 | 345 | // loadShiftRegister: Shifts out 8 bits (LSB-first) to the 74HC594. 346 | void loadShiftRegister(byte data) { 347 | digitalWrite(RCLK_PIN, LOW); 348 | for (int i = 0; i < 8; i++) { 349 | bool bitVal = (data & (1 << i)) != 0; 350 | digitalWrite(SER_PIN, bitVal ? HIGH : LOW); 351 | digitalWrite(SRCLK_PIN, HIGH); 352 | delayMicroseconds(10); 353 | digitalWrite(SRCLK_PIN, LOW); 354 | delayMicroseconds(10); 355 | } 356 | digitalWrite(RCLK_PIN, HIGH); 357 | delayMicroseconds(10); 358 | digitalWrite(RCLK_PIN, LOW); 359 | } 360 | 361 | // readSensors: Activates each row and reads column sensors. 362 | // Stores sensorState[row][col] directly (no inversion). 363 | void readSensors(){ 364 | for (int row = 0; row < 8; row++){ 365 | loadShiftRegister(rowPatterns[row]); 366 | delayMicroseconds(100); 367 | for (int col = 0; col < NUM_COLS; col++){ 368 | int sensorVal = digitalRead(colPins[col]); 369 | sensorState[row][col] = (sensorVal == LOW); 370 | } 371 | } 372 | loadShiftRegister(0x00); 373 | } 374 | 375 | // checkInitialBoard: Returns true only when every expected piece (non-space in initialBoard) is detected. 376 | bool checkInitialBoard(){ 377 | readSensors(); 378 | bool allPresent = true; 379 | for (int row = 0; row < 8; row++){ 380 | for (int col = 0; col < 8; col++){ 381 | if(initialBoard[row][col] != ' ' && !sensorState[row][col]){ 382 | allPresent = false; 383 | } 384 | } 385 | } 386 | return allPresent; 387 | } 388 | 389 | // updateSetupDisplay: Lights up each square (white) if a piece is detected. 390 | void updateSetupDisplay(){ 391 | for (int row = 0; row < 8; row++){ 392 | for (int col = 0; col < 8; col++){ 393 | int pixelIndex = col * NUM_COLS + (7 - row); 394 | if(initialBoard[row][col] != ' ' && sensorState[row][col]){ 395 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 396 | } else { 397 | strip.setPixelColor(pixelIndex, 0); 398 | } 399 | } 400 | } 401 | strip.show(); 402 | } 403 | 404 | // printBoardState: Prints the board grid to Serial; shows the expected piece if detected, or '-' if missing. 405 | void printBoardState(){ 406 | Serial.println("Current Board:"); 407 | for (int row = 0; row < 8; row++){ 408 | Serial.print("{ "); 409 | for (int col = 0; col < 8; col++){ 410 | char displayChar = ' '; 411 | if(initialBoard[row][col] != ' '){ 412 | displayChar = sensorState[row][col] ? initialBoard[row][col] : '-'; 413 | } 414 | Serial.print("'"); 415 | Serial.print(displayChar); 416 | Serial.print("'"); 417 | if(col < 7) Serial.print(", "); 418 | } 419 | Serial.println(" },"); 420 | } 421 | Serial.println(); 422 | } 423 | 424 | // fireworkAnimation: A simple firework animation from the center out, contracting back, then out again. 425 | void fireworkAnimation(){ 426 | float centerX = 3.5; 427 | float centerY = 3.5; 428 | // Expansion phase: 429 | for (float radius = 0; radius < 6; radius += 0.5) { 430 | for (int row = 0; row < 8; row++){ 431 | for (int col = 0; col < 8; col++){ 432 | float dx = col - centerX; 433 | float dy = row - centerY; 434 | float dist = sqrt(dx * dx + dy * dy); 435 | int pixelIndex = col * NUM_COLS + (7 - row); 436 | if (fabs(dist - radius) < 0.5) 437 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 438 | else 439 | strip.setPixelColor(pixelIndex, 0); 440 | } 441 | } 442 | strip.show(); 443 | delay(100); 444 | } 445 | // Contraction phase: 446 | for (float radius = 6; radius > 0; radius -= 0.5) { 447 | for (int row = 0; row < 8; row++){ 448 | for (int col = 0; col < 8; col++){ 449 | float dx = col - centerX; 450 | float dy = row - centerY; 451 | float dist = sqrt(dx * dx + dy * dy); 452 | int pixelIndex = col * NUM_COLS + (7 - row); 453 | if (fabs(dist - radius) < 0.5) 454 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 455 | else 456 | strip.setPixelColor(pixelIndex, 0); 457 | } 458 | } 459 | strip.show(); 460 | delay(100); 461 | } 462 | // Second expansion phase: 463 | for (float radius = 0; radius < 6; radius += 0.5) { 464 | for (int row = 0; row < 8; row++){ 465 | for (int col = 0; col < 8; col++){ 466 | float dx = col - centerX; 467 | float dy = row - centerY; 468 | float dist = sqrt(dx * dx + dy * dy); 469 | int pixelIndex = col * NUM_COLS + (7 - row); 470 | if (fabs(dist - radius) < 0.5) 471 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 472 | else 473 | strip.setPixelColor(pixelIndex, 0); 474 | } 475 | } 476 | strip.show(); 477 | delay(100); 478 | } 479 | // Clear all LEDs 480 | for (int i = 0; i < LED_COUNT; i++){ 481 | strip.setPixelColor(i, 0); 482 | } 483 | strip.show(); 484 | } 485 | 486 | // blinkSquare: Blinks the LED at the given square (row, col) three times. 487 | void blinkSquare(int row, int col){ 488 | int pixelIndex = col * NUM_COLS + (7 - row); 489 | for (int i = 0; i < 3; i++){ 490 | strip.setPixelColor(pixelIndex, strip.Color(0, 0, 0, 255)); 491 | strip.show(); 492 | delay(200); 493 | strip.setPixelColor(pixelIndex, 0); 494 | strip.show(); 495 | delay(200); 496 | } 497 | } 498 | 499 | void captureAnimation() { 500 | float centerX = 3.5; 501 | float centerY = 3.5; 502 | 503 | // Pulsing outward animation 504 | for (int pulse = 0; pulse < 3; pulse++) { 505 | for (int row = 0; row < 8; row++) { 506 | for (int col = 0; col < 8; col++) { 507 | float dx = col - centerX; 508 | float dy = row - centerY; 509 | float dist = sqrt(dx * dx + dy * dy); 510 | 511 | // Create a pulsing effect around the center 512 | float pulseWidth = 1.5 + pulse; 513 | int pixelIndex = col * NUM_COLS + (7 - row); 514 | 515 | if (dist >= pulseWidth - 0.5 && dist <= pulseWidth + 0.5) { 516 | // Alternate between red and orange for capture effect 517 | uint32_t color = (pulse % 2 == 0) 518 | ? strip.Color(255, 0, 0, 0) // Red 519 | : strip.Color(255, 165, 0, 0); // Orange 520 | strip.setPixelColor(pixelIndex, color); 521 | } else { 522 | strip.setPixelColor(pixelIndex, 0); 523 | } 524 | } 525 | } 526 | strip.show(); 527 | delay(150); 528 | } 529 | 530 | // Clear LEDs 531 | for (int i = 0; i < LED_COUNT; i++) { 532 | strip.setPixelColor(i, 0); 533 | } 534 | strip.show(); 535 | } 536 | 537 | void promotionAnimation(int col) { 538 | // Column-based waterfall animation 539 | for (int step = 0; step < 16; step++) { 540 | for (int row = 0; row < 8; row++) { 541 | int pixelIndex = col * NUM_COLS + (7 - row); 542 | 543 | // Create a golden wave moving up and down the column 544 | if ((step + row) % 8 < 4) { 545 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 546 | } else { 547 | strip.setPixelColor(pixelIndex, 0); 548 | } 549 | } 550 | strip.show(); 551 | delay(100); 552 | } 553 | 554 | // Clear the animation 555 | for (int row = 0; row < 8; row++) { 556 | int pixelIndex = col * NUM_COLS + (7 - row); 557 | strip.setPixelColor(pixelIndex, 0); 558 | } 559 | strip.show(); 560 | } 561 | 562 | // checkForPromotion: Checks if a pawn has reached the promotion rank and promotes it to a queen 563 | void checkForPromotion(int targetRow, int targetCol, char piece) { 564 | // Check if a white pawn (P) reached the 8th rank (row 7) 565 | if (piece == 'P' && targetRow == 7) { 566 | Serial.print("White pawn promoted to Queen at "); 567 | Serial.print((char)('a' + targetCol)); 568 | Serial.println("8"); 569 | 570 | // Play promotion animation 571 | promotionAnimation(targetCol); 572 | 573 | // Promote to queen in board state 574 | board[targetRow][targetCol] = 'Q'; 575 | 576 | // Wait for the player to replace the pawn with a queen 577 | Serial.println("Please replace the pawn with a queen piece"); 578 | 579 | // Flash the promotion square until the piece is removed and replaced 580 | int pixelIndex = targetCol * NUM_COLS + (7 - targetRow); 581 | 582 | // First wait for the pawn to be removed 583 | while (sensorState[targetRow][targetCol]) { 584 | // Blink the square to indicate action needed 585 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 586 | strip.show(); 587 | delay(250); 588 | strip.setPixelColor(pixelIndex, 0); 589 | strip.show(); 590 | delay(250); 591 | 592 | // Read sensors 593 | readSensors(); 594 | } 595 | 596 | Serial.println("Pawn removed, please place a queen"); 597 | 598 | // Then wait for the queen to be placed 599 | while (!sensorState[targetRow][targetCol]) { 600 | // Blink the square to indicate action needed 601 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 602 | strip.show(); 603 | delay(250); 604 | strip.setPixelColor(pixelIndex, 0); 605 | strip.show(); 606 | delay(250); 607 | 608 | // Read sensors 609 | readSensors(); 610 | } 611 | 612 | Serial.println("Queen placed, promotion complete"); 613 | 614 | // Final confirmation blink 615 | for (int i = 0; i < 3; i++) { 616 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 617 | strip.show(); 618 | delay(100); 619 | strip.setPixelColor(pixelIndex, 0); 620 | strip.show(); 621 | delay(100); 622 | } 623 | } 624 | // Check if a black pawn (p) reached the 1st rank (row 0) 625 | else if (piece == 'p' && targetRow == 0) { 626 | Serial.print("Black pawn promoted to Queen at "); 627 | Serial.print((char)('a' + targetCol)); 628 | Serial.println("1"); 629 | 630 | // Play promotion animation 631 | promotionAnimation(targetCol); 632 | 633 | // Promote to queen in board state 634 | board[targetRow][targetCol] = 'q'; 635 | 636 | // Wait for the player to replace the pawn with a queen 637 | Serial.println("Please replace the pawn with a queen piece"); 638 | 639 | // Flash the promotion square until the piece is removed and replaced 640 | int pixelIndex = targetCol * NUM_COLS + (7 - targetRow); 641 | 642 | // First wait for the pawn to be removed 643 | while (sensorState[targetRow][targetCol]) { 644 | // Blink the square to indicate action needed 645 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 646 | strip.show(); 647 | delay(250); 648 | strip.setPixelColor(pixelIndex, 0); 649 | strip.show(); 650 | delay(250); 651 | 652 | // Read sensors 653 | readSensors(); 654 | } 655 | 656 | Serial.println("Pawn removed, please place a queen"); 657 | 658 | // Then wait for the queen to be placed 659 | while (!sensorState[targetRow][targetCol]) { 660 | // Blink the square to indicate action needed 661 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 662 | strip.show(); 663 | delay(250); 664 | strip.setPixelColor(pixelIndex, 0); 665 | strip.show(); 666 | delay(250); 667 | 668 | // Read sensors 669 | readSensors(); 670 | } 671 | 672 | Serial.println("Queen placed, promotion complete"); 673 | 674 | // Final confirmation blink 675 | for (int i = 0; i < 3; i++) { 676 | strip.setPixelColor(pixelIndex, PROMOTION_COLOR); 677 | strip.show(); 678 | delay(100); 679 | strip.setPixelColor(pixelIndex, 0); 680 | strip.show(); 681 | delay(100); 682 | } 683 | } 684 | } 685 | // (This does not implement full chess rules.) 686 | void getPossibleMoves(int row, int col, int &moveCount, int moves[][2]) { 687 | moveCount = 0; 688 | char piece = board[row][col]; 689 | char pieceColor = (piece >= 'a' && piece <= 'z') ? 'b' : 'w'; 690 | 691 | // Convert to uppercase for easier comparison 692 | piece = (piece >= 'a' && piece <= 'z') ? piece - 32 : piece; 693 | 694 | switch(piece) { 695 | case 'P': { // Pawn movement 696 | int direction = (pieceColor == 'w') ? 1 : -1; 697 | 698 | // One square forward 699 | if (row + direction >= 0 && row + direction < 8 && board[row + direction][col] == ' ') { 700 | moves[moveCount][0] = row + direction; 701 | moves[moveCount][1] = col; 702 | moveCount++; 703 | 704 | // Initial two-square move 705 | if ((pieceColor == 'w' && row == 1) || (pieceColor == 'b' && row == 6)) { 706 | if (board[row + 2*direction][col] == ' ') { 707 | moves[moveCount][0] = row + 2*direction; 708 | moves[moveCount][1] = col; 709 | moveCount++; 710 | } 711 | } 712 | } 713 | 714 | // Diagonal captures 715 | int captureColumns[] = {col-1, col+1}; 716 | for (int i = 0; i < 2; i++) { 717 | if (captureColumns[i] >= 0 && captureColumns[i] < 8) { 718 | char targetPiece = board[row + direction][captureColumns[i]]; 719 | if (targetPiece != ' ' && 720 | ((pieceColor == 'w' && targetPiece >= 'a' && targetPiece <= 'z') || 721 | (pieceColor == 'b' && targetPiece >= 'A' && targetPiece <= 'Z'))) { 722 | moves[moveCount][0] = row + direction; 723 | moves[moveCount][1] = captureColumns[i]; 724 | moveCount++; 725 | } 726 | } 727 | } 728 | break; 729 | } 730 | 731 | case 'R': { // Rook movement 732 | int directions[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}}; 733 | for (int d = 0; d < 4; d++) { 734 | for (int step = 1; step < 8; step++) { 735 | int newRow = row + step * directions[d][0]; 736 | int newCol = col + step * directions[d][1]; 737 | 738 | if (newRow < 0 || newRow >= 8 || newCol < 0 || newCol >= 8) break; 739 | 740 | char targetPiece = board[newRow][newCol]; 741 | if (targetPiece == ' ') { 742 | moves[moveCount][0] = newRow; 743 | moves[moveCount][1] = newCol; 744 | moveCount++; 745 | } else { 746 | // Check if it's a capturable piece 747 | if ((pieceColor == 'w' && targetPiece >= 'a' && targetPiece <= 'z') || 748 | (pieceColor == 'b' && targetPiece >= 'A' && targetPiece <= 'Z')) { 749 | moves[moveCount][0] = newRow; 750 | moves[moveCount][1] = newCol; 751 | moveCount++; 752 | } 753 | break; 754 | } 755 | } 756 | } 757 | break; 758 | } 759 | 760 | case 'N': { // Knight movement 761 | int knightMoves[8][2] = {{2,1}, {1,2}, {-1,2}, {-2,1}, 762 | {-2,-1}, {-1,-2}, {1,-2}, {2,-1}}; 763 | for (int i = 0; i < 8; i++) { 764 | int newRow = row + knightMoves[i][0]; 765 | int newCol = col + knightMoves[i][1]; 766 | 767 | if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8) { 768 | char targetPiece = board[newRow][newCol]; 769 | if (targetPiece == ' ' || 770 | ((pieceColor == 'w' && targetPiece >= 'a' && targetPiece <= 'z') || 771 | (pieceColor == 'b' && targetPiece >= 'A' && targetPiece <= 'Z'))) { 772 | moves[moveCount][0] = newRow; 773 | moves[moveCount][1] = newCol; 774 | moveCount++; 775 | } 776 | } 777 | } 778 | break; 779 | } 780 | 781 | case 'B': { // Bishop movement 782 | int directions[4][2] = {{1,1}, {1,-1}, {-1,1}, {-1,-1}}; 783 | for (int d = 0; d < 4; d++) { 784 | for (int step = 1; step < 8; step++) { 785 | int newRow = row + step * directions[d][0]; 786 | int newCol = col + step * directions[d][1]; 787 | 788 | if (newRow < 0 || newRow >= 8 || newCol < 0 || newCol >= 8) break; 789 | 790 | char targetPiece = board[newRow][newCol]; 791 | if (targetPiece == ' ') { 792 | moves[moveCount][0] = newRow; 793 | moves[moveCount][1] = newCol; 794 | moveCount++; 795 | } else { 796 | // Check if it's a capturable piece 797 | if ((pieceColor == 'w' && targetPiece >= 'a' && targetPiece <= 'z') || 798 | (pieceColor == 'b' && targetPiece >= 'A' && targetPiece <= 'Z')) { 799 | moves[moveCount][0] = newRow; 800 | moves[moveCount][1] = newCol; 801 | moveCount++; 802 | } 803 | break; 804 | } 805 | } 806 | } 807 | break; 808 | } 809 | 810 | case 'Q': { // Queen movement (combination of rook and bishop) 811 | int directions[8][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}, 812 | {1,1}, {1,-1}, {-1,1}, {-1,-1}}; 813 | for (int d = 0; d < 8; d++) { 814 | for (int step = 1; step < 8; step++) { 815 | int newRow = row + step * directions[d][0]; 816 | int newCol = col + step * directions[d][1]; 817 | 818 | if (newRow < 0 || newRow >= 8 || newCol < 0 || newCol >= 8) break; 819 | 820 | char targetPiece = board[newRow][newCol]; 821 | if (targetPiece == ' ') { 822 | moves[moveCount][0] = newRow; 823 | moves[moveCount][1] = newCol; 824 | moveCount++; 825 | } else { 826 | // Check if it's a capturable piece 827 | if ((pieceColor == 'w' && targetPiece >= 'a' && targetPiece <= 'z') || 828 | (pieceColor == 'b' && targetPiece >= 'A' && targetPiece <= 'Z')) { 829 | moves[moveCount][0] = newRow; 830 | moves[moveCount][1] = newCol; 831 | moveCount++; 832 | } 833 | break; 834 | } 835 | } 836 | } 837 | break; 838 | } 839 | 840 | case 'K': { // King movement with simple range limitation 841 | int kingMoves[8][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}, 842 | {1,1}, {1,-1}, {-1,1}, {-1,-1}}; 843 | for (int i = 0; i < 8; i++) { 844 | int newRow = row + kingMoves[i][0]; 845 | int newCol = col + kingMoves[i][1]; 846 | 847 | if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 8) { 848 | char targetPiece = board[newRow][newCol]; 849 | if (targetPiece == ' ' || 850 | ((pieceColor == 'w' && targetPiece >= 'a' && targetPiece <= 'z') || 851 | (pieceColor == 'b' && targetPiece >= 'A' && targetPiece <= 'Z'))) { 852 | moves[moveCount][0] = newRow; 853 | moves[moveCount][1] = newCol; 854 | moveCount++; 855 | } 856 | } 857 | } 858 | break; 859 | } 860 | } 861 | } -------------------------------------------------------------------------------- /chess_bot.cpp: -------------------------------------------------------------------------------- 1 | #include "chess_bot.h" 2 | #include 3 | 4 | ChessBot::ChessBot(BoardDriver* boardDriver, ChessEngine* chessEngine, BotDifficulty diff) { 5 | _boardDriver = boardDriver; 6 | _chessEngine = chessEngine; 7 | difficulty = diff; 8 | 9 | // Set difficulty settings 10 | switch(difficulty) { 11 | case BOT_EASY: settings = StockfishSettings::easy(); break; 12 | case BOT_MEDIUM: settings = StockfishSettings::medium(); break; 13 | case BOT_HARD: settings = StockfishSettings::hard(); break; 14 | case BOT_EXPERT: settings = StockfishSettings::expert(); break; 15 | } 16 | 17 | isWhiteTurn = true; 18 | gameStarted = false; 19 | botThinking = false; 20 | wifiConnected = false; 21 | } 22 | 23 | void ChessBot::begin() { 24 | Serial.println("=== Starting Chess Bot Mode ==="); 25 | Serial.print("Bot Difficulty: "); 26 | 27 | switch(difficulty) { 28 | case BOT_EASY: Serial.println("Easy (Depth 6)"); break; 29 | case BOT_MEDIUM: Serial.println("Medium (Depth 10)"); break; 30 | case BOT_HARD: Serial.println("Hard (Depth 14)"); break; 31 | case BOT_EXPERT: Serial.println("Expert (Depth 16)"); break; 32 | } 33 | 34 | _boardDriver->clearAllLEDs(); 35 | _boardDriver->showLEDs(); 36 | 37 | // Connect to WiFi 38 | Serial.println("Connecting to WiFi..."); 39 | showConnectionStatus(); 40 | 41 | if (connectToWiFi()) { 42 | Serial.println("WiFi connected! Bot mode ready."); 43 | wifiConnected = true; 44 | 45 | // Show success animation 46 | for (int i = 0; i < 3; i++) { 47 | _boardDriver->clearAllLEDs(); 48 | _boardDriver->showLEDs(); 49 | delay(200); 50 | 51 | // Light up entire board green briefly 52 | for (int row = 0; row < 8; row++) { 53 | for (int col = 0; col < 8; col++) { 54 | _boardDriver->setSquareLED(row, col, 0, 255, 0); // Green 55 | } 56 | } 57 | _boardDriver->showLEDs(); 58 | delay(200); 59 | } 60 | 61 | initializeBoard(); 62 | waitForBoardSetup(); 63 | } else { 64 | Serial.println("Failed to connect to WiFi. Bot mode unavailable."); 65 | wifiConnected = false; 66 | 67 | // Show error animation (red flashing) 68 | for (int i = 0; i < 5; i++) { 69 | _boardDriver->clearAllLEDs(); 70 | _boardDriver->showLEDs(); 71 | delay(300); 72 | 73 | // Light up entire board red briefly 74 | for (int row = 0; row < 8; row++) { 75 | for (int col = 0; col < 8; col++) { 76 | _boardDriver->setSquareLED(row, col, 255, 0, 0); // Red 77 | } 78 | } 79 | _boardDriver->showLEDs(); 80 | delay(300); 81 | } 82 | 83 | _boardDriver->clearAllLEDs(); 84 | _boardDriver->showLEDs(); 85 | } 86 | } 87 | 88 | void ChessBot::update() { 89 | if (!wifiConnected) { 90 | return; // No WiFi, can't play against bot 91 | } 92 | 93 | if (!gameStarted) { 94 | return; // Waiting for initial setup 95 | } 96 | 97 | if (botThinking) { 98 | showBotThinking(); 99 | return; 100 | } 101 | 102 | _boardDriver->readSensors(); 103 | 104 | // Detect piece movements (player's turn - White pieces only) 105 | if (isWhiteTurn) { 106 | static unsigned long lastTurnDebug = 0; 107 | if (millis() - lastTurnDebug > 5000) { 108 | Serial.println("Your turn! Move a WHITE piece (uppercase letters)"); 109 | lastTurnDebug = millis(); 110 | } 111 | // Look for piece pickups and placements 112 | static int selectedRow = -1, selectedCol = -1; 113 | static bool piecePickedUp = false; 114 | 115 | // Check for piece pickup 116 | if (!piecePickedUp) { 117 | for (int row = 0; row < 8; row++) { 118 | for (int col = 0; col < 8; col++) { 119 | if (!_boardDriver->getSensorState(row, col) && _boardDriver->getSensorPrev(row, col)) { 120 | // Check what piece was picked up 121 | char piece = board[row][col]; 122 | 123 | if (piece != ' ') { 124 | // Player should only be able to move White pieces (uppercase) 125 | if (piece >= 'A' && piece <= 'Z') { 126 | selectedRow = row; 127 | selectedCol = col; 128 | piecePickedUp = true; 129 | 130 | Serial.print("Player picked up WHITE piece '"); 131 | Serial.print(board[row][col]); 132 | Serial.print("' at "); 133 | Serial.print((char)('a' + col)); 134 | Serial.print(8 - row); 135 | Serial.print(" (array position "); 136 | Serial.print(row); 137 | Serial.print(","); 138 | Serial.print(col); 139 | Serial.println(")"); 140 | 141 | // Show selected square 142 | _boardDriver->setSquareLED(row, col, 255, 0, 0); // Red 143 | 144 | // Show possible moves 145 | int moveCount = 0; 146 | int moves[27][2]; 147 | _chessEngine->getPossibleMoves(board, row, col, moveCount, moves); 148 | 149 | for (int i = 0; i < moveCount; i++) { 150 | _boardDriver->setSquareLED(moves[i][0], moves[i][1], 255, 255, 255); // White 151 | } 152 | _boardDriver->showLEDs(); 153 | break; 154 | } else { 155 | // Player tried to pick up a Black piece - not allowed! 156 | Serial.print("ERROR: You tried to pick up BLACK piece '"); 157 | Serial.print(piece); 158 | Serial.print("' at "); 159 | Serial.print((char)('a' + col)); 160 | Serial.print(8 - row); 161 | Serial.println(". You can only move WHITE pieces!"); 162 | 163 | // Flash red to indicate error 164 | _boardDriver->blinkSquare(row, col, 3); 165 | } 166 | } 167 | } 168 | } 169 | if (piecePickedUp) break; 170 | } 171 | } 172 | 173 | // Check for piece placement 174 | if (piecePickedUp) { 175 | for (int row = 0; row < 8; row++) { 176 | for (int col = 0; col < 8; col++) { 177 | if (_boardDriver->getSensorState(row, col) && !_boardDriver->getSensorPrev(row, col)) { 178 | // Check if piece was returned to its original position 179 | if (row == selectedRow && col == selectedCol) { 180 | // Piece returned to original position - cancel selection 181 | Serial.println("Piece returned to original position. Selection cancelled."); 182 | piecePickedUp = false; 183 | selectedRow = selectedCol = -1; 184 | 185 | // Clear all indicators 186 | _boardDriver->clearAllLEDs(); 187 | _boardDriver->showLEDs(); 188 | break; 189 | } 190 | 191 | // Piece placed somewhere else - validate move 192 | int moveCount = 0; 193 | int moves[27][2]; 194 | _chessEngine->getPossibleMoves(board, selectedRow, selectedCol, moveCount, moves); 195 | 196 | bool validMove = false; 197 | for (int i = 0; i < moveCount; i++) { 198 | if (moves[i][0] == row && moves[i][1] == col) { 199 | validMove = true; 200 | break; 201 | } 202 | } 203 | 204 | if (validMove) { 205 | char piece = board[selectedRow][selectedCol]; 206 | 207 | // Complete LED animations BEFORE API request 208 | processPlayerMove(selectedRow, selectedCol, row, col, piece); 209 | 210 | // Flash confirmation on destination square for player move 211 | confirmSquareCompletion(row, col); 212 | 213 | piecePickedUp = false; 214 | selectedRow = selectedCol = -1; 215 | 216 | // Switch to bot's turn 217 | isWhiteTurn = false; 218 | botThinking = true; 219 | 220 | Serial.println("Player move completed. Bot thinking..."); 221 | 222 | // Start bot move calculation 223 | makeBotMove(); 224 | } else { 225 | Serial.println("Invalid move! Please try again."); 226 | _boardDriver->blinkSquare(row, col, 3); // Blink red for invalid move 227 | 228 | // Restore move indicators - piece is still selected 229 | _boardDriver->clearAllLEDs(); 230 | 231 | // Show selected square again 232 | _boardDriver->setSquareLED(selectedRow, selectedCol, 255, 0, 0); // Red 233 | 234 | // Show possible moves again 235 | int moveCount = 0; 236 | int moves[27][2]; 237 | _chessEngine->getPossibleMoves(board, selectedRow, selectedCol, moveCount, moves); 238 | 239 | for (int i = 0; i < moveCount; i++) { 240 | _boardDriver->setSquareLED(moves[i][0], moves[i][1], 255, 255, 255); // White 241 | } 242 | _boardDriver->showLEDs(); 243 | 244 | Serial.println("Piece is still selected. Place it on a valid move or return it to its original position."); 245 | } 246 | break; 247 | } 248 | } 249 | } 250 | } 251 | } 252 | 253 | _boardDriver->updateSensorPrev(); 254 | } 255 | 256 | bool ChessBot::connectToWiFi() { 257 | // Check for WiFi module 258 | if (WiFi.status() == WL_NO_MODULE) { 259 | Serial.println("WiFi module not found!"); 260 | return false; 261 | } 262 | 263 | Serial.print("Attempting to connect to SSID: "); 264 | Serial.println(SECRET_SSID); 265 | 266 | int attempts = 0; 267 | while (WiFi.status() != WL_CONNECTED && attempts < 10) { 268 | WiFi.begin(SECRET_SSID, SECRET_PASS); 269 | delay(5000); 270 | attempts++; 271 | 272 | Serial.print("Connection attempt "); 273 | Serial.print(attempts); 274 | Serial.print("/10 - Status: "); 275 | Serial.println(WiFi.status()); 276 | } 277 | 278 | if (WiFi.status() == WL_CONNECTED) { 279 | Serial.println("Connected to WiFi!"); 280 | Serial.print("IP address: "); 281 | Serial.println(WiFi.localIP()); 282 | return true; 283 | } else { 284 | Serial.println("Failed to connect to WiFi"); 285 | return false; 286 | } 287 | } 288 | 289 | String ChessBot::makeStockfishRequest(String fen) { 290 | WiFiSSLClient client; 291 | 292 | Serial.println("Making API request to Stockfish..."); 293 | Serial.print("FEN: "); 294 | Serial.println(fen); 295 | 296 | if (client.connect(STOCKFISH_API_URL, STOCKFISH_API_PORT)) { 297 | // URL encode the FEN string 298 | String encodedFen = urlEncode(fen); 299 | 300 | // Make HTTP GET request 301 | String url = String(STOCKFISH_API_PATH) + "?fen=" + encodedFen + "&depth=" + String(settings.depth); 302 | 303 | Serial.print("Request URL: "); 304 | Serial.println(url); 305 | 306 | client.println("GET " + url + " HTTP/1.1"); 307 | client.println("Host: " + String(STOCKFISH_API_URL)); 308 | client.println("Connection: close"); 309 | client.println(); 310 | 311 | // Wait for response 312 | unsigned long startTime = millis(); 313 | while (client.connected() && (millis() - startTime < settings.timeoutMs)) { 314 | if (client.available()) { 315 | String response = client.readString(); 316 | client.stop(); 317 | 318 | // Debug: Print raw response 319 | Serial.println("=== RAW API RESPONSE ==="); 320 | Serial.println(response); 321 | Serial.println("=== END RAW RESPONSE ==="); 322 | 323 | return response; 324 | } 325 | delay(10); 326 | } 327 | 328 | client.stop(); 329 | Serial.println("API request timeout"); 330 | return ""; 331 | } else { 332 | Serial.println("Failed to connect to Stockfish API"); 333 | return ""; 334 | } 335 | } 336 | 337 | bool ChessBot::parseStockfishResponse(String response, String &bestMove) { 338 | // Find JSON content 339 | int jsonStart = response.indexOf("{"); 340 | if (jsonStart == -1) { 341 | Serial.println("No JSON found in response"); 342 | return false; 343 | } 344 | 345 | String json = response.substring(jsonStart); 346 | Serial.print("Extracted JSON: "); 347 | Serial.println(json); 348 | 349 | // Check if request was successful 350 | if (json.indexOf("\"success\":true") == -1) { 351 | Serial.println("API request was not successful"); 352 | return false; 353 | } 354 | 355 | // Parse bestmove field - format: "bestmove":"bestmove b7b6 ponder f3e5" 356 | int bestmoveStart = json.indexOf("\"bestmove\":\""); 357 | if (bestmoveStart == -1) { 358 | Serial.println("No bestmove found in response"); 359 | return false; 360 | } 361 | 362 | bestmoveStart += 12; // Skip "bestmove":" 363 | int bestmoveEnd = json.indexOf("\"", bestmoveStart); 364 | if (bestmoveEnd == -1) { 365 | Serial.println("Invalid bestmove format"); 366 | return false; 367 | } 368 | 369 | String fullMove = json.substring(bestmoveStart, bestmoveEnd); 370 | Serial.print("Full move string: "); 371 | Serial.println(fullMove); 372 | 373 | // Extract just the move part after "bestmove " and before " ponder" 374 | int moveStart = fullMove.indexOf("bestmove "); 375 | if (moveStart == -1) { 376 | Serial.println("No 'bestmove' prefix found"); 377 | return false; 378 | } 379 | 380 | moveStart += 9; // Skip "bestmove " 381 | int moveEnd = fullMove.indexOf(" ", moveStart); 382 | if (moveEnd == -1) { 383 | // No ponder part, take rest of string 384 | bestMove = fullMove.substring(moveStart); 385 | } else { 386 | bestMove = fullMove.substring(moveStart, moveEnd); 387 | } 388 | 389 | Serial.print("Parsed move: "); 390 | Serial.println(bestMove); 391 | 392 | return bestMove.length() >= 4; 393 | } 394 | 395 | void ChessBot::makeBotMove() { 396 | Serial.println("=== BOT MOVE CALCULATION ==="); 397 | Serial.print("Bot is playing as: "); 398 | Serial.println(isWhiteTurn ? "White" : "Black"); 399 | Serial.print("Current board state (FEN): "); 400 | 401 | String fen = boardToFEN(); 402 | String response = makeStockfishRequest(fen); 403 | 404 | if (response.length() > 0) { 405 | String bestMove; 406 | if (parseStockfishResponse(response, bestMove)) { 407 | int fromRow, fromCol, toRow, toCol; 408 | if (parseMove(bestMove, fromRow, fromCol, toRow, toCol)) { 409 | Serial.print("Bot move: "); 410 | Serial.println(bestMove); 411 | 412 | executeBotMove(fromRow, fromCol, toRow, toCol); 413 | 414 | // Switch back to player's turn 415 | isWhiteTurn = true; 416 | botThinking = false; 417 | 418 | Serial.println("Bot move completed. Your turn!"); 419 | } else { 420 | Serial.println("Failed to parse bot move"); 421 | botThinking = false; 422 | } 423 | } else { 424 | Serial.println("Failed to parse Stockfish response"); 425 | botThinking = false; 426 | } 427 | } else { 428 | Serial.println("No response from Stockfish API"); 429 | botThinking = false; 430 | } 431 | } 432 | 433 | String ChessBot::boardToFEN() { 434 | String fen = ""; 435 | 436 | // Board position - FEN expects rank 8 (black pieces) first, rank 1 (white pieces) last 437 | // Our array: row 0 = white pieces, row 7 = black pieces 438 | // So we need to reverse the order: start from row 7 and go to row 0 439 | for (int row = 7; row >= 0; row--) { 440 | int emptyCount = 0; 441 | for (int col = 0; col < 8; col++) { 442 | if (board[row][col] == ' ') { 443 | emptyCount++; 444 | } else { 445 | if (emptyCount > 0) { 446 | fen += String(emptyCount); 447 | emptyCount = 0; 448 | } 449 | fen += board[row][col]; 450 | } 451 | } 452 | if (emptyCount > 0) { 453 | fen += String(emptyCount); 454 | } 455 | if (row > 0) fen += "/"; 456 | } 457 | 458 | // Active color - when we call this, it's the bot's turn (Black) 459 | // But we need to tell Stockfish whose turn it actually is 460 | fen += isWhiteTurn ? " w" : " b"; 461 | 462 | Serial.print("Current turn in FEN: "); 463 | Serial.println(isWhiteTurn ? "White (w)" : "Black (b)"); 464 | Serial.print("Bot should be playing as: Black"); 465 | 466 | // Castling availability (simplified - assume all available initially) 467 | fen += " KQkq"; 468 | 469 | // En passant target square (simplified - assume none) 470 | fen += " -"; 471 | 472 | // Halfmove clock (simplified) 473 | fen += " 0"; 474 | 475 | // Fullmove number (simplified) 476 | fen += " 1"; 477 | 478 | Serial.print("Generated FEN: "); 479 | Serial.println(fen); 480 | 481 | return fen; 482 | } 483 | 484 | bool ChessBot::parseMove(String move, int &fromRow, int &fromCol, int &toRow, int &toCol) { 485 | if (move.length() < 4) return false; 486 | 487 | fromCol = move.charAt(0) - 'a'; 488 | fromRow = (move.charAt(1) - '0') - 1; // Convert 1-8 to 0-7 (1=row 0, 8=row 7) 489 | toCol = move.charAt(2) - 'a'; 490 | toRow = (move.charAt(3) - '0') - 1; // Convert 1-8 to 0-7 491 | 492 | // Debug coordinate conversion 493 | Serial.print("Move string: "); 494 | Serial.println(move); 495 | Serial.print("Parsed coordinates: ("); 496 | Serial.print(fromRow); 497 | Serial.print(","); 498 | Serial.print(fromCol); 499 | Serial.print(") to ("); 500 | Serial.print(toRow); 501 | Serial.print(","); 502 | Serial.print(toCol); 503 | Serial.println(")"); 504 | Serial.print("In chess notation: "); 505 | Serial.print((char)('a' + fromCol)); 506 | Serial.print(8 - fromRow); 507 | Serial.print(" to "); 508 | Serial.print((char)('a' + toCol)); 509 | Serial.print(8 - toRow); 510 | 511 | // Check for promotion 512 | if (move.length() >= 5) { 513 | char promotionPiece = move.charAt(4); 514 | Serial.print(" (promotes to "); 515 | Serial.print(promotionPiece); 516 | Serial.print(")"); 517 | } 518 | Serial.println(); 519 | 520 | return (fromRow >= 0 && fromRow < 8 && fromCol >= 0 && fromCol < 8 && 521 | toRow >= 0 && toRow < 8 && toCol >= 0 && toCol < 8); 522 | } 523 | 524 | void ChessBot::executeBotMove(int fromRow, int fromCol, int toRow, int toCol) { 525 | char piece = board[fromRow][fromCol]; 526 | char capturedPiece = board[toRow][toCol]; 527 | 528 | // Update board state 529 | board[toRow][toCol] = piece; 530 | board[fromRow][fromCol] = ' '; 531 | 532 | Serial.print("Bot wants to move piece from "); 533 | Serial.print((char)('a' + fromCol)); 534 | Serial.print(8 - fromRow); 535 | Serial.print(" to "); 536 | Serial.print((char)('a' + toCol)); 537 | Serial.println(8 - toRow); 538 | Serial.println("Please make this move on the physical board..."); 539 | 540 | // Show the move that needs to be made 541 | showBotMoveIndicator(fromRow, fromCol, toRow, toCol); 542 | 543 | // Wait for user to physically complete the bot's move 544 | waitForBotMoveCompletion(fromRow, fromCol, toRow, toCol); 545 | 546 | if (capturedPiece != ' ') { 547 | Serial.print("Piece captured: "); 548 | Serial.println(capturedPiece); 549 | _boardDriver->captureAnimation(); 550 | } 551 | 552 | // Flash confirmation on the destination square 553 | confirmSquareCompletion(toRow, toCol); 554 | 555 | Serial.println("Bot move completed. Your turn!"); 556 | } 557 | 558 | void ChessBot::showBotThinking() { 559 | static unsigned long lastUpdate = 0; 560 | static int thinkingStep = 0; 561 | 562 | if (millis() - lastUpdate > 500) { 563 | // Animated thinking indicator - pulse the corners 564 | _boardDriver->clearAllLEDs(); 565 | 566 | uint8_t brightness = (sin(thinkingStep * 0.3) + 1) * 127; 567 | 568 | _boardDriver->setSquareLED(0, 0, 0, 0, brightness); // Corner LEDs pulse blue 569 | _boardDriver->setSquareLED(0, 7, 0, 0, brightness); 570 | _boardDriver->setSquareLED(7, 0, 0, 0, brightness); 571 | _boardDriver->setSquareLED(7, 7, 0, 0, brightness); 572 | 573 | _boardDriver->showLEDs(); 574 | 575 | thinkingStep++; 576 | lastUpdate = millis(); 577 | } 578 | } 579 | 580 | void ChessBot::showConnectionStatus() { 581 | // Show WiFi connection attempt with animated LEDs 582 | for (int i = 0; i < 8; i++) { 583 | _boardDriver->setSquareLED(3, i, 0, 0, 255); // Blue row 584 | _boardDriver->showLEDs(); 585 | delay(200); 586 | } 587 | } 588 | 589 | void ChessBot::initializeBoard() { 590 | // Copy initial board state 591 | for (int row = 0; row < 8; row++) { 592 | for (int col = 0; col < 8; col++) { 593 | board[row][col] = INITIAL_BOARD[row][col]; 594 | } 595 | } 596 | } 597 | 598 | void ChessBot::waitForBoardSetup() { 599 | Serial.println("Please set up the chess board in starting position..."); 600 | 601 | while (!_boardDriver->checkInitialBoard(INITIAL_BOARD)) { 602 | _boardDriver->readSensors(); 603 | _boardDriver->updateSetupDisplay(INITIAL_BOARD); 604 | _boardDriver->showLEDs(); 605 | delay(100); 606 | } 607 | 608 | Serial.println("Board setup complete! Game starting..."); 609 | _boardDriver->fireworkAnimation(); 610 | gameStarted = true; 611 | 612 | // Show initial board state 613 | printCurrentBoard(); 614 | } 615 | 616 | void ChessBot::processPlayerMove(int fromRow, int fromCol, int toRow, int toCol, char piece) { 617 | char capturedPiece = board[toRow][toCol]; 618 | 619 | // Update board state 620 | board[toRow][toCol] = piece; 621 | board[fromRow][fromCol] = ' '; 622 | 623 | Serial.print("Player moved "); 624 | Serial.print(piece); 625 | Serial.print(" from "); 626 | Serial.print((char)('a' + fromCol)); 627 | Serial.print(8 - fromRow); 628 | Serial.print(" to "); 629 | Serial.print((char)('a' + toCol)); 630 | Serial.println(8 - toRow); 631 | 632 | if (capturedPiece != ' ') { 633 | Serial.print("Captured "); 634 | Serial.println(capturedPiece); 635 | _boardDriver->captureAnimation(); 636 | } 637 | 638 | // Check for pawn promotion 639 | if (_chessEngine->isPawnPromotion(piece, toRow)) { 640 | char promotedPiece = _chessEngine->getPromotedPiece(piece); 641 | board[toRow][toCol] = promotedPiece; 642 | Serial.print("Pawn promoted to "); 643 | Serial.println(promotedPiece); 644 | _boardDriver->promotionAnimation(toCol); 645 | } 646 | } 647 | 648 | String ChessBot::urlEncode(String str) { 649 | String encoded = ""; 650 | char c; 651 | char code0; 652 | char code1; 653 | 654 | for (int i = 0; i < str.length(); i++) { 655 | c = str.charAt(i); 656 | if (c == ' ') { 657 | encoded += "%20"; 658 | } else if (c == '/') { 659 | encoded += "%2F"; 660 | } else if (isalnum(c)) { 661 | encoded += c; 662 | } else { 663 | code1 = (c & 0xf) + '0'; 664 | if ((c & 0xf) > 9) { 665 | code1 = (c & 0xf) - 10 + 'A'; 666 | } 667 | c = (c >> 4) & 0xf; 668 | code0 = c + '0'; 669 | if (c > 9) { 670 | code0 = c - 10 + 'A'; 671 | } 672 | encoded += '%'; 673 | encoded += code0; 674 | encoded += code1; 675 | } 676 | } 677 | return encoded; 678 | } 679 | 680 | void ChessBot::showBotMoveIndicator(int fromRow, int fromCol, int toRow, int toCol) { 681 | // Clear all LEDs first 682 | _boardDriver->clearAllLEDs(); 683 | 684 | // Show source square flashing (where to pick up from) 685 | _boardDriver->setSquareLED(fromRow, fromCol, 255, 255, 255); // White flashing 686 | 687 | // Show destination square solid (where to place) 688 | _boardDriver->setSquareLED(toRow, toCol, 255, 255, 255); // White solid 689 | 690 | _boardDriver->showLEDs(); 691 | } 692 | 693 | void ChessBot::waitForBotMoveCompletion(int fromRow, int fromCol, int toRow, int toCol) { 694 | bool piecePickedUp = false; 695 | bool moveCompleted = false; 696 | static unsigned long lastBlink = 0; 697 | static bool blinkState = false; 698 | 699 | Serial.println("Waiting for you to complete the bot's move..."); 700 | 701 | while (!moveCompleted) { 702 | _boardDriver->readSensors(); 703 | 704 | // Blink the source square 705 | if (millis() - lastBlink > 500) { 706 | _boardDriver->clearAllLEDs(); 707 | if (blinkState && !piecePickedUp) { 708 | _boardDriver->setSquareLED(fromRow, fromCol, 255, 255, 255); // Flash source 709 | } 710 | _boardDriver->setSquareLED(toRow, toCol, 255, 255, 255); // Always show destination 711 | _boardDriver->showLEDs(); 712 | 713 | blinkState = !blinkState; 714 | lastBlink = millis(); 715 | } 716 | 717 | // Check if piece was picked up from source 718 | if (!piecePickedUp && !_boardDriver->getSensorState(fromRow, fromCol)) { 719 | piecePickedUp = true; 720 | Serial.println("Bot piece picked up, now place it on the destination..."); 721 | 722 | // Stop blinking source, just show destination 723 | _boardDriver->clearAllLEDs(); 724 | _boardDriver->setSquareLED(toRow, toCol, 255, 255, 255); 725 | _boardDriver->showLEDs(); 726 | } 727 | 728 | // Check if piece was placed on destination 729 | if (piecePickedUp && _boardDriver->getSensorState(toRow, toCol)) { 730 | moveCompleted = true; 731 | Serial.println("Bot move completed on physical board!"); 732 | } 733 | 734 | delay(50); 735 | _boardDriver->updateSensorPrev(); 736 | } 737 | } 738 | 739 | void ChessBot::confirmMoveCompletion() { 740 | // This will be called with specific square coordinates when we need them 741 | confirmSquareCompletion(-1, -1); // Default - no specific square 742 | } 743 | 744 | void ChessBot::confirmSquareCompletion(int row, int col) { 745 | if (row >= 0 && col >= 0) { 746 | // Flash specific square twice 747 | for (int flash = 0; flash < 2; flash++) { 748 | _boardDriver->setSquareLED(row, col, 0, 255, 0); // Green flash 749 | _boardDriver->showLEDs(); 750 | delay(150); 751 | 752 | _boardDriver->clearAllLEDs(); 753 | _boardDriver->showLEDs(); 754 | delay(150); 755 | } 756 | } else { 757 | // Flash entire board (fallback for when we don't have specific coords) 758 | for (int flash = 0; flash < 2; flash++) { 759 | for (int r = 0; r < 8; r++) { 760 | for (int c = 0; c < 8; c++) { 761 | _boardDriver->setSquareLED(r, c, 0, 255, 0); // Green flash 762 | } 763 | } 764 | _boardDriver->showLEDs(); 765 | delay(150); 766 | 767 | _boardDriver->clearAllLEDs(); 768 | _boardDriver->showLEDs(); 769 | delay(150); 770 | } 771 | } 772 | } 773 | 774 | void ChessBot::printCurrentBoard() { 775 | Serial.println("=== CURRENT BOARD STATE ==="); 776 | Serial.println(" a b c d e f g h"); 777 | for (int row = 0; row < 8; row++) { 778 | Serial.print(8 - row); 779 | Serial.print(" "); 780 | for (int col = 0; col < 8; col++) { 781 | char piece = board[row][col]; 782 | if (piece == ' ') { 783 | Serial.print(". "); 784 | } else { 785 | Serial.print(piece); 786 | Serial.print(" "); 787 | } 788 | } 789 | Serial.print(" "); 790 | Serial.println(8 - row); 791 | } 792 | Serial.println(" a b c d e f g h"); 793 | Serial.println("White pieces (uppercase): R N B Q K P"); 794 | Serial.println("Black pieces (lowercase): r n b q k p"); 795 | Serial.println("========================"); 796 | } 797 | 798 | void ChessBot::setDifficulty(BotDifficulty diff) { 799 | difficulty = diff; 800 | switch(difficulty) { 801 | case BOT_EASY: settings = StockfishSettings::easy(); break; 802 | case BOT_MEDIUM: settings = StockfishSettings::medium(); break; 803 | case BOT_HARD: settings = StockfishSettings::hard(); break; 804 | case BOT_EXPERT: settings = StockfishSettings::expert(); break; 805 | } 806 | 807 | Serial.print("Bot difficulty changed to: "); 808 | switch(difficulty) { 809 | case BOT_EASY: Serial.println("Easy"); break; 810 | case BOT_MEDIUM: Serial.println("Medium"); break; 811 | case BOT_HARD: Serial.println("Hard"); break; 812 | case BOT_EXPERT: Serial.println("Expert"); break; 813 | } 814 | } --------------------------------------------------------------------------------