X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=public%2Fjavascripts%2Fbase_rules.js;h=9f6ec9ecbb2b2e7ff3d197422d37462cb50cb9a7;hb=b955c65b942d09d24b5c3bed0d755d4f2f8f71f1;hp=ba77d7187538a7d98749c257c5ff10a39ab416c2;hpb=6e62b1c7d177585003e923d423025dff280a7525;p=vchess.git diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index ba77d718..9f6ec9ec 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -66,10 +66,13 @@ class ChessRules // 2) Check 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 + // 5) Check enpassant if (V.HasEnpassant && (!fenParsed.enpassant || !V.IsGoodEnpassant(fenParsed.enpassant))) { @@ -137,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} @@ -186,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, @@ -217,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! : []; } @@ -276,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 @@ -287,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) @@ -299,7 +304,8 @@ class ChessRules // Return current fen (game state) getFen() { - return this.getBaseFen() + " " + this.getTurnFen() + + return this.getBaseFen() + " " + + this.getTurnFen() + " " + this.movesCount + (V.HasFlags ? (" " + this.getFlagsFen()) : "") + (V.HasEnpassant ? (" " + this.getEnpassantFen()) : ""); } @@ -400,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[0]; //[0] to work with MarseilleRules + this.movesCount = parseInt(fenParsed.movesCount); this.setOtherVariables(fen); } @@ -492,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) @@ -628,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] @@ -727,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 @@ -817,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 = this.getOppCol(this.turn); + c = V.GetOppCol(this.turn); } const firstRank = (c == "w" ? V.size.x-1 : 0); @@ -989,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; + } } } @@ -1018,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) @@ -1050,58 +1068,29 @@ 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"); @@ -1147,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]); @@ -1165,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; @@ -1175,15 +1164,10 @@ class ChessRules for (let j=0; j eval2)) { @@ -1193,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)) { @@ -1232,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