|
| 1 | +import { isValidMove } from './validation'; |
| 2 | +import { type GameType } from '../Types'; |
| 3 | +import { DEFAULT_BOARD, newGame } from '../Utils'; // Assuming newGame and DEFAULT_BOARD are exported from Utils |
| 4 | + |
| 5 | +// Helper function to create game states |
| 6 | +const createGameState = ( |
| 7 | + boardSetup: number[] | null, |
| 8 | + dice: [number, number], |
| 9 | + color: 'white' | 'black', |
| 10 | + whitePrison: number = 0, |
| 11 | + blackPrison: number = 0, |
| 12 | + whiteHome: number = 0, |
| 13 | + blackHome: number = 0 |
| 14 | +): GameType => { |
| 15 | + const game = newGame(); |
| 16 | + if (boardSetup) { |
| 17 | + game.board = [...boardSetup]; |
| 18 | + } else { |
| 19 | + game.board = [...DEFAULT_BOARD]; |
| 20 | + } |
| 21 | + game.dice = dice; |
| 22 | + game.color = color; |
| 23 | + game.turn = 'player1'; // Dummy turn ID |
| 24 | + game.prison = { white: whitePrison, black: blackPrison }; |
| 25 | + game.home = { white: whiteHome, black: blackHome }; |
| 26 | + game.status = 'rolled'; // Assume dice have been rolled |
| 27 | + return game; |
| 28 | +}; |
| 29 | + |
| 30 | +describe('isValidMove - Standard Moves', () => { |
| 31 | + it('should allow White to move to an empty point with correct die', () => { |
| 32 | + const board = [...DEFAULT_BOARD]; |
| 33 | + board[11] = 1; // White piece on point 12 (0-indexed: 11) |
| 34 | + board[5] = 0; // Point 6 (0-indexed: 5) is empty |
| 35 | + const game = createGameState(board, [6, 1], 'white'); |
| 36 | + expect(isValidMove(game, 11, 5, 6)).toBe(true); |
| 37 | + }); |
| 38 | + |
| 39 | + it('should allow Black to move to their own piece with correct die', () => { |
| 40 | + const board = [...DEFAULT_BOARD]; |
| 41 | + board[12] = -1; // Black piece on point 13 (0-indexed: 12) |
| 42 | + board[17] = -1; // Black piece on point 18 (0-indexed: 17) |
| 43 | + const game = createGameState(board, [5, 2], 'black'); |
| 44 | + expect(isValidMove(game, 12, 17, 5)).toBe(true); |
| 45 | + }); |
| 46 | + |
| 47 | + it('should allow White to hit a Black blot', () => { |
| 48 | + const board = [...DEFAULT_BOARD]; |
| 49 | + board[20] = 1; // White piece on point 21 (0-indexed: 20) |
| 50 | + board[17] = -1; // Black blot on point 18 (0-indexed: 17) |
| 51 | + const game = createGameState(board, [3, 4], 'white'); |
| 52 | + expect(isValidMove(game, 20, 17, 3)).toBe(true); |
| 53 | + }); |
| 54 | + |
| 55 | + it('should not allow Black to land on White\'s blocked point', () => { |
| 56 | + const board = [...DEFAULT_BOARD]; |
| 57 | + board[5] = -1; // Black piece on point 6 (0-indexed: 5) |
| 58 | + board[8] = 2; // White blocked point at 9 (0-indexed: 8) |
| 59 | + const game = createGameState(board, [3, 1], 'black'); |
| 60 | + expect(isValidMove(game, 5, 8, 3)).toBe(false); |
| 61 | + }); |
| 62 | + |
| 63 | + it('should not allow White to move in the wrong direction', () => { |
| 64 | + const board = [...DEFAULT_BOARD]; |
| 65 | + board[5] = 1; // White piece on point 6 (0-indexed: 5) |
| 66 | + const game = createGameState(board, [3, 1], 'white'); |
| 67 | + expect(isValidMove(game, 5, 8, 3)).toBe(false); // Attempting to move 5 to 8 (black's direction) |
| 68 | + }); |
| 69 | + |
| 70 | + it('should not allow Black to move if distance does not match die', () => { |
| 71 | + const board = [...DEFAULT_BOARD]; |
| 72 | + board[12] = -1; |
| 73 | + const game = createGameState(board, [5, 2], 'black'); |
| 74 | + expect(isValidMove(game, 12, 18, 5)).toBe(false); // Should be 12 to 17 for die 5 |
| 75 | + }); |
| 76 | + |
| 77 | + it('should not allow moving opponent\'s piece', () => { |
| 78 | + const board = [...DEFAULT_BOARD]; |
| 79 | + board[11] = -1; // Black piece on point 12 |
| 80 | + const game = createGameState(board, [6, 1], 'white'); // White's turn |
| 81 | + expect(isValidMove(game, 11, 5, 6)).toBe(false); |
| 82 | + }); |
| 83 | + |
| 84 | + it('should not allow moving from an empty point', () => { |
| 85 | + const board = [...DEFAULT_BOARD]; |
| 86 | + board[11] = 0; // Point 12 is empty |
| 87 | + const game = createGameState(board, [6, 1], 'white'); |
| 88 | + expect(isValidMove(game, 11, 5, 6)).toBe(false); |
| 89 | + }); |
| 90 | +}); |
| 91 | + |
| 92 | +describe('isValidMove - Re-entry from Bar', () => { |
| 93 | + it('should allow White to re-enter from bar to an empty point with die 3', () => { |
| 94 | + // Point 3 (0-indexed: 2) |
| 95 | + const game = createGameState([...DEFAULT_BOARD], [3, 1], 'white', 1); |
| 96 | + game.board[2] = 0; // Ensure point 3 is empty |
| 97 | + expect(isValidMove(game, 'white', 2, 3)).toBe(true); |
| 98 | + }); |
| 99 | + |
| 100 | + it('should allow Black to re-enter from bar and hit a White blot with die 5', () => { |
| 101 | + // Target point 20 (0-indexed: 19, since 24 - 5 = 19) |
| 102 | + const board = [...DEFAULT_BOARD]; |
| 103 | + board[19] = 1; // White blot on point 20 |
| 104 | + const game = createGameState(board, [5, 2], 'black', 0, 1); |
| 105 | + expect(isValidMove(game, 'black', 19, 5)).toBe(true); |
| 106 | + }); |
| 107 | + |
| 108 | + it('should not allow White to re-enter if target point is blocked by Black', () => { |
| 109 | + const board = [...DEFAULT_BOARD]; |
| 110 | + board[3] = -2; // Black blocks point 4 (0-indexed: 3) |
| 111 | + const game = createGameState(board, [4, 1], 'white', 1); |
| 112 | + expect(isValidMove(game, 'white', 3, 4)).toBe(false); |
| 113 | + }); |
| 114 | + |
| 115 | + it('should not allow Black to re-enter if "from" is a board point', () => { |
| 116 | + const game = createGameState([...DEFAULT_BOARD], [5, 2], 'black', 0, 1); |
| 117 | + expect(isValidMove(game, 0, 19, 5)).toBe(false); // from is 0, not 'black' |
| 118 | + }); |
| 119 | + |
| 120 | + it('should not allow White to re-enter if "to" point does not match die', () => { |
| 121 | + const game = createGameState([...DEFAULT_BOARD], [3, 1], 'white', 1); |
| 122 | + expect(isValidMove(game, 'white', 1, 3)).toBe(false); // Die 3, target should be 2, not 1 |
| 123 | + }); |
| 124 | + |
| 125 | + it('should not allow re-entry attempt if no pieces are on the bar', () => { |
| 126 | + const game = createGameState([...DEFAULT_BOARD], [3, 1], 'white', 0); // No pieces on bar |
| 127 | + expect(isValidMove(game, 'white', 2, 3)).toBe(false); |
| 128 | + }); |
| 129 | + |
| 130 | + it('should not allow a board move if player has pieces on the bar', () => { |
| 131 | + const game = createGameState([...DEFAULT_BOARD], [3,1], 'white', 1); // White has piece on bar |
| 132 | + game.board[11] = 1; // White piece on board |
| 133 | + expect(isValidMove(game, 11, 8, 3)).toBe(false); // Attempting board move |
| 134 | + }); |
| 135 | +}); |
| 136 | + |
| 137 | +describe('isValidMove - Bearing Off', () => { |
| 138 | + const allWhiteHomeBoard = () => { |
| 139 | + const board = Array(24).fill(0); |
| 140 | + board[0] = 2; board[1] = 2; board[2] = 2; board[3] = 2; board[4] = 2; board[5] = 5; // 15 pieces |
| 141 | + return board; |
| 142 | + }; |
| 143 | + const allBlackHomeBoard = () => { |
| 144 | + const board = Array(24).fill(0); |
| 145 | + board[18] = -2; board[19] = -2; board[20] = -2; board[21] = -2; board[22] = -2; board[23] = -5; // 15 pieces |
| 146 | + return board; |
| 147 | + }; |
| 148 | + |
| 149 | + it('should allow White to bear off with exact die when all pieces are home', () => { |
| 150 | + const game = createGameState(allWhiteHomeBoard(), [3, 1], 'white'); |
| 151 | + // Bearing off from point 3 (0-indexed: 2) with die 3 |
| 152 | + expect(isValidMove(game, 2, 'off', 3)).toBe(true); |
| 153 | + }); |
| 154 | + |
| 155 | + it('should allow Black to bear off with exact die when all pieces are home', () => { |
| 156 | + const game = createGameState(allBlackHomeBoard(), [4, 2], 'black'); |
| 157 | + // Bearing off from point 21 (0-indexed: 20) with die 4 (24-4 = 20) |
| 158 | + expect(isValidMove(game, 20, 'off', 4)).toBe(true); |
| 159 | + }); |
| 160 | + |
| 161 | + it('should allow White to bear off with higher die when all pieces are home and it\'s the furthest piece', () => { |
| 162 | + const board = allWhiteHomeBoard(); |
| 163 | + board[5] = 1; // Furthest piece is on point 6 (0-indexed: 5) |
| 164 | + board[4] = 0; board[3] = 0; // Clear higher points for this test |
| 165 | + const game = createGameState(board, [6, 1], 'white'); |
| 166 | + // Try to bear off piece from point 6 (idx 5) with die 6 (exact) - This is the setup |
| 167 | + // Now, if piece was at idx 2 (point 3), die 5 should take it off if 5,4 are empty |
| 168 | + board[5]=0; board[4]=0; board[3]=0; board[2]=1; // piece at point 3 (idx 2) |
| 169 | + const game2 = createGameState(board, [5,1], 'white'); |
| 170 | + expect(isValidMove(game2, 2, 'off', 5)).toBe(true); |
| 171 | + }); |
| 172 | + |
| 173 | + it('should allow Black to bear off with higher die when all pieces are home and it\'s the furthest piece', () => { |
| 174 | + const board = allBlackHomeBoard(); |
| 175 | + board[18] = -1; // Furthest piece is on point 19 (0-indexed: 18) |
| 176 | + board[19] = 0; board[20] = 0; // Clear higher points |
| 177 | + const game = createGameState(board, [6, 1], 'black'); |
| 178 | + // Try to bear off piece from point 19 (idx 18) with die 6 (exact) |
| 179 | + // Now, if piece was at idx 21 (point 22), die 5 (target 24-5=19) should take it off if 19,20 are empty |
| 180 | + board[18]=0; board[19]=0; board[20]=0; board[21]=-1; // piece at point 22 (idx 21) |
| 181 | + const game2 = createGameState(board, [5,1], 'black'); |
| 182 | + expect(isValidMove(game2, 21, 'off', 5)).toBe(true); |
| 183 | + }); |
| 184 | + |
| 185 | + it('should NOT allow White to bear off with higher die if there is a piece on a higher point', () => { |
| 186 | + const board = allWhiteHomeBoard(); |
| 187 | + board[2] = 1; // Piece on point 3 (idx 2) |
| 188 | + board[4] = 1; // Piece on point 5 (idx 4) - this should be moved first |
| 189 | + const game = createGameState(board, [5,1], 'white'); // Die 5 |
| 190 | + expect(isValidMove(game, 2, 'off', 5)).toBe(false); // Cannot bear off from point 3 if point 5 has a piece |
| 191 | + }); |
| 192 | + |
| 193 | + |
| 194 | + it('should not allow White to bear off if pieces are outside home board', () => { |
| 195 | + const board = allWhiteHomeBoard(); |
| 196 | + board[6] = 1; // Piece on point 7 (outside home) |
| 197 | + const game = createGameState(board, [3, 1], 'white'); |
| 198 | + expect(isValidMove(game, 2, 'off', 3)).toBe(false); |
| 199 | + }); |
| 200 | + |
| 201 | + it('should not allow Black to bear off if piece is on the bar', () => { |
| 202 | + const game = createGameState(allBlackHomeBoard(), [4, 2], 'black', 0, 1); // Black piece on bar |
| 203 | + expect(isValidMove(game, 20, 'off', 4)).toBe(false); |
| 204 | + }); |
| 205 | + |
| 206 | + it('should not allow White to bear off if die does not match point and not covered by higher die rule', () => { |
| 207 | + const game = createGameState(allWhiteHomeBoard(), [3, 1], 'white'); |
| 208 | + // Attempt to bear off from point 5 (0-indexed: 4) with die 3. Point 3 (idx 2) is not empty. |
| 209 | + expect(isValidMove(game, 4, 'off', 3)).toBe(false); |
| 210 | + }); |
| 211 | + |
| 212 | + it('should not allow White to bear off from a point not in the home board (e.g. point 7)', () => { |
| 213 | + const game = createGameState(allWhiteHomeBoard(), [1,1], 'white'); |
| 214 | + // Technically, all pieces are home, but 'from' is invalid. |
| 215 | + // isValidMove should catch from < 0 || from > 5 for white bearoff. |
| 216 | + // This specific test is slightly redundant with the "pieces outside home" one if that one is general, |
| 217 | + // but good for explicitness on `from` point. |
| 218 | + // Let's ensure a piece is actually at point 7 for this test to be meaningful beyond just "all home" |
| 219 | + const board = allWhiteHomeBoard(); // All home |
| 220 | + board[6] = 1; // Add a white piece at point 7 (idx 6) |
| 221 | + // Now, the "all pieces home" check in isValidMove should fail. |
| 222 | + // If we manually bypass that and just test the from point: |
| 223 | + // The `isValidMove` check `if (from < 0 || from > 5) return false;` for white bearoff should catch this. |
| 224 | + // This test might be better framed as "cannot select 'from' outside home board for bearing off" |
| 225 | + // But isValidMove's internal logic for bearoff (is all home? then is 'from' valid?) covers this. |
| 226 | + // The existing "pieces outside home" test is more robust. |
| 227 | + // For this test, let's make sure "all pieces are home" but from is wrong. |
| 228 | + const gameAllHome = createGameState(allWhiteHomeBoard(), [1,1], 'white'); |
| 229 | + expect(isValidMove(gameAllHome, 6, 'off', 1)).toBe(false); // from=6 is point 7 |
| 230 | + }); |
| 231 | +}); |
0 commit comments