From: Benjamin Auder Date: Sun, 17 Jan 2021 23:02:44 +0000 (+0100) Subject: Add Yote X-Git-Url: https://git.auder.net/js/img/%7B%7B%20asset%28%27mixstore/images/banner.png%27%29%20%7D%7D?a=commitdiff_plain;h=cbe9537881e68f50c43f48d3699c4b248690fb4d;p=vchess.git Add Yote --- diff --git a/client/public/images/pieces/Konane/bp.svg b/client/public/images/pieces/Konane/bp.svg deleted file mode 100644 index 75e907e0..00000000 --- a/client/public/images/pieces/Konane/bp.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/client/public/images/pieces/Konane/bp.svg b/client/public/images/pieces/Konane/bp.svg new file mode 120000 index 00000000..dc5717d3 --- /dev/null +++ b/client/public/images/pieces/Konane/bp.svg @@ -0,0 +1 @@ +../Yote/wp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Konane/wp.svg b/client/public/images/pieces/Konane/wp.svg deleted file mode 100644 index 357079eb..00000000 --- a/client/public/images/pieces/Konane/wp.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/client/public/images/pieces/Konane/wp.svg b/client/public/images/pieces/Konane/wp.svg new file mode 120000 index 00000000..0d6535fd --- /dev/null +++ b/client/public/images/pieces/Konane/wp.svg @@ -0,0 +1 @@ +../Yote/bp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Yote/bp.svg b/client/public/images/pieces/Yote/bp.svg new file mode 100644 index 00000000..75e907e0 --- /dev/null +++ b/client/public/images/pieces/Yote/bp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/public/images/pieces/Yote/wp.svg b/client/public/images/pieces/Yote/wp.svg new file mode 100644 index 00000000..357079eb --- /dev/null +++ b/client/public/images/pieces/Yote/wp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index f1017024..7627f2dc 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -569,7 +569,7 @@ export default { } } if (score != "*" && ["analyze", "versus"].includes(this.mode)) { - const message = getScoreMessage(score); + const message = getScoreMessage(score, V.ReverseColors); // Show score on screen this.showEndgameMsg(score + " . " + this.st.tr[message]); } diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index cc3f6fd7..b176bd73 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -663,27 +663,29 @@ export default { this.$emit("click-square", sq); if (withPiece && !this.vr.onlyClick(sq)) { this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare); - // For potential drag'n drop, remember start coordinates - // (to center the piece on mouse cursor) - let parent = e.target.parentNode; //surrounding square - const rect = parent.getBoundingClientRect(); - this.start = { - x: rect.x + rect.width / 2, - y: rect.y + rect.width / 2, - id: parent.id - }; - // Add the moving piece to the board, just after current image - this.selectedPiece = e.target.cloneNode(); - Object.assign( - this.selectedPiece.style, - { - position: "absolute", - top: 0, - display: "inline-block", - zIndex: 3000 - } - ); - parent.insertBefore(this.selectedPiece, e.target.nextSibling); + if (this.possibleMoves.length > 0) { + // For potential drag'n drop, remember start coordinates + // (to center the piece on mouse cursor) + let parent = e.target.parentNode; //surrounding square + const rect = parent.getBoundingClientRect(); + this.start = { + x: rect.x + rect.width / 2, + y: rect.y + rect.width / 2, + id: parent.id + }; + // Add the moving piece to the board, just after current image + this.selectedPiece = e.target.cloneNode(); + Object.assign( + this.selectedPiece.style, + { + position: "absolute", + top: 0, + display: "inline-block", + zIndex: 3000 + } + ); + parent.insertBefore(this.selectedPiece, e.target.nextSibling); + } } } } diff --git a/client/src/translations/rules/Konane/en.pug b/client/src/translations/rules/Konane/en.pug index 9a15f67a..930f8c70 100644 --- a/client/src/translations/rules/Konane/en.pug +++ b/client/src/translations/rules/Konane/en.pug @@ -2,6 +2,9 @@ p.boxed | Capture orthogonally at each turn, "as in Draughts". | If you cannot capture, you lose. +p + a(href="https://en.wikipedia.org/wiki/Konane") Hawaiian Checkers + p. To initiate the game, the first player (black) must remove one of his stones either in the upper left or lower right corner, or in the center, diff --git a/client/src/translations/rules/Konane/es.pug b/client/src/translations/rules/Konane/es.pug index 7cb7f73f..ca177266 100644 --- a/client/src/translations/rules/Konane/es.pug +++ b/client/src/translations/rules/Konane/es.pug @@ -2,6 +2,9 @@ p.boxed | Captura ortogonalmente en cada turno, "como a las Damas". | Si no es posible la captura, ha perdido. +p + a(href="https://en.wikipedia.org/wiki/Konane") Damas hawaianas + p. Para iniciar el juego, el primer jugador (negras) debe eliminar uno de sus piedras en la esquina superior izquierda o inferior derecha, diff --git a/client/src/translations/rules/Konane/fr.pug b/client/src/translations/rules/Konane/fr.pug index 23297370..7cdc286b 100644 --- a/client/src/translations/rules/Konane/fr.pug +++ b/client/src/translations/rules/Konane/fr.pug @@ -2,6 +2,9 @@ p.boxed | Capturez orthogonalement à chaque tour, "comme aux Dames". | Si aucune capture n'est possible, vous avez perdu. +p + a(href="https://en.wikipedia.org/wiki/Konane") Dames hawaïennes + p. Pour initialiser la partie, le premier joueur (noirs) doit retirer une de ses pierres soit dans le coin supérieur gauche ou inférieur droit, diff --git a/client/src/translations/rules/Yote/en.pug b/client/src/translations/rules/Yote/en.pug index 3a33838b..9689de8b 100644 --- a/client/src/translations/rules/Yote/en.pug +++ b/client/src/translations/rules/Yote/en.pug @@ -1,2 +1,51 @@ p.boxed - | TODO + | Move a stone or capture by jumping orthogonally. + +p Traditional game in some Western Africa countries. + +p + | The rules described here correspond to + a(href="https://www.youtube.com/watch?v=yuqHy8GOZ_Q") this video + | , with the alternative voctory condition. + | See also + a(href="http://www.cyningstan.com/game/342/yot") this introduction + | . + +p. + It seems that the color of pieces starting the game isn't really determined, + so I decided to start with black stones, as in Go game or Othello. + The following is mostly copy-paste from Wikipedia: + +p. + The game is played on a 5×6 board, which is empty at the beginning of the + game. Each player has twelve pieces in hand. Players alternate turns, + with Black moving first. In a move, a player may either: +ul + li Place a piece in hand on any empty cell of the board. + li. + Move one of their pieces already on the board orthogonally to an empty + adjacent cell. + li. + Capture an opponent's piece if it is orthogonally adjacent to a player's + piece, by jumping to the empty cell immediately beyond it. + The captured piece is removed from the board, and the capturing player + removes another of the opponent's pieces of his choosing from the board. + +figure.diagram-container + .diagram.diag12 + | fen:6/1p2P1/3p2/1p1P2/5P d2,d4: + .diagram.diag22 + | fen:6/3PP1/6/1p4/5P: + figcaption Before and after the marked capture + removal of b4. + +p. + The game ends when a player has no stones on the board (after move 1), + or if he cannot play a move. He loses in both cases. + If only a low number of pieces remain on the board, captures might be + impossible and after a sequences of attempts players may agree on a draw. + +h4 Notes + +p Two consecutive normal moves cannot cancel each other. + +p To remove a stone after a capture, simply click on it. diff --git a/client/src/translations/rules/Yote/es.pug b/client/src/translations/rules/Yote/es.pug index 3a33838b..20170baa 100644 --- a/client/src/translations/rules/Yote/es.pug +++ b/client/src/translations/rules/Yote/es.pug @@ -1,2 +1,55 @@ p.boxed - | TODO + | Mueve una piedra o captura mediante un salto ortogonal. + +p Juego tradicional en algunos países de África Occidental. + +p + | Las reglas descritas aquí corresponden a + a(href="https://www.youtube.com/watch?v=yuqHy8GOZ_Q") este video + | , con la condición de victoria alternativa. + | Ver también + a(href="http://www.cyningstan.com/game/342/yot") esta introducción + | . + +p. + Parece que el color de las piezas que comienzan el juego no es realmente + decidido, así que decidí comenzar con las piedras negras, como en + juego de Go u Othello. Lo siguiente se ha tomado principalmente de Wikipedia: + +p. + El juego tiene lugar en un tablero inicialmente vacío de 5x6. + Cada jugador tiene doce piezas en la mano. Los jugadores se alternan vueltas, + comenzando por las negras. Durante un movimiento, podemos: +ul + li Coloque una pieza de la reserva en un espacio vacío. + li. + Mueve una piedra que ya esté en el tablero, + ortogonalmente a un cuadrado vacío. + li. + Capturar una pieza del oponente si está ortogonalmente adyacente a esa + del jugador, saltando al cuadrado vacío inmediatamente después. + La piedra capturada se elimina del juego y el jugador también elimina + otra pieza opuesta de su elección en el tablero. + +figure.diagram-container + .diagram.diag12 + | fen:6/1p2P1/3p2/1p1P2/5P d2,d4: + .diagram.diag22 + | fen:6/3PP1/6/1p4/5P: + figcaption Antes y después de la captura + eliminación marcada en b4. + +p. + El juego termina cuando un jugador no tiene más piedras en el tablero + (después del primer movimiento), o si no puede ejecutar una jugada. + Pierde en ambos casos. + Si solo quedan en juego un pequeño número de piezas, + las capturas pueden no ser posibles, y en este caso los jugadores pueden + después de algunos intentos, decida empatar. + +h4 Notas + +p Dos jugadas normales consecutivos no pueden anularse entre sí. + +p. + Para eliminar una piedra después de la captura, + simplemente haga clic en ella. diff --git a/client/src/translations/rules/Yote/fr.pug b/client/src/translations/rules/Yote/fr.pug index 3a33838b..da87c565 100644 --- a/client/src/translations/rules/Yote/fr.pug +++ b/client/src/translations/rules/Yote/fr.pug @@ -1,2 +1,52 @@ p.boxed - | TODO + | Déplacez une pierre ou capturez via un saut orthogonal. + +p Jeu traditionnel dans certains pays d'Afrique de l'Ouest. + +p + | Les règles décrites ici correspondent à + a(href="https://www.youtube.com/watch?v=yuqHy8GOZ_Q") cette vidéo + | , avec la condition de victoire alternative. + | Voir aussi + a(href="http://www.cyningstan.com/game/342/yot") cette introduction + | . + +p. + Il semble que la couleur des pièces démarrant la partie ne soit pas vraiment + déterminée, donc j'ai décidé de commencer avec les pierres noires, comme au + jeu de Go ou à Othello. La suite est essentiellement reprise de Wikipedia : + +p. + Le jeu se déroule sur un plateau de taille 5x6 initialement vide. + Chaque joueur dispose de douze pièces en main. Les joueurs alternent les + tours, en commençant par les noirs. Lors d'un coup, on peut : +ul + li Placer une pièce de la réserve sur une case vide. + li. + Déplacer une pierre déjà sur le plateau, orthogonalement vers une case vide. + li. + Capturer une pièce adverse si elle est orthogonalement adjacente à celle + du joueur, en sautant vers la case vide située immédiatement après. + La pierre capturée est retirée du jeu, et le joueur enlève aussi une autre + pièce adverse de son choix sur le plateau. + +figure.diagram-container + .diagram.diag12 + | fen:6/1p2P1/3p2/1p1P2/5P d2,d4: + .diagram.diag22 + | fen:6/3PP1/6/1p4/5P: + figcaption Avant et après la capture marquée + suppression en b4. + +p. + La partie se termine quand un joueur n'a plus de pierres sur le plateau + (après le premier coup), ou s'il est dans l'incapacité de jouer un coup. + Il perd dans les deux cas. + Si seulement un petit nombre de pièces restent en jeu, de nouvelles + captures pourraient être impossibles, et dans ce cas les joueurs peuvent + après quelques essais décider d'un match nul. + +h4 Notes + +p Deux coups normaux consécutifs ne peuvent s'annuler l'un l'autre. + +p Pour supprimer une pierre après capture, cliquez simplement dessus. diff --git a/client/src/utils/scoring.js b/client/src/utils/scoring.js index c659b7d1..3d541289 100644 --- a/client/src/utils/scoring.js +++ b/client/src/utils/scoring.js @@ -1,12 +1,12 @@ // Default score message if none provided -export function getScoreMessage(score) { +export function getScoreMessage(score, reverseColors) { let eogMessage = "Undefined"; //not translated: unused switch (score) { case "1-0": - eogMessage = "White win"; + eogMessage = (!reverseColors ? "White win" : "Black win"); break; case "0-1": - eogMessage = "Black win"; + eogMessage = (!reverseColors ? "Black win" : "White win"); break; case "1/2": eogMessage = "Draw"; diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js index db9cb577..64ce48b3 100644 --- a/client/src/variants/Crazyhouse.js +++ b/client/src/variants/Crazyhouse.js @@ -188,7 +188,7 @@ export class CrazyhouseRules extends ChessRules { if (!super.atLeastOneMove()) { // Search one reserve move for (let i = 0; i < V.RESERVE_PIECES.length; i++) { - let moves = this.filterValid( + const moves = this.filterValid( this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i]) ); if (moves.length > 0) return true; diff --git a/client/src/variants/Konane.js b/client/src/variants/Konane.js index 8f285efc..1ff77ee9 100644 --- a/client/src/variants/Konane.js +++ b/client/src/variants/Konane.js @@ -1,7 +1,5 @@ import { ChessRules, Move, PiPo } from "@/base_rules"; -// TODO: Maybe more flexible end of game messages (V.ColorsReversed ?!) - export class KonaneRules extends ChessRules { static get HasFlags() { @@ -12,10 +10,18 @@ export class KonaneRules extends ChessRules { return false; } + static get ReverseColors() { + return true; + } + static get PIECES() { return V.PAWN; } + getPiece() { + return V.PAWN; + } + getPpath(b) { return "Konane/" + b; } diff --git a/client/src/variants/Yote.js b/client/src/variants/Yote.js index 0d81759c..dc1ef684 100644 --- a/client/src/variants/Yote.js +++ b/client/src/variants/Yote.js @@ -1,7 +1,398 @@ -import { ChessRules } from "@/base_rules"; +import { ChessRules, Move, PiPo } from "@/base_rules"; export class YoteRules extends ChessRules { - // TODO + static get HasFlags() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get Monochrome() { + return true; + } + + static get Notoodark() { + return true; + } + + static get ReverseColors() { + return true; + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // 3) Check reserves + if ( + !fenParsed.reserve || + !fenParsed.reserve.match(/^([0-9]{1,2},){2,2}$/) + ) { + return false; + } + // 4) Check lastMove + if (!fenParsed.lastMove) return false; + const lmParts = fenParsed.lastMove.split(","); + for (lp of lmParts) { + if (lp != "-" && !lp.match(/^([a-f][1-5]){2,2}$/)) return false; + } + return true; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { + reserve: fenParts[3], + lastMove: fenParts[4] + } + ); + } + + static GenRandInitFen(randomness) { + return "6/6/6/6/6 w 0 12,12 -,-"; + } + + getFen() { + return ( + super.getFen() + " " + + this.getReserveFen() + " " + + this.getLastmoveFen() + ); + } + + getFenForRepeat() { + return super.getFenForRepeat() + "_" + this.getReserveFen(); + } + + getReserveFen() { + return ( + (!this.reserve["w"] ? 0 : this.reserve["w"][V.PAWN]) + "," + + (!this.reserve["b"] ? 0 : this.reserve["b"][V.PAWN]) + ); + } + + getLastmoveFen() { + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + return ( + ( + !lm['w'] + ? '-' + : V.CoordsToSquare(lm['w'].start) + V.CoordsToSquare(lm['w'].end) + ) + + "," + + ( + !lm['b'] + ? '-' + : V.CoordsToSquare(lm['b'].start) + V.CoordsToSquare(lm['b'].end) + ) + ); + } + + setOtherVariables(fen) { + const fenParsed = V.ParseFen(fen); + const reserve = fenParsed.reserve.split(",").map(x => parseInt(x, 10)); + this.reserve = { + w: { [V.PAWN]: reserve[0] }, + b: { [V.PAWN]: reserve[1] } + }; + // And last moves (to avoid undoing your last move) + const lmParts = fenParsed.lastMove.split(","); + this.lastMove = [{ w: null, b: null }]; + ['w', 'b'].forEach((c, i) => { + if (lmParts[i] != '-') { + this.lastMove[0][c] = { + start: V.SquareToCoords(lmParts[i].substr(0, 2)), + end: V.SquareToCoords(lmParts[i].substr(2)) + }; + } + }); + // Local stack to know if (current) last move captured something + this.captures = [false]; + } + + static get size() { + return { x: 5, y: 6 }; + } + + static get PIECES() { + return [V.PAWN]; + } + + getColor(i, j) { + if (i >= V.size.x) return i == V.size.x ? "w" : "b"; + return this.board[i][j].charAt(0); + } + + getPiece() { + return V.PAWN; + } + + getPpath(b) { + return "Yote/" + b; + } + + getReservePpath(index, color) { + return "Yote/" + color + V.PAWN; + } + + static get RESERVE_PIECES() { + return [V.PAWN]; + } + + canIplay(side, [x, y]) { + if (this.turn != side) return false; + const L = this.captures.length; + if (!this.captures[L-1]) return this.getColor(x, y) == side; + return (x < V.size.x && this.getColor(x, y) != side); + } + + hoverHighlight(x, y) { + const L = this.captures.length; + if (!this.captures[L-1]) return false; + const oppCol = V.GetOppCol(this.turn); + return (this.board[x][y] != V.EMPTY && this.getColor(x, y) == oppCol); + } + + // TODO: onlyClick() doesn't fulfill exactly its role. + // Seems that there is some lag... TOFIX + onlyClick([x, y]) { + const L = this.captures.length; + return (this.captures[L-1] && this.getColor(x, y) != this.turn); + } + + // PATCH related to above TO-DO: + getPossibleMovesFrom([x, y]) { + if (x < V.size.x && this.board[x][y] == V.EMPTY) return []; + return super.getPossibleMovesFrom([x, y]); + } + + doClick([x, y]) { + const L = this.captures.length; + if (!this.captures[L-1]) return null; + const oppCol = V.GetOppCol(this.turn); + if (this.board[x][y] == V.EMPTY || this.getColor(x, y) != oppCol) + return null; + return new Move({ + appear: [], + vanish: [ new PiPo({ x: x, y: y, c: oppCol, p: V.PAWN }) ], + end: { x: x, y: y } + }); + } + + getReserveMoves(x) { + const color = this.turn; + if (this.reserve[color][V.PAWN] == 0) return []; + 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) { + let mv = new Move({ + appear: [ + new PiPo({ + x: i, + y: j, + c: color, + p: V.PAWN + }) + ], + vanish: [], + start: { x: x, y: 0 }, //a bit artificial... + end: { x: i, y: j } + }); + moves.push(mv); + } + } + } + return moves; + } + + getPotentialMovesFrom([x, y]) { + const L = this.captures.length; + if (this.captures[L-1]) { + if (x >= V.size.x) return []; + const mv = this.doClick([x, y]); + return (!!mv ? [mv] : []); + } + if (x >= V.size.x) + return this.getReserveMoves([x, y]); + return this.getPotentialPawnMoves([x, y]); + } + + getPotentialPawnMoves([x, y]) { + let moves = []; + const color = this.turn; + const L = this.lastMove.length; + const lm = this.lastMove[L-1]; + let forbiddenStep = null; + if (!!lm[color]) { + forbiddenStep = [ + lm[color].start.x - lm[color].end.x, + lm[color].start.y - lm[color].end.y + ]; + } + const oppCol = V.GetOppCol(color); + for (let s of V.steps[V.ROOK]) { + if ( + !!forbiddenStep && + s[0] == forbiddenStep[0] && s[1] == forbiddenStep[1] + ) { + continue; + } + const [i1, j1] = [x + s[0], y + s[1]]; + if (V.OnBoard(i1, j1)) { + if (this.board[i1][j1] == V.EMPTY) + moves.push(super.getBasicMove([x, y], [i1, j1])); + else if (this.getColor(i1, j1) == oppCol) { + const [i2, j2] = [i1 + s[0], j1 + s[1]]; + if (V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY) { + let mv = new Move({ + appear: [ + new PiPo({ x: i2, y: j2, c: color, p: V.PAWN }) + ], + vanish: [ + new PiPo({ x: x, y: y, c: color, p: V.PAWN }), + new PiPo({ x: i1, y: j1, c: oppCol, p: V.PAWN }) + ] + }); + moves.push(mv); + } + } + } + } + return moves; + } + + getAllPotentialMoves() { + let moves = super.getAllPotentialMoves(); + const color = this.turn; + moves = moves.concat( + this.getReserveMoves(V.size.x + (color == "w" ? 0 : 1))); + return moves; + } + + filterValid(moves) { + return moves; + } + + getCheckSquares() { + return []; + } + + atLeastOneMove() { + if (!super.atLeastOneMove()) { + // Search one reserve move + const moves = + this.getReserveMoves(V.size.x + (this.turn == "w" ? 0 : 1)); + if (moves.length > 0) return true; + return false; + } + return true; + } + + play(move) { + const color = this.turn; + move.turn = color; //for undo + const L = this.lastMove.length; + if (color == 'w') + this.lastMove.push({ w: null, b: this.lastMove[L-1]['b'] }); + if (move.appear.length == move.vanish.length) { //== 1 + // Normal move (non-capturing, non-dropping, non-removal) + let lm = this.lastMove[L - (color == 'w' ? 0 : 1)]; + if (!lm[color]) lm[color] = {}; + lm[color].start = move.start; + lm[color].end = move.end; + } + const oppCol = V.GetOppCol(color); + V.PlayOnBoard(this.board, move); + const captureNotEnding = ( + move.vanish.length == 2 && + this.board.some(b => b.some(cell => cell != "" && cell[0] == oppCol)) + ); + this.captures.push(captureNotEnding); + // Change turn unless I just captured something, + // and an opponent stone can be removed from board. + if (!captureNotEnding) { + this.turn = oppCol; + this.movesCount++; + } + this.postPlay(move); + } + + undo(move) { + V.UndoOnBoard(this.board, move); + if (this.turn == 'b') this.lastMove.pop(); + else this.lastMove['b'] = null; + this.captures.pop(); + if (move.turn != this.turn) { + this.turn = move.turn; + this.movesCount--; + } + this.postUndo(move); + } + + postPlay(move) { + if (move.vanish.length == 0) { + const color = move.appear[0].c; + this.reserve[color][V.PAWN]--; + if (this.reserve[color][V.PAWN] == 0) delete this.reserve[color]; + } + } + + postUndo(move) { + if (move.vanish.length == 0) { + const color = move.appear[0].c; + if (!this.reserve[color]) this.reserve[color] = { [V.PAWN]: 0 }; + this.reserve[color][V.PAWN]++; + } + } + + getCurrentScore() { + if (this.movesCount <= 2) return "*"; + const color = this.turn; + // If no stones on board, or no move available, I lose + if ( + this.board.every(b => { + return b.every(cell => { + return (cell == "" || cell[0] != color); + }); + }) + || + !this.atLeastOneMove() + ) { + return (color == 'w' ? "0-1" : "1-0"); + } + return "*"; + } + + static get SEARCH_DEPTH() { + return 4; + } + + evalPosition() { + let evaluation = super.evalPosition(); + // Add reserves: + evaluation += this.reserve["w"][V.PAWN]; + evaluation -= this.reserve["b"][V.PAWN]; + return evaluation; + } + + getNotation(move) { + if (move.vanish.length == 0) + // Placement: + return "@" + V.CoordsToSquare(move.end); + if (move.appear.length == 0) + // Removal after capture: + return V.CoordsToSquare(move.start) + "X"; + return ( + V.CoordsToSquare(move.start) + + (move.vanish.length == 2 ? "x" : "") + + V.CoordsToSquare(move.end) + ); + } };