From: Benjamin Auder Date: Sat, 16 Jan 2021 02:16:51 +0000 (+0100) Subject: Add Bario draft. Small bugs to fix in Refusal and Bario X-Git-Url: https://git.auder.net/app_dev.php/doc/current/js/index.js?a=commitdiff_plain;h=2a0672a98b555f0fecfd951d583e69419769d411;p=vchess.git Add Bario draft. Small bugs to fix in Refusal and Bario --- diff --git a/TODO b/TODO index 53cace86..7a50f280 100644 --- a/TODO +++ b/TODO @@ -2,14 +2,7 @@ PROBABLY WON'T FIX: Embedded rules language not updated when language is set (in Analyse, Game and Problems) If new live game starts in background, "new game" notify OK but not first move. -NEW VARIANTS: -https://www.pychess.org/variant/shogun -https://www.chessvariants.com/incinf.dir/bario.html - https://www.chessvariants.com/index/listcomments.php?order=DESC&itemid=Bario - https://www.bario-chess-checkers-chessphotography-spaceart.de/ - https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg - -Non-chess: ( won't add draughts: https://lidraughts.org/ ) +NEW VARIANTS, Non-chess ( won't add draughts: https://lidraughts.org/ ) Gomoku, Konane Fanorona https://fr.wikipedia.org/wiki/Fanorona Yoté https://fr.wikipedia.org/wiki/Yot%C3%A9 http://www.zillionsofgames.com/cgi-bin/zilligames/submissions.cgi/92187?do=show;id=960 diff --git a/client/public/images/pieces/Bario/bu.svg b/client/public/images/pieces/Bario/bu.svg new file mode 120000 index 00000000..330db4d0 --- /dev/null +++ b/client/public/images/pieces/Bario/bu.svg @@ -0,0 +1 @@ +../Hidden/bp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Bario/wu.svg b/client/public/images/pieces/Bario/wu.svg new file mode 120000 index 00000000..f52e53d7 --- /dev/null +++ b/client/public/images/pieces/Bario/wu.svg @@ -0,0 +1 @@ +../Hidden/wp.svg \ No newline at end of file diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 0a68127f..4c90f1d8 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -662,7 +662,7 @@ export const ChessRules = class ChessRules { case V.QUEEN: return this.getPotentialQueenMoves(sq); case V.KING: return this.getPotentialKingMoves(sq); } - return []; //never reached + return []; //never reached (but some variants may use it: Bario...) } // Build a regular move from its initial and destination squares. diff --git a/client/src/translations/rules/Bario/en.pug b/client/src/translations/rules/Bario/en.pug index 21203baa..df8d8b60 100644 --- a/client/src/translations/rules/Bario/en.pug +++ b/client/src/translations/rules/Bario/en.pug @@ -1 +1,6 @@ p.boxed TODO + +p Some links - rules unwritten yet - variant slightly buggish for now. +p https://www.chessvariants.com/incinf.dir/bario.html +p https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg +p https://www.bario-chess-checkers-chessphotography-spaceart.de/ diff --git a/client/src/translations/rules/Refusal/en.pug b/client/src/translations/rules/Refusal/en.pug index 3a33838b..c5ad81a4 100644 --- a/client/src/translations/rules/Refusal/en.pug +++ b/client/src/translations/rules/Refusal/en.pug @@ -1,2 +1,23 @@ p.boxed - | TODO + | You can forbid the opponent to play the move he wanted. + +p. + All rules are as in normal chess, except for the following: each turn, + a player has the right to refuse at most one move of the opponent. + To refuse a move, re-play it from end to starting square. + +p. + Two moves promoting the same pawn on the same square, but to a + different type of piece, count as two different moves. + +p If a player has only one legal move, this move must be accepted. + +h3 More information + +p + | See the + a(href="https://www.chessvariants.com/other.dir/refusal.html") + | chessvariants page + | . + +p Inventor: Fred Galvin (1958) diff --git a/client/src/translations/rules/Refusal/es.pug b/client/src/translations/rules/Refusal/es.pug index 3a33838b..c2f75fca 100644 --- a/client/src/translations/rules/Refusal/es.pug +++ b/client/src/translations/rules/Refusal/es.pug @@ -1,2 +1,23 @@ p.boxed - | TODO + | Puedes evitar que el oponente haga cualquier movimiento que quiera. + +p. + Todo va como en el ajedrez ortodoxo, excepto por un detalle: + en cada turno, un jugador tiene derecho a rechazar como máximo una jugada + del oponente. Para rechazar un movimiento, vuelva a reproducirlo al revés. + +p. + Dos movimientos, cada uno promoviendo un peón en la misma casilla, + pero en dos piezas diferentes, se ven como jugadas diferentes. + +p Si un jugador tiene solo un movimiento legal, debe ser aceptado. + +h3 Más información + +p + | Ver la + a(href="https://www.chessvariants.com/other.dir/refusal.html") + | página chessvariants + | . + +p Inventor: Fred Galvin (1958) diff --git a/client/src/translations/rules/Refusal/fr.pug b/client/src/translations/rules/Refusal/fr.pug index 3a33838b..e1b1750e 100644 --- a/client/src/translations/rules/Refusal/fr.pug +++ b/client/src/translations/rules/Refusal/fr.pug @@ -1,2 +1,23 @@ p.boxed - | TODO + | Vous pouvez empêcher l'adversaire de jouer le coup qu'il souhaite. + +p. + Tout se déroule comme aux échecs orthodoxes, à l'exception d'un détail : + à chaque tour, un joueur a le droit de refuser au plus un coup adverse. + Pour refuser un coup, re-jouez le à l'envers. + +p. + Deux coups promouvant chacun un pion sur la même case, mais en deux pièces + différentes, sont vus comme des coups différents. + +p Si un joueur n'a qu'un seul coup légal, celui-ci doit être accepté. + +h3 Plus d'information + +p + | Voir la + a(href="https://www.chessvariants.com/other.dir/refusal.html") + | page chessvariants + | . + +p Inventeur : Fred Galvin (1958) 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); + } + +}; diff --git a/client/src/variants/Refusal.js b/client/src/variants/Refusal.js new file mode 100644 index 00000000..e4ec04d8 --- /dev/null +++ b/client/src/variants/Refusal.js @@ -0,0 +1,201 @@ +import { ChessRules } from "@/base_rules"; +import { randInt } from "@/utils/alea"; + +// TODO: Two moves, both promoting the same pawn, but to a different type of piece, count as two different moves. +// ==> need to accept temporarily pawn promotions even if on forbidden square, and check afterward if promoted type changed (info in lastMove) + +export class RefusalRules extends ChessRules { + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + if (!V.ParseFen(fen).lastMove) return false; + return true; + } + + static ParseFen(fen) { + return Object.assign( + { lastMove: fen.split(" ")[5] }, + ChessRules.ParseFen(fen) + ); + } + + getFen() { + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + return super.getFen() + " " + JSON.stringify(lm); + } + + // NOTE: with this variant's special rule, + // some extra repetitions could be detected... TODO (...) + + static GenRandInitFen(randomness) { + return ChessRules.GenRandInitFen(randomness) + " null"; + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + this.lastMove = [JSON.parse(V.ParseFen(fen).lastMove)]; //may be null + } + + canIplay(side, [x, y]) { + if (super.canIplay(side, [x, y])) return true; + if (this.turn != side) return false; + // Check if playing last move, reversed: + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + return (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y); + } + + getPotentialMovesFrom([x, y]) { + if (this.getColor(x, y) != this.turn) { + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + if (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y) { + let revLm = JSON.parse(JSON.stringify(lm)); + let tmp = revLm.appear; + revLm.appear = revLm.vanish; + revLm.vanish = tmp; + tmp = revLm.start; + revLm.start = revLm.end; + revLm.end = tmp; + return [revLm]; + } + return []; + } + return super.getPotentialMovesFrom([x, y]); + } + + // NOTE: do not take refusal move into account here (two own moves) + atLeastTwoMoves() { + let movesCounter = 0; + const color = this.turn; + 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) { + const moves = this.getPotentialMovesFrom([i, j]); + for (let m of moves) { + if (m.vanish[0].c == color && this.filterValid([m]).length > 0) { + movesCounter++; + if (movesCounter >= 2) return true; + } + } + } + } + } + return false; + } + + filterValid(moves) { + if (moves.length == 0) return []; + const color = this.turn; + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + return moves.filter(m => { + if ( + !!lm && !!lm.refusal && + m.start.x == lm.end.x && m.start.y == lm.end.y && + m.end.x == lm.start.x && m.end.y == lm.start.y + ) { + return false; + } + // NOTE: not using this.play()/undo() ==> infinite loop + V.PlayOnBoard(this.board, m); + if (m.appear[0].p == V.KING) + this.kingPos[m.appear[0].c] = [m.appear[0].x, m.appear[0].y]; + const res = !this.underCheck(color); + V.UndoOnBoard(this.board, m); + if (m.vanish[0].p == V.KING) + this.kingPos[m.vanish[0].c] = [m.vanish[0].x, m.vanish[0].y]; + return res; + }); + } + + prePlay(move) { + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + if ( + // My previous move was already refused? + (!!lm && this.getColor(lm.end.x, lm.end.y) == this.turn) || + // I've only one move available? + !this.atLeastTwoMoves() + ) { + move.noRef = true; + } + // NOTE: refusal could be recomputed, but, it's easier like this + if (move.vanish[0].c != this.turn) move.refusal = true; + } + + getEpSquare(move) { + if (!move.refusal) return super.getEpSquare(move); + return null; //move refusal + } + + postPlay(move) { + if (!move.refusal) super.postPlay(move); + else { + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + this.disaggregateFlags(JSON.parse(lm.flags)); + } + // NOTE: explicitely give fields, because some are assigned in BaseGame + let mvInLm = { + start: move.start, + end: move.end, + appear: move.appear, + vanish: move.vanish, + flags: move.flags + }; + if (!!move.noRef) mvInLm.noRef = true; + if (!!move.refusal) mvInLm.refusal = true; + this.lastMove.push(mvInLm); + } + + postUndo(move) { + if (!move.refusal) super.postUndo(move); + this.lastMove.pop(); + } + + getAllPotentialMoves() { + const color = this.turn; + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + let potentialMoves = []; + 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 || + // Add move refusal: + (!!lm && lm.end.x == i && lm.end.y == j) + ) + ) { + Array.prototype.push.apply( + potentialMoves, + this.getPotentialMovesFrom([i, j]) + ); + } + } + } + return potentialMoves; + } + + getComputerMove() { + // Just play at random for now... (TODO?) + // Refuse last move with odds 1/3. + const moves = this.getAllValidMoves(); + const refusal = moves.find(m => m.vanish[0].c != this.turn); + if (!!refusal) { + if (Math.random() <= 0.33) return refusal; + const others = moves.filter(m => m.vanish[0].c == this.turn); + return others[randInt(others.length)]; + } + else return moves[randInt(moves.length)]; + } + + getNotation(move) { + if (move.vanish[0].c != this.turn) return "Refuse"; + return super.getNotation(move); + } + +}; diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js index fcf63b4e..05d8f2ae 100644 --- a/client/src/variants/Wormhole.js +++ b/client/src/variants/Wormhole.js @@ -57,7 +57,8 @@ export class WormholeRules extends ChessRules { // A knight shift1 = movement[0]; shift2 = movement[1]; - } else { + } + else { shift1 = movement; shift2 = null; }