X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fbase_rules.js;h=d2b8a7fd98e1dab4d6531d6522687afd1fd37b7a;hb=f35b9960e1c527fc400ebac85321bd4712459da3;hp=350e0de642f59a78536af55c3fab92a3278f0853;hpb=d7c00f6a7d6ad573df2a27965bf763b3bb1d0c18;p=vchess.git diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 350e0de6..d2b8a7fd 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -46,16 +46,26 @@ export const ChessRules = class ChessRules { static get CanAnalyze() { return true; } + // Patch: issues with javascript OOP, objects can't access static fields. + get canAnalyze() { + return V.CanAnalyze; + } // Some variants show incomplete information, // and thus show only a partial moves list or no list at all. static get ShowMoves() { return "all"; } + get showMoves() { + return V.ShowMoves; + } - // Path to pieces - static getPpath(b) { - return b; //usual pieces in pieces/ folder + // Some variants always show the same orientation + static get CanFlip() { + return true; + } + get canFlip() { + return V.CanFlip; } // Turn "wb" into "B" (for FEN) @@ -160,6 +170,11 @@ export const ChessRules = class ChessRules { return V.CoordToColumn(coords.y) + (V.size.x - coords.x); } + // Path to pieces + getPpath(b) { + return b; //usual pieces in pieces/ folder + } + // Aggregates flags into one object aggregateFlags() { return this.castleFlags; @@ -180,17 +195,19 @@ export const ChessRules = class ChessRules { } // Argument is a move: const move = moveOrSquare; - const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x]; + const s = move.start, + e = move.end; // NOTE: next conditions are first for Atomic, and last for Checkered if ( move.appear.length > 0 && - Math.abs(sx - ex) == 2 && + Math.abs(s.x - e.x) == 2 && + s.y == e.y && move.appear[0].p == V.PAWN && ["w", "b"].includes(move.appear[0].c) ) { return { - x: (sx + ex) / 2, - y: sy + x: (s.x + e.x) / 2, + y: s.y }; } return undefined; //default @@ -221,11 +238,20 @@ export const ChessRules = class ChessRules { ///////////// // FEN UTILS - // Setup the initial random (assymetric) position - static GenRandInitFen() { + // Setup the initial random (asymmetric) position + static GenRandInitFen(randomness) { + if (randomness == 0) + // Deterministic: + return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 1111 -"; + let pieces = { w: new Array(8), b: new Array(8) }; - // Shuffle pieces on first and last rank + // Shuffle pieces on first (and last rank if randomness == 2) for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w']; + break; + } + let positions = ArrayFun.range(8); // Get random squares for bishops @@ -267,12 +293,13 @@ export const ChessRules = class ChessRules { pieces[c][knight2Pos] = "n"; pieces[c][rook2Pos] = "r"; } + // Add turn + flags + enpassant return ( pieces["b"].join("") + "/pppppppp/8/8/8/8/PPPPPPPP/" + pieces["w"].join("").toUpperCase() + " w 0 1111 -" - ); //add turn + flags + enpassant + ); } // "Parse" FEN: just return untransformed string data @@ -357,9 +384,9 @@ export const ChessRules = class ChessRules { for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) { const character = rows[i][indexInRow]; const num = parseInt(character); + // If num is a number, just shift j: if (!isNaN(num)) j += num; - //just shift j - //something at position i,j + // Else: something at position i,j else board[i][j++] = V.fen2board(character); } } @@ -370,7 +397,6 @@ export const ChessRules = class ChessRules { setFlags(fenflags) { // white a-castle, h-castle, black a-castle, h-castle this.castleFlags = { w: [true, true], b: [true, true] }; - if (!fenflags) return; for (let i = 0; i < 4; i++) this.castleFlags[i < 2 ? "w" : "b"][i % 2] = fenflags.charAt(i) == "1"; } @@ -378,12 +404,12 @@ export const ChessRules = class ChessRules { ////////////////// // INITIALIZATION - constructor(fen) { - this.re_init(fen); - } - // Fen string fully describes the game state - re_init(fen) { + constructor(fen) { + if (!fen) + // In printDiagram() fen isn't supply because only getPpath() is used + // TODO: find a better solution! + return; const fenParsed = V.ParseFen(fen); this.board = V.GetBoard(fenParsed.position); this.turn = fenParsed.turn[0]; //[0] to work with MarseilleRules @@ -435,7 +461,7 @@ export const ChessRules = class ChessRules { if (V.HasEnpassant) { const epSq = parsedFen.enpassant != "-" - ? V.SquareToCoords(parsedFen.enpassant) + ? this.getEpSquare(parsedFen.enpassant) : undefined; this.epSquares = [epSq]; } @@ -450,7 +476,7 @@ export const ChessRules = class ChessRules { return { x: 8, y: 8 }; } - // Color of thing on suqare (i,j). 'undefined' if square is empty + // Color of thing on square (i,j). 'undefined' if square is empty getColor(i, j) { return this.board[i][j].charAt(0); } @@ -526,7 +552,7 @@ export const ChessRules = class ChessRules { //////////////////// // MOVES GENERATION - // All possible moves from selected square (assumption: color is OK) + // All possible moves from selected square getPotentialMovesFrom([x, y]) { switch (this.getPiece(x, y)) { case V.PAWN: @@ -578,6 +604,7 @@ export const ChessRules = class ChessRules { }) ); } + return mv; } @@ -590,7 +617,7 @@ export const ChessRules = class ChessRules { let j = y + step[1]; while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { moves.push(this.getBasicMove([x, y], [i, j])); - if (oneStep !== undefined) continue outerLoop; + if (oneStep) continue outerLoop; i += step[0]; j += step[1]; } @@ -617,8 +644,8 @@ export const ChessRules = class ChessRules { x + shiftX == lastRank ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] : [V.PAWN]; - // One square forward if (this.board[x + shiftX][y] == V.EMPTY) { + // One square forward for (let piece of finalPieces) { moves.push( this.getBasicMove([x, y], [x + shiftX, y], { @@ -722,10 +749,11 @@ export const ChessRules = class ChessRules { const oppCol = V.GetOppCol(c); let moves = []; let i = 0; + // King, then rook: const finalSquares = [ [2, 3], [V.size.y - 2, V.size.y - 3] - ]; //king, then rook + ]; castlingCheck: for ( let castleSide = 0; castleSide < 2; @@ -815,12 +843,10 @@ export const ChessRules = class ChessRules { // (for engine and game end) getAllValidMoves() { const color = this.turn; - const oppCol = V.GetOppCol(color); let potentialMoves = []; for (let i = 0; i < V.size.x; i++) { for (let j = 0; j < V.size.y; j++) { - // Next condition "!= oppCol" to work with checkered variant - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + if (this.getColor(i, j) == color) { Array.prototype.push.apply( potentialMoves, this.getPotentialMovesFrom([i, j]) @@ -834,10 +860,9 @@ export const ChessRules = class ChessRules { // Stop at the first move found atLeastOneMove() { const color = this.turn; - const oppCol = V.GetOppCol(color); for (let i = 0; i < V.size.x; i++) { for (let j = 0; j < V.size.y; j++) { - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { + if (this.getColor(i, j) == color) { const moves = this.getPotentialMovesFrom([i, j]); if (moves.length > 0) { for (let k = 0; k < moves.length; k++) { @@ -862,10 +887,31 @@ export const ChessRules = class ChessRules { ); } + // Generic method for non-pawn pieces ("sliding or jumping"): + // is x,y attacked by a piece of color in array 'colors' ? + isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { + rx += step[0]; + ry += step[1]; + } + if ( + V.OnBoard(rx, ry) && + this.getPiece(rx, ry) === piece && + colors.includes(this.getColor(rx, ry)) + ) { + return true; + } + } + return false; + } + // Is square x,y attacked by 'colors' pawns ? isAttackedByPawn([x, y], colors) { for (let c of colors) { - let pawnShift = c == "w" ? 1 : -1; + const pawnShift = c == "w" ? 1 : -1; if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { for (let i of [-1, 1]) { if ( @@ -924,27 +970,6 @@ export const ChessRules = class ChessRules { ); } - // Generic method for non-pawn pieces ("sliding or jumping"): - // is x,y attacked by a piece of color in array 'colors' ? - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { - for (let step of steps) { - let rx = x + step[0], - ry = y + step[1]; - while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { - rx += step[0]; - ry += step[1]; - } - if ( - V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) - ) { - return true; - } - } - return false; - } - // Is color under check after his move ? underCheck(color) { return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]); @@ -967,6 +992,8 @@ export const ChessRules = class ChessRules { // After move is played, update variables + flags updateVariables(move) { let piece = undefined; + // TODO: update variables before move is played, and just use this.turn ? + // (doesn't work in general, think MarseilleChess) let c = undefined; if (move.vanish.length >= 1) { // Usual case, something is moved @@ -977,9 +1004,8 @@ export const ChessRules = class ChessRules { 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 + if (!['w','b'].includes(c)) { + // Checkered, for example c = V.GetOppCol(this.turn); } const firstRank = c == "w" ? V.size.x - 1 : 0; @@ -1022,9 +1048,9 @@ export const ChessRules = class ChessRules { play(move) { // DEBUG: - // if (!this.states) this.states = []; - // const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); - // this.states.push(stateFen); +// if (!this.states) this.states = []; +// 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)); @@ -1043,9 +1069,9 @@ export const ChessRules = class ChessRules { this.unupdateVariables(move); // DEBUG: - // const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); - // if (stateFen != this.states[this.states.length-1]) debugger; - // this.states.pop(); +// const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen(); +// if (stateFen != this.states[this.states.length-1]) debugger; +// this.states.pop(); } /////////////// @@ -1054,7 +1080,6 @@ export const ChessRules = class ChessRules { // What is the score ? (Interesting if game is over) getCurrentScore() { if (this.atLeastOneMove()) - // game not over return "*"; // Game over @@ -1096,29 +1121,17 @@ export const ChessRules = class ChessRules { return 3; } - // NOTE: works also for extinction chess because depth is 3... getComputerMove() { const maxeval = V.INFINITY; const color = this.turn; // Some variants may show a bigger moves list to the human (Switching), // thus the argument "computer" below (which is generally ignored) - let moves1 = this.getAllValidMoves("computer"); + let moves1 = this.getAllValidMoves(); + if (moves1.length == 0) - //TODO: this situation should not happen + // TODO: this situation should not happen return null; - // Can I mate in 1 ? (for Magnetic & Extinction) - for (let i of shuffle(ArrayFun.range(moves1.length))) { - this.play(moves1[i]); - let finish = Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE; - if (!finish) { - const score = this.getCurrentScore(); - if (["1-0", "0-1"].includes(score)) finish = true; - } - this.undo(moves1[i]); - if (finish) return moves1[i]; - } - // Rank moves using a min-max at depth 2 for (let i = 0; i < moves1.length; i++) { // Initial self evaluation is very low: "I'm checkmated" @@ -1130,7 +1143,7 @@ export const ChessRules = class ChessRules { // Initial enemy evaluation is very low too, for him eval2 = (color == "w" ? 1 : -1) * maxeval; // Second half-move: - let moves2 = this.getAllValidMoves("computer"); + let moves2 = this.getAllValidMoves(); for (let j = 0; j < moves2.length; j++) { this.play(moves2[j]); const score2 = this.getCurrentScore(); @@ -1166,6 +1179,7 @@ export const ChessRules = class ChessRules { moves1.sort((a, b) => { return (color == "w" ? 1 : -1) * (b.eval - a.eval); }); +// console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; })); let candidates = [0]; //indices of candidates moves for (let j = 1; j < moves1.length && moves1[j].eval == moves1[0].eval; j++) @@ -1191,7 +1205,7 @@ export const ChessRules = class ChessRules { return (color == "w" ? 1 : -1) * (b.eval - a.eval); }); } 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 < moves1.length && moves1[j].eval == moves1[0].eval; j++) @@ -1206,7 +1220,7 @@ export const ChessRules = class ChessRules { if (score != "*") return score == "1/2" ? 0 : (score == "1-0" ? 1 : -1) * maxeval; if (depth == 0) return this.evalPosition(); - const moves = this.getAllValidMoves("computer"); + const moves = this.getAllValidMoves(); let v = color == "w" ? -maxeval : maxeval; if (color == "w") { for (let i = 0; i < moves.length; i++) { @@ -1216,8 +1230,9 @@ export const ChessRules = class ChessRules { alpha = Math.max(alpha, v); if (alpha >= beta) break; //beta cutoff } - } //color=="b" + } else { + // color=="b" for (let i = 0; i < moves.length; i++) { this.play(moves[i]); v = Math.min(v, this.alphabeta(depth - 1, alpha, beta)); @@ -1251,7 +1266,7 @@ export const ChessRules = class ChessRules { // TODO: un-ambiguous notation (switch on piece type, check directions...) getNotation(move) { if (move.appear.length == 2 && move.appear[0].p == V.KING) - //castle + // Castle return move.end.y < move.start.y ? "0-0-0" : "0-0"; // Translate final square @@ -1265,10 +1280,10 @@ export const ChessRules = class ChessRules { // Capture const startColumn = V.CoordToColumn(move.start.y); notation = startColumn + "x" + finalSquare; - } //no capture + } else notation = finalSquare; if (move.appear.length > 0 && move.appear[0].p != V.PAWN) - //promotion + // Promotion notation += "=" + move.appear[0].p.toUpperCase(); return notation; }