MoKy

Yolanda R position.cpp

Jun 1st, 2025
35
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.03 KB | None | 1 0
  1. #include "position.h"
  2. #include <algorithm>
  3.  
  4. // Initialize ray table
  5. RayTable Rays;
  6.  
  7. // Generate all legal moves for the current player
  8. std::vector<Move> Position::generateMoves() const {
  9. std::vector<Move> moves;
  10.  
  11. // Masks for empty and occupied squares
  12. Bitboard emptyMask = emptySquares;
  13.  
  14. // Mask of stones belonging to side to move
  15. Bitboard playerBits = whiteToMove ? whitePieces : blackPieces;
  16. int stonesInHand = whiteToMove ? whiteStonesInHand : blackStonesInHand;
  17.  
  18. // 1. Mandatory FLIP moves: flip the nearest non-flipped blocker (own or opponent)
  19. std::vector<Move> flipMoves;
  20.  
  21. // Mask of all stones (including flipped)
  22. Bitboard allStonesMask = whitePieces | blackPieces | flippedPieces;
  23.  
  24. // Mask of non-flipped stones that can be flipped
  25. Bitboard nonFlippedStoneMask = whitePieces | blackPieces;
  26.  
  27. for (Bitboard bb = playerBits; bb; bb &= bb - 1) {
  28. int fromSq = __builtin_ctz(bb);
  29.  
  30. // Helper: find the first blocker (of any type) along a ray
  31. auto firstBlock = [&](const Bitboard ray, bool reversed) -> int {
  32. Bitboard blockers = ray & allStonesMask;
  33. if (!blockers) return -1; // No blocker found
  34.  
  35. // Use ctz directly for forward direction, adjusted clz for reversed
  36. return reversed ? 31 - __builtin_clz(blockers) : __builtin_ctz(blockers);
  37. };
  38.  
  39. // Check all four orthogonal directions
  40. int northBlocker = firstBlock(Rays.getNorth(fromSq), false);
  41. int eastBlocker = firstBlock(Rays.getEast(fromSq), false);
  42. int southBlocker = firstBlock(Rays.getSouth(fromSq), true);
  43. int westBlocker = firstBlock(Rays.getWest(fromSq), true);
  44.  
  45. // Add FLIP moves for non-flipped blockers
  46. if (northBlocker != -1 && (nonFlippedStoneMask & (Bitboard(1) << northBlocker))) {
  47. flipMoves.emplace_back(fromSq, northBlocker, FLIP);
  48. }
  49. if (eastBlocker != -1 && (nonFlippedStoneMask & (Bitboard(1) << eastBlocker))) {
  50. flipMoves.emplace_back(fromSq, eastBlocker, FLIP);
  51. }
  52. if (southBlocker != -1 && (nonFlippedStoneMask & (Bitboard(1) << southBlocker))) {
  53. flipMoves.emplace_back(fromSq, southBlocker, FLIP);
  54. }
  55. if (westBlocker != -1 && (nonFlippedStoneMask & (Bitboard(1) << westBlocker))) {
  56. flipMoves.emplace_back(fromSq, westBlocker, FLIP);
  57. }
  58. }
  59.  
  60. // FLIP has absolute priority - if any available, return only those
  61. if (!flipMoves.empty()) {
  62. return flipMoves;
  63. }
  64.  
  65. // 2. No FLIP possible: generate DROP, MOVE, REMOVE
  66.  
  67. // 2a) DROP: place from hand onto any empty square
  68. if (stonesInHand > 0) {
  69. for (Bitboard em = emptyMask; em; em &= em - 1) {
  70. int sq = __builtin_ctz(em);
  71. moves.emplace_back(NO_SQUARE, sq, DROP);
  72. }
  73. }
  74.  
  75. // 2b) MOVE: slide stones to empty squares before first blocker or edge
  76. for (Bitboard bb = playerBits; bb; bb &= bb - 1) {
  77. int fromSq = __builtin_ctz(bb);
  78.  
  79. // Process each direction
  80. auto collectSlides = [&](const Bitboard ray, bool reversed) {
  81. Bitboard blockers = ray & allStonesMask;
  82. Bitboard reach;
  83.  
  84. if (blockers) {
  85. int blockSq = reversed ? 31 - __builtin_clz(blockers) : __builtin_ctz(blockers);
  86.  
  87. // Calculate reachable squares up to blocker
  88. if (!reversed)
  89. reach = ray & ((Bitboard(1) << blockSq) - 1);
  90. else
  91. reach = ray & ~((Bitboard(1) << (blockSq + 1)) - 1);
  92. } else {
  93. // No blockers - can reach all squares in this direction
  94. reach = ray;
  95. }
  96.  
  97. // Generate moves to all empty squares in reach
  98. for (Bitboard tgt = reach & emptyMask; tgt; tgt &= tgt - 1) {
  99. int toSq = __builtin_ctz(tgt);
  100. moves.emplace_back(fromSq, toSq, MOVE);
  101. }
  102. };
  103.  
  104. collectSlides(Rays.getNorth(fromSq), false);
  105. collectSlides(Rays.getEast(fromSq), false);
  106. collectSlides(Rays.getSouth(fromSq), true);
  107. collectSlides(Rays.getWest(fromSq), true);
  108. }
  109.  
  110. // 2c) REMOVE: move onto flipped stone, removing it
  111. for (Bitboard bb = playerBits; bb; bb &= bb - 1) {
  112. int fromSq = __builtin_ctz(bb);
  113.  
  114. auto collectRemoves = [&](const Bitboard ray, bool reversed) {
  115. Bitboard blockers = ray & allStonesMask;
  116. if (!blockers) return;
  117.  
  118. int blockSq = reversed ? 31 - __builtin_clz(blockers) : __builtin_ctz(blockers);
  119. Bitboard mask = Bitboard(1) << blockSq;
  120.  
  121. // Can only remove flipped stones
  122. if (flippedPieces & mask) {
  123. moves.emplace_back(fromSq, blockSq, REMOVE);
  124. }
  125. };
  126.  
  127. collectRemoves(Rays.getNorth(fromSq), false);
  128. collectRemoves(Rays.getEast(fromSq), false);
  129. collectRemoves(Rays.getSouth(fromSq), true);
  130. collectRemoves(Rays.getWest(fromSq), true);
  131. }
  132.  
  133. return moves;
  134. }
  135.  
  136. // Execute a move and update position state
  137. void Position::makeMove(const Move& move) {
  138. // Store the previous Zobrist key for unmake
  139. uint64_t previousKey = zobristKey;
  140.  
  141. // Compute bit masks for source and target
  142. Bitboard fromBit = move.from == NO_SQUARE ? 0 : (Bitboard(1) << move.from);
  143. Bitboard toBit = Bitboard(1) << move.to;
  144.  
  145. // Update side to move in zobrist key
  146. zobristKey ^= ZOBRIST_SIDE;
  147.  
  148. switch (move.type) {
  149. case FLIP:
  150. // Remove moving stone into hand
  151. if (whiteToMove) {
  152. whitePieces &= ~fromBit;
  153. whiteStonesInHand++;
  154. zobristKey ^= Zobrist::whitePiece[move.from];
  155. zobristKey ^= Zobrist::whiteHandCount[whiteStonesInHand-1];
  156. zobristKey ^= Zobrist::whiteHandCount[whiteStonesInHand];
  157. } else {
  158. blackPieces &= ~fromBit;
  159. blackStonesInHand++;
  160. zobristKey ^= Zobrist::blackPiece[move.from];
  161. zobristKey ^= Zobrist::blackHandCount[blackStonesInHand-1];
  162. zobristKey ^= Zobrist::blackHandCount[blackStonesInHand];
  163. }
  164.  
  165. // Mark target as flipped
  166. flippedPieces |= toBit;
  167. zobristKey ^= Zobrist::flipped[move.to];
  168.  
  169. // Remove target from whichever pieces it belonged to
  170. if (whitePieces & toBit) {
  171. whitePieces &= ~toBit;
  172. zobristKey ^= Zobrist::whitePiece[move.to];
  173. } else if (blackPieces & toBit) {
  174. blackPieces &= ~toBit;
  175. zobristKey ^= Zobrist::blackPiece[move.to];
  176. }
  177. break;
  178.  
  179. case DROP:
  180. // Place stone from hand to board
  181. if (whiteToMove) {
  182. whiteStonesInHand--;
  183. whitePieces |= toBit;
  184. zobristKey ^= Zobrist::whiteHandCount[whiteStonesInHand+1];
  185. zobristKey ^= Zobrist::whiteHandCount[whiteStonesInHand];
  186. zobristKey ^= Zobrist::whitePiece[move.to];
  187. } else {
  188. blackStonesInHand--;
  189. blackPieces |= toBit;
  190. zobristKey ^= Zobrist::blackHandCount[blackStonesInHand+1];
  191. zobristKey ^= Zobrist::blackHandCount[blackStonesInHand];
  192. zobristKey ^= Zobrist::blackPiece[move.to];
  193. }
  194. break;
  195.  
  196. case MOVE:
  197. // Move stone on board
  198. if (whiteToMove) {
  199. whitePieces &= ~fromBit;
  200. whitePieces |= toBit;
  201. zobristKey ^= Zobrist::whitePiece[move.from];
  202. zobristKey ^= Zobrist::whitePiece[move.to];
  203. } else {
  204. blackPieces &= ~fromBit;
  205. blackPieces |= toBit;
  206. zobristKey ^= Zobrist::blackPiece[move.from];
  207. zobristKey ^= Zobrist::blackPiece[move.to];
  208. }
  209. break;
  210.  
  211. case REMOVE:
  212. // Move onto flipped stone and remove it
  213. if (whiteToMove) {
  214. whitePieces &= ~fromBit;
  215. whitePieces |= toBit;
  216. zobristKey ^= Zobrist::whitePiece[move.from];
  217. zobristKey ^= Zobrist::whitePiece[move.to];
  218. } else {
  219. blackPieces &= ~fromBit;
  220. blackPieces |= toBit;
  221. zobristKey ^= Zobrist::blackPiece[move.from];
  222. zobristKey ^= Zobrist::blackPiece[move.to];
  223. }
  224. // Unflip the target
  225. flippedPieces &= ~toBit;
  226. zobristKey ^= Zobrist::flipped[move.to];
  227. break;
  228. }
  229.  
  230. // Recompute empty squares mask using direct hex value
  231. emptySquares = ~(whitePieces | blackPieces | flippedPieces) & 0xFFFF;
  232.  
  233. // Update move counters and history
  234. halfmoveClock++;
  235. fullmoveNumber += (whiteToMove ? 0 : 1);
  236. history.push_back(previousKey); // Store the previous key
  237.  
  238. // Switch side to move
  239. whiteToMove = !whiteToMove;
  240. }
  241.  
  242. // Check if the game is over
  243. bool Position::isGameOver() const {
  244. // Game is over if no legal moves
  245. return generateMoves().empty();
  246. }
  247.  
  248. // Determine the winner (if game is over)
  249. StoneColor Position::getWinner() const {
  250. if (!isGameOver()) {
  251. return NONE; // Game still in progress
  252. }
  253.  
  254. // The player who has no legal moves WINS (according to Yolanda rules)
  255. return whiteToMove ? WHITE : BLACK;
  256. }
  257.  
  258. // Evaluation function
  259. int Position::evaluate() const {
  260. // Material difference
  261. int whiteMaterial = __builtin_popcount(whitePieces) + whiteStonesInHand;
  262. int blackMaterial = __builtin_popcount(blackPieces) + blackStonesInHand;
  263.  
  264. // Count flipped stones
  265. int flippedCount = __builtin_popcount(flippedPieces);
  266.  
  267. // Evaluate mobility (number of legal moves)
  268. int mobilityBonus = 3;
  269. int moveCount = generateMoves().size();
  270. int mobilityScore = 0;
  271.  
  272. // In Yolanda, having no moves is a WIN
  273. if (moveCount == 0) {
  274. return whiteToMove ? +10000 : -10000;
  275. } else {
  276. mobilityScore = moveCount * mobilityBonus * (whiteToMove ? 1 : -1);
  277. }
  278.  
  279. // Return evaluation from white's perspective
  280. return (whiteMaterial - blackMaterial) * 15 + mobilityScore;
  281. }
  282.  
  283. // Unmake a move (for search)
  284. void Position::unmakeMove(const Move& move, StoneColor originalTarget) {
  285. // Switch side to move back
  286. whiteToMove = !whiteToMove;
  287.  
  288. // Revert history tracking
  289. halfmoveClock--;
  290. if (!whiteToMove) {
  291. fullmoveNumber--;
  292. }
  293.  
  294. // Get the previous Zobrist key
  295. uint64_t previousKey = 0;
  296. if (!history.empty()) {
  297. previousKey = history.back();
  298. history.pop_back();
  299. }
  300.  
  301. // Handle each move type differently
  302. switch (move.type) {
  303. case FLIP:
  304. // Restore moving stone from hand
  305. if (whiteToMove) {
  306. whitePieces |= (Bitboard(1) << move.from);
  307. whiteStonesInHand--;
  308. } else {
  309. blackPieces |= (Bitboard(1) << move.from);
  310. blackStonesInHand--;
  311. }
  312.  
  313. // Unflip the target
  314. flippedPieces &= ~(Bitboard(1) << move.to);
  315.  
  316. // Restore original color of target
  317. if (originalTarget == WHITE) {
  318. whitePieces |= (Bitboard(1) << move.to);
  319. } else if (originalTarget == BLACK) {
  320. blackPieces |= (Bitboard(1) << move.to);
  321. }
  322. break;
  323.  
  324. case DROP:
  325. // Remove stone from board back to hand
  326. if (whiteToMove) {
  327. whitePieces &= ~(Bitboard(1) << move.to);
  328. whiteStonesInHand++;
  329. } else {
  330. blackPieces &= ~(Bitboard(1) << move.to);
  331. blackStonesInHand++;
  332. }
  333. break;
  334.  
  335. case MOVE:
  336. // Move stone back to original position
  337. if (whiteToMove) {
  338. whitePieces &= ~(Bitboard(1) << move.to);
  339. whitePieces |= (Bitboard(1) << move.from);
  340. } else {
  341. blackPieces &= ~(Bitboard(1) << move.to);
  342. blackPieces |= (Bitboard(1) << move.from);
  343. }
  344. break;
  345.  
  346. case REMOVE:
  347. // Move stone back to original position
  348. if (whiteToMove) {
  349. whitePieces &= ~(Bitboard(1) << move.to);
  350. whitePieces |= (Bitboard(1) << move.from);
  351. } else {
  352. blackPieces &= ~(Bitboard(1) << move.to);
  353. blackPieces |= (Bitboard(1) << move.from);
  354. }
  355.  
  356. // Restore flipped stone
  357. flippedPieces |= (Bitboard(1) << move.to);
  358. break;
  359. }
  360.  
  361. // Recompute empty squares with direct hex value
  362. emptySquares = ~(whitePieces | blackPieces | flippedPieces) & 0xFFFF;
  363.  
  364. // Restore the previous Zobrist key directly
  365. zobristKey = previousKey;
  366. }
  367.  
  368. // Display the board state
  369. void Position::printBoard() const {
  370. std::cout << "\n +---+---+---+---+\n";
  371. for (int row = BOARD_SIZE - 1; row >= 0; row--) {
  372. std::cout << (row + 1) << " |";
  373. for (int col = 0; col < BOARD_SIZE; col++) {
  374. int square = row * BOARD_SIZE + col;
  375. Bitboard mask = Bitboard(1) << square;
  376. char piece = ' ';
  377. if (whitePieces & mask) piece = 'W';
  378. else if (blackPieces & mask) piece = 'B';
  379. else if (flippedPieces & mask) piece = 'O';
  380. else piece = '.';
  381. std::cout << ' ' << piece << ' ' << '|';
  382. }
  383. if (row == BOARD_SIZE - 1) {
  384. std::cout << " [" << blackStonesInHand << "]";
  385. if (!whiteToMove) std::cout << "*";
  386. } else if (row == 0) {
  387. std::cout << " [" << whiteStonesInHand << "]";
  388. if (whiteToMove) std::cout << "*";
  389. }
  390. std::cout << "\n +---+---+---+---+\n";
  391. }
  392. std::cout << " a b c d \n";
  393. }
  394.  
  395. // Debug function for FLIP detection
  396. void Position::debugFlipDetection(int fromSquare, int toSquare) const {
  397. // Validate squares
  398. if (fromSquare < 0 || fromSquare >= SQUARE_COUNT || toSquare < 0 || toSquare >= SQUARE_COUNT) {
  399. std::cout << "Invalid square indices!" << std::endl;
  400. return;
  401. }
  402.  
  403. // Check if 'from' square has a piece
  404. Bitboard fromBit = Bitboard(1) << fromSquare;
  405. if (!(whitePieces & fromBit) && !(blackPieces & fromBit)) {
  406. std::cout << "No piece at " << SQUARE_NAMES[fromSquare] << std::endl;
  407. return;
  408. }
  409.  
  410. // Check if 'to' square has a piece
  411. Bitboard toBit = Bitboard(1) << toSquare;
  412. if (!(whitePieces & toBit) && !(blackPieces & toBit) && !(flippedPieces & toBit)) {
  413. std::cout << "No piece at " << SQUARE_NAMES[toSquare] << std::endl;
  414. return;
  415. }
  416.  
  417. // Check if 'to' square has a flipped piece
  418. if (flippedPieces & toBit) {
  419. std::cout << "Target at " << SQUARE_NAMES[toSquare] << " is already flipped" << std::endl;
  420. return;
  421. }
  422.  
  423. // Determine direction
  424. int fromRow = fromSquare / BOARD_SIZE;
  425. int fromCol = fromSquare % BOARD_SIZE;
  426. int toRow = toSquare / BOARD_SIZE;
  427. int toCol = toSquare % BOARD_SIZE;
  428.  
  429. // Calculate deltas
  430. int rowDelta = toRow - fromRow;
  431. int colDelta = toCol - fromCol;
  432.  
  433. std::cout << "From " << SQUARE_NAMES[fromSquare] << " to " << SQUARE_NAMES[toSquare] << ":" << std::endl;
  434. std::cout << "Row delta: " << rowDelta << ", Col delta: " << colDelta << std::endl;
  435.  
  436. // Check if orthogonal
  437. if (rowDelta != 0 && colDelta != 0) {
  438. std::cout << "Not orthogonal movement!" << std::endl;
  439. return;
  440. }
  441.  
  442. // Determine ray direction
  443. Bitboard ray;
  444. bool reversed = false;
  445.  
  446. if (rowDelta > 0 && colDelta == 0) {
  447. std::cout << "Direction: North" << std::endl;
  448. ray = Rays.getNorth(fromSquare);
  449. } else if (rowDelta < 0 && colDelta == 0) {
  450. std::cout << "Direction: South" << std::endl;
  451. ray = Rays.getSouth(fromSquare);
  452. reversed = true;
  453. } else if (rowDelta == 0 && colDelta > 0) {
  454. std::cout << "Direction: East" << std::endl;
  455. ray = Rays.getEast(fromSquare);
  456. } else if (rowDelta == 0 && colDelta < 0) {
  457. std::cout << "Direction: West" << std::endl;
  458. ray = Rays.getWest(fromSquare);
  459. reversed = true;
  460. }
  461.  
  462. // Print ray content
  463. std::cout << "Ray bitmap: 0x" << std::hex << ray << std::dec << std::endl;
  464.  
  465. // Check if target is in ray
  466. if (!(ray & toBit)) {
  467. std::cout << "Target is not in ray path!" << std::endl;
  468. return;
  469. }
  470.  
  471. // Mask of all stones
  472. Bitboard allStonesMask = whitePieces | blackPieces | flippedPieces;
  473.  
  474. // Find the first blocker
  475. Bitboard blockers = ray & allStonesMask;
  476. if (!blockers) {
  477. std::cout << "No blockers found in ray!" << std::endl;
  478. return;
  479. }
  480.  
  481. int blockSq = reversed
  482. ? 31 - __builtin_clz(blockers)
  483. : __builtin_ctz(blockers);
  484.  
  485. std::cout << "First blocker: " << SQUARE_NAMES[blockSq] << std::endl;
  486.  
  487. if (blockSq != toSquare) {
  488. std::cout << "First blocker is not the target!" << std::endl;
  489. return;
  490. }
  491.  
  492. // Check if a FLIP move would be possible
  493. Bitboard targetBit = Bitboard(1) << blockSq;
  494. if ((whitePieces & targetBit) || (blackPieces & targetBit)) {
  495. std::cout << "A FLIP move is possible from " << SQUARE_NAMES[fromSquare]
  496. << " to " << SQUARE_NAMES[blockSq] << std::endl;
  497. } else {
  498. std::cout << "Target is flipped - cannot FLIP" << std::endl;
  499. }
  500. }
Advertisement
Add Comment
Please, Sign In to add comment