ripred

Battleship_v3.ino

Aug 31st, 2025 (edited)
251
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 43.57 KB | Source Code | 0 0
  1. /**
  2.  * Battleship_v3.ino
  3.  * Two-Arduino Battleship over AltSoftSerial (ATmega328 / Arduino Nano).
  4.  *
  5.  * Wiring (Nano <-> Nano):
  6.  *   - Cross TX/RX:  D9 (TX)  ->  D8 (RX) on the other Nano
  7.  *                   D8 (RX)  ->  D9 (TX) on the other Nano
  8.  *   - Common GND
  9.  *
  10.  * For testing with Python host via USB, set TEST_WITH_PYTHON to 1 below,
  11.  * connect Python to Nano USB at 57600, no adapter needed, debug prints disabled, AI mode hardcoded.
  12.  *
  13.  * USB Serial (debug): 115200
  14.  * AltSoftSerial link: 57600
  15.  *
  16.  * Human mode: manual ship placement (e.g., "A1 E") and moves ("B4", "A10"),
  17.  * AI mode: automatic placement and hunt/target shooting.
  18.  *
  19.  * Author: Trent M. Wyatt ([email protected])
  20.  * Date: September, 2025
  21.  * License: MIT
  22.  *  
  23.  */
  24.  
  25. #define TEST_WITH_PYTHON 0  // Set to 1 for testing with Python via USB Serial as link
  26.  
  27. #if TEST_WITH_PYTHON
  28. #define DEBUG_PRINT 0
  29. #else
  30. #define DEBUG_PRINT 1
  31. #endif
  32.  
  33. #if !TEST_WITH_PYTHON
  34. #include <AltSoftSerial.h>
  35. #endif
  36.  
  37. #include <Arduino.h>
  38.  
  39. // ------------------------------ Constants ------------------------------------
  40.  
  41. // Grid size and cell count.
  42. enum : uint8_t { GRID_W = 10, GRID_H = 10, CELLS = GRID_W * GRID_H };
  43.  
  44. // Ship types and lengths.
  45. enum ship_type_t : uint8_t {
  46.     CARRIER = 0,
  47.     BATTLESHIP = 1,
  48.     CRUISER = 2,
  49.     SUBMARINE = 3,
  50.     DESTROYER = 4,
  51.     NUM_SHIP_TYPES = 5
  52. };
  53.  
  54. static const uint8_t SHIP_LEN[NUM_SHIP_TYPES] = { 5, 4, 3, 3, 2 };
  55. static const char * const SHIP_NAME[NUM_SHIP_TYPES] = {
  56.     "Carrier", "Battleship", "Cruiser", "Submarine", "Destroyer"
  57. };
  58.  
  59. // Directions and shot results.
  60. enum dir_t : uint8_t { NORTH = 0, SOUTH = 1, WEST = 2, EAST = 3 };
  61.  
  62. enum result_code_t : uint8_t {
  63.     RES_MISS   = 0,
  64.     RES_HIT    = 1,
  65.     RES_SUNK   = 2,
  66.     RES_REPEAT = 3,
  67.     RES_ERROR  = 255
  68. };
  69.  
  70. // Link protocol packet types.
  71. enum pkt_type_t : uint8_t {
  72.     PKT_HELLO       = 0x01,  // payload: 4b token
  73.     PKT_START       = 0x02,  // no payload (host->guest)
  74.     PKT_MOVE        = 0x10,  // payload: 1b idx (0..99)
  75.     PKT_RESULT      = 0x11,  // payload: 1b result_code
  76.     PKT_GAMEOVER    = 0x12,  // payload: 1b winner_id
  77.     PKT_PLACE_CHECK = 0x31,  // (unused here; tolerated if received)
  78.     PKT_PLACE_REPLY = 0x32,  // (unused here; tolerated if received)
  79.     PKT_PLACE_DONE  = 0x33   // no payload (peer finished placement)
  80. };
  81.  
  82. static const uint8_t PKT_SYNC = 0x42;
  83.  
  84. // Serial speeds and debug options.
  85. #define DEBUG_SERIAL_BAUD 115200
  86. #define LINK_BAUD         57600
  87. #define PRINT_BOARDS_EVERY_TURN 1
  88.  
  89. // Timeout for waiting in placement and handshake (ms)
  90. #define WAIT_TIMEOUT 30000
  91.  
  92. // ------------------------------ Utilities ------------------------------------
  93.  
  94. // Convert (x,y) to linear index.
  95. static inline uint8_t xy_to_idx(uint8_t x, uint8_t y) {
  96.     return (uint8_t)(y * GRID_W + x);
  97. }
  98.  
  99. // Extract x from linear index.
  100. static inline uint8_t idx_x(uint8_t idx) {
  101.     return (uint8_t)(idx % GRID_W);
  102. }
  103.  
  104. // Extract y from linear index.
  105. static inline uint8_t idx_y(uint8_t idx) {
  106.     return (uint8_t)(idx / GRID_W);
  107. }
  108.  
  109. // Coordinate pair.
  110. struct xy_t {
  111.     uint8_t x;
  112.     uint8_t y;
  113.  
  114.     // Return true if inside the board.
  115.     inline bool in_bounds() const {
  116.         return x < GRID_W && y < GRID_H;
  117.     }
  118. };
  119.  
  120. // Ship record.
  121. struct ship_t {
  122.     ship_type_t type;
  123.     uint8_t     len;
  124.     xy_t        start;
  125.     dir_t       dir;
  126.     uint8_t     hits;
  127.  
  128.     // Initialize to a particular ship type.
  129.     void reset(ship_type_t t) {
  130.         type  = t;
  131.         len   = SHIP_LEN[t];
  132.         start = { 0, 0 };
  133.         dir   = SOUTH;
  134.         hits  = 0;
  135.     }
  136. };
  137.  
  138. #if !TEST_WITH_PYTHON
  139. // One AltSoftSerial link shared by the sketch.
  140. AltSoftSerial link;
  141. #else
  142. #define link Serial
  143. #endif
  144.  
  145. // ------------------------------ Board ----------------------------------------
  146.  
  147. // Per-player state and helpers.
  148. struct board_t {
  149.     // 0xFF = water, else ship index [0..NUM_SHIP_TYPES-1].
  150.     uint8_t  my_grid[CELLS];
  151.  
  152.     // Incoming shot marks (0/1).
  153.     uint8_t  my_shots[CELLS];
  154.  
  155.     // Our view of opponent cells (0=unknown,1=miss,2=hit).
  156.     uint8_t  opp_view[CELLS];
  157.  
  158.     // Our tried moves (0/1).
  159.     uint8_t  my_moves[CELLS];
  160.  
  161.     // Our ships.
  162.     ship_t   my_ships[NUM_SHIP_TYPES];
  163.  
  164.     // Remaining unhit ship cells (17 at start).
  165.     uint8_t  ships_remaining_cells;
  166.  
  167.     // Reset board to empty with full fleet unhit.
  168.     void clear() {
  169.         for (uint16_t i = 0; i < CELLS; ++i) {
  170.             my_grid[i]  = 0xFF;
  171.         }
  172.         for (uint16_t i = 0; i < CELLS; ++i) {
  173.             my_shots[i] = 0;
  174.         }
  175.         for (uint16_t i = 0; i < CELLS; ++i) {
  176.             opp_view[i] = 0;
  177.         }
  178.         for (uint16_t i = 0; i < CELLS; ++i) {
  179.             my_moves[i] = 0;
  180.         }
  181.         for (uint8_t i = 0; i < NUM_SHIP_TYPES; ++i) {
  182.             my_ships[i].reset((ship_type_t)i);
  183.         }
  184.         ships_remaining_cells = 5 + 4 + 3 + 3 + 2;
  185.     }
  186.  
  187.     // Return true if a ship of len fits at s in direction d, without
  188.     // overlapping our own ships or leaving bounds.
  189.     bool can_place(uint8_t len, xy_t s, dir_t d) const {
  190.         if (!s.in_bounds()) {
  191.             return false;
  192.         }
  193.         int dx = 0;
  194.         int dy = 0;
  195.         switch (d) {
  196.             case NORTH: { dy = -1; break; }
  197.             case SOUTH: { dy =  1; break; }
  198.             case WEST:  { dx = -1; break; }
  199.             case EAST:  { dx =  1; break; }
  200.         }
  201.         int x = s.x;
  202.         int y = s.y;
  203.         for (uint8_t i = 0; i < len; ++i) {
  204.             if (x < 0 || y < 0 || x >= GRID_W || y >= GRID_H) {
  205.                 return false;
  206.             }
  207.             uint8_t idx = xy_to_idx((uint8_t)x, (uint8_t)y);
  208.             if (my_grid[idx] != 0xFF) {
  209.                 return false;
  210.             }
  211.             x += dx;
  212.             y += dy;
  213.         }
  214.         return true;
  215.     }
  216.  
  217.     // Write ship cells into our grid.
  218.     void place(ship_t &sh, xy_t s, dir_t d, uint8_t ship_index) {
  219.         sh.start = s;
  220.         sh.dir   = d;
  221.         int dx = 0;
  222.         int dy = 0;
  223.         switch (d) {
  224.             case NORTH: { dy = -1; break; }
  225.             case SOUTH: { dy =  1; break; }
  226.             case WEST:  { dx = -1; break; }
  227.             case EAST:  { dx =  1; break; }
  228.         }
  229.         int x = s.x;
  230.         int y = s.y;
  231.         for (uint8_t i = 0; i < sh.len; ++i) {
  232.             uint8_t idx = xy_to_idx((uint8_t)x, (uint8_t)y);
  233.             my_grid[idx] = ship_index;
  234.             x += dx;
  235.             y += dy;
  236.         }
  237.     }
  238.  
  239.     // Produce the linear cells covered by ship if placed at s,d.
  240.     static void cells_for(const ship_t &sh, xy_t s, dir_t d,
  241.                           uint8_t *out_cells) {
  242.         int dx = 0;
  243.         int dy = 0;
  244.         switch (d) {
  245.             case NORTH: { dy = -1; break; }
  246.             case SOUTH: { dy =  1; break; }
  247.             case WEST:  { dx = -1; break; }
  248.             case EAST:  { dx =  1; break; }
  249.         }
  250.         int x = s.x;
  251.         int y = s.y;
  252.         for (uint8_t i = 0; i < sh.len; ++i) {
  253.             out_cells[i] = xy_to_idx((uint8_t)x, (uint8_t)y);
  254.             x += dx;
  255.             y += dy;
  256.         }
  257.     }
  258.  
  259.     // Apply an incoming shot to our board and return the result.
  260.     result_code_t receive_shot(uint8_t idx, uint8_t &sunk_ship_type) {
  261.         if (idx >= CELLS) {
  262.             return RES_ERROR;
  263.         }
  264.         if (my_shots[idx]) {
  265.             return RES_REPEAT;
  266.         }
  267.         my_shots[idx] = 1;
  268.         uint8_t cell = my_grid[idx];
  269.         if (cell == 0xFF) {
  270.             return RES_MISS;
  271.         }
  272.         ship_t &sh = my_ships[cell];
  273.         sh.hits++;
  274.         ships_remaining_cells--;
  275.         if (sh.hits >= sh.len) {
  276.             sunk_ship_type = (uint8_t)sh.type;
  277.             return RES_SUNK;
  278.         }
  279.         return RES_HIT;
  280.     }
  281.  
  282.     // Mark our opponent view for a given result at idx.
  283.     void mark_opp(uint8_t idx, result_code_t r) {
  284.         if (idx >= CELLS) {
  285.             return;
  286.         }
  287.         switch (r) {
  288.             case RES_MISS: { opp_view[idx] = 1; break; }
  289.             case RES_HIT:  { opp_view[idx] = 2; break; }
  290.             case RES_SUNK: { opp_view[idx] = 2; break; }
  291.             default       : { break; }
  292.         }
  293.     }
  294.  
  295.     // Return true if we already fired at idx.
  296.     bool move_was_made(uint8_t idx) const {
  297.         return idx < CELLS && my_moves[idx];
  298.     }
  299.  
  300.     // Remember that we fired at idx.
  301.     void remember_move(uint8_t idx) {
  302.         if (idx < CELLS) {
  303.             my_moves[idx] = 1;
  304.         }
  305.     }
  306.  
  307.     // Fast xorshift32 PRNG step.
  308.     static uint32_t rand32(uint32_t &state) {
  309.         uint32_t x = state;
  310.         x ^= x << 13;
  311.         x ^= x >> 17;
  312.         x ^= x << 5;
  313.         state = x;
  314.         return x;
  315.     }
  316.  
  317.     // Collect some ADC noise and micros to seed PRNG.
  318.     static uint32_t seed_from_adc() {
  319.         uint32_t s = micros();
  320.         for (int i = 0; i < 8; ++i) {
  321.             s ^= (uint32_t)analogRead(A0) << (i * 2);
  322.             delay(1);
  323.         }
  324.         return s ^ micros();
  325.     }
  326.  
  327.     // Pick a valid placement for a ship (AI).
  328.     static bool ai_pick_ship_placement(const board_t &b, const ship_t &sh,
  329.         uint32_t &rng_state, xy_t &s_out, dir_t &d_out) {
  330.         for (uint16_t tries = 0; tries < 600; ++tries) {
  331.             dir_t d = (dir_t)(rand32(rng_state) % 4);
  332.             xy_t s;
  333.             switch (d) {
  334.                 case EAST: {
  335.                     s = { (uint8_t)(rand32(rng_state) %
  336.                           (GRID_W - sh.len + 1)),
  337.                           (uint8_t)(rand32(rng_state) % GRID_H) };
  338.                     break;
  339.                 }
  340.                 case WEST: {
  341.                     s = { (uint8_t)((sh.len - 1) +
  342.                           (rand32(rng_state) %
  343.                           (GRID_W - sh.len + 1))),
  344.                           (uint8_t)(rand32(rng_state) % GRID_H) };
  345.                     break;
  346.                 }
  347.                 case SOUTH: {
  348.                     s = { (uint8_t)(rand32(rng_state) % GRID_W),
  349.                           (uint8_t)(rand32(rng_state) %
  350.                           (GRID_H - sh.len + 1)) };
  351.                     break;
  352.                 }
  353.                 case NORTH: {
  354.                     s = { (uint8_t)(rand32(rng_state) % GRID_W),
  355.                           (uint8_t)((sh.len - 1) +
  356.                           (rand32(rng_state) %
  357.                           (GRID_H - sh.len + 1))) };
  358.                     break;
  359.                 }
  360.             }
  361.             if (b.can_place(sh.len, s, d)) {
  362.                 s_out = s;
  363.                 d_out = d;
  364.                 return true;
  365.             }
  366.         }
  367.         return false;
  368.     }
  369.  
  370.     // Choose next shot with improved targeting.
  371.     uint8_t choose_move(uint32_t &rng_state) {
  372.         // Phase 1: extend any horizontal or vertical line of hits.
  373.         for (uint8_t y = 0; y < GRID_H; ++y) {
  374.             for (uint8_t x = 0; x < GRID_W; ++x) {
  375.                 uint8_t idx = xy_to_idx(x, y);
  376.                 if (opp_view[idx] != 2) {
  377.                     continue;
  378.                 }
  379.  
  380.                 // Horizontal run detection and extension.
  381.                 bool left_hit  = (x > 0) &&
  382.                                  (opp_view[xy_to_idx(x - 1, y)] == 2);
  383.                 bool right_hit = (x + 1 < GRID_W) &&
  384.                                  (opp_view[xy_to_idx(x + 1, y)] == 2);
  385.                 if (left_hit || right_hit) {
  386.                     int lx = x;
  387.                     int rx = x;
  388.                     while ((lx > 0) &&
  389.                            (opp_view[xy_to_idx(lx - 1, y)] == 2)) {
  390.                         --lx;
  391.                     }
  392.                     while ((rx + 1 < GRID_W) &&
  393.                            (opp_view[xy_to_idx(rx + 1, y)] == 2)) {
  394.                         ++rx;
  395.                     }
  396.  
  397.                     uint8_t cand[2];
  398.                     uint8_t n_c = 0;
  399.                     if (lx > 0) {
  400.                         cand[n_c++] = xy_to_idx((uint8_t)(lx - 1), y);
  401.                     }
  402.                     if (rx + 1 < GRID_W) {
  403.                         cand[n_c++] = xy_to_idx((uint8_t)(rx + 1), y);
  404.                     }
  405.  
  406.                     if (n_c == 2 && (rand32(rng_state) & 1)) {
  407.                         uint8_t t = cand[0];
  408.                         cand[0] = cand[1];
  409.                         cand[1] = t;
  410.                     }
  411.  
  412.                     for (uint8_t i = 0; i < n_c; ++i) {
  413.                         uint8_t c = cand[i];
  414.                         if (!move_was_made(c) && opp_view[c] == 0) {
  415.                             return c;
  416.                         }
  417.                     }
  418.                 }
  419.  
  420.                 // Vertical run detection and extension.
  421.                 bool up_hit   = (y > 0) &&
  422.                                 (opp_view[xy_to_idx(x, y - 1)] == 2);
  423.                 bool down_hit = (y + 1 < GRID_H) &&
  424.                                 (opp_view[xy_to_idx(x, y + 1)] == 2);
  425.                 if (up_hit || down_hit) {
  426.                     int ty = y;
  427.                     int by = y;
  428.                     while ((ty > 0) &&
  429.                            (opp_view[xy_to_idx(x, ty - 1)] == 2)) {
  430.                         --ty;
  431.                     }
  432.                     while ((by + 1 < GRID_H) &&
  433.                            (opp_view[xy_to_idx(x, by + 1)] == 2)) {
  434.                         ++by;
  435.                     }
  436.  
  437.                     uint8_t cand[2];
  438.                     uint8_t n_c = 0;
  439.                     if (ty > 0) {
  440.                         cand[n_c++] = xy_to_idx(x, (uint8_t)(ty - 1));
  441.                     }
  442.                     if (by + 1 < GRID_H) {
  443.                         cand[n_c++] = xy_to_idx(x, (uint8_t)(by + 1));
  444.                     }
  445.  
  446.                     if (n_c == 2 && (rand32(rng_state) & 1)) {
  447.                         uint8_t t = cand[0];
  448.                         cand[0] = cand[1];
  449.                         cand[1] = t;
  450.                     }
  451.  
  452.                     for (uint8_t i = 0; i < n_c; ++i) {
  453.                         uint8_t c = cand[i];
  454.                         if (!move_was_made(c) && opp_view[c] == 0) {
  455.                             return c;
  456.                         }
  457.                     }
  458.                 }
  459.             }
  460.         }
  461.  
  462.         // Phase 2: probe around any single hit in random neighbor order.
  463.         for (uint8_t y = 0; y < GRID_H; ++y) {
  464.             for (uint8_t x = 0; x < GRID_W; ++x) {
  465.                 uint8_t idx = xy_to_idx(x, y);
  466.                 if (opp_view[idx] != 2) {
  467.                     continue;
  468.                 }
  469.  
  470.                 uint8_t nbr[4];
  471.                 uint8_t n_n = 0;
  472.                 if (x > 0) {
  473.                     nbr[n_n++] = xy_to_idx(x - 1, y);
  474.                 }
  475.                 if (x + 1 < GRID_W) {
  476.                     nbr[n_n++] = xy_to_idx(x + 1, y);
  477.                 }
  478.                 if (y > 0) {
  479.                     nbr[n_n++] = xy_to_idx(x, y - 1);
  480.                 }
  481.                 if (y + 1 < GRID_H) {
  482.                     nbr[n_n++] = xy_to_idx(x, y + 1);
  483.                 }
  484.  
  485.                 for (uint8_t i = 0; i + 1 < n_n; ++i) {
  486.                     uint8_t j = (uint8_t)(i +
  487.                         (rand32(rng_state) % (uint8_t)(n_n - i)));
  488.                     uint8_t t = nbr[i];
  489.                     nbr[i] = nbr[j];
  490.                     nbr[j] = t;
  491.                 }
  492.  
  493.                 for (uint8_t i = 0; i < n_n; ++i) {
  494.                     uint8_t c = nbr[i];
  495.                     if (!move_was_made(c) && opp_view[c] == 0) {
  496.                         return c;
  497.                     }
  498.                 }
  499.             }
  500.         }
  501.  
  502.         // Phase 3: parity hunt on unknown cells.
  503.         for (uint16_t tries = 0; tries < 400; ++tries) {
  504.             uint8_t x = rand32(rng_state) % GRID_W;
  505.             uint8_t y = rand32(rng_state) % GRID_H;
  506.             if (((x ^ y) & 1) != 0) {
  507.                 uint8_t idx = xy_to_idx(x, y);
  508.                 if (!move_was_made(idx) && opp_view[idx] == 0) {
  509.                     return idx;
  510.                 }
  511.             }
  512.         }
  513.  
  514.         // Phase 4: first unknown.
  515.         for (uint8_t i = 0; i < CELLS; ++i) {
  516.             if (!move_was_made(i) && opp_view[i] == 0) {
  517.                 return i;
  518.             }
  519.         }
  520.         return 0;
  521.     }
  522.  
  523.     // Print our own board to Stream.
  524.     void print_my_board(Stream &s) const {
  525. #if DEBUG_PRINT
  526.         s.println(F("My Board: ('#'=ship, 'x'=hit, 'o'=miss)"));
  527.         for (uint8_t y = 0; y < GRID_H; ++y) {
  528.             for (uint8_t x = 0; x < GRID_W; ++x) {
  529.                 uint8_t idx = xy_to_idx(x, y);
  530.                 char c = '.';
  531.                 if (my_shots[idx]) {
  532.                     c = (my_grid[idx] == 0xFF) ? 'o' : 'x';
  533.                 }
  534.                 else {
  535.                     if (my_grid[idx] != 0xFF) {
  536.                         c = '#';
  537.                     }
  538.                 }
  539.                 s.print(c);
  540.             }
  541.             s.println();
  542.         }
  543. #else
  544.         (void)s;
  545. #endif
  546.     }
  547.  
  548.     // Print our view of opponent to Stream.
  549.     void print_opp_view(Stream &s) const {
  550. #if DEBUG_PRINT
  551.         s.println(F("Opponent View: ('?'=unknown, 'x'=hit, 'o'=miss)"));
  552.         for (uint8_t y = 0; y < GRID_H; ++y) {
  553.             for (uint8_t x = 0; x < GRID_W; ++x) {
  554.                 uint8_t idx = xy_to_idx(x, y);
  555.                 char c = '?';
  556.                 switch (opp_view[idx]) {
  557.                     case 1: { c = 'o'; break; }
  558.                     case 2: { c = 'x'; break; }
  559.                     default: { break; }
  560.                 }
  561.                 s.print(c);
  562.             }
  563.             s.println();
  564.         }
  565. #else
  566.         (void)s;
  567. #endif
  568.     }
  569. };
  570.  
  571. // ------------------------------ Globals --------------------------------------
  572.  
  573. static board_t board;
  574. static bool     am_host = false;
  575. static bool     my_turn = false;
  576. static uint8_t  my_id   = 0;
  577. static uint8_t  opp_id  = 1;
  578. static bool     game_over = false;
  579. static uint32_t rng_state = 0;
  580.  
  581. static bool     is_human = false;
  582. static bool     manual_ship_placement = true;
  583.  
  584. // ------------------------------ Link I/O -------------------------------------
  585.  
  586. // Write a framed packet to AltSoftSerial.
  587. static void link_write_packet(uint8_t type, const uint8_t *payload,
  588.                               uint8_t len) {
  589.     uint8_t crc = type ^ len;
  590.     link.write(PKT_SYNC);
  591.     link.write(type);
  592.     link.write(len);
  593.     for (uint8_t i = 0; i < len; ++i) {
  594.         link.write(payload[i]);
  595.         crc ^= payload[i];
  596.     }
  597.     link.write(crc);
  598. #if !TEST_WITH_PYTHON
  599.     link.flushOutput();
  600. #else
  601.     link.flush();
  602. #endif
  603. }
  604.  
  605. // Read a framed packet with timeout. Returns payload length or -1.
  606. static int link_read_packet(uint8_t &type, uint8_t *buf, uint8_t maxlen,
  607.                             uint16_t timeout_ms) {
  608.     const uint32_t deadline = millis() + timeout_ms;
  609.     enum { ST_SYNC, ST_TYPE, ST_LEN, ST_PAYLOAD, ST_CRC } st = ST_SYNC;
  610.     uint8_t len = 0;
  611.     uint8_t crc = 0;
  612.     uint8_t got = 0;
  613.     while ((int32_t)(deadline - millis()) > 0) {
  614.         if (!link.available()) {
  615.             continue;
  616.         }
  617.         uint8_t b = (uint8_t)link.read();
  618.         switch (st) {
  619.             case ST_SYNC: {
  620.                 if (b == PKT_SYNC) {
  621.                     st = ST_TYPE;
  622.                 }
  623.                 break;
  624.             }
  625.             case ST_TYPE: {
  626.                 type = b;
  627.                 crc  = b;
  628.                 st   = ST_LEN;
  629.                 break;
  630.             }
  631.             case ST_LEN: {
  632.                 len = b;
  633.                 crc ^= b;
  634.                 if (len > maxlen) {
  635.                     for (uint8_t i = 0; i < len + 1; ++i) {
  636.                         while (!link.available()) {
  637.                             // spin
  638.                         }
  639.                         (void)link.read();
  640.                     }
  641.                     st = ST_SYNC;
  642.                 }
  643.                 else {
  644.                     got = 0;
  645.                     st  = (len == 0) ? ST_CRC : ST_PAYLOAD;
  646.                 }
  647.                 break;
  648.             }
  649.             case ST_PAYLOAD: {
  650.                 buf[got++] = b;
  651.                 crc ^= b;
  652.                 if (got >= len) {
  653.                     st = ST_CRC;
  654.                 }
  655.                 break;
  656.             }
  657.             case ST_CRC: {
  658.                 if (crc == b) {
  659.                     return len;
  660.                 }
  661.                 st = ST_SYNC;
  662.                 break;
  663.             }
  664.         }
  665.     }
  666.     return -1;
  667. }
  668.  
  669. // ------------------------------ Helpers --------------------------------------
  670.  
  671. // Discard any bytes available on a stream.
  672. static void read_flush_input(Stream &s) {
  673.     while (s.available()) {
  674.         (void)s.read();
  675.     }
  676. }
  677.  
  678. // Read a line with timeout into buf, NUL-terminated.
  679. static bool read_line(Stream &s, char *buf, size_t maxlen,
  680.                       uint32_t timeout_ms) {
  681.     size_t n = 0;
  682.     const uint32_t deadline = millis() + timeout_ms;
  683.     while ((int32_t)(deadline - millis()) > 0) {
  684.         while (s.available()) {
  685.             char c = (char)s.read();
  686.             if (c == '\r') {
  687.                 continue;
  688.             }
  689.             if (c == '\n') {
  690.                 buf[n] = 0;
  691.                 return true;
  692.             }
  693.             if (n + 1 < maxlen) {
  694.                 buf[n++] = c;
  695.             }
  696.         }
  697.         // Poll link for unexpected packets during wait
  698.         if (link.available()) {
  699.             uint8_t type;
  700.             uint8_t pbuf[16];
  701.             int pn = link_read_packet(type, pbuf, sizeof(pbuf), 5);
  702.             if (pn >= 0) {
  703.                 if (type == PKT_HELLO && pn == 4) {
  704. #if DEBUG_PRINT
  705.                     Serial.println(F("Detected peer restart. Resetting game..."));
  706. #endif
  707.                     game_over = false;
  708.                     board.clear();
  709.                     do_handshake();
  710.                     placement_phase();
  711.                 }
  712.                 if (type == PKT_PLACE_CHECK && pn >= 1) {
  713.                     uint8_t rc = 0;
  714.                     link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  715.                 }
  716.             }
  717.         }
  718.     }
  719.     buf[n] = 0;
  720.     return n > 0;
  721. }
  722.  
  723. // Uppercase ASCII letter if needed.
  724. static inline uint8_t to_upper(uint8_t c) {
  725.     return (c >= 'a' && c <= 'z') ? (uint8_t)(c - 'a' + 'A') : c;
  726. }
  727.  
  728. // Parse "A1".."J10" into linear index.
  729. static bool parse_cell_str(const char *p, uint8_t &idx_out) {
  730.     while (*p == ' ' || *p == '\t') {
  731.         ++p;
  732.     }
  733.     if (!*p) {
  734.         return false;
  735.     }
  736.     char col = to_upper(*p++);
  737.     if (col < 'A' || col > 'J') {
  738.         return false;
  739.     }
  740.     while (*p == ' ' || *p == '\t') {
  741.         ++p;
  742.     }
  743.     if (!*p) {
  744.         return false;
  745.     }
  746.     int row = 0;
  747.     if (*p < '0' || *p > '9') {
  748.         return false;
  749.     }
  750.     while (*p >= '0' && *p <= '9') {
  751.         row = row * 10 + (*p - '0');
  752.         ++p;
  753.     }
  754.     while (*p == ' ' || *p == '\t') {
  755.         ++p;
  756.     }
  757.     if (row < 1 || row > 10) {
  758.         return false;
  759.     }
  760.     uint8_t x = (uint8_t)(col - 'A');
  761.     uint8_t y = (uint8_t)(row - 1);
  762.     if (x >= GRID_W || y >= GRID_H) {
  763.         return false;
  764.     }
  765.     idx_out = xy_to_idx(x, y);
  766.     return true;
  767. }
  768.  
  769. // Parse direction token after cell: N/S/E/W (U/D/L/R also accepted).
  770. static bool parse_dir_str(const char *p, dir_t &d_out) {
  771.     while (*p && *p != ' ' && *p != '\t') {
  772.         ++p;
  773.     }
  774.     while (*p == ' ' || *p == '\t') {
  775.         ++p;
  776.     }
  777.     if (!*p) {
  778.         return false;
  779.     }
  780.     char c = to_upper(*p);
  781.     if (c == 'N' || c == 'U') {
  782.         d_out = NORTH;
  783.         return true;
  784.     }
  785.     if (c == 'S' || c == 'D') {
  786.         d_out = SOUTH;
  787.         return true;
  788.     }
  789.     if (c == 'W' || c == 'L') {
  790.         d_out = WEST;
  791.         return true;
  792.     }
  793.     if (c == 'E' || c == 'R') {
  794.         d_out = EAST;
  795.         return true;
  796.     }
  797.     return false;
  798. }
  799.  
  800. // Print cell in "A1" style.
  801. static void print_cell(uint8_t idx) {
  802. #if DEBUG_PRINT
  803.     Serial.print((char)('A' + idx_x(idx)));
  804.     Serial.print((int)(idx_y(idx) + 1));
  805. #endif
  806. }
  807.  
  808. // Print a banner for current turn.
  809. static void print_turn_banner() {
  810. #if DEBUG_PRINT
  811.     Serial.print(F("\n--- Turn: Player "));
  812.     Serial.print((int)(my_turn ? my_id : opp_id));
  813.     Serial.println(F(" ---"));
  814. #endif
  815. }
  816.  
  817. // Handle peer restart by resetting game state and restarting handshake/placement.
  818. static void handle_peer_restart() {
  819. #if DEBUG_PRINT
  820.     Serial.println(F("Detected peer restart. Resetting game..."));
  821. #endif
  822.     game_over = false;
  823.     board.clear();
  824.     do_handshake();
  825.     placement_phase();
  826. }
  827.  
  828. // ---------------------------------- Placement -------------------------------
  829.  
  830. // Service any unexpected PKT_PLACE_CHECK by replying OK.
  831. static void service_place_check_once_nonblocking() {
  832.     if (!link.available()) {
  833.         return;
  834.     }
  835.     uint8_t type;
  836.     uint8_t buf[16];
  837.     int n = link_read_packet(type, buf, sizeof(buf), 5);
  838.     if (n >= 0) {
  839.         if (type == PKT_HELLO && n == 4) {
  840.             handle_peer_restart();
  841.         }
  842.         if (type == PKT_PLACE_CHECK && n >= 1) {
  843.             uint8_t rc = 0;
  844.             link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  845.         }
  846.     }
  847. }
  848.  
  849. // Wait for the peer to signal placement completion.
  850. static void wait_for_peer_placement_done() {
  851. #if DEBUG_PRINT
  852.     Serial.println(F("[Placement] Waiting for peer to finish..."));
  853. #endif
  854.     uint32_t start = millis();
  855.     uint32_t last_log = start;
  856.     while (true) {
  857.         uint8_t type;
  858.         uint8_t buf[4];
  859.         int n = link_read_packet(type, buf, sizeof(buf), 200);
  860.         if (n >= 0) {
  861.             if (type == PKT_PLACE_DONE) {
  862. #if DEBUG_PRINT
  863.                 Serial.println(F("[Placement] Peer finished."));
  864. #endif
  865.                 break;
  866.             }
  867.             if (type == PKT_HELLO && n == 4) {
  868.                 handle_peer_restart();
  869.                 return;  // Exit after restart to avoid stuck loop
  870.             }
  871.             if (type == PKT_PLACE_CHECK && n >= 1) {
  872.                 uint8_t rc = 0;
  873.                 link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  874.             }
  875.         }
  876.         uint32_t current = millis();
  877.         if (current - start > WAIT_TIMEOUT) {
  878. #if DEBUG_PRINT
  879.             Serial.println(F("[Placement] Timeout waiting for peer. Forcing restart..."));
  880. #endif
  881.             uint8_t dummy_token[4] = {0,0,0,0};
  882.             link_write_packet(PKT_HELLO, dummy_token, 4);
  883.             handle_peer_restart();
  884.             return;
  885.         }
  886.         if (current - last_log > 10000) {
  887. #if DEBUG_PRINT
  888.             Serial.println(F("[Placement] Still waiting for peer..."));
  889. #endif
  890.             last_log = current;
  891.         }
  892.     }
  893. }
  894.  
  895. // Prompt a human to place all ships (no cross-player validation).
  896. static void collect_human_fleet_placements(bool /*unused*/) {
  897. #if DEBUG_PRINT
  898.     Serial.println(F("\n=== Human Fleet Placement ==="));
  899.     Serial.println(F("Format: A1 E   or   b4 south"));
  900. #endif
  901.     for (uint8_t i = 0; i < NUM_SHIP_TYPES; ++i) {
  902.         ship_t &sh = board.my_ships[i];
  903.         while (true) {
  904. #if DEBUG_PRINT
  905.             Serial.print(F("\nPlace "));
  906.             Serial.print(SHIP_NAME[sh.type]);
  907.             Serial.print(F(" (length "));
  908.             Serial.print(sh.len);
  909.             Serial.println(F(")"));
  910.             Serial.print(F("> "));
  911. #endif
  912.             char line[32];
  913.             read_flush_input(Serial);
  914.             if (!read_line(Serial, line, sizeof(line), 600000)) {
  915.                 service_place_check_once_nonblocking();
  916.                 continue;
  917.             }
  918.             uint8_t idx0;
  919.             if (!parse_cell_str(line, idx0)) {
  920. #if DEBUG_PRINT
  921.                 Serial.println(F("Bad cell. Use A1..J10."));
  922. #endif
  923.                 continue;
  924.             }
  925.             dir_t d;
  926.             if (!parse_dir_str(line, d)) {
  927. #if DEBUG_PRINT
  928.                 Serial.println(F("Bad direction. Use N/S/E/W."));
  929. #endif
  930.                 continue;
  931.             }
  932.             xy_t s = { idx_x(idx0), idx_y(idx0) };
  933.             if (!board.can_place(sh.len, s, d)) {
  934. #if DEBUG_PRINT
  935.                 Serial.println(F("Does not fit / overlaps your ships."));
  936. #endif
  937.                 continue;
  938.             }
  939.             uint8_t cells[5];
  940.             board_t::cells_for(sh, s, d, cells);
  941.             board.place(sh, s, d, i);
  942. #if DEBUG_PRINT
  943.             Serial.print(F("Placed "));
  944.             Serial.print(SHIP_NAME[sh.type]);
  945.             Serial.print(F(" at "));
  946.             print_cell(cells[0]);
  947.             Serial.println();
  948.             board.print_my_board(Serial);
  949. #endif
  950.             break;
  951.         }
  952.     }
  953.     link_write_packet(PKT_PLACE_DONE, nullptr, 0);
  954. #if DEBUG_PRINT
  955.     Serial.println(F("[Placement] All ships placed."));
  956. #endif
  957. }
  958.  
  959. // AI placement with no cross-player validation.
  960. static void ai_fleet_placement_no_crosscheck() {
  961. #if DEBUG_PRINT
  962.     Serial.println(F("\n=== Computer Fleet Placement ==="));
  963. #endif
  964.     for (uint8_t i = 0; i < NUM_SHIP_TYPES; ++i) {
  965.         ship_t &sh = board.my_ships[i];
  966.         while (true) {
  967.             xy_t s;
  968.             dir_t d;
  969.             if (!board_t::ai_pick_ship_placement(board, sh, rng_state,
  970.                                                  s, d)) {
  971.                 continue;
  972.             }
  973.             uint8_t cells[5];
  974.             board_t::cells_for(sh, s, d, cells);
  975.             board.place(sh, s, d, i);
  976. #if DEBUG_PRINT
  977.             Serial.print(F("Placed "));
  978.             Serial.print(SHIP_NAME[sh.type]);
  979.             Serial.print(F(" at "));
  980.             print_cell(cells[0]);
  981.             Serial.println();
  982. #endif
  983.             break;
  984.         }
  985.     }
  986.     link_write_packet(PKT_PLACE_DONE, nullptr, 0);
  987. #if DEBUG_PRINT
  988.     Serial.println(F("[Placement] Computer placed all ships."));
  989. #endif
  990. }
  991.  
  992. // ------------------------------ Gameplay -------------------------------------
  993.  
  994. // If a GAMEOVER arrives, consume and mark state.
  995. static void maybe_receive_gameover_nonblocking() {
  996.     if (!link.available()) {
  997.         return;
  998.     }
  999.     uint8_t type;
  1000.     uint8_t buf[4];
  1001.     int n = link_read_packet(type, buf, sizeof(buf), 5);
  1002.     if (n >= 0) {
  1003.         if (type == PKT_HELLO && n == 4) {
  1004.             handle_peer_restart();
  1005.         }
  1006.         if (type == PKT_GAMEOVER && n >= 1) {
  1007.             uint8_t winner = buf[0];
  1008.             if (winner == my_id) {
  1009. #if DEBUG_PRINT
  1010.                 Serial.println(F("\n*** GAME OVER: We win. ***"));
  1011. #endif
  1012.             }
  1013.             else {
  1014. #if DEBUG_PRINT
  1015.                 Serial.println(F("\n*** GAME OVER: Opponent wins. ***"));
  1016. #endif
  1017.             }
  1018.             game_over = true;
  1019.         }
  1020.         if (type == PKT_PLACE_CHECK && n >= 1) {
  1021.             uint8_t rc = 0;
  1022.             link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  1023.         }
  1024.     }
  1025. }
  1026.  
  1027. // Send MOVE, await RESULT (with retry). Returns true on success.
  1028. static bool send_move_and_get_result(uint8_t idx, result_code_t &res_out) {
  1029.     uint8_t c = idx;
  1030.     link_write_packet(PKT_MOVE, &c, 1);
  1031.     uint32_t last_log = millis();
  1032.     for (uint8_t tries = 0; tries < 10; ++tries) {
  1033.         uint8_t type;
  1034.         uint8_t buf[4];
  1035.         int n = link_read_packet(type, buf, sizeof(buf), 1500);
  1036.         if (n >= 0) {
  1037.             if (type == PKT_RESULT && n >= 1) {
  1038.                 res_out = (result_code_t)buf[0];
  1039.                 return true;
  1040.             }
  1041.             if (type == PKT_HELLO && n == 4) {
  1042.                 handle_peer_restart();
  1043.             }
  1044.             if (type == PKT_PLACE_CHECK && n >= 1) {
  1045.                 uint8_t rc = 0;
  1046.                 link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  1047.             }
  1048.         }
  1049.         link_write_packet(PKT_MOVE, &c, 1);
  1050.         uint32_t current = millis();
  1051.         if (current - last_log > 10000) {
  1052. #if DEBUG_PRINT
  1053.             Serial.println(F("Still waiting for RESULT..."));
  1054. #endif
  1055.             last_log = current;
  1056.         }
  1057.     }
  1058.     return false;
  1059. }
  1060.  
  1061. // Handle an incoming MOVE and send RESULT.
  1062. static void respond_to_incoming_move(uint8_t idx) {
  1063.     uint8_t sunk_type = 0xFF;
  1064.     result_code_t r = board.receive_shot(idx, sunk_type);
  1065.     uint8_t rc = (uint8_t)r;
  1066.     link_write_packet(PKT_RESULT, &rc, 1);
  1067.  
  1068. #if DEBUG_PRINT
  1069.     Serial.print(F("Incoming shot at "));
  1070.     print_cell(idx);
  1071.     Serial.print(F(" -> "));
  1072.     switch (r) {
  1073.         case RES_MISS: {
  1074.             Serial.println(F("MISS"));
  1075.             break;
  1076.         }
  1077.         case RES_HIT: {
  1078.             Serial.println(F("HIT"));
  1079.             break;
  1080.         }
  1081.         case RES_SUNK: {
  1082.             Serial.print(F("SUNK "));
  1083.             Serial.println((int)sunk_type);
  1084.             break;
  1085.         }
  1086.         case RES_REPEAT: {
  1087.             Serial.println(F("REPEAT"));
  1088.             break;
  1089.         }
  1090.         default: {
  1091.             Serial.println(F("ERROR"));
  1092.             break;
  1093.         }
  1094.     }
  1095. #endif
  1096.  
  1097.     if (board.ships_remaining_cells == 0) {
  1098.         uint8_t winner = opp_id;
  1099.         link_write_packet(PKT_GAMEOVER, &winner, 1);
  1100. #if DEBUG_PRINT
  1101.         Serial.println(F("\n*** GAME OVER: Opponent wins. ***"));
  1102. #endif
  1103.         game_over = true;
  1104.     }
  1105. }
  1106.  
  1107. // Prompt human for a legal shot coordinate.
  1108. static uint8_t prompt_human_move_idx() {
  1109.     char line[16];
  1110.     while (true) {
  1111. #if DEBUG_PRINT
  1112.         Serial.print(F("Enter move (e.g., A1): "));
  1113. #endif
  1114.         read_flush_input(Serial);
  1115.         if (!read_line(Serial, line, sizeof(line), 600000)) {
  1116.             continue;
  1117.         }
  1118.         uint8_t idx;
  1119.         if (!parse_cell_str(line, idx)) {
  1120. #if DEBUG_PRINT
  1121.             Serial.println(F("Bad coordinate. Try again."));
  1122. #endif
  1123.             continue;
  1124.         }
  1125.         if (board.move_was_made(idx)) {
  1126. #if DEBUG_PRINT
  1127.             Serial.println(F("Already shot there. Try again."));
  1128. #endif
  1129.             continue;
  1130.         }
  1131.         return idx;
  1132.     }
  1133. }
  1134.  
  1135. // Show our boards if enabled.
  1136. static void show_boards_if_configured() {
  1137. #if PRINT_BOARDS_EVERY_TURN
  1138.     board.print_my_board(Serial);
  1139.     board.print_opp_view(Serial);
  1140. #endif
  1141. }
  1142.  
  1143. // Service link for unexpected packets non-blocking (HELLO, PLACE_CHECK, GAMEOVER).
  1144. static void service_link_nonblocking() {
  1145.     if (!link.available()) {
  1146.         return;
  1147.     }
  1148.     uint8_t type;
  1149.     uint8_t buf[4];
  1150.     int n = link_read_packet(type, buf, sizeof(buf), 5);
  1151.     if (n >= 0) {
  1152.         if (type == PKT_HELLO && n == 4) {
  1153.             handle_peer_restart();
  1154.         }
  1155.         if (type == PKT_PLACE_CHECK && n >= 1) {
  1156.             uint8_t rc = 0;
  1157.             link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  1158.         }
  1159.         if (type == PKT_GAMEOVER && n >= 1) {
  1160.             uint8_t winner = buf[0];
  1161.             if (winner == my_id) {
  1162. #if DEBUG_PRINT
  1163.                 Serial.println(F("\n*** GAME OVER: We win. ***"));
  1164. #endif
  1165.             }
  1166.             else {
  1167. #if DEBUG_PRINT
  1168.                 Serial.println(F("\n*** GAME OVER: Opponent wins. ***"));
  1169. #endif
  1170.             }
  1171.             game_over = true;
  1172.         }
  1173.     }
  1174. }
  1175.  
  1176. // --------------------------- Handshake / Setup -------------------------------
  1177.  
  1178. // Exchange HELLO, decide host, then START if host.
  1179. static void do_handshake() {
  1180.     uint32_t my_token = (board_t::seed_from_adc() ^ 0xA5A5A5A5UL);
  1181.     rng_state = my_token ^ 0x55AA3311UL;
  1182.  
  1183.     uint8_t tx[4] = {
  1184.         (uint8_t)my_token,
  1185.         (uint8_t)(my_token >> 8),
  1186.         (uint8_t)(my_token >> 16),
  1187.         (uint8_t)(my_token >> 24)
  1188.     };
  1189.     uint32_t start = millis();
  1190.     uint32_t last_log = start;
  1191.     uint32_t peer_token = 0;
  1192.  
  1193.     while (true) {
  1194.         link_write_packet(PKT_HELLO, tx, 4);
  1195.         delay(50);  // Small delay to allow peer to receive
  1196.         uint8_t type;
  1197.         uint8_t buf[4];
  1198.         int n = link_read_packet(type, buf, sizeof(buf), 500);
  1199.         if (n == 4 && type == PKT_HELLO) {
  1200.             peer_token = (uint32_t)buf[0] |
  1201.                          ((uint32_t)buf[1] << 8) |
  1202.                          ((uint32_t)buf[2] << 16) |
  1203.                          ((uint32_t)buf[3] << 24);
  1204.             break;
  1205.         }
  1206.         uint32_t current = millis();
  1207.         if (current - start > WAIT_TIMEOUT) {
  1208. #if DEBUG_PRINT
  1209.             Serial.println(F("[Handshake] Timeout waiting for peer HELLO. Forcing restart..."));
  1210. #endif
  1211.             handle_peer_restart();
  1212.             return;
  1213.         }
  1214.         if (current - last_log > 10000) {
  1215. #if DEBUG_PRINT
  1216.             Serial.println(F("[Handshake] Still waiting for peer HELLO..."));
  1217. #endif
  1218.             last_log = current;
  1219.         }
  1220.     }
  1221.  
  1222.     if (my_token == peer_token) {
  1223.         delay(10);
  1224.         do_handshake();
  1225.         return;
  1226.     }
  1227.  
  1228.     am_host = (my_token > peer_token);
  1229.     my_id   = am_host ? 0 : 1;
  1230.     opp_id  = am_host ? 1 : 0;
  1231.  
  1232.     if (am_host) {
  1233.         link_write_packet(PKT_START, nullptr, 0);
  1234.         my_turn = true;
  1235. #if DEBUG_PRINT
  1236.         Serial.println(F("[Handshake] HOST. I start."));
  1237. #endif
  1238.     }
  1239.     else {
  1240.         uint8_t type;
  1241.         uint8_t buf[1];
  1242.         uint32_t start = millis();
  1243.         uint32_t last_log = start;
  1244.         while (true) {
  1245.             int n = link_read_packet(type, buf, sizeof(buf), 3000);
  1246.             if (n == 0 && type == PKT_START) {
  1247.                 break;
  1248.             }
  1249.             uint32_t current = millis();
  1250.             if (current - start > WAIT_TIMEOUT) {
  1251. #if DEBUG_PRINT
  1252.                 Serial.println(F("[Handshake] Timeout waiting for host START. Forcing restart..."));
  1253. #endif
  1254.                 handle_peer_restart();
  1255.                 return;
  1256.             }
  1257.             if (current - last_log > 10000) {
  1258. #if DEBUG_PRINT
  1259.                 Serial.println(F("[Handshake] Still waiting for host START..."));
  1260. #endif
  1261.                 last_log = current;
  1262.             }
  1263.         }
  1264.         my_turn = false;
  1265. #if DEBUG_PRINT
  1266.         Serial.println(F("[Handshake] GUEST. Opponent starts."));
  1267. #endif
  1268.     }
  1269. }
  1270.  
  1271. #if !TEST_WITH_PYTHON
  1272. // Ask user for player mode; default is Computer.
  1273. static void prompt_player_mode() {
  1274.     Serial.println(F("\n=== Player Setup ==="));
  1275.     Serial.println(F("Type 'H' for Human, 'C' for Computer."));
  1276.     Serial.println(F("Default: C after 5s."));
  1277.     Serial.print(F("> "));
  1278.     read_flush_input(Serial);
  1279.     char line[8];
  1280.     if (read_line(Serial, line, sizeof(line), 5000)) {
  1281.         is_human = (to_upper(line[0]) == 'H');
  1282.     }
  1283.     else {
  1284.         is_human = false;
  1285.     }
  1286.  
  1287.     if (is_human) {
  1288.         Serial.println(F("Manual ship placement? (Y/n)"));
  1289.         Serial.println(F("Default: Y after 5s."));
  1290.         Serial.print(F("> "));
  1291.         if (read_line(Serial, line, sizeof(line), 5000)) {
  1292.             manual_ship_placement = (to_upper(line[0]) != 'N');
  1293.         }
  1294.         else {
  1295.             manual_ship_placement = true;
  1296.         }
  1297.     }
  1298.     else {
  1299.         manual_ship_placement = false;
  1300.         Serial.println(F("Computer player selected."));
  1301.     }
  1302. }
  1303. #endif
  1304.  
  1305. // Perform placement phase.
  1306. static void placement_phase() {
  1307.     if (am_host) {
  1308. #if DEBUG_PRINT
  1309.         Serial.println(F("[Placement] HOST placing fleet..."));
  1310. #endif
  1311.         if (is_human && manual_ship_placement) {
  1312.             collect_human_fleet_placements(false);
  1313.         }
  1314.         else {
  1315.             ai_fleet_placement_no_crosscheck();
  1316.         }
  1317. #if DEBUG_PRINT
  1318.         Serial.println(F("[Placement] Waiting for peer..."));
  1319. #endif
  1320.         wait_for_peer_placement_done();
  1321.     }
  1322.     else {
  1323. #if DEBUG_PRINT
  1324.         Serial.println(F("[Placement] Waiting for host..."));
  1325. #endif
  1326.         wait_for_peer_placement_done();
  1327. #if DEBUG_PRINT
  1328.         Serial.println(F("[Placement] GUEST placing fleet..."));
  1329. #endif
  1330.         if (is_human && manual_ship_placement) {
  1331.             collect_human_fleet_placements(false);
  1332.         }
  1333.         else {
  1334.             ai_fleet_placement_no_crosscheck();
  1335.         }
  1336.     }
  1337. }
  1338.  
  1339. // ------------------------------ Arduino API ----------------------------------
  1340.  
  1341. // Arduino setup entrypoint.
  1342. void setup() {
  1343. #if !TEST_WITH_PYTHON
  1344.     Serial.begin(DEBUG_SERIAL_BAUD);
  1345.     while (!Serial) {
  1346.         // Nano returns immediately
  1347.     }
  1348.     delay(100);
  1349.  
  1350.     analogReference(DEFAULT);
  1351.     pinMode(A0, INPUT);
  1352.  
  1353. #if DEBUG_PRINT
  1354.     Serial.println(F("\nBattleship starting..."));
  1355.     Serial.println(F("AltSoftSerial on D9 (TX), D8 (RX)."));
  1356. #endif
  1357.  
  1358.     prompt_player_mode();
  1359.  
  1360.     link.begin(LINK_BAUD);
  1361. #else
  1362.     is_human = false;
  1363.     manual_ship_placement = false;
  1364.     Serial.begin(LINK_BAUD);
  1365.     analogReference(DEFAULT);
  1366.     pinMode(A0, INPUT);
  1367. #endif
  1368.     delay(50);
  1369.  
  1370.     board.clear();
  1371.     do_handshake();
  1372.     placement_phase();
  1373.  
  1374. #if DEBUG_PRINT
  1375.     Serial.println(F("Ships placed."));
  1376.     board.print_my_board(Serial);
  1377. #endif
  1378. }
  1379.  
  1380. // Arduino loop entrypoint.
  1381. void loop() {
  1382.     if (game_over) {
  1383.         service_link_nonblocking();
  1384.         delay(100);
  1385.         return;
  1386.     }
  1387.  
  1388.     print_turn_banner();
  1389.  
  1390.     if (my_turn) {
  1391.         uint8_t idx = 0;
  1392.         if (is_human) {
  1393.             idx = prompt_human_move_idx();
  1394.         }
  1395.         else {
  1396.             idx = board.choose_move(rng_state);
  1397.         }
  1398.  
  1399.         if (board.move_was_made(idx)) {
  1400.             for (uint8_t i = 0; i < CELLS; ++i) {
  1401.                 if (!board.move_was_made(i) && board.opp_view[i] == 0) {
  1402.                     idx = i;
  1403.                     break;
  1404.                 }
  1405.             }
  1406.         }
  1407.  
  1408.         board.remember_move(idx);
  1409.  
  1410. #if DEBUG_PRINT
  1411.         Serial.print(F("My shot at "));
  1412.         print_cell(idx);
  1413.         Serial.println();
  1414. #endif
  1415.  
  1416.         result_code_t r;
  1417.         if (!send_move_and_get_result(idx, r)) {
  1418. #if DEBUG_PRINT
  1419.             Serial.println(F("Link error waiting for RESULT."));
  1420. #endif
  1421.             maybe_receive_gameover_nonblocking();
  1422.             delay(50);
  1423.             return;
  1424.         }
  1425.  
  1426.         board.mark_opp(idx, r);
  1427.  
  1428. #if DEBUG_PRINT
  1429.         Serial.print(F("Result: "));
  1430.         switch (r) {
  1431.             case RES_MISS: {
  1432.                 Serial.println(F("MISS"));
  1433.                 break;
  1434.             }
  1435.             case RES_HIT: {
  1436.                 Serial.println(F("HIT"));
  1437.                 break;
  1438.             }
  1439.             case RES_SUNK: {
  1440.                 Serial.println(F("SUNK!"));
  1441.                 break;
  1442.             }
  1443.             case RES_REPEAT: {
  1444.                 Serial.println(F("REPEAT? (duplicate)"));
  1445.                 break;
  1446.             }
  1447.             default: {
  1448.                 Serial.println(F("ERROR"));
  1449.                 break;
  1450.             }
  1451.         }
  1452. #endif
  1453.  
  1454.         maybe_receive_gameover_nonblocking();
  1455.         my_turn = false;
  1456.     }
  1457.     else {
  1458.         uint8_t type;
  1459.         uint8_t buf[2];
  1460.         int n = link_read_packet(type, buf, sizeof(buf), 5000);
  1461.         if (n >= 0) {
  1462.             if (type == PKT_MOVE && n >= 1) {
  1463.                 uint8_t idx = buf[0];
  1464.                 respond_to_incoming_move(idx);
  1465.                 maybe_receive_gameover_nonblocking();
  1466.                 my_turn = !game_over;
  1467.             }
  1468.             else {
  1469.                 if (type == PKT_HELLO && n == 4) {
  1470.                     handle_peer_restart();
  1471.                 }
  1472.                 if (type == PKT_PLACE_CHECK && n >= 1) {
  1473.                     uint8_t rc = 0;
  1474.                     link_write_packet(PKT_PLACE_REPLY, &rc, 1);
  1475.                 }
  1476.                 if (type == PKT_GAMEOVER && n >= 1) {
  1477.                     uint8_t winner = buf[0];
  1478.                     if (winner == my_id) {
  1479. #if DEBUG_PRINT
  1480.                         Serial.println(F("\n*** GAME OVER: We win. ***"));
  1481. #endif
  1482.                     }
  1483.                     else {
  1484. #if DEBUG_PRINT
  1485.                         Serial.println(F("\n*** GAME OVER: Opponent wins. ***"));
  1486. #endif
  1487.                     }
  1488.                     game_over = true;
  1489.                 }
  1490.                 delay(10);
  1491.                 return;
  1492.             }
  1493.         }
  1494.         else {
  1495.             service_place_check_once_nonblocking();
  1496.             maybe_receive_gameover_nonblocking();
  1497.             delay(10);
  1498.             return;
  1499.         }
  1500.     }
  1501.  
  1502.     show_boards_if_configured();
  1503.     delay(30);
  1504. }
Advertisement
Add Comment
Please, Sign In to add comment