X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FMonster.js;h=bbba319380d3a9824ff2d28616a638a85e284090;hb=4313762da3237b04f204e121a20cab3ba7bb5dd2;hp=1fb6a7738d0a1b3f23b62ae66541889e63ad6d02;hpb=dd3ec6627a920f29b410cfeb781137efecefab52;p=vchess.git diff --git a/client/src/variants/Monster.js b/client/src/variants/Monster.js index 1fb6a773..bbba3193 100644 --- a/client/src/variants/Monster.js +++ b/client/src/variants/Monster.js @@ -2,21 +2,54 @@ import { ChessRules } from "@/base_rules"; import { randInt } from "@/utils/alea"; export class MonsterRules extends ChessRules { + + static get Options() { + return { + check: [ + { + label: "Random", + defaut: false, + variable: "random" + } + ], + select: [ + { + label: "Number of pawns", + variable: "pawnsCount", + defaut: 4, + options: [ + { label: "Two", value: 2 }, + { label: "Four", value: 4 }, + { label: "Six", value: 6 } + ] + } + ] + }; + } + + static AbbreviateOptions(opts) { + return opts["pawnsCount"]; + } + static IsGoodFlags(flags) { // Only black can castle return !!flags.match(/^[a-z]{2,2}$/); } - static GenRandInitFen(randomness) { - if (randomness == 2) randomness--; - const fen = ChessRules.GenRandInitFen(randomness); + static GenRandInitFen(options) { + const baseFen = ChessRules.GenRandInitFen( + { randomness: (options.random ? 1 : 0) }); + let pawnsLine = ""; + switch (options.pawnsCount) { + case 2: pawnsLine = "3PP3"; break; + case 4: pawnsLine = "2PPPP2"; break; + case 6: pawnsLine = "1PPPPPP1"; break; + } return ( // 26 first chars are 6 rows + 6 slashes - fen.substr(0, 26) + baseFen.substr(0, 26) + pawnsLine + "/4K3 w 0 " + // En passant available, and "half-castle" - .concat("1PPPPPP1/4K3 w 0 ") - .concat(fen.substr(-6, 2)) - .concat(" -") + baseFen.substr(-6, 2) + " -" ); } @@ -39,30 +72,28 @@ export class MonsterRules extends ChessRules { if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]); // White doesn't castle: return this.getSlideNJumpMoves( - [x, y], - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), - "oneStep" - ); + [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1); } - isAttacked(sq, color, castling) { - const singleMoveAttack = super.isAttacked(sq, color); - if (singleMoveAttack) return true; - if (color == 'b' || !!castling) return singleMoveAttack; - // Attacks by white: double-move allowed - const curTurn = this.turn; - this.turn = 'w'; - const w1Moves = super.getAllPotentialMoves(); - this.turn = curTurn; - for (let move of w1Moves) { - this.play(move); - const res = super.isAttacked(sq, 'w'); - this.undo(move); - if (res) return res; - } + isAttacked() { + // Goal is king capture => no checks return false; } + filterValid(moves) { + return moves; + } + + getCheckSquares() { + return []; + } + + getCurrentScore() { + const color = this.turn; + if (this.kingPos[color][0] < 0) return (color == 'w' ? "0-1" : "1-0"); + return "*"; + } + play(move) { move.flags = JSON.stringify(this.aggregateFlags()); if (this.turn == 'b' || this.subTurn == 2) @@ -71,9 +102,17 @@ export class MonsterRules extends ChessRules { V.PlayOnBoard(this.board, move); if (this.turn == 'w') { if (this.subTurn == 1) this.movesCount++; - else this.turn = 'b'; - this.subTurn = 3 - this.subTurn; - } else { + if ( + this.subTurn == 2 || + // King captured + (move.vanish.length == 2 && move.vanish[1].p == V.KING) + ) { + this.turn = 'b'; + this.subTurn = 1; + } + else this.subTurn = 2; + } + else { this.turn = 'w'; this.movesCount++; } @@ -105,9 +144,12 @@ export class MonsterRules extends ChessRules { // Definition of 'c' in base class doesn't work: const c = move.vanish[0].c; const piece = move.vanish[0].p; - if (piece == V.KING) { - this.kingPos[c][0] = move.appear[0].x; - this.kingPos[c][1] = move.appear[0].y; + if (piece == V.KING) + this.kingPos[c] = [move.appear[0].x, move.appear[0].y]; + if (move.vanish.length == 2 && move.vanish[1].p == V.KING) { + // Opponent's king is captured, game over + this.kingPos[move.vanish[1].c] = [-1, -1]; + move.captureKing = true; //for undo } this.updateCastleFlags(move, piece); } @@ -120,52 +162,49 @@ export class MonsterRules extends ChessRules { if (this.subTurn == 2) this.subTurn = 1; else this.turn = 'b'; this.movesCount--; - } else { + } + else { this.turn = 'w'; - this.subTurn = 2; + this.subTurn = (!move.captureKing ? 2 : 1); } this.postUndo(move); } - filterValid(moves) { - if (this.turn == 'w' && this.subTurn == 1) { - return moves.filter(m1 => { - this.play(m1); - // NOTE: no recursion because next call will see subTurn == 2 - const res = super.atLeastOneMove(); - this.undo(m1); - return res; - }); - } - return super.filterValid(moves); - } - - static get SEARCH_DEPTH() { - return 1; + postUndo(move) { + if (move.vanish.length == 2 && move.vanish[1].p == V.KING) + // Opponent's king was captured + this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y]; + super.postUndo(move); } + // Custom search at depth 1(+1) getComputerMove() { - const color = this.turn; - if (color == 'w') { + const getBestWhiteMove = (terminal) => { // Generate all sequences of 2-moves - const moves1 = this.getAllValidMoves(); + let moves1 = this.getAllValidMoves(); moves1.forEach(m1 => { m1.eval = -V.INFINITY; m1.move2 = null; this.play(m1); - const moves2 = this.getAllValidMoves(); - moves2.forEach(m2 => { - this.play(m2); - const eval2 = this.evalPosition(); - this.undo(m2); - if (eval2 > m1.eval) { - m1.eval = eval2; - m1.move2 = m2; - } - }); + if (!!terminal) m1.eval = this.evalPosition(); + else { + const moves2 = this.getAllValidMoves(); + moves2.forEach(m2 => { + this.play(m2); + const eval2 = this.evalPosition() + 0.05 - Math.random() / 10; + this.undo(m2); + if (eval2 > m1.eval) { + m1.eval = eval2; + m1.move2 = m2; + } + }); + } this.undo(m1); }); moves1.sort((a, b) => b.eval - a.eval); + if (!!terminal) + // The move itself doesn't matter, only its eval: + return moves1[0]; let candidates = [0]; for ( let i = 1; @@ -178,8 +217,32 @@ export class MonsterRules extends ChessRules { const move2 = moves1[idx].move2; delete moves1[idx]["move2"]; return [moves1[idx], move2]; - } - // For black at depth 1, super method is fine: - return super.getComputerMove(); + }; + + const getBestBlackMove = () => { + let moves = this.getAllValidMoves(); + moves.forEach(m => { + m.eval = V.INFINITY; + this.play(m); + const evalM = getBestWhiteMove("terminal").eval + this.undo(m); + if (evalM < m.eval) m.eval = evalM; + }); + moves.sort((a, b) => a.eval - b.eval); + let candidates = [0]; + for ( + let i = 1; + i < moves.length && moves[i].eval == moves[0].eval; + i++ + ) { + candidates.push(i); + } + const idx = candidates[randInt(candidates.length)]; + return moves[idx]; + }; + + const color = this.turn; + return (color == 'w' ? getBestWhiteMove() : getBestBlackMove()); } + };