| 1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
| 2 | import { randInt } from "@/utils/alea"; |
| 3 | |
| 4 | export class MusketeerRules extends ChessRules { |
| 5 | |
| 6 | // Extra pieces get strange letters because many taken by combinations below |
| 7 | static get LEOPARD() { |
| 8 | return "d"; |
| 9 | } |
| 10 | static get CANNON() { |
| 11 | return "w"; |
| 12 | } |
| 13 | static get UNICORN() { |
| 14 | return "x"; |
| 15 | } |
| 16 | static get ELEPHANT() { |
| 17 | return "e"; |
| 18 | } |
| 19 | static get HAWK() { |
| 20 | return "h"; |
| 21 | } |
| 22 | static get FORTRESS() { |
| 23 | return "f"; |
| 24 | } |
| 25 | static get SPIDER() { |
| 26 | return "y"; |
| 27 | } |
| 28 | |
| 29 | static get RESERVE_PIECES() { |
| 30 | return ( |
| 31 | [V.LEOPARD, V.CANNON, V.UNICORN, V.ELEPHANT, |
| 32 | V.HAWK, V.FORTRESS, V.SPIDER] |
| 33 | ); |
| 34 | } |
| 35 | |
| 36 | static get PIECES() { |
| 37 | return ChessRules.PIECES.concat(V.RESERVE_PIECES); |
| 38 | } |
| 39 | |
| 40 | // Decode if normal piece, or + piece1 or piece2 |
| 41 | getPiece(i, j) { |
| 42 | if (i >= V.size.x) return V.RESERVE_PIECES[j]; |
| 43 | const piece = this.board[i][j].charAt(1); |
| 44 | if (V.PIECES.includes(piece)) return piece; |
| 45 | // Augmented piece: |
| 46 | switch (piece) { |
| 47 | case 'a': |
| 48 | case 'c': |
| 49 | return 'b'; |
| 50 | case 'j': |
| 51 | case 'l': |
| 52 | return 'k'; |
| 53 | case 'm': |
| 54 | case 'o': |
| 55 | return 'n'; |
| 56 | case 's': |
| 57 | case 't': |
| 58 | return 'q'; |
| 59 | case 'u': |
| 60 | case 'v': |
| 61 | return 'r'; |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | getColor(i, j) { |
| 66 | if (i >= V.size.x) return i == V.size.x ? "w" : "b"; |
| 67 | return this.board[i][j].charAt(0); |
| 68 | } |
| 69 | |
| 70 | // Code: a/c = bishop + piece1/piece2 j/l for king, |
| 71 | // m/o for knight, s/t for queen, u/v for rook |
| 72 | static get AUGMENTED_PIECES() { |
| 73 | return [ |
| 74 | 'a', |
| 75 | 'c', |
| 76 | 'j', |
| 77 | 'l', |
| 78 | 'm', |
| 79 | 'o', |
| 80 | 's', |
| 81 | 't', |
| 82 | 'u', |
| 83 | 'v' |
| 84 | ]; |
| 85 | } |
| 86 | |
| 87 | getPpath(b) { |
| 88 | return (ChessRules.PIECES.includes(b[1]) ? "" : "Musketeer/") + b; |
| 89 | } |
| 90 | |
| 91 | getReservePpath(index, color) { |
| 92 | return "Musketeer/" + color + V.RESERVE_PIECES[index]; |
| 93 | } |
| 94 | |
| 95 | // Decode above notation into additional piece |
| 96 | getExtraPiece(symbol) { |
| 97 | if (['a','j','m','s','u'].includes(symbol)) |
| 98 | return this.extraPieces[0]; |
| 99 | return this.extraPieces[1]; |
| 100 | } |
| 101 | |
| 102 | // Inverse operation: augment piece |
| 103 | getAugmented(piece) { |
| 104 | const p1 = [2, 3].includes(this.movesCount); |
| 105 | switch (piece) { |
| 106 | case V.ROOK: return (p1 ? 'u' : 'v'); |
| 107 | case V.KNIGHT: return (p1 ? 'm' : 'o'); |
| 108 | case V.BISHOP: return (p1 ? 'a' : 'c'); |
| 109 | case V.QUEEN: return (p1 ? 's' : 't'); |
| 110 | case V.KING: return (p1 ? 'j' : 'l'); |
| 111 | } |
| 112 | return '_'; //never reached |
| 113 | } |
| 114 | |
| 115 | static IsGoodFen(fen) { |
| 116 | if (!ChessRules.IsGoodFen(fen)) return false; |
| 117 | const fenParsed = V.ParseFen(fen); |
| 118 | // 5) Check extra pieces |
| 119 | if (!fenParsed.extraPieces) return false; |
| 120 | // Not exact matching (would need to look at movesCount), but OK for now |
| 121 | if (!fenParsed.extraPieces.match(/^[dwxejfy-]{2,2}$/)) return false; |
| 122 | return true; |
| 123 | } |
| 124 | |
| 125 | static IsGoodPosition(position) { |
| 126 | if (position.length == 0) return false; |
| 127 | const rows = position.split("/"); |
| 128 | if (rows.length != V.size.x) return false; |
| 129 | let kings = { "w": 0, "b": 0 }; |
| 130 | const allPiecesCodes = V.PIECES.concat(V.AUGMENTED_PIECES); |
| 131 | const kingBlackCodes = ['j','k','l']; |
| 132 | const kingWhiteCodes = ['J','K','L']; |
| 133 | for (let row of rows) { |
| 134 | let sumElts = 0; |
| 135 | for (let i = 0; i < row.length; i++) { |
| 136 | if (kingBlackCodes.includes(row[i])) kings['b']++; |
| 137 | else if (kingWhiteCodes.includes(row[i])) kings['w']++; |
| 138 | if (allPiecesCodes.includes(row[i].toLowerCase())) sumElts++; |
| 139 | else { |
| 140 | const num = parseInt(row[i], 10); |
| 141 | if (isNaN(num)) return false; |
| 142 | sumElts += num; |
| 143 | } |
| 144 | } |
| 145 | if (sumElts != V.size.y) return false; |
| 146 | } |
| 147 | // Both kings should be on board, only one of each color: |
| 148 | if (Object.values(kings).some(v => v != 1)) return false; |
| 149 | return true; |
| 150 | } |
| 151 | |
| 152 | static ParseFen(fen) { |
| 153 | const fenParts = fen.split(" "); |
| 154 | return Object.assign( |
| 155 | ChessRules.ParseFen(fen), |
| 156 | { extraPieces: fenParts[5] } |
| 157 | ); |
| 158 | } |
| 159 | |
| 160 | static GenRandInitFen(randomness) { |
| 161 | return ChessRules.GenRandInitFen(randomness) + " --"; |
| 162 | } |
| 163 | |
| 164 | getFen() { |
| 165 | return super.getFen() + " " + this.extraPieces.join(""); |
| 166 | } |
| 167 | |
| 168 | setOtherVariables(fen) { |
| 169 | super.setOtherVariables(fen); |
| 170 | // Extra pieces may not be defined yet (thus '-') |
| 171 | this.extraPieces = V.ParseFen(fen).extraPieces.split(""); |
| 172 | // At early stages, also init reserves |
| 173 | if (this.movesCount <= 5) { |
| 174 | const condShow = (piece) => { |
| 175 | if (this.movesCount == 0) return true; |
| 176 | if (this.movesCount == 1) return piece != this.extraPieces[0]; |
| 177 | if (this.movesCount <= 3) return this.extraPiece.includes(piece); |
| 178 | return this.extraPiece[1] == piece; |
| 179 | } |
| 180 | this.reserve = { w : {}, b: {} }; |
| 181 | for (let c of ['w', 'b']) { |
| 182 | V.RESERVE_PIECES.forEach(p => |
| 183 | this.reserve[c][p] = condShow(p) ? 1 : 0); |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | // Kings may be augmented: |
| 189 | scanKings(fen) { |
| 190 | this.kingPos = { w: [-1, -1], b: [-1, -1] }; |
| 191 | const rows = V.ParseFen(fen).position.split("/"); |
| 192 | for (let i = 0; i < rows.length; i++) { |
| 193 | let k = 0; //column index on board |
| 194 | for (let j = 0; j < rows[i].length; j++) { |
| 195 | const piece = rows[i].charAt(j); |
| 196 | if (['j','k','l'].includes(piece.toLowerCase())) { |
| 197 | const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b'); |
| 198 | this.kingPos[color] = [i, k]; |
| 199 | } |
| 200 | else { |
| 201 | const num = parseInt(rows[i].charAt(j), 10); |
| 202 | if (!isNaN(num)) k += num - 1; |
| 203 | } |
| 204 | k++; |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | getReserveMoves([x, y]) { |
| 210 | const color = this.turn; |
| 211 | const p = V.RESERVE_PIECES[y]; |
| 212 | if ( |
| 213 | this.reserve[color][p] == 0 || |
| 214 | ([2, 3].includes(this.movesCount) && p != this.extraPieces[0]) || |
| 215 | ([4, 5].includes(this.movesCount) && p != this.extraPieces[1]) |
| 216 | ) { |
| 217 | return []; |
| 218 | } |
| 219 | let moves = []; |
| 220 | const iIdx = |
| 221 | (this.movesCount <= 1 ? [2, 3, 4, 5] : [color == 'w' ? 7 : 0]); |
| 222 | const mappingAppear = [ [3, 4], [3, 3], [4, 4], [4, 3] ]; |
| 223 | for (let i of iIdx) { |
| 224 | for (let j = 0; j < V.size.y; j++) { |
| 225 | if ( |
| 226 | (this.movesCount <= 1 && this.board[i][j] == V.EMPTY) || |
| 227 | ( |
| 228 | this.movesCount >= 2 && |
| 229 | ChessRules.PIECES.includes(this.board[i][j].charAt(1)) |
| 230 | ) |
| 231 | ) { |
| 232 | const [appearX, appearY] = |
| 233 | this.movesCount <= 1 |
| 234 | ? mappingAppear[this.movesCount] |
| 235 | : [i, j]; |
| 236 | const pOnBoard = |
| 237 | (this.movesCount >= 2 ? this.board[i][j].charAt(1) : ''); |
| 238 | let mv = new Move({ |
| 239 | appear: [ |
| 240 | new PiPo({ |
| 241 | x: appearX, |
| 242 | y: appearY, |
| 243 | c: color, |
| 244 | p: (this.movesCount <= 1 ? p : this.getAugmented(pOnBoard)) |
| 245 | }) |
| 246 | ], |
| 247 | vanish: [], |
| 248 | start: { x: x, y: y }, //a bit artificial... |
| 249 | end: { x: i, y: j } |
| 250 | }); |
| 251 | if (this.movesCount >= 2) |
| 252 | mv.vanish.push(new PiPo({ x: i, y: j, c: color, p: pOnBoard })) |
| 253 | moves.push(mv); |
| 254 | } |
| 255 | } |
| 256 | } |
| 257 | return moves; |
| 258 | } |
| 259 | |
| 260 | // Assumption: movesCount >= 6 |
| 261 | getPotentialMovesFrom([x, y]) { |
| 262 | // Standard moves. If piece not in usual list, new piece appears. |
| 263 | const initialPiece = this.getPiece(x, y); |
| 264 | if (V.RESERVE_PIECES.includes(initialPiece)) { |
| 265 | switch (initialPiece) { |
| 266 | case V.LEOPARD: return this.getPotentialLeopardMoves([x, y]); |
| 267 | case V.CANNON: return this.getPotentialCannonMoves([x, y]); |
| 268 | case V.UNICORN: return this.getPotentialUnicornMoves([x, y]); |
| 269 | case V.ELEPHANT: return this.getPotentialElephantMoves([x, y]); |
| 270 | case V.HAWK: return this.getPotentialHawkMoves([x, y]); |
| 271 | case V.FORTRESS: return this.getPotentialFortressMoves([x, y]); |
| 272 | case V.SPIDER: return this.getPotentialSpiderMoves([x, y]); |
| 273 | } |
| 274 | return []; //never reached |
| 275 | } |
| 276 | // Following is mostly copy-paste from Titan Chess (TODO?) |
| 277 | let moves = []; |
| 278 | if (initialPiece == V.PAWN) { |
| 279 | const promotions = |
| 280 | ChessRules.PawnSpecs.promotions.concat(this.extraPieces); |
| 281 | moves = super.getPotentialPawnMoves([x, y], promotions); |
| 282 | } |
| 283 | else moves = super.getPotentialMovesFrom([x, y]); |
| 284 | const color = this.turn; |
| 285 | if ( |
| 286 | ((color == 'w' && x == 7) || (color == "b" && x == 0)) && |
| 287 | V.AUGMENTED_PIECES.includes(this.board[x][y][1]) |
| 288 | ) { |
| 289 | const newPiece = this.getExtraPiece(this.board[x][y][1]); |
| 290 | moves.forEach(m => { |
| 291 | m.appear[0].p = initialPiece; |
| 292 | m.appear.push( |
| 293 | new PiPo({ |
| 294 | p: newPiece, |
| 295 | c: color, |
| 296 | x: x, |
| 297 | y: y |
| 298 | }) |
| 299 | ); |
| 300 | }); |
| 301 | moves.forEach(m => { |
| 302 | if (m.vanish.length <= 1) return; |
| 303 | const [vx, vy] = [m.vanish[1].x, m.vanish[1].y]; |
| 304 | if ( |
| 305 | m.appear.length >= 2 && //3 if the king was also augmented |
| 306 | m.vanish.length == 2 && |
| 307 | m.vanish[1].c == color && |
| 308 | V.AUGMENTED_PIECES.includes(this.board[vx][vy][1]) |
| 309 | ) { |
| 310 | // Castle, rook is an "augmented piece" |
| 311 | m.appear[1].p = V.ROOK; |
| 312 | m.appear.push( |
| 313 | new PiPo({ |
| 314 | p: this.getExtraPiece(this.board[vx][vy][1]), |
| 315 | c: color, |
| 316 | x: vx, |
| 317 | y: vy |
| 318 | }) |
| 319 | ); |
| 320 | } |
| 321 | }); |
| 322 | } |
| 323 | return moves; |
| 324 | } |
| 325 | |
| 326 | // All types of leaps used here: |
| 327 | static get Leap2Ortho() { |
| 328 | return [ [-2, 0], [0, -2], [2, 0], [0, 2] ]; |
| 329 | } |
| 330 | static get Leap2Diago() { |
| 331 | return [ [-2, -2], [-2, 2], [2, -2], [2, 2] ]; |
| 332 | } |
| 333 | static get Leap3Ortho() { |
| 334 | return [ [-3, 0], [0, -3], [3, 0], [0, 3] ]; |
| 335 | } |
| 336 | static get Leap3Diago() { |
| 337 | return [ [-3, -3], [-3, 3], [3, -3], [3, 3] ]; |
| 338 | } |
| 339 | static get CamelSteps() { |
| 340 | return [ |
| 341 | [-3, -1], [-3, 1], [-1, -3], [-1, 3], |
| 342 | [1, -3], [1, 3], [3, -1], [3, 1] |
| 343 | ]; |
| 344 | } |
| 345 | static get VerticalKnight() { |
| 346 | return [ [-2, -1], [-2, 1], [2, -1], [2, 1] ]; |
| 347 | } |
| 348 | static get HorizontalKnight() { |
| 349 | return [ [-1, -2], [-1, 2], [1, -2], [1, 2] ]; |
| 350 | } |
| 351 | |
| 352 | getPotentialLeopardMoves(sq) { |
| 353 | return ( |
| 354 | this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 2) |
| 355 | .concat(super.getPotentialKnightMoves(sq)) |
| 356 | ); |
| 357 | } |
| 358 | |
| 359 | getPotentialCannonMoves(sq) { |
| 360 | const steps = |
| 361 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]) |
| 362 | .concat(V.Leap2Ortho).concat(V.HorizontalKnight); |
| 363 | return super.getSlideNJumpMoves(sq, steps, 1); |
| 364 | } |
| 365 | |
| 366 | getPotentialUnicornMoves(sq) { |
| 367 | return ( |
| 368 | super.getPotentialKnightMoves(sq) |
| 369 | .concat(super.getSlideNJumpMoves(sq, V.CamelSteps, 1)) |
| 370 | ); |
| 371 | } |
| 372 | |
| 373 | getPotentialElephantMoves(sq) { |
| 374 | const steps = |
| 375 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]) |
| 376 | .concat(V.Leap2Ortho) |
| 377 | .concat(V.Leap2Diago); |
| 378 | return super.getSlideNJumpMoves(sq, steps, 1); |
| 379 | } |
| 380 | |
| 381 | getPotentialHawkMoves(sq) { |
| 382 | const steps = |
| 383 | V.Leap2Ortho.concat(V.Leap2Diago) |
| 384 | .concat(V.Leap3Ortho).concat(V.Leap3Diago); |
| 385 | return super.getSlideNJumpMoves(sq, steps, 1); |
| 386 | } |
| 387 | |
| 388 | getPotentialFortressMoves(sq) { |
| 389 | const steps = V.Leap2Ortho.concat(V.VerticalKnight) |
| 390 | return ( |
| 391 | super.getSlideNJumpMoves(sq, steps, 1) |
| 392 | .concat(this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 3)) |
| 393 | ); |
| 394 | } |
| 395 | |
| 396 | getPotentialSpiderMoves(sq) { |
| 397 | const steps = V.Leap2Ortho.concat(V.steps[V.KNIGHT]) |
| 398 | return ( |
| 399 | super.getSlideNJumpMoves(sq, steps, 1) |
| 400 | .concat(this.getSlideNJumpMoves(sq, V.steps[V.BISHOP], 2)) |
| 401 | ); |
| 402 | } |
| 403 | |
| 404 | getPossibleMovesFrom([x, y]) { |
| 405 | if (this.movesCount <= 5) |
| 406 | return (x >= V.size.x ? this.getReserveMoves([x, y]) : []); |
| 407 | return super.getPossibleMovesFrom([x, y]); |
| 408 | } |
| 409 | |
| 410 | getAllValidMoves() { |
| 411 | if (this.movesCount >= 6) return super.getAllValidMoves(); |
| 412 | let moves = []; |
| 413 | const color = this.turn; |
| 414 | for (let i = 0; i < V.RESERVE_PIECES.length; i++) { |
| 415 | moves = moves.concat( |
| 416 | this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) |
| 417 | ); |
| 418 | } |
| 419 | return moves; |
| 420 | } |
| 421 | |
| 422 | atLeastOneMove() { |
| 423 | if (this.movesCount <= 5) return true; |
| 424 | return super.atLeastOneMove(); |
| 425 | } |
| 426 | |
| 427 | isAttacked(sq, color) { |
| 428 | if (super.isAttacked(sq, color)) return true; |
| 429 | if ( |
| 430 | this.extraPieces.includes(V.LEOPARD) && |
| 431 | this.isAttackedByLeopard(sq, color) |
| 432 | ) { |
| 433 | return true; |
| 434 | } |
| 435 | if ( |
| 436 | this.extraPieces.includes(V.CANNON) && |
| 437 | this.isAttackedByCannon(sq, color) |
| 438 | ) { |
| 439 | return true; |
| 440 | } |
| 441 | if ( |
| 442 | this.extraPieces.includes(V.UNICORN) && |
| 443 | this.isAttackedByUnicorn(sq, color) |
| 444 | ) { |
| 445 | return true; |
| 446 | } |
| 447 | if ( |
| 448 | this.extraPieces.includes(V.ELEPHANT) && |
| 449 | this.isAttackedByElephant(sq, color) |
| 450 | ) { |
| 451 | return true; |
| 452 | } |
| 453 | if ( |
| 454 | this.extraPieces.includes(V.HAWK) && |
| 455 | this.isAttackedByHawk(sq, color) |
| 456 | ) { |
| 457 | return true; |
| 458 | } |
| 459 | if ( |
| 460 | this.extraPieces.includes(V.FORTRESS) && |
| 461 | this.isAttackedByFortress(sq, color) |
| 462 | ) { |
| 463 | return true; |
| 464 | } |
| 465 | if ( |
| 466 | this.extraPieces.includes(V.SPIDER) && |
| 467 | this.isAttackedBySpider(sq, color) |
| 468 | ) { |
| 469 | return true; |
| 470 | } |
| 471 | return false; |
| 472 | } |
| 473 | |
| 474 | isAttackedByLeopard(sq, color) { |
| 475 | return ( |
| 476 | super.isAttackedBySlideNJump( |
| 477 | sq, color, V.LEOPARD, V.steps[V.KNIGHT], 1) || |
| 478 | this.isAttackedBySlideNJump(sq, color, V.LEOPARD, V.steps[V.BISHOP], 2) |
| 479 | ); |
| 480 | } |
| 481 | |
| 482 | isAttackedByCannon(sq, color) { |
| 483 | const steps = |
| 484 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]) |
| 485 | .concat(V.Leap2Ortho).concat(V.HorizontalKnight); |
| 486 | return super.isAttackedBySlideNJump(sq, color, V.CANNON, steps, 1); |
| 487 | } |
| 488 | |
| 489 | isAttackedByUnicorn(sq, color) { |
| 490 | const steps = V.steps[V.KNIGHT].concat(V.CamelSteps) |
| 491 | return ( |
| 492 | super.isAttackedBySlideNJump(sq, color, V.UNICORN, steps, 1) |
| 493 | ); |
| 494 | } |
| 495 | |
| 496 | isAttackedByElephant(sq, color) { |
| 497 | const steps = |
| 498 | V.steps[V.ROOK].concat(V.steps[V.BISHOP]) |
| 499 | .concat(V.Leap2Ortho) |
| 500 | .concat(V.Leap2Diago); |
| 501 | return ( |
| 502 | super.isAttackedBySlideNJump(sq, color, V.ELEPHANT, steps, 1) |
| 503 | ); |
| 504 | } |
| 505 | |
| 506 | isAttackedByHawk(sq, color) { |
| 507 | const steps = |
| 508 | V.Leap2Ortho.concat(V.Leap2Diago) |
| 509 | .concat(V.Leap3Ortho).concat(V.Leap3Diago); |
| 510 | return super.isAttackedBySlideNJump(sq, color, V.HAWK, steps, 1); |
| 511 | } |
| 512 | |
| 513 | isAttackedByFortress(sq, color) { |
| 514 | const steps = V.Leap2Ortho.concat(V.VerticalKnight) |
| 515 | return ( |
| 516 | super.isAttackedBySlideNJump(sq, color, V.FORTRESS, steps, 1) || |
| 517 | this.isAttackedBySlideNJump(sq, color, V.FORTRESS, V.steps[V.BISHOP], 3) |
| 518 | ); |
| 519 | } |
| 520 | |
| 521 | isAttackedBySpider(sq, color) { |
| 522 | const steps = V.Leap2Ortho.concat(V.steps[V.KNIGHT]) |
| 523 | return ( |
| 524 | super.isAttackedBySlideNJump(sq, color, V.SPIDER, steps, 1) || |
| 525 | this.isAttackedBySlideNJump(sq, color, V.SPIDER, V.steps[V.BISHOP], 2) |
| 526 | ); |
| 527 | } |
| 528 | |
| 529 | getCheckSquares() { |
| 530 | if (this.movesCount <= 6) return []; |
| 531 | return super.getCheckSquares(); |
| 532 | } |
| 533 | |
| 534 | // At movesCount == 0,1: show full reserves [minus chosen piece1] |
| 535 | // At movesCount == 2,3: show reserve with only 2 selected pieces |
| 536 | // At movesCount == 4,5: show reserve with only piece2 |
| 537 | // Then, no reserve. |
| 538 | postPlay(move) { |
| 539 | if (this.movesCount > 6) super.postPlay(move); |
| 540 | else { |
| 541 | switch (this.movesCount) { |
| 542 | case 1: |
| 543 | this.reserve['w'][move.appear[0].p]--; |
| 544 | this.reserve['b'][move.appear[0].p]--; |
| 545 | this.extraPieces[0] = move.appear[0].p; |
| 546 | break; |
| 547 | case 2: |
| 548 | this.extraPieces[1] = move.appear[0].p; |
| 549 | for (let p of V.RESERVE_PIECES) { |
| 550 | const resVal = (this.extraPieces.includes(p) ? 1 : 0); |
| 551 | this.reserve['w'][p] = resVal; |
| 552 | this.reserve['b'][p] = resVal; |
| 553 | } |
| 554 | break; |
| 555 | case 3: |
| 556 | this.reserve['w'][this.extraPieces[0]]--; |
| 557 | break; |
| 558 | case 4: |
| 559 | this.reserve['b'][this.extraPieces[0]]--; |
| 560 | break; |
| 561 | case 5: |
| 562 | this.reserve['w'][this.extraPieces[1]]--; |
| 563 | break; |
| 564 | case 6: |
| 565 | this.reserve = null; |
| 566 | this.board[3][3] = ""; |
| 567 | this.board[3][4] = ""; |
| 568 | break; |
| 569 | } |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | postUndo(move) { |
| 574 | if (this.movesCount >= 6) super.postUndo(move); |
| 575 | else { |
| 576 | switch (this.movesCount) { |
| 577 | case 0: |
| 578 | this.reserve['w'][move.appear[0].p]++; |
| 579 | this.reserve['b'][move.appear[0].p]++; |
| 580 | this.extraPieces[0] = '-'; |
| 581 | break; |
| 582 | case 1: |
| 583 | this.extraPieces[1] = '-'; |
| 584 | for (let p of V.RESERVE_PIECES) { |
| 585 | const resVal = (p != this.extraPieces[0] ? 1 : 0); |
| 586 | this.reserve['w'][p] = resVal; |
| 587 | this.reserve['b'][p] = resVal; |
| 588 | } |
| 589 | break; |
| 590 | case 2: |
| 591 | this.reserve['w'][this.extraPieces[0]]++; |
| 592 | break; |
| 593 | case 3: |
| 594 | this.reserve['b'][this.extraPieces[0]]++; |
| 595 | break; |
| 596 | case 4: |
| 597 | this.reserve['w'][this.extraPieces[1]]++; |
| 598 | break; |
| 599 | case 5: |
| 600 | this.reserve = { w: {}, b: {} }; |
| 601 | for (let c of ['w', 'b']) |
| 602 | V.RESERVE_PIECES.forEach(p => this.reserve[c][p] = 0); |
| 603 | this.reserve['b'][this.extraPieces[1]] = 1; |
| 604 | this.board[3][3] = 'b' + this.extraPieces[1]; |
| 605 | this.board[3][4] = 'w' + this.extraPieces[0]; |
| 606 | break; |
| 607 | } |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | getComputerMove() { |
| 612 | if (this.movesCount >= 6) return super.getComputerMove(); |
| 613 | // Choose a move at random |
| 614 | const moves = this.getAllValidMoves(); |
| 615 | return moves[randInt(moves.length)]; |
| 616 | } |
| 617 | |
| 618 | static get SEARCH_DEPTH() { |
| 619 | return 2; |
| 620 | } |
| 621 | |
| 622 | evalPosition() { |
| 623 | let evaluation = 0; |
| 624 | for (let i = 0; i < V.size.x; i++) { |
| 625 | for (let j = 0; j < V.size.y; j++) { |
| 626 | if (this.board[i][j] != V.EMPTY) { |
| 627 | const sign = this.getColor(i, j) == "w" ? 1 : -1; |
| 628 | const piece = this.getPiece(i, j); |
| 629 | evaluation += sign * V.VALUES[piece]; |
| 630 | const symbol = this.board[i][j][1]; |
| 631 | if (V.AUGMENTED_PIECES.includes(symbol)) { |
| 632 | const extraPiece = this.getExtraPiece(symbol); |
| 633 | evaluation += sign * V.VALUES[extraPiece] |
| 634 | } |
| 635 | } |
| 636 | } |
| 637 | } |
| 638 | return evaluation; |
| 639 | } |
| 640 | |
| 641 | static get VALUES() { |
| 642 | return Object.assign( |
| 643 | { |
| 644 | d: 6.7, |
| 645 | w: 7.5, |
| 646 | x: 5.6, |
| 647 | e: 6.3, |
| 648 | h: 5.5, |
| 649 | f: 7.6, |
| 650 | y: 8.15 |
| 651 | }, |
| 652 | ChessRules.VALUES |
| 653 | ); |
| 654 | } |
| 655 | |
| 656 | static get ExtraDictionary() { |
| 657 | return { |
| 658 | [V.LEOPARD]: { prefix: 'L', name: "Leopard" }, |
| 659 | [V.CANNON]: { prefix: 'C', name: "Cannon" }, |
| 660 | [V.UNICORN]: { prefix: 'U', name: "Unicorn" }, |
| 661 | [V.ELEPHANT]: { prefix: 'E', name: "Elephant" }, |
| 662 | [V.HAWK]: { prefix: 'H', name: "Hawk" }, |
| 663 | [V.FORTRESS]: { prefix: 'F', name: "Fortress" }, |
| 664 | [V.SPIDER]: { prefix: 'S', name: "Spider" } |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | getNotation(move) { |
| 669 | if (this.movesCount <= 5) { |
| 670 | if (this.movesCount <= 1) |
| 671 | return V.ExtraDictionary[move.appear[0].p].name; |
| 672 | // Put something on the board: |
| 673 | return ( |
| 674 | V.ExtraDictionary[V.RESERVE_PIECES[move.start.y]].prefix + |
| 675 | "@" + V.CoordsToSquare(move.end) |
| 676 | ); |
| 677 | } |
| 678 | let notation = ""; |
| 679 | if ( |
| 680 | V.AUGMENTED_PIECES.includes(move.vanish[0].p) || |
| 681 | ( |
| 682 | move.vanish.length >= 2 && |
| 683 | V.AUGMENTED_PIECES.includes(move.vanish[1].p) |
| 684 | ) |
| 685 | ) { |
| 686 | // Simplify move before calling super.getNotation() |
| 687 | let smove = JSON.parse(JSON.stringify(move)); |
| 688 | if (ChessRules.PIECES.includes(move.vanish[0].p)) { |
| 689 | // Castle with an augmented rook |
| 690 | smove.appear.pop(); |
| 691 | smove.vanish[1].p = smove.appear[1].p; |
| 692 | } |
| 693 | else { |
| 694 | // Moving an augmented piece |
| 695 | smove.appear.pop(); |
| 696 | smove.vanish[0].p = smove.appear[0].p; |
| 697 | if ( |
| 698 | smove.vanish.length == 2 && |
| 699 | smove.vanish[0].c == smove.vanish[1].c && |
| 700 | V.AUGMENTED_PIECES.includes(move.vanish[1].p) |
| 701 | ) { |
| 702 | // Castle with an augmented rook |
| 703 | smove.appear.pop(); |
| 704 | smove.vanish[1].p = smove.appear[1].p; |
| 705 | } |
| 706 | } |
| 707 | notation = super.getNotation(smove); |
| 708 | } |
| 709 | // Else, more common case: |
| 710 | notation = super.getNotation(move); |
| 711 | const pieceSymbol = notation.charAt(0).toLowerCase(); |
| 712 | if (move.vanish[0].p != V.PAWN && V.RESERVE_PIECES.includes(pieceSymbol)) |
| 713 | notation = V.ExtraDictionary[pieceSymbol].prefix + notation.substr(1); |
| 714 | return notation; |
| 715 | } |
| 716 | |
| 717 | }; |