--- /dev/null
+../Yote/bp.svg
\ No newline at end of file
--- /dev/null
+../Yote/wp.svg
\ No newline at end of file
// if more than 9 consecutive free spaces, break the integer,
// otherwise FEN parsing will fail.
if (count <= 9) return count;
- // Currently only boards of size up to 11 or 12:
- return "9" + (count - 9);
+ // Most boards of size < 18:
+ if (count <= 18) return "9" + (count - 9);
+ // Except Gomoku:
+ return "99" + (count - 18);
};
let position = "";
for (let i = 0; i < V.size.x; i++) {
showCheck && lightSquare && incheckSq[ci][cj],
"incheck-dark":
showCheck && !lightSquare && incheckSq[ci][cj],
- "hover-highlight": this.vr.hoverHighlight(ci, cj)
+ "hover-highlight":
+ this.vr.hoverHighlight(
+ [ci, cj], !this.analyze ? this.userColor : null)
},
attrs: {
id: getSquareId({ x: ci, y: cj })
return path;
},
re_setDrawings: function() {
+ // Add some drawing on board (for some variants + arrows and circles)
+ const boardElt = document.getElementById("gamePosition");
+ if (!boardElt) return;
// Remove current canvas, if any
const curCanvas = document.getElementById("arrowCanvas");
if (!!curCanvas) curCanvas.parentNode.removeChild(curCanvas);
- // Add some drawing on board (for some variants + arrows and circles)
- const boardElt = document.getElementById("gamePosition");
const squareWidth = boardElt.offsetWidth / V.size.y;
const bPos = boardElt.getBoundingClientRect();
let svgArrows = [];
width: 8.33%
padding-bottom: 8.33%
+div.board19
+ width: 5.26%
+ padding-bottom: 5.26%
+
img.piece
width: 100%
z-index: 10
a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com
a(href="https://glukkazan.github.io/") Dagaz demo + server
a(href="https://www.jocly.com/#/games") jocly.com
+ a(href="http://www.iggamecenter.com/") iggamecenter.com
a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
a(href="https://schemingmind.com/") schemingmind.com
a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com
a(href="https://glukkazan.github.io/") Dagaz demo + servidor
a(href="https://www.jocly.com/#/games") jocly.com
+ a(href="http://www.iggamecenter.com/") iggamecenter.com
a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
a(href="https://schemingmind.com/") schemingmind.com
a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com
a(href="https://glukkazan.github.io/") Dagaz demo + serveur
a(href="https://www.jocly.com/#/games") jocly.com
+ a(href="http://www.iggamecenter.com/") iggamecenter.com
a(href="https://musketeerchess.net/home/index.html") musketeerchess.net
a(href="https://schemingmind.com/") schemingmind.com
a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr
"Explosive captures (v2)": "Explosive captures (v2)",
"Extra bishops and knights": "Extra bishops and knights",
"Faster development": "Faster development",
+ "First capture wins": "First capture wins",
"Four new pieces": "Four new pieces",
"Free initial setup": "Free initial setup",
"Friendly pieces": "Friendly pieces",
"Explosive captures (v2)": "Capturas explosivas (v2)",
"Extra bishops and knights": "Alfiles y caballos adicionales",
"Faster development": "Desarrollo acelerado",
+ "First capture wins": "La primera captura gana",
"Four new pieces": "Quatro nuevas piezas",
"Free initial setup": "Posición inicial libre",
"Friendly pieces": "Piezas amistosas",
"Explosive captures (v2)": "Captures explosives (v2)",
"Extra bishops and knights": "Fous et cavaliers supplémentaires",
"Faster development": "Développement accéléré",
+ "First capture wins": "La première capture gagne",
"Four new pieces": "Quatre nouvelles pièces",
"Free initial setup": "Position initiale libre",
"Friendly pieces": "Pièces amies",
--- /dev/null
+p.boxed
+ | The first player to capture something wins.
+
+p
+ | This game follows the rules of
+ a(href="https://en.wikipedia.org/wiki/Go_(game)") Weiqi
+ | , or Go in japanese. However, the first player to achieve a capture
+ | wins, and the board size is arbitrarily reduced to 12 x 12.
+
+h3 Rules summary
+
+p.
+ No diagonals are considered on the board.
+ Therefore, "adjacent" will mean "orthogonally adjacent".
+
+ul
+ li Players alternate turns, starting with Black.
+ li.
+ A move consists in putting a stone on an intersection of the board.
+ This stone will never move. However, it can be captured.
+ li.
+ A move adjacent to a connected group of enemy stones "kills" the group
+ if it has exactly one liberty left: its stones are removed from the board.
+ The capturing player thus wins.
+
+p.
+ Considering stones as vertices on a graph, linked by an edge if they
+ are adjacent, a connected group is a connected sub-graph.
+ On the diagram below, removing a stone at the marked location
+ breaks the connection.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:93/93/93/2PPP2p4/4P2p4/3PP2p4/3P2ppp3/2PP2p1p3/2P5pp2/93/93/93 d7,h6:
+ .diagram.diag22
+ | fen:93/93/93/2PPP2p4/4P2p4/4P2p4/3P2p1p3/2PP2p1p3/2P5pp2/93/93/93 d7,h6:
+ figcaption.
+ Left: both groups connected.
+ Right: "both disconnected" (2 groups for black, 3 for white).
+
+p.
+ The liberties of a group are all the free intersections adjacent to a stone
+ of the group, as illustrated.
+
+figure.diagram-container
+ .diagram
+ | fen:5pp5/93/93/93/3P8/3PP5pp/91p1/93/93/6P5/93/93 e12,f11,g11,h12,c7,c8,d9,e8,f7,e6,d6,g2,g4,f3,h3,k5,l6,j6,j7,k8,l8:
+ figcaption Surrounding marks indicate groups' liberties.
+
+p.
+ The initial configuration is formed by two pairs of groups of one stone
+ each, with only two liberties per group. Everything is disconnected.
--- /dev/null
+p.boxed
+ | El primer jugador en capturar algo gana.
+
+p
+ | Este juego sigue las reglas de
+ a(href="https://en.wikipedia.org/wiki/Go_(game)") Weiqi
+ | o Go en japonés. Sin embargo, el primer jugador en capturar
+ | gana, y el tablero se reduce arbitrariamente a 12 x 12.
+
+h3 Resumen de reglas
+
+p.
+ Las diagonales no se consideran en el tablero.
+ Por tanto, "adyacente" significará "ortogonalmente adyacente".
+
+ul
+ li Los jugadores alternan turnos, comenzando con las negras.
+ li.
+ Un movimiento consiste en colocar una piedra en una intersección del
+ tablero. Esta piedra nunca se moverá. Sin embargo, se puede capturar.
+ li.
+ Un golpe adyacente a un grupo de piedras enemigas conectadas "mata" a este
+ grupo si sólo le queda una libertad: sus piedras se retiran del juego.
+ El jugador capturador gana.
+
+p.
+ Considerando piedras como vértices de un gráfico, conectados por un borde
+ si son adyacentes, un grupo conectado es un subgrafo conectado.
+ En el diagrama a continuación, retire una piedra en la ubicación marcada
+ romper la conexión.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:93/93/93/2PPP2p4/4P2p4/3PP2p4/3P2ppp3/2PP2p1p3/2P5pp2/93/93/93 d7,h6:
+ .diagram.diag22
+ | fen:93/93/93/2PPP2p4/4P2p4/4P2p4/3P2p1p3/2PP2p1p3/2P5pp2/93/93/93 d7,h6:
+ figcaption.
+ Izquierda: los dos grupos conectados.
+ Derecha: "ambos desconectados" (2 grupos para negro, 3 para blanco).
+
+p.
+ Las libertades de un grupo son todas las intersecciones libres adyacentes a
+ una piedra de grupo, como se muestra.
+
+figure.diagram-container
+ .diagram
+ | fen:5pp5/93/93/93/3P8/3PP5pp/91p1/93/93/6P5/93/93 e12,f11,g11,h12,c7,c8,d9,e8,f7,e6,d6,g2,g4,f3,h3,k5,l6,j6,j7,k8,l8:
+ figcaption Marcas circundantes que indican libertades grupales.
+
+p.
+ La configuración inicial está formada por dos pares de grupos de una piedra,
+ con solo dos libertades por grupo. Todo está desconectado.
--- /dev/null
+p.boxed
+ | Le premier joueur qui capture quelque chose gagne.
+
+p
+ | Ce jeu suit les règles du
+ a(href="https://en.wikipedia.org/wiki/Go_(game)") Weiqi
+ | , ou Go en japonais. Cependant, le premier joueur à effectuer une capture
+ | gagne, et le plateau est arbitrairement réduit à 12 x 12.
+
+h3 Résumé des règles
+
+p.
+ Les diagonales ne sont pas considérées sur le plateau.
+ Ainsi, "adjacent" signifiera "orthogonalement adjacent".
+
+ul
+ li Les joueurs alternent les tours, démarrant par les noirs.
+ li.
+ Un coup consiste à poser une pierre sur une intersection du plateau.
+ Cette pierre ne bougera jamais. Elle peut cependant être capturée.
+ li.
+ Un coup adjacent à un groupe de pierres ennemies connectées "tue" ce
+ groupe s'il ne lui reste plus qu'une seule liberté : ses pierres sont
+ retirées du jeu. Le joueur capturant gagne alors.
+
+p.
+ Considérant les pierres comme des sommets d'un graphe, reliées par une arête
+ si elles sont adjacentes, un groupe connecté est un sous-graphe connecté.
+ Sur le diagramme ci-dessous, enlever une pierre à l'emplacement marqué
+ casse la connection.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:93/93/93/2PPP2p4/4P2p4/3PP2p4/3P2ppp3/2PP2p1p3/2P5pp2/93/93/93 d7,h6:
+ .diagram.diag22
+ | fen:93/93/93/2PPP2p4/4P2p4/4P2p4/3P2p1p3/2PP2p1p3/2P5pp2/93/93/93 d7,h6:
+ figcaption.
+ Gauche : les deux groupes connectés.
+ Droite : "les deux déconnectés" (2 groupes pour noir, 3 pour blanc).
+
+p.
+ Les libertés d'un groupe sont toutes les intersections libres adjacentes à
+ une pierre du groupe, comme illustré.
+
+figure.diagram-container
+ .diagram
+ | fen:5pp5/93/93/93/3P8/3PP5pp/91p1/93/93/6P5/93/93 e12,f11,g11,h12,c7,c8,d9,e8,f7,e6,d6,g2,g4,f3,h3,k5,l6,j6,j7,k8,l8:
+ figcaption Les marques entourantes indiquant les libertés des groupes.
+
+p.
+ La configuration initiale est formée de deux paires de groupes à une pierre,
+ avec seulement deux libertés par groupe. Tout est déconnecté.
p.boxed
- | TODO
+ | Align five stones in any direction.
+
+p.
+ At each turn, put a stone on the board, on any empty intersection.
+ Once on the board, it will not move. The first player to align
+ five stones (or more) in any direction wins the game.
+ However, if White (playing in second) can achieve such an alignment right
+ after Black, the game is a draw.
+
+figure.diagram-container
+ .diagram
+ | fen:991/991/991/991/991/94P5/93p6/7ppp1p7/9Pp8/9pP8/7PPP1P7/93P6/94p5/991/991/991/991/991/991 k9,k12:
+ figcaption.
+ If black plays at the lower marked point,
+ White replies at the other mark: draw.
p.boxed
- | TODO
+ | Alinee cinco piedras en cualquier dirección.
+
+p.
+ En cada turno, coloque una piedra en el tablero, en una intersección vacía.
+ Una vez que la piedra esté en su lugar, no se moverá. El primer jugador en
+ alinear cinco (o más) piedras en cualquier dirección gana.
+ Sin embargo, si las blancas (jugando segundas) pueden alcanzar
+ este gol justo después de las negras, el juego es un empate.
+
+figure.diagram-container
+ .diagram
+ | fen:991/991/991/991/991/94P5/93p6/7ppp1p7/9Pp8/9pP8/7PPP1P7/93P6/94p5/991/991/991/991/991/991 k9,k12:
+ figcaption.
+ Si las negras juegan en el punto anotado más bajo,
+ el blanco responde a la otra marca: empate.
p.boxed
- | TODO
+ | Alignez cinq pierres dans n'importe quelle direction.
+
+p.
+ À chaque tour, placez une pierre sur le plateau, sur une intersection vide.
+ Une fois la pierre en place elle ne bougera plus. Le premier joueur à
+ aligner cinq pierres (ou plus) dans n'importe quelle direction remporte
+ la victoire. Cependant, si les blancs (jouant en second) peuvent attindre
+ ce but juste après les noirs, la partie est nulle.
+
+figure.diagram-container
+ .diagram
+ | fen:991/991/991/991/991/94P5/93p6/7ppp1p7/9Pp8/9pP8/7PPP1P7/93P6/94p5/991/991/991/991/991/991 k9,k12:
+ figcaption.
+ Si les noirs jouent au point marqué inférieur,
+ les blancs répondent à l'autre marque : nulle.
p Some games not chess related.
-
var varlist = [
+ "Atarigo",
"Emergo",
"Fanorona",
"Gomoku",
p Algunos juegos no están relacionados con el ajedrez.
-
var varlist = [
+ "Atarigo",
"Emergo",
"Fanorona",
"Gomoku",
p Quelques jeux non connectés aux échecs.
-
var varlist = [
+ "Atarigo",
"Emergo",
"Fanorona",
"Gomoku",
--- /dev/null
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+import { ArrayFun } from "@/utils/array";
+
+export class AtarigoRules extends ChessRules {
+
+ static get Monochrome() {
+ return true;
+ }
+
+ static get Notoodark() {
+ return true;
+ }
+
+ static get Lines() {
+ let lines = [];
+ // Draw all inter-squares lines, shifted:
+ for (let i = 0; i < V.size.x; i++)
+ lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]);
+ for (let j = 0; j < V.size.y; j++)
+ lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]);
+ return lines;
+ }
+
+ static get HasFlags() {
+ return false;
+ }
+
+ static get HasEnpassant() {
+ return false;
+ }
+
+ static get ReverseColors() {
+ return true;
+ }
+
+ 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 (row[i].toLowerCase() == V.PAWN) 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);
+ // 3) Check capture "flag"
+ if (!fenParsed.capture || !fenParsed.capture.match(/^[01]$/))
+ return false;
+ return true;
+ }
+
+ static ParseFen(fen) {
+ const fenParts = fen.split(" ");
+ return Object.assign(
+ ChessRules.ParseFen(fen),
+ // Capture field allows to compute the score cleanly.
+ { capture: fenParts[3] }
+ );
+ }
+
+ static get size() {
+ return { x: 12, y: 12 };
+ }
+
+ static GenRandInitFen() {
+ return "93/93/93/93/93/5Pp5/5pP5/93/93/93/93/93 w 0 0";
+ }
+
+ getFen() {
+ return super.getFen() + " " + (this.capture ? 1 : 0);
+ }
+
+ setOtherVariables(fen) {
+ this.capture = parseInt(V.ParseFen(fen).capture, 10);
+ }
+
+ getPiece() {
+ return V.PAWN;
+ }
+
+ getPpath(b) {
+ return "Gomoku/" + b;
+ }
+
+ onlyClick() {
+ return true;
+ }
+
+ canIplay(side, [x, y]) {
+ return (side == this.turn && this.board[x][y] == V.EMPTY);
+ }
+
+ hoverHighlight([x, y], side) {
+ if (!!side && side != this.turn) return false;
+ return (this.board[x][y] == V.EMPTY);
+ }
+
+ searchForEmptySpace([x, y], color, explored) {
+ if (explored[x][y]) return false; //didn't find empty space
+ explored[x][y] = true;
+ let res = false;
+ for (let s of V.steps[V.ROOK]) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (V.OnBoard(i, j)) {
+ if (this.board[i][j] == V.EMPTY) res = true;
+ else if (this.getColor(i, j) == color)
+ res = this.searchForEmptySpace([i, j], color, explored) || res;
+ }
+ }
+ return res;
+ }
+
+ doClick([x, y]) {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ let move = new Move({
+ appear: [
+ new PiPo({ x: x, y: y, c: color, p: V.PAWN })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ V.PlayOnBoard(this.board, move); //put the stone
+ let noSuicide = false;
+ let captures = [];
+ for (let s of V.steps[V.ROOK]) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (V.OnBoard(i, j)) {
+ if (this.board[i][j] == V.EMPTY) noSuicide = true; //clearly
+ else if (this.getColor(i, j) == color) {
+ // Free space for us = not a suicide
+ if (!noSuicide) {
+ let explored = ArrayFun.init(V.size.x, V.size.y, false);
+ noSuicide = this.searchForEmptySpace([i, j], color, explored);
+ }
+ }
+ else {
+ // Free space for opponent = not a capture
+ let explored = ArrayFun.init(V.size.x, V.size.y, false);
+ const captureSomething =
+ !this.searchForEmptySpace([i, j], oppCol, explored);
+ if (captureSomething) {
+ for (let ii = 0; ii < V.size.x; ii++) {
+ for (let jj = 0; jj < V.size.y; jj++) {
+ if (explored[ii][jj]) {
+ captures.push(
+ new PiPo({ x: ii, y: jj, c: oppCol, p: V.PAWN })
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ V.UndoOnBoard(this.board, move); //remove the stone
+ if (!noSuicide && captures.length == 0) return null;
+ Array.prototype.push.apply(move.vanish, captures);
+ return move;
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ const move = this.doClick([x, y]);
+ return (!move ? [] : [move]);
+ }
+
+ getAllPotentialMoves() {
+ 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) {
+ const mv = this.doClick(i, j);
+ if (!!mv) moves.push(mv);
+ }
+ }
+ }
+ return moves;
+ }
+
+ filterValid(moves) {
+ return moves;
+ }
+
+ getCheckSquares() {
+ return [];
+ }
+
+ postPlay(move) {
+ if (move.vanish.length >= 1) this.capture = true;
+ }
+
+ postUndo() {
+ this.capture = false;
+ }
+
+ getCurrentScore() {
+ if (this.capture) return (this.turn == 'w' ? "0-1" : "1-0");
+ return "*";
+ }
+
+ getComputerMove() {
+ const moves = super.getAllValidMoves();
+ if (moves.length == 0) return null;
+ // Just random mover for now... writing a good bot is far out of scope
+ return moves[randInt(moves.length)];
+ }
+
+ getNotation(move) {
+ return V.CoordsToSquare(move.end);
+ }
+
+};
return super.getPotentialMovesFrom([x, y]);
}
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
return this.movesCount == 0 && [1, 6].includes(x);
}
);
}
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
const c = this.turn;
return (
this.movesCount <= 1 &&
return true;
}
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
if (this.subTurn == 1) return false;
const L = this.firstMove.length;
const fm = this.firstMove[L-1];
export class YoteRules extends ChessRules {
// TODO
+ //If (as white) a pile W1/B1 jumps over another pile W2/B2, it lets on the intermediate square exactly W2 men, to end as W1/(B1+B2).
+ //In the first case in the video, W1=1, B1=0, W2=0, B2=1 ==> 1/1 and finally 1/2 with nothing on intermediate squares since W2 is always 0.
+ //In the second case, W1=1, B1=0, W2=1, B2=1 ==> 1 man left on intermediate square, end as 1/1.
+ //...I think it's that (?). Not very well explained either on Wikipedia or mindsports.nl :/
+ //Found this link: http://www.iggamecenter.com/info/en/emergo.html - so it's all clear now ! I'll add the game soon.
+ //Btw, I'm not a big fan of this naming "men" for pieces, but, won't contradict the author on that 
};
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
export class GomokuRules extends ChessRules {
- // TODO
+ static get Monochrome() {
+ return true;
+ }
+
+ static get Notoodark() {
+ return true;
+ }
+
+ static get Lines() {
+ let lines = [];
+ // Draw all inter-squares lines, shifted:
+ for (let i = 0; i < V.size.x; i++)
+ lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]);
+ for (let j = 0; j < V.size.y; j++)
+ lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]);
+ return lines;
+ }
+
+ static get HasFlags() {
+ return false;
+ }
+
+ static get HasEnpassant() {
+ return false;
+ }
+
+ static get ReverseColors() {
+ return true;
+ }
+
+ 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 (row[i].toLowerCase() == V.PAWN) 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 get size() {
+ return { x: 19, y: 19 };
+ }
+
+ static GenRandInitFen() {
+ return [...Array(19)].map(e => "991").join('/') + " w 0";
+ }
+
+ setOtherVariables() {}
+
+ getPiece() {
+ return V.PAWN;
+ }
+
+ getPpath(b) {
+ return "Gomoku/" + b;
+ }
+
+ onlyClick() {
+ return true;
+ }
+
+ canIplay(side, [x, y]) {
+ return (side == this.turn && this.board[x][y] == V.EMPTY);
+ }
+
+ hoverHighlight([x, y], side) {
+ if (!!side && side != this.turn) return false;
+ return (this.board[x][y] == V.EMPTY);
+ }
+
+ doClick([x, y]) {
+ return (
+ new Move({
+ appear: [
+ new PiPo({ x: x, y: y, c: this.turn, p: V.PAWN })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 },
+ })
+ );
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ return [this.doClick([x, y])];
+ }
+
+ getAllPotentialMoves() {
+ let moves = [];
+ for (let i = 0; i < 19; i++) {
+ for (let j=0; j < 19; j++) {
+ if (this.board[i][j] == V.EMPTY) moves.push(this.doClick(i, j));
+ }
+ }
+ return moves;
+ }
+
+ filterValid(moves) {
+ return moves;
+ }
+
+ getCheckSquares() {
+ return [];
+ }
+
+ postPlay() {}
+ postUndo() {}
+
+ countAlignedStones([x, y], color) {
+ let maxInLine = 0;
+ for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+ // Skip half of steps, since we explore both directions
+ if (s[0] == -1 || (s[0] == 0 && s[1] == -1)) continue;
+ let countInLine = 1;
+ for (let dir of [-1, 1]) {
+ let [i, j] = [x + dir * s[0], y + dir * s[1]];
+ while (
+ V.OnBoard(i, j) &&
+ this.board[i][j] != V.EMPTY &&
+ this.getColor(i, j) == color
+ ) {
+ countInLine++;
+ i += dir * s[0];
+ j += dir * s[1];
+ }
+ }
+ if (countInLine > maxInLine) maxInLine = countInLine;
+ }
+ return maxInLine;
+ }
+
+ getCurrentScore() {
+ let fiveAlign = { w: false, b: false, wNextTurn: false };
+ for (let i=0; i<19; i++) {
+ for (let j=0; j<19; j++) {
+ if (this.board[i][j] == V.EMPTY) {
+ if (
+ !fiveAlign.wNextTurn &&
+ this.countAlignedStones([i, j], 'b') >= 5
+ ) {
+ fiveAlign.wNextTurn = true;
+ }
+ }
+ else {
+ const c = this.getColor(i, j);
+ if (!fiveAlign[c] && this.countAlignedStones([i, j], c) >= 5)
+ fiveAlign[c] = true;
+ }
+ }
+ }
+ if (fiveAlign['w']) {
+ if (fiveAlign['b']) return "1/2";
+ if (this.turn == 'b' && fiveAlign.wNextTurn) return "*";
+ return "1-0";
+ }
+ if (fiveAlign['b']) return "0-1";
+ return "*";
+ }
+
+ getComputerMove() {
+ const color = this.turn;
+ let candidates = [];
+ let curMax = 0;
+ for (let i=0; i<19; i++) {
+ for (let j=0; j<19; j++) {
+ if (this.board[i][j] == V.EMPTY) {
+ const nbAligned = this.countAlignedStones([i, j], color);
+ if (nbAligned >= curMax) {
+ const move = new Move({
+ appear: [
+ new PiPo({ x: i, y: j, c: color, p: V.PAWN })
+ ],
+ vanish: [],
+ start: { x: -1, y: -1 }
+ });
+ if (nbAligned > curMax) {
+ curMax = nbAligned;
+ candidates = [move];
+ }
+ else candidates.push(move);
+ }
+ }
+ }
+ }
+ // Among a priori equivalent moves, select the most central ones.
+ // Of course this is not good, but can help this ultra-basic bot.
+ let bestCentrality = 0;
+ candidates.forEach(c => {
+ const deltaX = Math.min(c.end.x, 18 - c.end.x);
+ const deltaY = Math.min(c.end.y, 18 - c.end.y);
+ c.centrality = deltaX * deltaX + deltaY * deltaY;
+ if (c.centrality > bestCentrality) bestCentrality = c.centrality;
+ });
+ const threshold = Math.min(32, bestCentrality);
+ const finalCandidates = candidates.filter(c => c.centrality >= threshold);
+ return finalCandidates[randInt(finalCandidates.length)];
+ }
+
+ getNotation(move) {
+ return V.CoordsToSquare(move.end);
+ }
};
return "xx";
}
- hoverHighlight(x, y) {
+ hoverHighlight() {
return this.movesCount == 0;
}
this.captures = []; //reinit for each move
}
- hoverHighlight(x, y) {
- if (this.movesCount >= 2) return false;
+ hoverHighlight([x, y], side) {
const c = this.turn;
+ if (this.movesCount >= 2 || (!!side && side != c)) return false;
if (c == 'w') return (x == y && [0, 3, 4, 7].includes(x));
// "Black": search for empty square and allow nearby
for (let i of [0, 3, 4, 7]) {
export class MadhouseRules extends ChessRules {
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
// Testing move validity results in an infinite update loop.
// TODO: find a way to test validity anyway.
return (this.subTurn == 2 && this.board[x][y] == V.EMPTY);
export class PocketknightRules extends ChessRules {
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
// Testing move validity results in an infinite update loop.
// TODO: find a way to test validity anyway.
return (this.subTurn == 2 && this.board[x][y] == V.EMPTY);
export class TeleportRules extends ChessRules {
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
// Testing move validity results in an infinite update loop.
// TODO: find a way to test validity anyway.
return (this.subTurn == 2 && this.board[x][y] == V.EMPTY);
return moves;
}
- hoverHighlight(x, y) {
+ hoverHighlight([x, y]) {
const c = this.turn;
return (
this.movesCount <= 3 &&
return (x < V.size.x && this.getColor(x, y) != side);
}
- // TODO: hoverHighlight() would well take an arg "side"...
- hoverHighlight(x, y) {
+ hoverHighlight([x, y], side) {
+ if (!!side && side != this.turn) return false;
const L = this.captures.length;
if (!this.captures[L-1]) return false;
const oppCol = V.GetOppCol(this.turn);
('Antiking2', 'Keep antiking in check (v2)'),
('Antimatter', 'Dangerous collisions'),
('Arena', 'Middle battle'),
+ ('Atarigo', 'First capture wins'),
('Atomic1', 'Explosive captures (v1)'),
('Atomic2', 'Explosive captures (v2)'),
('Avalanche', 'Pawnfalls'),