X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=718261d2a3509f1fad550324c0c4dd2dece050ad;hb=26b8e4f7c71030d49e44fe1d89632ef91b886d67;hp=8b8165a0685ef5f189cc83a1bc1daa36d42707ee;hpb=26c1e3bd4d3e9fb7c86e25c0f423bea57b977111;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 8b8165a0..718261d2 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -64,22 +64,19 @@ class ChessRules if (!V.IsGoodPosition(fenParsed.position)) return false; // 2) Check turn - if (!fenParsed.turn || !["w","b"].includes(fenParsed.turn)) + if (!fenParsed.turn || !V.IsGoodTurn(fenParsed.turn)) return false; - // 3) Check flags + // 3) Check moves count + if (!fenParsed.movesCount || !(parseInt(fenParsed.movesCount) >= 0)) + return false; + // 4) Check flags if (V.HasFlags && (!fenParsed.flags || !V.IsGoodFlags(fenParsed.flags))) return false; - // 4) Check enpassant - if (V.HasEnpassant) + // 5) Check enpassant + if (V.HasEnpassant && + (!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant))) { - if (!fenParsed.enpassant) - return false; - if (fenParsed.enpassant != "-") - { - const ep = V.SquareToCoords(fenParsed.enpassant); - if (isNaN(ep.x) || !V.OnBoard(ep)) - return false; - } + return false; } return true; } @@ -113,12 +110,29 @@ class ChessRules return true; } + // For FEN checking + static IsGoodTurn(turn) + { + return ["w","b"].includes(turn); + } + // For FEN checking static IsGoodFlags(flags) { return !!flags.match(/^[01]{4,4}$/); } + static IsGoodEnpassant(enpassant) + { + if (enpassant != "-") + { + const ep = V.SquareToCoords(fenParsed.enpassant); + if (isNaN(ep.x) || !V.OnBoard(ep)) + return false; + } + return true; + } + // 3 --> d (column number to letter) static CoordToColumn(colnum) { @@ -126,9 +140,9 @@ class ChessRules } // d --> 3 (column letter to number) - static ColumnToCoord(colnum) + static ColumnToCoord(column) { - return String.fromCharCode(97 + colnum); + return column.charCodeAt(0) - 97; } // a4 --> {x:3,y:0} @@ -175,7 +189,8 @@ class ChessRules // Argument is a move: const move = moveOrSquare; const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x]; - if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2) + // TODO: next conditions are first for Atomic, and third for Checkered + if (move.appear.length > 0 && move.appear[0].p == V.PAWN && ["w","b"].includes(move.appear[0].c) && Math.abs(sx - ex) == 2) { return { x: (sx + ex)/2, @@ -276,8 +291,9 @@ class ChessRules { position: fenParts[0], turn: fenParts[1], + movesCount: fenParts[2], }; - let nextIdx = 2; + let nextIdx = 3; if (V.HasFlags) Object.assign(res, {flags: fenParts[nextIdx++]}); if (V.HasEnpassant) @@ -288,7 +304,8 @@ class ChessRules // Return current fen (game state) getFen() { - return this.getBaseFen() + " " + this.turn + + return this.getBaseFen() + " " + + this.getTurnFen() + " " + this.movesCount + (V.HasFlags ? (" " + this.getFlagsFen()) : "") + (V.HasEnpassant ? (" " + this.getEnpassantFen()) : ""); } @@ -326,6 +343,11 @@ class ChessRules return position; } + getTurnFen() + { + return this.turn; + } + // Flags part of the FEN string getFlagsFen() { @@ -384,12 +406,12 @@ class ChessRules // INITIALIZATION // Fen string fully describes the game state - constructor(fen, moves) + constructor(fen) { - this.moves = moves; const fenParsed = V.ParseFen(fen); this.board = V.GetBoard(fenParsed.position); - this.turn = (fenParsed.turn || "w"); + this.turn = fenParsed.turn[0]; //[0] to work with MarseilleRules + this.movesCount = parseInt(fenParsed.movesCount); this.setOtherVariables(fen); } @@ -476,15 +498,15 @@ class ChessRules } // Get opponent color - getOppCol(color) + static GetOppCol(color) { return (color=="w" ? "b" : "w"); } - get lastMove() + // Get next color (for compatibility with 3 and 4 players games) + static GetNextCol(color) { - const L = this.moves.length; - return (L>0 ? this.moves[L-1] : null); + return V.GetOppCol(color); } // Pieces codes (for a clearer code) @@ -612,7 +634,8 @@ class ChessRules const lastRank = (color == "w" ? 0 : sizeX-1); const pawnColor = this.getColor(x,y); //can be different for checkered - if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true + // NOTE: next condition is generally true (no pawn on last rank) + if (x+shiftX >= 0 && x+shiftX < sizeX) { const finalPieces = x + shiftX == lastRank ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN] @@ -956,11 +979,28 @@ class ChessRules board[psq.x][psq.y] = psq.c + psq.p; } - // Before move is played, update variables + flags + // After move is played, update variables + flags updateVariables(move) { - const piece = move.vanish[0].p; - const c = move.vanish[0].c; + let piece = undefined; + let c = undefined; + if (move.vanish.length >= 1) + { + // Usual case, something is moved + piece = move.vanish[0].p; + c = move.vanish[0].c; + } + else + { + // Crazyhouse-like variants + piece = move.appear[0].p; + c = move.appear[0].c; + } + if (c == "c") //if (!["w","b"].includes(c)) + { + // 'c = move.vanish[0].c' doesn't work for Checkered + c = this.getOppCol(this.turn); + } const firstRank = (c == "w" ? V.size.x-1 : 0); // Update king position + flags @@ -968,22 +1008,27 @@ class ChessRules { this.kingPos[c][0] = move.appear[0].x; this.kingPos[c][1] = move.appear[0].y; - this.castleFlags[c] = [false,false]; + if (V.HasFlags) + this.castleFlags[c] = [false,false]; return; } - const oppCol = this.getOppCol(c); - const oppFirstRank = (V.size.x-1) - firstRank; - if (move.start.x == firstRank //our rook moves? - && this.INIT_COL_ROOK[c].includes(move.start.y)) - { - const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1); - this.castleFlags[c][flagIdx] = false; - } - else if (move.end.x == oppFirstRank //we took opponent rook? - && this.INIT_COL_ROOK[oppCol].includes(move.end.y)) + if (V.HasFlags) { - const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1); - this.castleFlags[oppCol][flagIdx] = false; + // Update castling flags if rooks are moved + const oppCol = this.getOppCol(c); + const oppFirstRank = (V.size.x-1) - firstRank; + if (move.start.x == firstRank //our rook moves? + && this.INIT_COL_ROOK[c].includes(move.start.y)) + { + const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1); + this.castleFlags[c][flagIdx] = false; + } + else if (move.end.x == oppFirstRank //we took opponent rook? + && this.INIT_COL_ROOK[oppCol].includes(move.end.y)) + { + const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1); + this.castleFlags[oppCol][flagIdx] = false; + } } } @@ -997,14 +1042,12 @@ class ChessRules this.kingPos[c] = [move.start.x, move.start.y]; } - play(move, ingame) + play(move) { // DEBUG: // if (!this.states) this.states = []; -// if (!ingame) this.states.push(this.getFen()); - - if (!!ingame) - move.notation = [this.getNotation(move), this.getLongNotation(move)]; +// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); +// this.states.push(stateFen); if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo) @@ -1012,14 +1055,8 @@ class ChessRules this.epSquares.push( this.getEpSquare(move) ); V.PlayOnBoard(this.board, move); this.turn = this.getOppCol(this.turn); - this.moves.push(move); + this.movesCount++; this.updateVariables(move); - - if (!!ingame) - { - // Hash of current game state *after move*, to detect repetitions - move.hash = hex_md5(this.getFen()); - } } undo(move) @@ -1030,60 +1067,31 @@ class ChessRules this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); this.turn = this.getOppCol(this.turn); - this.moves.pop(); + this.movesCount--; this.unupdateVariables(move); // DEBUG: -// if (this.getFen() != this.states[this.states.length-1]) -// debugger; +// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); +// if (stateFen != this.states[this.states.length-1]) debugger; // this.states.pop(); } /////////////// // END OF GAME - // Check for 3 repetitions (position + flags + turn) - checkRepetition() + // What is the score ? (Interesting if game is over) + getCurrentScore() { - if (!this.hashStates) - this.hashStates = {}; - const startIndex = - Object.values(this.hashStates).reduce((a,b) => { return a+b; }, 0) - // Update this.hashStates with last move (or all moves if continuation) - // NOTE: redundant storage, but faster and moderate size - for (let i=startIndex; i { return (elt >= 3); }); - } - - // Is game over ? And if yes, what is the score ? - checkGameOver() - { - if (this.checkRepetition()) - return "1/2"; - if (this.atLeastOneMove()) // game not over return "*"; // Game over - return this.checkGameEnd(); - } - - // No moves are possible: compute score - checkGameEnd() - { const color = this.turn; // No valid move: stalemate or checkmate? if (!this.isAttacked(this.kingPos[color], [this.getOppCol(color)])) return "1/2"; // OK, checkmate - return color == "w" ? "0-1" : "1-0"; + return (color == "w" ? "0-1" : "1-0"); } /////////////// @@ -1126,11 +1134,10 @@ class ChessRules { this.play(moves1[i]); let finish = (Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE); - if (!finish && !this.atLeastOneMove()) + if (!finish) { - // Test mate (for other variants) - const score = this.checkGameEnd(); - if (score != "1/2") + const score = this.getCurrentScore(); + if (["1-0","0-1"].includes(score)) finish = true; } this.undo(moves1[i]); @@ -1144,8 +1151,9 @@ class ChessRules // Initial self evaluation is very low: "I'm checkmated" moves1[i].eval = (color=="w" ? -1 : 1) * maxeval; this.play(moves1[i]); + const score1 = this.getCurrentScore(); let eval2 = undefined; - if (this.atLeastOneMove()) + if (score1 == "*") { // Initial enemy evaluation is very low too, for him eval2 = (color=="w" ? 1 : -1) * maxeval; @@ -1154,15 +1162,10 @@ class ChessRules for (let j=0; j eval2)) { @@ -1172,10 +1175,7 @@ class ChessRules } } else - { - const score = this.checkGameEnd(); - eval2 = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval); - } + eval2 = (score1=="1/2" ? 0 : (score1=="1-0" ? 1 : -1) * maxeval); if ((color=="w" && eval2 > moves1[i].eval) || (color=="b" && eval2 < moves1[i].eval)) { @@ -1211,7 +1211,7 @@ class ChessRules } else return currentBest; - //console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); +// console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); candidates = [0]; for (let j=1; j'; - pgn += '[Date "' + getDate(new Date()) + '"]
'; - pgn += '[White "' + (mycolor=='w'?'Myself':opponent) + '"]
'; - pgn += '[Black "' + (mycolor=='b'?'Myself':opponent) + '"]
'; - pgn += '[FenStart "' + fenStart + '"]
'; - pgn += '[Fen "' + this.getFen() + '"]
'; - pgn += '[Result "' + score + '"]

'; - - // Standard PGN - for (let i=0; i