+++ /dev/null
-<?xml version="1.0"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
-<defs><radialGradient id="rg" cx=".47" cy=".49" r=".48">
-<stop offset=".7" stop-color="#FFF"/>
-<stop offset=".9" stop-color="#DDD"/>
-<stop offset="1" stop-color="#777"/>
-</radialGradient></defs>
-<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
-</svg>
--- /dev/null
+../Yote/wp.svg
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
-<defs><radialGradient id="rg" cx=".3" cy=".3" r=".8">
-<stop offset="0" stop-color="#777"/>
-<stop offset=".3" stop-color="#222"/>
-<stop offset="1" stop-color="#000"/>
-</radialGradient></defs>
-<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
-</svg>
--- /dev/null
+../Yote/bp.svg
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".47" cy=".49" r=".48">
+<stop offset=".7" stop-color="#FFF"/>
+<stop offset=".9" stop-color="#DDD"/>
+<stop offset="1" stop-color="#777"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
+</svg>
--- /dev/null
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".3" cy=".3" r=".8">
+<stop offset="0" stop-color="#777"/>
+<stop offset=".3" stop-color="#222"/>
+<stop offset="1" stop-color="#000"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
+</svg>
}
}
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]);
}
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);
+ }
}
}
}
| 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,
| 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,
| 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,
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.
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.
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.
// 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";
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;
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() {
return false;
}
+ static get ReverseColors() {
+ return true;
+ }
+
static get PIECES() {
return V.PAWN;
}
+ getPiece() {
+ return V.PAWN;
+ }
+
getPpath(b) {
return "Konane/" + b;
}
-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)
+ );
+ }
};