X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FWormhole.js;h=fcf63b4ed92d732835d2ba50a93a831a64ff15c8;hb=7e8a7ea1cb66adb4a987badfb0a3c2f99a21bd0a;hp=14766ff3b8e6b2ea7f70a9acdad726e8a37ba706;hpb=e71161fbfffe53b0f4b174e0467cdd98cc70b7b0;p=vchess.git diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js index 14766ff3..fcf63b4e 100644 --- a/client/src/variants/Wormhole.js +++ b/client/src/variants/Wormhole.js @@ -1,21 +1,6 @@ -import { ChessRules, PiPo, Move } from "@/base_rules"; -import { ArrayFun } from "@/utils/array"; -import { randInt } from "@/utils/alea"; - -// TODO: -// Short-range pieces: -// rook 1 or 2 squares orthogonal -// bishop 1 or 2 diagonal -// queen = bishop + rook -// knight: one square orthogonal + 1 diagonal (only acepted desc) -// no castle or en passant. Promotion possible only by capture (otherwise hole) - -export const VariantRules = class WormholeRules extends ChessRules { - // TODO: redefine pieces movements, taking care of holes (auxiliary func: getSquareAfter(shiftX,shiftY)) - // this aux func could return null / undefined - // revoir getPotentialMoves et isAttacked : tout ce qui touche au board avec calcul, - // car les "board[x+..][y+..]" deviennent des board[getSquareAfter...] - // Special FEN sign for holes: 'x' +import { ChessRules } from "@/base_rules"; + +export class WormholeRules extends ChessRules { static get HasFlags() { return false; @@ -25,31 +10,316 @@ export const VariantRules = class WormholeRules extends ChessRules { return false; } - getSquareAfter(sq, shift) { - // TODO + static get HOLE() { + return "xx"; + } + + static board2fen(b) { + if (b[0] == 'x') return 'x'; + return ChessRules.board2fen(b); + } + + static fen2board(f) { + if (f == 'x') return V.HOLE; + return ChessRules.fen2board(f); } getPpath(b) { - if (b.indexOf('x') >= 0) - return "Wormhole/hole.svg"; + if (b[0] == 'x') return "Wormhole/hole"; return b; } - // TODO: postUpdateVars: board[start] = "xx"; --> V.HOLE + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + let kings = { "k": 0, "K": 0 }; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (['K','k'].includes(row[i])) kings[row[i]]++; + if (['x'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num)) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + if (Object.values(kings).some(v => v != 1)) return false; + return true; + } + + getSquareAfter(square, movement) { + let shift1, shift2; + if (Array.isArray(movement[0])) { + // A knight + shift1 = movement[0]; + shift2 = movement[1]; + } else { + shift1 = movement; + shift2 = null; + } + const tryMove = (init, shift) => { + let step = [ + shift[0] / Math.abs(shift[0]) || 0, + shift[1] / Math.abs(shift[1]) || 0, + ]; + const nbSteps = Math.max(Math.abs(shift[0]), Math.abs(shift[1])); + let stepsAchieved = 0; + let sq = [init[0] + step[0], init[1] + step[1]]; + while (V.OnBoard(sq[0],sq[1])) { + if (this.board[sq[0]][sq[1]] != V.HOLE) + stepsAchieved++; + if (stepsAchieved < nbSteps) { + sq[0] += step[0]; + sq[1] += step[1]; + } + else break; + } + if (stepsAchieved < nbSteps) + // The move is impossible + return null; + return sq; + }; + // First, apply shift1 + let dest = tryMove(square, shift1); + if (dest && shift2) + // A knight: apply second shift + dest = tryMove(dest, shift2); + return dest; + } + + // NOTE (TODO?): some extra work done in some function because informations + // on one step should ease the computation for a step in the same direction. + static get steps() { + return { + r: [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1], + [-2, 0], + [2, 0], + [0, -2], + [0, 2] + ], + // Decompose knight movements into one step orthogonal + one diagonal + n: [ + [[0, -1], [-1, -1]], + [[0, -1], [1, -1]], + [[-1, 0], [-1,-1]], + [[-1, 0], [-1, 1]], + [[0, 1], [-1, 1]], + [[0, 1], [1, 1]], + [[1, 0], [1, -1]], + [[1, 0], [1, 1]] + ], + b: [ + [-1, -1], + [-1, 1], + [1, -1], + [1, 1], + [-2, -2], + [-2, 2], + [2, -2], + [2, 2] + ], + k: [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1], + [-1, -1], + [-1, 1], + [1, -1], + [1, 1] + ] + }; + } + + getJumpMoves([x, y], steps) { + let moves = []; + for (let step of steps) { + const sq = this.getSquareAfter([x,y], step); + if (sq && + ( + this.board[sq[0]][sq[1]] == V.EMPTY || + this.canTake([x, y], sq) + ) + ) { + moves.push(this.getBasicMove([x, y], sq)); + } + } + return moves; + } + + // What are the pawn moves from square x,y ? + getPotentialPawnMoves([x, y]) { + const color = this.turn; + let moves = []; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + + const sq1 = this.getSquareAfter([x,y], [shiftX,0]); + if (sq1 && this.board[sq1[0]][y] == V.EMPTY) { + // One square forward (cannot be a promotion) + moves.push(this.getBasicMove([x, y], [sq1[0], y])); + if (x == startRank) { + // If two squares after is available, then move is possible + const sq2 = this.getSquareAfter([x,y], [2*shiftX,0]); + if (sq2 && this.board[sq2[0]][y] == V.EMPTY) + // Two squares jump + moves.push(this.getBasicMove([x, y], [sq2[0], y])); + } + } + // Captures + for (let shiftY of [-1, 1]) { + const sq = this.getSquareAfter([x,y], [shiftX,shiftY]); + if ( + !!sq && + this.board[sq[0]][sq[1]] != V.EMPTY && + this.canTake([x, y], [sq[0], sq[1]]) + ) { + const finalPieces = sq[0] == lastRank + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.PAWN]; + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [sq[0], sq[1]], { + c: color, + p: piece + }) + ); + } + } + } + + return moves; + } + + getPotentialRookMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.ROOK]); + } + + getPotentialKnightMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.KNIGHT]); + } + + getPotentialBishopMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.BISHOP]); + } - updateVariables(move) { - super.updateVariables(move); + getPotentialQueenMoves(sq) { + return this.getJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); } - unupdateVariables(move) { - super.unupdateVariables(move); + getPotentialKingMoves(sq) { + return this.getJumpMoves(sq, V.steps[V.KING]); + } + + isAttackedByJump([x, y], color, piece, steps) { + for (let step of steps) { + const sq = this.getSquareAfter([x,y], step); + if ( + sq && + this.getPiece(sq[0], sq[1]) == piece && + this.getColor(sq[0], sq[1]) == color + ) { + return true; + } + } + return false; + } + + isAttackedByPawn([x, y], color) { + const pawnShift = (color == "w" ? 1 : -1); + for (let i of [-1, 1]) { + const sq = this.getSquareAfter([x,y], [pawnShift,i]); + if ( + sq && + this.getPiece(sq[0], sq[1]) == V.PAWN && + this.getColor(sq[0], sq[1]) == color + ) { + return true; + } + } + return false; + } + + isAttackedByRook(sq, color) { + return this.isAttackedByJump(sq, color, V.ROOK, V.steps[V.ROOK]); + } + + isAttackedByKnight(sq, color) { + // NOTE: knight attack is not symmetric in this variant: + // steps order need to be reversed. + return this.isAttackedByJump( + sq, + color, + V.KNIGHT, + V.steps[V.KNIGHT].map(s => s.reverse()) + ); + } + + isAttackedByBishop(sq, color) { + return this.isAttackedByJump(sq, color, V.BISHOP, V.steps[V.BISHOP]); + } + + isAttackedByQueen(sq, color) { + return this.isAttackedByJump( + sq, + color, + V.QUEEN, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); + } + + isAttackedByKing(sq, color) { + return this.isAttackedByJump(sq, color, V.KING, V.steps[V.KING]); + } + + // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical. + // This is a side-effect on board generated by the move. + static PlayOnBoard(board, move) { + board[move.vanish[0].x][move.vanish[0].y] = V.HOLE; + for (let psq of move.appear) board[psq.x][psq.y] = psq.c + psq.p; + } + + getCurrentScore() { + if (this.atLeastOneMove()) return "*"; + // No valid move: I lose + return this.turn == "w" ? "0-1" : "1-0"; + } + + static get SEARCH_DEPTH() { + return 2; + } + + evalPosition() { + let evaluation = 0; + for (let i = 0; i < V.size.x; i++) { + for (let j = 0; j < V.size.y; j++) { + if (![V.EMPTY,V.HOLE].includes(this.board[i][j])) { + const sign = this.getColor(i, j) == "w" ? 1 : -1; + evaluation += sign * V.VALUES[this.getPiece(i, j)]; + } + } + } + return evaluation; } getNotation(move) { const piece = this.getPiece(move.start.x, move.start.y); // Indicate start square + dest square, because holes distort the board let notation = - piece.toUpperCase() + + (piece != V.PAWN ? piece.toUpperCase() : "") + V.CoordsToSquare(move.start) + (move.vanish.length > move.appear.length ? "x" : "") + V.CoordsToSquare(move.end); @@ -58,4 +328,5 @@ export const VariantRules = class WormholeRules extends ChessRules { notation += "=" + move.appear[0].p.toUpperCase(); return notation; } + };