| 1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
| 2 | import { randInt } from "@/utils/alea"; |
| 3 | |
| 4 | export class JanggiRules extends ChessRules { |
| 5 | |
| 6 | static get Options() { |
| 7 | return null; |
| 8 | } |
| 9 | |
| 10 | static get Monochrome() { |
| 11 | return true; |
| 12 | } |
| 13 | |
| 14 | static get Notoodark() { |
| 15 | return true; |
| 16 | } |
| 17 | |
| 18 | static get Lines() { |
| 19 | let lines = []; |
| 20 | // Draw all inter-squares lines, shifted: |
| 21 | for (let i = 0; i < V.size.x; i++) |
| 22 | lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]); |
| 23 | for (let j = 0; j < V.size.y; j++) |
| 24 | lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]); |
| 25 | // Add palaces: |
| 26 | lines.push([[0.5, 3.5], [2.5, 5.5]]); |
| 27 | lines.push([[0.5, 5.5], [2.5, 3.5]]); |
| 28 | lines.push([[9.5, 3.5], [7.5, 5.5]]); |
| 29 | lines.push([[9.5, 5.5], [7.5, 3.5]]); |
| 30 | return lines; |
| 31 | } |
| 32 | |
| 33 | // No castle, but flag: bikjang |
| 34 | static get HasCastle() { |
| 35 | return false; |
| 36 | } |
| 37 | |
| 38 | static get HasEnpassant() { |
| 39 | return false; |
| 40 | } |
| 41 | |
| 42 | static get ELEPHANT() { |
| 43 | return "e"; |
| 44 | } |
| 45 | |
| 46 | static get CANNON() { |
| 47 | return "c"; |
| 48 | } |
| 49 | |
| 50 | static get ADVISOR() { |
| 51 | return "a"; |
| 52 | } |
| 53 | |
| 54 | static get PIECES() { |
| 55 | return [V.PAWN, V.ROOK, V.KNIGHT, V.ELEPHANT, V.ADVISOR, V.KING, V.CANNON]; |
| 56 | } |
| 57 | |
| 58 | getPpath(b) { |
| 59 | return "Janggi/" + b; |
| 60 | } |
| 61 | |
| 62 | static get size() { |
| 63 | return { x: 10, y: 9}; |
| 64 | } |
| 65 | |
| 66 | static IsGoodFlags(flags) { |
| 67 | // bikjang status of last move + pass |
| 68 | return !!flags.match(/^[0-2]{2,2}$/); |
| 69 | } |
| 70 | |
| 71 | aggregateFlags() { |
| 72 | return [this.bikjangFlag, this.passFlag]; |
| 73 | } |
| 74 | |
| 75 | disaggregateFlags(flags) { |
| 76 | this.bikjangFlag = flags[0]; |
| 77 | this.passFlag = flags[1]; |
| 78 | } |
| 79 | |
| 80 | getFlagsFen() { |
| 81 | return this.bikjangFlag.toString() + this.passFlag.toString() |
| 82 | } |
| 83 | |
| 84 | setFlags(fenflags) { |
| 85 | this.bikjangFlag = parseInt(fenflags.charAt(0), 10); |
| 86 | this.passFlag = parseInt(fenflags.charAt(1), 10); |
| 87 | } |
| 88 | |
| 89 | setOtherVariables(fen) { |
| 90 | super.setOtherVariables(fen); |
| 91 | // Sub-turn is useful only at first move... |
| 92 | this.subTurn = 1; |
| 93 | } |
| 94 | |
| 95 | getPotentialMovesFrom([x, y]) { |
| 96 | let moves = []; |
| 97 | const c = this.getColor(x, y); |
| 98 | const oppCol = V.GetOppCol(c); |
| 99 | if (this.kingPos[c][0] == x && this.kingPos[c][1] == y) { |
| 100 | // Add pass move (might be impossible if undercheck) |
| 101 | moves.push( |
| 102 | new Move({ |
| 103 | appear: [], |
| 104 | vanish: [], |
| 105 | start: { x: this.kingPos[c][0], y: this.kingPos[c][1] }, |
| 106 | end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] } |
| 107 | }) |
| 108 | ); |
| 109 | } |
| 110 | // TODO: next "if" is mutually exclusive with the block above |
| 111 | if (this.movesCount <= 1) { |
| 112 | const firstRank = (this.movesCount == 0 ? 9 : 0); |
| 113 | const initDestFile = new Map([[1, 2], [7, 6]]); |
| 114 | // Only option is knight --> elephant swap: |
| 115 | if ( |
| 116 | x == firstRank && |
| 117 | !!initDestFile.get(y) && |
| 118 | this.getPiece(x, y) == V.KNIGHT |
| 119 | ) { |
| 120 | const destFile = initDestFile.get(y); |
| 121 | moves.push( |
| 122 | new Move({ |
| 123 | appear: [ |
| 124 | new PiPo({ |
| 125 | x: x, |
| 126 | y: destFile, |
| 127 | c: c, |
| 128 | p: V.KNIGHT |
| 129 | }), |
| 130 | new PiPo({ |
| 131 | x: x, |
| 132 | y: y, |
| 133 | c: c, |
| 134 | p: V.ELEPHANT |
| 135 | }) |
| 136 | ], |
| 137 | vanish: [ |
| 138 | new PiPo({ |
| 139 | x: x, |
| 140 | y: y, |
| 141 | c: c, |
| 142 | p: V.KNIGHT |
| 143 | }), |
| 144 | new PiPo({ |
| 145 | x: x, |
| 146 | y: destFile, |
| 147 | c: c, |
| 148 | p: V.ELEPHANT |
| 149 | }) |
| 150 | ], |
| 151 | start: { x: x, y: y }, |
| 152 | end: { x: x, y: destFile } |
| 153 | }) |
| 154 | ); |
| 155 | } |
| 156 | } |
| 157 | else { |
| 158 | let normalMoves = []; |
| 159 | switch (this.getPiece(x, y)) { |
| 160 | case V.PAWN: |
| 161 | normalMoves = this.getPotentialPawnMoves([x, y]); |
| 162 | break; |
| 163 | case V.ROOK: |
| 164 | normalMoves = this.getPotentialRookMoves([x, y]); |
| 165 | break; |
| 166 | case V.KNIGHT: |
| 167 | normalMoves = this.getPotentialKnightMoves([x, y]); |
| 168 | break; |
| 169 | case V.ELEPHANT: |
| 170 | normalMoves = this.getPotentialElephantMoves([x, y]); |
| 171 | break; |
| 172 | case V.ADVISOR: |
| 173 | normalMoves = this.getPotentialAdvisorMoves([x, y]); |
| 174 | break; |
| 175 | case V.KING: |
| 176 | normalMoves = this.getPotentialKingMoves([x, y]); |
| 177 | break; |
| 178 | case V.CANNON: |
| 179 | normalMoves = this.getPotentialCannonMoves([x, y]); |
| 180 | break; |
| 181 | } |
| 182 | Array.prototype.push.apply(moves, normalMoves); |
| 183 | } |
| 184 | return moves; |
| 185 | } |
| 186 | |
| 187 | getPotentialPawnMoves([x, y]) { |
| 188 | const c = this.getColor(x, y); |
| 189 | const oppCol = V.GetOppCol(c); |
| 190 | const shiftX = (c == 'w' ? -1 : 1); |
| 191 | const rank23 = (oppCol == 'w' ? [8, 7] : [1, 2]); |
| 192 | let steps = [[shiftX, 0], [0, -1], [0, 1]]; |
| 193 | // Diagonal moves inside enemy palace: |
| 194 | if (y == 4 && x == rank23[0]) |
| 195 | Array.prototype.push.apply(steps, [[shiftX, 1], [shiftX, -1]]); |
| 196 | else if (x == rank23[1]) { |
| 197 | if (y == 3) steps.push([shiftX, 1]); |
| 198 | else if (y == 5) steps.push([shiftX, -1]); |
| 199 | } |
| 200 | return super.getSlideNJumpMoves([x, y], steps, 1); |
| 201 | } |
| 202 | |
| 203 | knightStepsFromRookStep(step) { |
| 204 | if (step[0] == 0) return [ [1, 2*step[1]], [-1, 2*step[1]] ]; |
| 205 | return [ [2*step[0], 1], [2*step[0], -1] ]; |
| 206 | } |
| 207 | |
| 208 | getPotentialKnightMoves([x, y]) { |
| 209 | let steps = []; |
| 210 | for (let rookStep of ChessRules.steps[V.ROOK]) { |
| 211 | const [i, j] = [x + rookStep[0], y + rookStep[1]]; |
| 212 | if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { |
| 213 | Array.prototype.push.apply(steps, |
| 214 | // These moves might be impossible, but need to be checked: |
| 215 | this.knightStepsFromRookStep(rookStep)); |
| 216 | } |
| 217 | } |
| 218 | return super.getSlideNJumpMoves([x, y], steps, 1); |
| 219 | } |
| 220 | |
| 221 | elephantStepsFromRookStep(step) { |
| 222 | if (step[0] == 0) return [ [2, 3*step[1]], [-2, 3*step[1]] ]; |
| 223 | return [ [3*step[0], 2], [3*step[0], -2] ]; |
| 224 | } |
| 225 | |
| 226 | getPotentialElephantMoves([x, y]) { |
| 227 | let steps = []; |
| 228 | for (let rookStep of ChessRules.steps[V.ROOK]) { |
| 229 | const eSteps = this.elephantStepsFromRookStep(rookStep); |
| 230 | const [i, j] = [x + rookStep[0], y + rookStep[1]]; |
| 231 | if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { |
| 232 | // Check second crossing: |
| 233 | const knightSteps = this.knightStepsFromRookStep(rookStep); |
| 234 | for (let k of [0, 1]) { |
| 235 | const [ii, jj] = [x + knightSteps[k][0], y + knightSteps[k][1]]; |
| 236 | if (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY) |
| 237 | steps.push(eSteps[k]); //ok: same ordering |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | return super.getSlideNJumpMoves([x, y], steps, 1); |
| 242 | } |
| 243 | |
| 244 | palacePeopleMoves([x, y]) { |
| 245 | const c = this.getColor(x, y); |
| 246 | let steps = []; |
| 247 | // Orthogonal steps: |
| 248 | if (x < (c == 'w' ? 9 : 2)) steps.push([1, 0]); |
| 249 | if (x > (c == 'w' ? 7 : 0)) steps.push([-1, 0]); |
| 250 | if (y > 3) steps.push([0, -1]); |
| 251 | if (y < 5) steps.push([0, 1]); |
| 252 | // Diagonal steps, if in the middle or corner: |
| 253 | if ( |
| 254 | y != 4 && |
| 255 | ( |
| 256 | (c == 'w' && x != 8) || |
| 257 | (c == 'b' && x != 1) |
| 258 | ) |
| 259 | ) { |
| 260 | // In a corner: maximum one diagonal step available |
| 261 | let step = null; |
| 262 | const direction = (c == 'w' ? -1 : 1); |
| 263 | if ((c == 'w' && x == 9) || (c == 'b' && x == 0)) { |
| 264 | // On first line |
| 265 | if (y == 3) step = [direction, 1]; |
| 266 | else step = [direction, -1]; |
| 267 | } |
| 268 | else if ((c == 'w' && x == 7) || (c == 'b' && x == 2)) { |
| 269 | // On third line |
| 270 | if (y == 3) step = [-direction, 1]; |
| 271 | else step = [-direction, -1]; |
| 272 | } |
| 273 | steps.push(step); |
| 274 | } |
| 275 | else if ( |
| 276 | y == 4 && |
| 277 | ( |
| 278 | (c == 'w' && x == 8) || |
| 279 | (c == 'b' && x == 1) |
| 280 | ) |
| 281 | ) { |
| 282 | // At the middle: all directions available |
| 283 | Array.prototype.push.apply(steps, ChessRules.steps[V.BISHOP]); |
| 284 | } |
| 285 | return super.getSlideNJumpMoves([x, y], steps, 1); |
| 286 | } |
| 287 | |
| 288 | getPotentialAdvisorMoves(sq) { |
| 289 | return this.palacePeopleMoves(sq); |
| 290 | } |
| 291 | |
| 292 | getPotentialKingMoves(sq) { |
| 293 | return this.palacePeopleMoves(sq); |
| 294 | } |
| 295 | |
| 296 | getPotentialRookMoves([x, y]) { |
| 297 | let moves = super.getPotentialRookMoves([x, y]); |
| 298 | if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) { |
| 299 | // In a corner of a palace: move along diagonal |
| 300 | const step = [[0, 7].includes(x) ? 1 : -1, 4 - y]; |
| 301 | const oppCol = V.GetOppCol(this.getColor(x, y)); |
| 302 | for (let i of [1, 2]) { |
| 303 | const [xx, yy] = [x + i * step[0], y + i * step[1]]; |
| 304 | if (this.board[xx][yy] == V.EMPTY) |
| 305 | moves.push(this.getBasicMove([x, y], [xx, yy])); |
| 306 | else { |
| 307 | if (this.getColor(xx, yy) == oppCol) |
| 308 | moves.push(this.getBasicMove([x, y], [xx, yy])); |
| 309 | break; |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | else if (y == 4 && [1, 8].includes(x)) { |
| 314 | // In the middle of a palace: 4 one-diagonal-step to check |
| 315 | Array.prototype.push.apply( |
| 316 | moves, |
| 317 | super.getSlideNJumpMoves([x, y], ChessRules.steps[V.BISHOP], 1) |
| 318 | ); |
| 319 | } |
| 320 | return moves; |
| 321 | } |
| 322 | |
| 323 | // NOTE: (mostly) duplicated from Shako (TODO?) |
| 324 | getPotentialCannonMoves([x, y]) { |
| 325 | const oppCol = V.GetOppCol(this.turn); |
| 326 | let moves = []; |
| 327 | // Look in every direction until an obstacle (to jump) is met |
| 328 | for (const step of V.steps[V.ROOK]) { |
| 329 | let i = x + step[0]; |
| 330 | let j = y + step[1]; |
| 331 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { |
| 332 | i += step[0]; |
| 333 | j += step[1]; |
| 334 | } |
| 335 | // Then, search for an enemy (if jumped piece isn't a cannon) |
| 336 | if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) { |
| 337 | i += step[0]; |
| 338 | j += step[1]; |
| 339 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { |
| 340 | moves.push(this.getBasicMove([x, y], [i, j])); |
| 341 | i += step[0]; |
| 342 | j += step[1]; |
| 343 | } |
| 344 | if ( |
| 345 | V.OnBoard(i, j) && |
| 346 | this.getColor(i, j) == oppCol && |
| 347 | this.getPiece(i, j) != V.CANNON |
| 348 | ) { |
| 349 | moves.push(this.getBasicMove([x, y], [i, j])); |
| 350 | } |
| 351 | } |
| 352 | } |
| 353 | if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) { |
| 354 | // In a corner of a palace: hop over next obstacle if possible |
| 355 | const step = [[0, 7].includes(x) ? 1 : -1, 4 - y]; |
| 356 | const [x1, y1] = [x + step[0], y + step[1]]; |
| 357 | const [x2, y2] = [x + 2 * step[0], y + 2 * step[1]]; |
| 358 | if ( |
| 359 | this.board[x1][y1] != V.EMPTY && |
| 360 | this.getPiece(x1, y1) != V.CANNON && |
| 361 | ( |
| 362 | this.board[x2][y2] == V.EMPTY || |
| 363 | ( |
| 364 | this.getColor(x2, y2) == oppCol && |
| 365 | this.getPiece(x2, y2) != V.CANNON |
| 366 | ) |
| 367 | ) |
| 368 | ) { |
| 369 | moves.push(this.getBasicMove([x, y], [x2, y2])); |
| 370 | } |
| 371 | } |
| 372 | return moves; |
| 373 | } |
| 374 | |
| 375 | // (King) Never attacked by advisor, since it stays in the palace |
| 376 | isAttacked(sq, color) { |
| 377 | return ( |
| 378 | this.isAttackedByPawn(sq, color) || |
| 379 | this.isAttackedByRook(sq, color) || |
| 380 | this.isAttackedByKnight(sq, color) || |
| 381 | this.isAttackedByElephant(sq, color) || |
| 382 | this.isAttackedByCannon(sq, color) |
| 383 | ); |
| 384 | } |
| 385 | |
| 386 | onPalaceDiagonal([x, y]) { |
| 387 | return ( |
| 388 | (y == 4 && [1, 8].includes(x)) || |
| 389 | ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) |
| 390 | ); |
| 391 | } |
| 392 | |
| 393 | isAttackedByPawn([x, y], color) { |
| 394 | const shiftX = (color == 'w' ? 1 : -1); //shift from king |
| 395 | if (super.isAttackedBySlideNJump( |
| 396 | [x, y], color, V.PAWN, [[shiftX, 0], [0, 1], [0, -1]], 1) |
| 397 | ) { |
| 398 | return true; |
| 399 | } |
| 400 | if (this.onPalaceDiagonal([x, y])) { |
| 401 | for (let yStep of [-1, 1]) { |
| 402 | const [xx, yy] = [x + shiftX, y + yStep]; |
| 403 | if ( |
| 404 | this.onPalaceDiagonal([xx,yy]) && |
| 405 | this.board[xx][yy] != V.EMPTY && |
| 406 | this.getColor(xx, yy) == color && |
| 407 | this.getPiece(xx, yy) == V.PAWN |
| 408 | ) { |
| 409 | return true; |
| 410 | } |
| 411 | } |
| 412 | } |
| 413 | return false; |
| 414 | } |
| 415 | |
| 416 | knightStepsFromBishopStep(step) { |
| 417 | return [ [2*step[0], step[1]], [step[0], 2*step[1]] ]; |
| 418 | } |
| 419 | |
| 420 | isAttackedByKnight([x, y], color) { |
| 421 | // Check bishop steps: if empty, look continuation knight step |
| 422 | let steps = []; |
| 423 | for (let s of ChessRules.steps[V.BISHOP]) { |
| 424 | const [i, j] = [x + s[0], y + s[1]]; |
| 425 | if ( |
| 426 | V.OnBoard(i, j) && |
| 427 | this.board[i][j] == V.EMPTY |
| 428 | ) { |
| 429 | Array.prototype.push.apply(steps, this.knightStepsFromBishopStep(s)); |
| 430 | } |
| 431 | } |
| 432 | return ( |
| 433 | super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, 1) |
| 434 | ); |
| 435 | } |
| 436 | |
| 437 | elephantStepsFromBishopStep(step) { |
| 438 | return [ [3*step[0], 2*step[1]], [2*step[0], 3*step[1]] ]; |
| 439 | } |
| 440 | |
| 441 | isAttackedByElephant([x, y], color) { |
| 442 | // Check bishop steps: if empty, look continuation elephant step |
| 443 | let steps = []; |
| 444 | for (let s of ChessRules.steps[V.BISHOP]) { |
| 445 | const [i1, j1] = [x + s[0], y + s[1]]; |
| 446 | const [i2, j2] = [x + 2*s[0], y + 2*s[1]]; |
| 447 | if ( |
| 448 | V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY && |
| 449 | V.OnBoard(i1, j1) && this.board[i1][j1] == V.EMPTY |
| 450 | ) { |
| 451 | Array.prototype.push.apply(steps, this.elephantStepsFromBishopStep(s)); |
| 452 | } |
| 453 | } |
| 454 | return ( |
| 455 | super.isAttackedBySlideNJump([x, y], color, V.ELEPHANT, steps, 1) |
| 456 | ); |
| 457 | } |
| 458 | |
| 459 | isAttackedByRook([x, y], color) { |
| 460 | if (super.isAttackedByRook([x, y], color)) return true; |
| 461 | // Also check diagonals, if inside palace |
| 462 | if (this.onPalaceDiagonal([x, y])) { |
| 463 | // TODO: next scan is clearly suboptimal |
| 464 | for (let s of ChessRules.steps[V.BISHOP]) { |
| 465 | for (let i of [1, 2]) { |
| 466 | const [xx, yy] = [x + i * s[0], y + i * s[1]]; |
| 467 | if ( |
| 468 | V.OnBoard(xx, yy) && |
| 469 | this.onPalaceDiagonal([xx, yy]) |
| 470 | ) { |
| 471 | if (this.board[xx][yy] != V.EMPTY) { |
| 472 | if ( |
| 473 | this.getColor(xx, yy) == color && |
| 474 | this.getPiece(xx, yy) == V.ROOK |
| 475 | ) { |
| 476 | return true; |
| 477 | } |
| 478 | break; |
| 479 | } |
| 480 | } |
| 481 | else continue; |
| 482 | } |
| 483 | } |
| 484 | } |
| 485 | return false; |
| 486 | } |
| 487 | |
| 488 | // NOTE: (mostly) duplicated from Shako (TODO?) |
| 489 | isAttackedByCannon([x, y], color) { |
| 490 | // Reversed process: is there an obstacle in line, |
| 491 | // and a cannon next in the same line? |
| 492 | for (const step of V.steps[V.ROOK]) { |
| 493 | let [i, j] = [x+step[0], y+step[1]]; |
| 494 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { |
| 495 | i += step[0]; |
| 496 | j += step[1]; |
| 497 | } |
| 498 | if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) { |
| 499 | // Keep looking in this direction |
| 500 | i += step[0]; |
| 501 | j += step[1]; |
| 502 | while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { |
| 503 | i += step[0]; |
| 504 | j += step[1]; |
| 505 | } |
| 506 | if ( |
| 507 | V.OnBoard(i, j) && |
| 508 | this.getPiece(i, j) == V.CANNON && |
| 509 | this.getColor(i, j) == color |
| 510 | ) { |
| 511 | return true; |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 | return false; |
| 516 | } |
| 517 | |
| 518 | getCurrentScore() { |
| 519 | if ([this.bikjangFlag, this.passFlag].includes(2)) return "1/2"; |
| 520 | const color = this.turn; |
| 521 | // super.atLeastOneMove() does not consider passing (OK) |
| 522 | if (this.underCheck(color) && !super.atLeastOneMove()) |
| 523 | return (color == "w" ? "0-1" : "1-0"); |
| 524 | return "*"; |
| 525 | } |
| 526 | |
| 527 | static get VALUES() { |
| 528 | return { |
| 529 | p: 2, |
| 530 | r: 13, |
| 531 | n: 5, |
| 532 | e: 3, |
| 533 | a: 3, |
| 534 | c: 7, |
| 535 | k: 1000 |
| 536 | }; |
| 537 | } |
| 538 | |
| 539 | static get SEARCH_DEPTH() { |
| 540 | return 2; |
| 541 | } |
| 542 | |
| 543 | static GenRandInitFen() { |
| 544 | // No randomization here (but initial setup choice) |
| 545 | return ( |
| 546 | "rnea1aenr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNEA1AENR w 0 00" |
| 547 | ); |
| 548 | } |
| 549 | |
| 550 | play(move) { |
| 551 | move.subTurn = this.subTurn; //much easier |
| 552 | if (this.movesCount >= 2 || this.subTurn == 2 || move.vanish.length == 0) { |
| 553 | this.turn = V.GetOppCol(this.turn); |
| 554 | this.subTurn = 1; |
| 555 | this.movesCount++; |
| 556 | } |
| 557 | else this.subTurn = 2; |
| 558 | move.flags = JSON.stringify(this.aggregateFlags()); |
| 559 | V.PlayOnBoard(this.board, move); |
| 560 | this.postPlay(move); |
| 561 | } |
| 562 | |
| 563 | postPlay(move) { |
| 564 | if (move.vanish.length > 0) super.postPlay(move); |
| 565 | else if (this.movesCount > 2) this.passFlag++; |
| 566 | // Update bikjang flag |
| 567 | if (this.kingPos['w'][1] == this.kingPos['b'][1]) { |
| 568 | const y = this.kingPos['w'][1]; |
| 569 | let bikjang = true; |
| 570 | for (let x = this.kingPos['b'][0] + 1; x < this.kingPos['w'][0]; x++) { |
| 571 | if (this.board[x][y] != V.EMPTY) { |
| 572 | bikjang = false; |
| 573 | break; |
| 574 | } |
| 575 | } |
| 576 | if (bikjang) this.bikjangFlag++; |
| 577 | else this.bikjangFlag = 0; |
| 578 | } |
| 579 | else this.bikjangFlag = 0; |
| 580 | } |
| 581 | |
| 582 | undo(move) { |
| 583 | this.disaggregateFlags(JSON.parse(move.flags)); |
| 584 | V.UndoOnBoard(this.board, move); |
| 585 | this.postUndo(move); |
| 586 | if (this.movesCount >= 2 || this.subTurn == 1 || move.vanish.length == 0) { |
| 587 | this.turn = V.GetOppCol(this.turn); |
| 588 | this.movesCount--; |
| 589 | } |
| 590 | this.subTurn = move.subTurn; |
| 591 | } |
| 592 | |
| 593 | postUndo(move) { |
| 594 | if (move.vanish.length > 0) super.postUndo(move); |
| 595 | } |
| 596 | |
| 597 | getComputerMove() { |
| 598 | if (this.movesCount <= 1) { |
| 599 | // Special case: swap and pass at random |
| 600 | const moves1 = this.getAllValidMoves(); |
| 601 | const m1 = moves1[randInt(moves1.length)]; |
| 602 | this.play(m1); |
| 603 | if (m1.vanish.length == 0) { |
| 604 | this.undo(m1); |
| 605 | return m1; |
| 606 | } |
| 607 | const moves2 = this.getAllValidMoves(); |
| 608 | const m2 = moves2[randInt(moves2.length)]; |
| 609 | this.undo(m1); |
| 610 | return [m1, m2]; |
| 611 | } |
| 612 | return super.getComputerMove(); |
| 613 | } |
| 614 | |
| 615 | getNotation(move) { |
| 616 | if (move.vanish.length == 0) return "pass"; |
| 617 | if (move.appear.length == 2) return "S"; //"swap" |
| 618 | let notation = super.getNotation(move); |
| 619 | if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN) |
| 620 | notation = "P" + notation.substr(1); |
| 621 | return notation; |
| 622 | } |
| 623 | |
| 624 | }; |