X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FBario.js;fp=client%2Fsrc%2Fvariants%2FBario.js;h=5b5b33c39ba28326448a6dd4b1571758fba9c891;hb=2a0672a98b555f0fecfd951d583e69419769d411;hp=0000000000000000000000000000000000000000;hpb=de9e4580cc3953e6b13b5ec775ce9f3a0b21e1a0;p=vchess.git diff --git a/client/src/variants/Bario.js b/client/src/variants/Bario.js new file mode 100644 index 00000000..5b5b33c3 --- /dev/null +++ b/client/src/variants/Bario.js @@ -0,0 +1,544 @@ +import { ChessRules, PiPo, Move } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +// TODO: issue with undo of specialisation to cover check, subTurn decremented to 0 + +export class BarioRules extends ChessRules { + + // Does not really seem necessary (although the author mention it) + // Instead, first move = pick a square for the king. + static get HasCastle() { + return false; + } + + // Undetermined piece form: + static get UNDEFINED() { + return 'u'; + } + + static get PIECES() { + return ChessRules.PIECES.concat(V.UNDEFINED); + } + + getPpath(b) { + if (b[1] == V.UNDEFINED) return "Bario/" + b; + return b; + } + + canIplay(side, [x, y]) { + if (this.movesCount >= 2) return super.canIplay(side, [x, y]); + return ( + this.turn == side && + ( + (side == 'w' && x == 7) || + (side == 'b' && x == 0) + ) + ); + } + + hoverHighlight(x, y) { + const c = this.turn; + return ( + this.movesCount <= 1 && + ( + (c == 'w' && x == 7) || + (c == 'b' && x == 0) + ) + ); + } + + // Initiate the game by choosing a square for the king: + doClick(square) { + const c = this.turn; + if ( + this.movesCount >= 2 || + ( + (c == 'w' && square[0] != 7) || + (c == 'b' && square[0] != 0) + ) + ) { + return null; + } + return new Move({ + appear: [ + new PiPo({ x: square[0], y: square[1], c: c, p: V.KING }) + ], + vanish: [], + start: { x: -1, y: -1 }, + }); + } + + // Do not check kings (TODO: something more subtle!) + static IsGoodPosition(position) { + if (position.length == 0) return false; + const rows = position.split("/"); + if (rows.length != V.size.x) return false; + for (let row of rows) { + let sumElts = 0; + for (let i = 0; i < row.length; i++) { + if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; + else { + const num = parseInt(row[i], 10); + if (isNaN(num) || num <= 0) return false; + sumElts += num; + } + } + if (sumElts != V.size.y) return false; + } + return true; + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{8,8}$/)) + if (!fenParsed.capture) return false; + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + { + reserve: fenParts[4], + capture: fenParts[5] + }, + ChessRules.ParseFen(fen) + ); + } + + getReserveFen() { + let counts = new Array(8); + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + counts[i] = this.reserve["w"][V.PIECES[i]]; + counts[4 + i] = this.reserve["b"][V.PIECES[i]]; + } + return counts.join(""); + } + + getCaptureFen() { + const L = this.captureUndefined.length; + const cu = this.captureUndefined[L-1]; + return (!!cu ? V.CoordsToSquare(cu) : "-"); + } + + getFen() { + return ( + super.getFen() + " " + + this.getReserveFen() + " " + + this.getCaptureFen() + ); + } + + getFenForRepeat() { + return ( + super.getFenForRepeat() + "_" + + this.getReserveFen() + "_" + + this.getCaptureFen() + ); + } + + static GenRandInitFen() { + return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -"; + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + const reserve = + V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10)); + this.reserve = { + w: { + [V.ROOK]: reserve[0], + [V.KNIGHT]: reserve[1], + [V.BISHOP]: reserve[2], + [V.QUEEN]: reserve[3] + }, + b: { + [V.ROOK]: reserve[4], + [V.KNIGHT]: reserve[5], + [V.BISHOP]: reserve[6], + [V.QUEEN]: reserve[7] + } + }; + const cu = V.ParseFen(fen).capture; + this.captureUndefined = [cu == '-' ? null : V.SquareToCoords(cu)]; + this.subTurn = (cu == "-" ? 1 : 0); + // Local stack of pieces' definitions + this.definitions = []; + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + getPiece(i, j) { + if (i >= V.size.x) return V.RESERVE_PIECES[j]; + return this.board[i][j].charAt(1); + } + + getReservePpath(index, color) { + return color + V.RESERVE_PIECES[index]; + } + + static get RESERVE_PIECES() { + return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; + } + + getReserveMoves([x, y]) { + const color = this.turn; + const p = V.RESERVE_PIECES[y]; + if (this.reserve[color][p] == 0) return []; + // 2 cases, subTurn == 0 => target this.captureUndefined only (one square) + if (this.subTurn == 0) { + const L = this.captureUndefined.length; + const cu = this.captureUndefined[L-1]; + return ( + new Move({ + appear: [ + new PiPo({ x: cu.x, y: cu.y, c: color, p: p }) + ], + vanish: [ + new PiPo({ x: cu.x, y: cu.y, c: color, p: V.UNDEFINED }) + ], + start: { x: x, y: y } + }) + ); + } + // or, subTurn == 1 => target any undefined piece that we own. + let moves = []; + 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) == color && + this.getPiece(i, j) == V.UNDEFINED + ) { + let mv = new Move({ + appear: [ + new PiPo({ x: i, y: j, c: color, p: p }) + ], + vanish: [ + new PiPo({ x: i, y: j, c: color, p: V.UNDEFINED }) + ], + start: { x: x, y: y }, + end: { x: i, y: j } + }); + moves.push(mv); + } + } + } + return moves; + } + + getPotentialMovesFrom([x, y]) { + if (this.subTurn == 0) { + if (x < V.size.x) return []; + return this.getReserveMoves([x, y]); + } + if (this.subTurn == 1) { + // Both normal move (from defined piece) and definition allowed + if (x >= V.size.x) return this.getReserveMoves([x, y]); + if (this.getPiece(x, y) == V.UNDEFINED) return []; + } + // subTurn == 1 and we move any piece, or + // subTurn == 2 and we can only move the just-defined piece + if (this.subTurn == 2) { + const L = this.definitions.length; //at least 1 + const df = this.definitions[L-1]; + if (x != df.x || y != df.y) return []; + } + return super.getPotentialMovesFrom([x, y]); + } + + getAllValidMoves() { + const getAllReserveMoves = () => { + let moves = []; + const color = this.turn; + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + moves = moves.concat( + this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i]) + ); + } + return moves; + } + if (this.subTurn == 0) return getAllReserveMoves(); + let moves = super.getAllPotentialMoves(); + if (this.subTurn == 1) + moves = moves.concat(getAllReserveMoves()); + return this.filterValid(moves); + } + + filterValid(moves) { + const color = this.turn; + return moves.filter(m => { + if (m.vanish.length == 0) return true; + const start = { x: m.vanish[0].x, y: m.vanish[0].y }; + const end = { x: m.appear[0].x, y: m.appear[0].y }; + if (start.x == end.x && start.y == end.y) return true; //unfinished turn + this.play(m); + const res = !this.underCheck(color); + this.undo(m); + return res; + }); + } + + atLeastOneMove() { + const atLeastOneReserveMove = () => { + for (let i = 0; i < V.RESERVE_PIECES.length; i++) { + let moves = this.filterValid( + this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) + ); + if (moves.length > 0) return true; + } + return false; + }; + if (this.subTurn == 0) return true; //always one reserve for an undefined + if (!super.atLeastOneMove()) return atLeastOneReserveMove(); + return true; + } + + underCheck(color) { + if (super.underCheck(color)) return true; + // Aux func for piece attack on king (no pawn) + const pieceAttackOn = (p, [x1, y1], [x2, y2]) => { + const shift = [x2 - x1, y2 - y1]; + const absShift = shift.map(Math.abs); + if ( + ( + p == V.KNIGHT && + (absShift[0] + absShift[1] != 3 || shift[0] == 0 || shift[1] == 0) + ) || + (p == V.ROOK && shift[0] != 0 && shift[1] != 0) || + (p == V.BISHOP && absShift[0] != absShift[1]) || + ( + p == V.QUEEN && + shift[0] != 0 && shift[1] != 0 && absShift[0] != absShift[1] + ) + ) { + return false; + } + // Step is compatible with piece: + const step = [ + shift[0] / Math.abs(shift[0]) || 0, + shift[1] / Math.abs(shift[1]) || 0 + ]; + let [i, j] = [x1 + step[0], y1 + step[1]]; + while (i != x2 || j != y2) { + if (this.board[i][j] != V.EMPTY) return false; + i += step[0]; + j += step[1]; + } + return true; + }; + // Check potential specializations of undefined using reserve: + const oppCol = V.GetOppCol(color); + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol && + this.getPiece(i, j) == V.UNDEFINED + ) { + for (let p of V.RESERVE_PIECES) { + if ( + this.reserve[oppCol][p] >= 1 && + pieceAttackOn(p, [i, j], this.kingPos[color]) + ) { + return true; + } + } + } + } + } + return false; + } + + play(move) { + const toNextPlayer = () => { + V.PlayOnBoard(this.board, move); + this.turn = V.GetOppCol(this.turn); + this.subTurn = + (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED ? 0 : 1); + this.movesCount++; + this.postPlay(move); + }; + if (move.vanish.length == 0) { + toNextPlayer(); + return; + } + const start = { x: move.vanish[0].x, y: move.vanish[0].y }; + const end = { x: move.appear[0].x, y: move.appear[0].y }; + if (start.x == end.x && start.y == end.y) { + // Specialisation (subTurn == 1 before 2), or Removal (subTurn == 0). + // In both cases, turn not over, and a piece removed from reserve + this.reserve[this.turn][move.appear[0].p]--; + if (move.appear[0].c == move.vanish[0].c) { + // Specialisation: play "move" on board + V.PlayOnBoard(this.board, move); + this.definitions.push(move.end); + } + this.subTurn++; + } + else { + // Normal move (subTurn 1 or 2: change turn) + this.epSquares.push(this.getEpSquare(move)); + toNextPlayer(); + } + } + + postPlay(move) { + const color = V.GetOppCol(this.turn); + if (move.vanish.length == 0) { + this.kingPos[color] = [move.end.x, move.end.y]; + const firstRank = (color == 'w' ? 7 : 0); + for (let j = 0; j < 8; j++) { + if (j != move.end.y) this.board[firstRank][j] = color + V.UNDEFINED; + } + } + else { + if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED) + this.captureUndefined.push(move.end); + if (move.appear[0].p == V.KING) super.postPlay(move); + else { + // If now all my pieces are defined, back to undefined state, + // only if at least two different kind of pieces on board! + // Store current state in move (cannot infer it after!) + if ( + this.board.every(b => { + return b.every(cell => { + return ( + cell == V.EMPTY || + cell[0] != color || + cell[1] != V.UNDEFINED + ); + }); + }) + ) { + const piecesList = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]; + let myPieces = {}; + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == color + ) { + const p = this.getPiece(i, j); + if (piecesList.includes(p)) + myPieces[p] = (!myPieces[p] ? 1 : myPieces[p] + 1); + } + } + } + const pk = Object.keys(myPieces); + if (pk.length >= 2) { + move.position = this.getBaseFen(); + for (let p of pk) this.reserve[color][p] = myPieces[p]; + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == color && + piecesList.includes(this.getPiece(i, j)) + ) { + this.board[i][j] = color + V.UNDEFINED; + } + } + } + } + } + } + } + } + + undo(move) { + const toPrevPlayer = () => { + V.UndoOnBoard(this.board, move); + this.turn = V.GetOppCol(this.turn); + this.movesCount--; + this.postUndo(move); + }; + if (move.vanish.length == 0) { + toPrevPlayer(); + return; + } + const start = { x: move.vanish[0].x, y: move.vanish[0].y }; + const end = { x: move.appear[0].x, y: move.appear[0].y }; + if (start.x == end.x && start.y == end.y) { + this.reserve[this.turn][move.appear[0].p]++; + if (move.appear[0].c == move.vanish[0].c) { + V.UndoOnBoard(this.board, move); + this.definitions.pop(); + } + this.subTurn--; + } + else { + this.epSquares.pop(); + toPrevPlayer(); + } + } + + postUndo(move) { + const color = this.turn; + if (move.vanish.length == 0) { + this.kingPos[color] = [-1, -1]; + const firstRank = (color == 'w' ? 7 : 0); + for (let j = 0; j < 8; j++) this.board[firstRank][j] = ""; + } + else { + if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED) + this.captureUndefined.pop(); + if (move.appear[0].p == V.KING) super.postUndo(move); + else { + if (!!move.position) { + this.board = V.GetBoard(move.position); + this.reserve[color] = { + [V.ROOK]: 0, + [V.KNIGHT]: 0, + [V.BISHOP]: 0, + [V.QUEEN]: 0 + } + } + } + } + } + + getComputerMove() { + const color = this.turn; + // Just play at random for now... + let mvArray = []; + while (this.turn == color) { + const moves = this.getAllValidMoves(); + const choice = moves[randInt(moves.length)]; + mvArray.push(choice); + this.play(choice); + } + for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]); + return (mvArray.length == 1? mvArray[0] : mvArray); + } + + static get VALUES() { + return Object.assign({ u: 0 }, ChessRules.VALUES); + } + + // NOTE: evalPosition is wrong, but unused (random mover) + + getNotation(move) { + const end = { x: move.appear[0].x, y: move.appear[0].y }; + const endSquare = V.CoordsToSquare(end); + if (move.vanish.length == 0) return "K@" + endSquare; + const start = { x: move.vanish[0].x, y: move.vanish[0].y }; + if (start.x == end.x && start.y == end.y) { + // Something is specialized, or removed + const symbol = move.appear[0].p.toUpperCase(); + if (move.appear[0].c == move.vanish[0].c) + // Specialisation + return symbol + "@" + endSquare; + // Removal: + return symbol + endSquare + "X"; + } + // Normal move + return super.getNotation(move); + } + +};