X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=9f6ec9ecbb2b2e7ff3d197422d37462cb50cb9a7;hb=b955c65b942d09d24b5c3bed0d755d4f2f8f71f1;hp=7a42a385bba0514b125192572327195c355f06c7;hpb=4f7723a1a0d1554f16c699017ae308079aa43a69;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 7a42a385..9f6ec9ec 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, @@ -206,7 +221,7 @@ class ChessRules // On which squares is color under check ? (for interface) getCheckSquares(color) { - return this.isAttacked(this.kingPos[color], [this.getOppCol(color)]) + return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]) ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate! : []; } @@ -265,7 +280,7 @@ class ChessRules return pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + pieces["w"].join("").toUpperCase() + - " w 1111 -"; //add turn + flags + enpassant + " w 0 1111 -"; //add turn + flags + enpassant } // "Parse" FEN: just return untransformed string data @@ -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] @@ -711,7 +734,7 @@ class ChessRules return []; //x isn't first rank, or king has moved (shortcut) // Castling ? - const oppCol = this.getOppCol(c); + const oppCol = V.GetOppCol(c); let moves = []; let i = 0; const finalSquares = [ [2,3], [V.size.y-2,V.size.y-3] ]; //king, then rook @@ -801,7 +824,7 @@ class ChessRules getAllValidMoves() { const color = this.turn; - const oppCol = this.getOppCol(color); + const oppCol = V.GetOppCol(color); let potentialMoves = []; for (let i=0; i= 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 = V.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 = V.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,29 +1042,23 @@ 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) if (V.HasEnpassant) this.epSquares.push( this.getEpSquare(move) ); + if (!move.color) + move.color = this.turn; //for interface V.PlayOnBoard(this.board, move); - this.turn = this.getOppCol(this.turn); - this.moves.push(move); + this.turn = V.GetOppCol(this.turn); + 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) @@ -1029,61 +1068,32 @@ class ChessRules if (V.HasFlags) this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); - this.turn = this.getOppCol(this.turn); - this.moves.pop(); + this.turn = V.GetOppCol(this.turn); + 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() - { - 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() + // What is the score ? (Interesting if game is over) + getCurrentScore() { - 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)])) + if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])) return "1/2"; // OK, checkmate - return color == "w" ? "0-1" : "1-0"; + return (color == "w" ? "0-1" : "1-0"); } /////////////// @@ -1126,11 +1136,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 +1153,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 +1164,10 @@ class ChessRules for (let j=0; j eval2)) { @@ -1172,10 +1177,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 +1213,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 move.appear.length ? "x" : "") + finalSquare; } } - - // Complete the usual notation, may be required for de-ambiguification - getLongNotation(move) - { - // Not encoding move. But short+long is enough - return V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end); - } - - // The score is already computed when calling this function - getPGN(mycolor, score, fenStart, mode) - { - let pgn = ""; - pgn += '[Site "vchess.club"]
'; - const opponent = mode=="human" ? "Anonymous" : "Computer"; - pgn += '[Variant "' + variant + '"]
'; - 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