return V.ShowMoves;
}
+ // Some variants always show the same orientation
+ static get CanFlip() {
+ return true;
+ }
+ get canFlip() {
+ return V.CanFlip;
+ }
+
// Turn "wb" into "B" (for FEN)
static board2fen(b) {
return b[0] == "w" ? b[1].toUpperCase() : b[1];
setFlags(fenflags) {
// white a-castle, h-castle, black a-castle, h-castle
this.castleFlags = { w: [true, true], b: [true, true] };
- if (!fenflags) return;
for (let i = 0; i < 4; i++)
this.castleFlags[i < 2 ? "w" : "b"][i % 2] = fenflags.charAt(i) == "1";
}
x + shiftX == lastRank
? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
: [V.PAWN];
- // One square forward
if (this.board[x + shiftX][y] == V.EMPTY) {
+ // One square forward
for (let piece of finalPieces) {
moves.push(
this.getBasicMove([x, y], [x + shiftX, y], {
#controls
button(@click="gotoBegin()") <<
button(@click="undo()") <
- button(@click="flip()") ⇅
+ button(v-if="canFlip" @click="flip()") ⇅
button(@click="play()") >
button(@click="gotoEnd()") >>
#belowControls
: (this.vr ? this.vr.showMoves : "none");
},
showTurn: function() {
- return this.game.score == '*' && this.vr && this.vr.showMoves != "all";
+ return (
+ this.game.score == '*' &&
+ this.vr &&
+ (this.vr.showMoves != "all" || !this.vr.canFlip)
+ );
},
turn: function() {
- return this.vr
- ? this.st.tr[(this.vr.turn == 'w' ? "White" : "Black") + " to move"]
+ if (!this.vr)
+ return "";
+ if (this.vr.showMoves != "all")
+ return this.st.tr[(this.vr.turn == 'w' ? "White" : "Black") + " to move"]
+ // Cannot flip: racing king or circular chess
+ return this.vr.movesCount == 0 && this.game.mycolor == "w"
+ ? this.st.tr["It's your turn!"]
: "";
},
canAnalyze: function() {
return this.game.mode != "analyze" && this.vr && this.vr.canAnalyze;
},
+ canFlip: function() {
+ return this.vr && this.vr.canFlip;
+ },
allowDownloadPGN: function() {
return this.game.score != "*" || (this.vr && this.vr.showMoves == "all");
}
#controls
margin: 0 auto
+ text-align: center
button
display: inline-block
width: 20%
}
},
[...Array(sizeX).keys()].map(i => {
- let ci = this.orientation == "w" ? i : sizeX - i - 1;
+ let ci = !V.CanFlip || this.orientation == "w" ? i : sizeX - i - 1;
return h(
"div",
{
style: { opacity: this.choices.length > 0 ? "0.5" : "1" }
},
[...Array(sizeY).keys()].map(j => {
- let cj = this.orientation == "w" ? j : sizeY - j - 1;
+ let cj = !V.CanFlip || this.orientation == "w" ? j : sizeY - j - 1;
let elems = [];
if (
this.vr.board[ci][cj] != V.EMPTY &&
"Highlight last move and checks?": "Highlight last move and checks?",
Instructions: "Instructions",
"Invalid email": "Invalid email",
+ "It's your turn!": "It's your turn!",
"is not online": "is not online",
Language: "Language",
"Live challenges": "Live challenges",
"Lose all pieces": "Lose all pieces",
"Mate any piece": "Mate any piece",
"Move twice": "Move twice",
+ "Neverending rows": "Neverending rows",
"Pawns move diagonally": "Pawns move diagonally",
"Reuse pieces": "Reuse pieces",
"Reverse captures": "Reverse captures",
"Highlight last move and checks?": "¿Resaltar el último movimiento y jaques?",
Instructions: "Instrucciones",
"Invalid email": "Email inválido",
+ "It's your turn!": "¡Es su turno!",
"is not online": "no está en línea",
Language: "Idioma",
"Live challenges": "Desafíos en vivo",
"Lose all pieces": "Perder todas las piezas",
"Mate any piece": "Matar cualquier pieza",
"Move twice": "Mover dos veces",
+ "Neverending rows": "Filas interminables",
"Pawns move diagonally": "Peones se mueven en diagonal",
"Reuse pieces": "Reutilizar piezas",
"Reverse captures": "Capturas invertidas",
"Highlight last move and checks?": "Mettre en valeur le dernier coup et les échecs ?",
Instructions: "Instructions",
"Invalid email": "Email invalide",
+ "It's your turn!": "À vous de jouer !",
"is not online": "n'est pas en ligne",
Language: "Langue",
"Live challenges": "Défis en direct",
"Lose all pieces": "Perdez toutes les pièces",
"Mate any piece": "Mater n'importe quelle pièce",
"Move twice": "Jouer deux coups",
+ "Neverending rows": "Rangées sans fin",
"Pawns move diagonally": "Les pions vont en diagonale",
"Reuse pieces": "Réutiliser les pièces",
"Reverse captures": "Captures inversées",
-p https://www.chessvariants.com/d.betza/chessvar/race.html 8x8 race chess
-
-p See also: https://www.chessvariants.com/shape.dir/x_torus.html
-
p.boxed
- | If a piece captures one of the same kind, both disappear.
-
-p.
- The defensive power of pawns is thus increased, because they don't fear
- captures (by other pawns).
+ | Ranks 1 and 8 communicate. Pawns all go the same way and never promote.
p.
- Endings are also affected quite a lot, and sometimes new threats occur:
- on the diagram, 3.Bxg7 wins a pawn because 3...Bxg7 would make both
- bishops disappear.
+ In the initial position, it is often possible to win in a few moves by
+ giving check first: that is why this is forbidden.
figure.diagram-container
.diagram
- | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR:
- figcaption After 1.b3 c5 2.Bb2 Nc6
+ | fen:8/8/pppppppp/rkbrnnqb/8/8/PPPPPPPP/BBRNKQNR:
+ figcaption 1.Nc3+ would win quickly.
+
+p.
+ Note that since ranks 1 and 8 communicate, a black pawn on the 8th rank
+ threatens a piece on the first rank. For example pawn d6 to d8 would check
+ the white king.
+
+p.
+ Due to the unusual pawns rules (they all go the same way: up the board),
+ en passant captures do not exist. Castling is also disabled because
+ the king is vulnerable in both directions.
h3 Source
p
- a(href="https://www.chessvariants.com/rules/antimatter-chess") Antimatter chess
+ a(href="https://www.chessvariants.com/d.betza/chessvar/race.html") 8x8 Race Chess
| on chessvariants.com.
p.boxed
- | Si una pieza captura otra del mismo tipo, las dos desaparecen.
+ | Las filas 1 y 8 se comunican.
+ | Todos los peones van en la misma dirección y nunca son promovidos.
p.
- El poder defensivo de los peones aumenta así, ya que no temen
- más capturas (por otros peones).
-
-p.
- Las finales también se ven muy afectadas y, a veces, nuevas amenazas
- ocurren: en el diagrama, 3.Bxg7 gana un peón porque 3...Bxg7 causaría
- la desaparición de los dos alfiles.
+ En la posición inicial, a menudo es posible ganar en unos pocos
+ movimientos que comienzan con jaque: por eso está prohibido.
figure.diagram-container
.diagram
- | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR:
- figcaption Después de 1.b3 c5 2.Bb2 Nc6
+ | fen:8/8/pppppppp/rkbrnnqb/8/8/PPPPPPPP/BBRNKQNR:
+ figcaption 1.Nc3+ ganaría rápidamente.
+
+p.
+ Tenga en cuenta que dado que las filas 1 y 8 se comunican, un peón negro
+ en el octavo fila amenaza una pieza en el primero.
+ Por ejemplo, el peón d6 a d8 pondría en jaque el rey blanco.
+
+p.
+ Debido a las reglas de peones inusuales (todos van en la misma
+ dirección: arriba), no hay capturas en passant.
+ El enroque tampoco es posible porque el rey es vulnerable
+ en ambas direcciones.
h3 Fuente
p
| La
- a(href="https://www.chessvariants.com/rules/antimatter-chess") variante Antimateria
+ a(href="https://www.chessvariants.com/d.betza/chessvar/race.html") variante 8x8 Race
| en chessvariants.com.
p.boxed
- | Si une pièce en capture une autre du même type, les deux disparaissent.
+ | Les rangées 1 et 8 communiquent.
+ | Les pions vont tous dans le même sens et ne sont jamais promus.
p.
- Le pouvoir défensif des pions est ainsi augmenté, puisqu'ils ne craignent
- plus les captures (par d'autres pions).
-
-p.
- Les finales sont aussi beaucoup affectées, et parfois de nouvelles menaces
- surviennent : sur le diagramme, 3.Bxg7 gagne un pion car 3...Bxg7 provoquerait
- la disparition des deux fous.
+ Dans la position initiale, il est souvent possible de gagner en quelques
+ coups en commençant par donner échec : c'est pourquoi on l'interdit.
figure.diagram-container
.diagram
- | fen:r1bqkbnr/pp1ppppp/2n5/2p5/8/1P6/PBPPPPPP/RN1QKBNR:
- figcaption After 1.b3 c5 2.Bb2 Nc6
+ | fen:8/8/pppppppp/rkbrnnqb/8/8/PPPPPPPP/BBRNKQNR:
+ figcaption 1.Nc3+ gagnerait rapidement.
+
+p.
+ Notez que puisque les rangées 1 et 8 communiquent, un pion noir sur la 8eme
+ rangée menace une pièce sur la première. Par exemple pion d6 à d8 mettrait
+ en échec le roi blanc.
+
+p.
+ À cause des règles inhabituelles sur les pions (ils vont tous dans la même
+ direction : vers le haut), les captures en passant n'existent pas.
+ Le roque n'est pas possible non plus car le roi est vulnérable
+ dans les deux directions.
h3 Source
p
| La
- a(href="https://www.chessvariants.com/rules/antimatter-chess") variante Antimatière
+ a(href="https://www.chessvariants.com/d.betza/chessvar/race.html") variante 8x8 Race
| sur chessvariants.com.
--- /dev/null
+p.boxed
+ | Columns 'a' and 'h' communicate.
+
+p.
+ Column 'a' is now just to the right of column 'h': a pawn on h2 can
+ take on a3, and a bishop on h3 can go to a4, and so on.
+
+p.
+ This allows a single piece to give a double check. On the following diagram
+ the bishop give check on b5 by two distinct paths: e2-d3-c4 and g2-h3-a4.
+ Covering both attacking lines is impossible so the king has to move.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:8/2p5/3n4/1k6/8/8/2PP4/5B1K:
+ .diagram.diag22
+ | fen:4k3/2p5/8/8/7N/8/3P4/7K a2,b3,b5,a6,g2,f3,f5,g6:
+ figcaption Left: double-check on b5. Right: possible knight moves from h4.
+
+h3 Source
+
+p
+ a(href="https://www.chessvariants.com/boardrules.dir/cylindrical.html") Cylinder Chess
+ | on chessvariants.com.
--- /dev/null
+p.boxed
+ | Las columnas 'a' y 'h' se comunican.
+
+p.
+ La columna 'a' está ahora a la derecha de la columna 'h':
+ un peón en h2 puede tomar a3, y un alfil en h3 puede ir en a4, etc.
+
+p.
+ Esto permite dobles jaques dadas por una sola pieza. En el diagrama
+ a continuación, el alfil da jaque por dos caminos diferentes: e2-d3-c4 y g2-h3-a4.
+ No es posible cubrir las dos líneas de ataque, por lo que el rey debe moverse.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:8/2p5/3n4/1k6/8/8/2PP4/5B1K:
+ .diagram.diag22
+ | fen:4k3/2p5/8/8/7N/8/3P4/7K a2,b3,b5,a6,g2,f3,f5,g6:
+ figcaption.
+ Izquierda: doble-jaque en b5. Derecha: posibles movimientos de caballo desde h4.
+
+h3 Fuente
+
+p
+ | La
+ a(href="https://www.chessvariants.com/boardrules.dir/cylindrical.html")
+ | variante cilíndrica
+ | en chessvariants.com.
--- /dev/null
+p.boxed
+ | Les colonnes 'a' et 'h' commniquent.
+
+p.
+ La colonne 'a' est maintenant juste à la droite de la colonne 'h' :
+ un pion en h2 peut prendre en a3, et un fou en h3 peut aller en a4, etc.
+
+p.
+ Ceci permet des double échecs donnés par une seule pièce. Sur le diagramme
+ suivant, le fou donne échec par deux chemins différents : e2-d3-c4 et g2-h3-a4.
+ Il n'est pas possible de couvrir les deux lignes d'attaque, donc le roi doit bouger.
+
+figure.diagram-container
+ .diagram.diag12
+ | fen:8/2p5/3n4/1k6/8/8/2PP4/5B1K:
+ .diagram.diag22
+ | fen:4k3/2p5/8/8/7N/8/3P4/7K a2,b3,b5,a6,g2,f3,f5,g6:
+ figcaption.
+ Gauche: double-échec en b5. Droite: possibles coups de cavalier depuis h4.
+
+h3 Source
+
+p
+ | La
+ a(href="https://www.chessvariants.com/boardrules.dir/cylindrical.html")
+ | variante cylindrique
+ | sur chessvariants.com.
figure.diagram-container
.diagram
- | fen:qbppnprp/prppbkpn/8/8/8/8/QBPPNPRP/PRPPBKPN:
+ | fen:qbppnprp/prpbpkpn/8/8/8/8/QBPPNPRP/PRPPKBPN:
figcaption Possible starting position.
p Notes
ul
- li The computer plays for now totally at random.
+ li.
+ The computer uses a basic strategy, way inferior to what a human could do
+ but still better than random play.
li.
Pieces are randomly set on the two first ranks.
The king may be on second rank, and attacked by an enemy rook or queen.
figure.diagram-container
.diagram
- | fen:qbppnprp/prppbkpn/8/8/8/8/QBPPNPRP/PRPPBKPN:
+ | fen:qbppnprp/prpbpkpn/8/8/8/8/QBPPNPRP/PRPPKBPN:
figcaption Posible posición inicial.
p Notas
ul
- li La computadora actualmente está jugando completamente al azar.
+ li.
+ La computadora usa una estrategia básica, mucho más baja de lo que
+ humano podría hacer pero mejor que un juego aleatorio.
li.
Las piezas se distribuyen al azar en las dos primeras filas.
El rey podría estar en la segunda fila, atacado por una torre o dama enemiga.
figure.diagram-container
.diagram
- | fen:qbppnprp/prppbkpn/8/8/8/8/QBPPNPRP/PRPPBKPN:
+ | fen:qbppnprp/prpbpkpn/8/8/8/8/QBPPNPRP/PRPPKBPN:
figcaption Possible position initiale.
p Notes
ul
- li L'ordinateur joue pour l'instant totalement au hasard.
+ li.
+ L'ordinateur utilise une stratégie basique, nettement inférieure à ce qu'un
+ humain pourrait faire mais meilleure qu'un jeu aléatoire.
li.
Les pièces sont réparties au hasard sur les deux premières rangées.
Le roi pourrait être sur la seconde rangée, attaqué par une tour ou dame ennemie.
w: [...Array(8).fill(true)], //pawns can move 2 squares?
b: [...Array(8).fill(true)]
};
- if (!fenflags) return;
const flags = fenflags.substr(4); //skip first 4 digits, for castle
for (let c of ["w", "b"]) {
for (let i = 0; i < 8; i++)
getPotentialMovesFrom([x, y]) {
let standardMoves = super.getPotentialMovesFrom([x, y]);
const lastRank = this.turn == "w" ? 0 : 7;
- if (this.getPiece(x, y) == V.KING) return standardMoves; //king has to be treated differently (for castles)
+ // King has to be treated differently (for castles)
+ if (this.getPiece(x, y) == V.KING) return standardMoves;
let moves = [];
standardMoves.forEach(m => {
if (m.vanish[0].p == V.PAWN) {
import { randInt, shuffle } from "@/utils/alea";
export const VariantRules = class CircularRules extends ChessRules {
- static get HasFlags() {
+ static get HasEnpassant() {
return false;
}
- static get HasEnpassant() {
+ static get CanFlip() {
return false;
}
- // TODO: CanFlip --> also for racing kings (answer is false)
+ setFlags(fenflags) {
+ this.pawnFlags = {
+ w: [...Array(8).fill(true)], //pawns can move 2 squares?
+ b: [...Array(8).fill(true)]
+ };
+ for (let c of ["w", "b"]) {
+ for (let i = 0; i < 8; i++)
+ this.pawnFlags[c][i] = fenflags.charAt((c == "w" ? 0 : 8) + i) == "1";
+ }
+ }
+
+ aggregateFlags() {
+ return this.pawnFlags;
+ }
+
+ disaggregateFlags(flags) {
+ this.pawnFlags = flags;
+ }
- // TODO: shuffle on 1st and 5th ranks
static GenRandInitFen() {
let pieces = { w: new Array(8), b: new Array(8) };
- // Shuffle pieces on first and last rank
+ // Shuffle pieces on first and fifth rank
for (let c of ["w", "b"]) {
let positions = ArrayFun.range(8);
pieces[c][rook2Pos] = "r";
}
return (
+ "8/8/pppppppp/" +
pieces["b"].join("") +
- "/pppppppp/8/8/8/8/PPPPPPPP/" +
+ "/8/8/PPPPPPPP/" +
pieces["w"].join("").toUpperCase() +
- " w 0"
+ // 16 flags: can pawns advance 2 squares?
+ " w 0 1111111111111111"
);
}
- // TODO: adapt this for a circular board
+ // Output basically x % 8 (circular board)
+ static ComputeX(x) {
+ let res = x % V.size.x;
+ if (res < 0)
+ res += V.size.x;
+ return res;
+ }
+
getSlideNJumpMoves([x, y], steps, oneStep) {
let moves = [];
outerLoop: for (let step of steps) {
- let i = x + step[0];
+ let i = V.ComputeX(x + step[0]);
let j = y + step[1];
while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
moves.push(this.getBasicMove([x, y], [i, j]));
if (oneStep !== undefined) continue outerLoop;
- i += step[0];
+ i = V.ComputeX(i + step[0]);
j += step[1];
}
if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
return moves;
}
- // TODO: adapt: all pawns go in thz same direction!
getPotentialPawnMoves([x, y]) {
const color = this.turn;
let moves = [];
const [sizeX, sizeY] = [V.size.x, V.size.y];
- const shiftX = color == "w" ? -1 : 1;
- const firstRank = color == "w" ? sizeX - 1 : 0;
- const startRank = color == "w" ? sizeX - 2 : 1;
- const lastRank = color == "w" ? 0 : sizeX - 1;
- const pawnColor = this.getColor(x, y); //can be different for checkered
-
- // NOTE: next condition is generally true (no pawn on last rank)
- if (x + shiftX >= 0 && x + shiftX < sizeX) {
- const finalPieces =
- x + shiftX == lastRank
- ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
- : [V.PAWN];
- // One square forward
- if (this.board[x + shiftX][y] == V.EMPTY) {
- for (let piece of finalPieces) {
- moves.push(
- this.getBasicMove([x, y], [x + shiftX, y], {
- c: pawnColor,
- p: piece
- })
- );
- }
- // Next condition because pawns on 1st rank can generally jump
- if (
- [startRank, firstRank].includes(x) &&
- this.board[x + 2 * shiftX][y] == V.EMPTY
- ) {
- // Two squares jump
- moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
- }
+ // All pawns go in the same direction!
+ const shiftX = -1;
+ const startRank = color == "w" ? sizeX - 2 : 2;
+
+ // One square forward
+ const nextRow = V.ComputeX(x + shiftX);
+ if (this.board[nextRow][y] == V.EMPTY) {
+ moves.push(this.getBasicMove([x, y], [nextRow, y]));
+ if (
+ x == startRank &&
+ this.pawnFlags[color][y] &&
+ this.board[x + 2 * shiftX][y] == V.EMPTY
+ ) {
+ // Two squares jump
+ moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
}
- // Captures
- for (let shiftY of [-1, 1]) {
- if (
- y + shiftY >= 0 &&
- y + shiftY < sizeY &&
- this.board[x + shiftX][y + shiftY] != V.EMPTY &&
- this.canTake([x, y], [x + shiftX, y + shiftY])
- ) {
- for (let piece of finalPieces) {
- moves.push(
- this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
- c: pawnColor,
- p: piece
- })
- );
- }
- }
+ }
+ // Captures
+ for (let shiftY of [-1, 1]) {
+ if (
+ y + shiftY >= 0 &&
+ y + shiftY < sizeY &&
+ this.board[nextRow][y + shiftY] != V.EMPTY &&
+ this.canTake([x, y], [nextRow, y + shiftY])
+ ) {
+ moves.push(this.getBasicMove([x, y], [nextRow, y + shiftY]));
}
}
return moves;
}
- // What are the king moves from square x,y ?
getPotentialKingMoves(sq) {
return this.getSlideNJumpMoves(
sq,
);
}
- // TODO: check boundaries here as well
+ filterValid(moves) {
+ const filteredMoves = super.filterValid(moves);
+ // If at least one full move made, everything is allowed:
+ if (this.movesCount >= 2)
+ return filteredMoves;
+ // Else, forbid check:
+ const oppCol = V.GetOppCol(this.turn);
+ return filteredMoves.filter(m => {
+ this.play(m);
+ const res = !this.underCheck(oppCol);
+ this.undo(m);
+ return res;
+ });
+ }
+
isAttackedByPawn([x, y], colors) {
for (let c of colors) {
- let pawnShift = c == "w" ? 1 : -1;
- if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
- for (let i of [-1, 1]) {
- if (
- y + i >= 0 &&
- y + i < V.size.y &&
- this.getPiece(x + pawnShift, y + i) == V.PAWN &&
- this.getColor(x + pawnShift, y + i) == c
- ) {
- return true;
- }
+ let pawnShift = 1;
+ const attackerRow = V.ComputeX(x + pawnShift);
+ for (let i of [-1, 1]) {
+ if (
+ y + i >= 0 &&
+ y + i < V.size.y &&
+ this.getPiece(attackerRow, y + i) == V.PAWN &&
+ this.getColor(attackerRow, y + i) == c
+ ) {
+ return true;
}
}
}
return false;
}
- // TODO: adapt this function
isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
for (let step of steps) {
- let rx = x + step[0],
+ let rx = V.ComputeX(x + step[0]),
ry = y + step[1];
while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
- rx += step[0];
+ rx = V.ComputeX(rx + step[0]);
ry += step[1];
}
if (
}
return false;
}
+
+ getFlagsFen() {
+ // Return pawns flags
+ let flags = "";
+ for (let c of ["w", "b"]) {
+ for (let i = 0; i < 8; i++) flags += this.pawnFlags[c][i] ? "1" : "0";
+ }
+ return flags;
+ }
+
+ updateVariables(move) {
+ const c = move.vanish[0].c;
+ const secondRank = {"w":6, "b":2};
+ // Update king position + flags
+ if (move.vanish[0].p == V.KING && move.appear.length > 0) {
+ this.kingPos[c][0] = move.appear[0].x;
+ this.kingPos[c][1] = move.appear[0].y;
+ }
+ else if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
+ // This move turns off a 2-squares pawn flag
+ this.pawnFlags[c][move.start.y] = false;
+ }
+
+ static get VALUES() {
+ return {
+ p: 1,
+ r: 5,
+ n: 3,
+ b: 4,
+ q: 10,
+ k: 1000
+ };
+ }
};
--- /dev/null
+import { ChessRules, PiPo, Move } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt, shuffle } from "@/utils/alea";
+
+export const VariantRules = class CylinderRules extends ChessRules {
+ // Output basically x % 8 (circular board)
+ static ComputeY(y) {
+ let res = y % V.size.y;
+ if (res < 0)
+ res += V.size.y;
+ return res;
+ }
+
+ getSlideNJumpMoves([x, y], steps, oneStep) {
+ let moves = [];
+ outerLoop: for (let step of steps) {
+ let i = x + step[0];
+ let j = V.ComputeY(y + step[1]);
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ moves.push(this.getBasicMove([x, y], [i, j]));
+ if (oneStep !== undefined) continue outerLoop;
+ i += step[0];
+ j = V.ComputeY(j + step[1]);
+ }
+ if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
+ moves.push(this.getBasicMove([x, y], [i, j]));
+ }
+ return moves;
+ }
+
+ getPotentialPawnMoves([x, y]) {
+ const color = this.turn;
+ let moves = [];
+ const [sizeX, sizeY] = [V.size.x, V.size.y];
+ const shiftX = color == "w" ? -1 : 1;
+ const startRank = color == "w" ? sizeX - 2 : 1;
+ const lastRank = color == "w" ? 0 : sizeX - 1;
+
+ const finalPieces =
+ x + shiftX == lastRank
+ ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
+ : [V.PAWN];
+ if (this.board[x + shiftX][y] == V.EMPTY) {
+ // One square forward
+ for (let piece of finalPieces) {
+ moves.push(
+ this.getBasicMove([x, y], [x + shiftX, y], {
+ c: color,
+ p: piece
+ })
+ );
+ }
+ if (
+ x == startRank &&
+ this.board[x + 2 * shiftX][y] == V.EMPTY
+ ) {
+ // Two squares jump
+ moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
+ }
+ }
+ // Captures
+ for (let shiftY of [-1, 1]) {
+ const nextFile = V.ComputeY(y + shiftY);
+ if (
+ this.board[x + shiftX][nextFile] != V.EMPTY &&
+ this.canTake([x, y], [x + shiftX, nextFile])
+ ) {
+ for (let piece of finalPieces) {
+ moves.push(
+ this.getBasicMove([x, y], [x + shiftX, nextFile], {
+ c: color,
+ p: piece
+ })
+ );
+ }
+ }
+ }
+
+ // En passant
+ const Lep = this.epSquares.length;
+ const epSquare = this.epSquares[Lep - 1]; //always at least one element
+ if (
+ !!epSquare &&
+ epSquare.x == x + shiftX &&
+ Math.abs( (epSquare.y - y) % V.size.y ) == 1
+ ) {
+ let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+ enpassantMove.vanish.push({
+ x: x,
+ y: epSquare.y,
+ p: "p",
+ c: this.getColor(x, epSquare.y)
+ });
+ moves.push(enpassantMove);
+ }
+
+ return moves;
+ }
+
+ isAttackedByPawn([x, y], colors) {
+ for (let c of colors) {
+ let pawnShift = c == "w" ? 1 : -1;
+ if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
+ for (let i of [-1, 1]) {
+ const nextFile = V.ComputeY(y + i);
+ if (
+ this.getPiece(x + pawnShift, nextFile) == V.PAWN &&
+ this.getColor(x + pawnShift, nextFile) == c
+ ) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
+ for (let step of steps) {
+ let rx = x + step[0],
+ ry = V.ComputeY(y + step[1]);
+ while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+ rx += step[0];
+ ry = V.ComputeY(ry + step[1]);
+ }
+ if (
+ V.OnBoard(rx, ry) &&
+ this.getPiece(rx, ry) === piece &&
+ colors.includes(this.getColor(rx, ry))
+ ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static get VALUES() {
+ return {
+ p: 1,
+ r: 5,
+ n: 3,
+ b: 4,
+ q: 10,
+ k: 1000
+ };
+ }
+};
// Can I take something ? If yes, do it if it seems good...
if (move.vanish.length == 2 && move.vanish[1].c != color) {
- //avoid castle
+ // OK this isn't a castling move
const myPieceVal = V.VALUES[move.appear[0].p];
const hisPieceVal = V.VALUES[move.vanish[1].p];
- if (myPieceVal <= hisPieceVal) move.eval = hisPieceVal - myPieceVal + 2;
- //favor captures
+ // Favor captures
+ if (myPieceVal <= hisPieceVal) move.eval = hisPieceVal - myPieceVal + 1;
else {
// Taking a pawn with minor piece,
// or minor piece or pawn with a rook,
// or anything but a queen with a queen,
// or anything with a king.
- // ==> Do it at random, although
- // this is clearly inferior to what a human can deduce...
- move.eval = Math.random() < 0.5 ? 1 : -1;
+ move.eval = hisPieceVal - myPieceVal;
+ //Math.random() < 0.5 ? 1 : -1;
}
}
}
pieces[c][rook2Pos] = "u";
}
let upFen = pieces["b"].join("");
- upFen = upFen.substr(0,8) + "/" + upFen.substr(8);
+ upFen = upFen.substr(0,8) + "/" + upFen.substr(8).split("").reverse().join("");
let downFen = pieces["b"].join("").toUpperCase();
- downFen = downFen.substr(0,8) + "/" + downFen.substr(8);
+ downFen = downFen.substr(0,8) + "/" + downFen.substr(8).split("").reverse().join("");
return upFen + "/8/8/8/8/" + downFen + " w 0";
}
}
getComputerMove() {
- // Just return a random move. TODO: something smarter...
- const moves = this.getAllValidMoves();
- return moves[randInt(moves.length)];
+ const color = this.turn;
+ let moves = this.getAllValidMoves();
+ for (let move of moves) {
+ move.eval = 0; //a priori...
+
+ // Can I take something ? If yes, do it with some probability
+ if (move.vanish.length == 2 && move.vanish[1].c != color) {
+ // OK this isn't a castling move
+ const myPieceVal = V.VALUES[move.appear[0].p];
+ const hisPieceVal = Object.keys(V.HIDDEN_DECODE).includes(move.vanish[1].p)
+ ? undefined
+ : V.VALUES[move.vanish[1].p];
+ if (!hisPieceVal) {
+ // Opponent's piece is unknown: do not take too much risk
+ move.eval = -myPieceVal + 1.5; //so that pawns always take
+ }
+ // Favor captures
+ else if (myPieceVal <= hisPieceVal)
+ move.eval = hisPieceVal - myPieceVal + 1;
+ else {
+ // Taking a pawn with minor piece,
+ // or minor piece or pawn with a rook,
+ // or anything but a queen with a queen,
+ // or anything with a king.
+ move.eval = hisPieceVal - myPieceVal;
+ }
+ } else {
+ // If no capture, favor small step moves,
+ // but sometimes move the knight anyway
+ const penalty = V.Decode(move.vanish[0].p) != V.KNIGHT
+ ? Math.abs(move.end.x - move.start.x) + Math.abs(move.end.y - move.start.y)
+ : (Math.random() < 0.5 ? 3 : 1);
+ move.eval -= penalty / (V.size.x + V.size.y - 1);
+ }
+
+ // TODO: also favor movements toward the center?
+ }
+
+ moves.sort((a, b) => b.eval - a.eval);
+ let candidates = [0];
+ for (let j = 1; j < moves.length && moves[j].eval == moves[0].eval; j++)
+ candidates.push(j);
+ return moves[candidates[randInt(candidates.length)]];
}
getNotation(move) {
// Very simple criterion for now: kings position
return this.kingPos["w"][0] + this.kingPos["b"][0];
}
+
+ getNotation(move) {
+ // Translate final square
+ const finalSquare = V.CoordsToSquare(move.end);
+
+ const piece = this.getPiece(move.start.x, move.start.y);
+ if (piece == V.PAWN) {
+ // Pawn move
+ let notation = "";
+ if (move.vanish.length == 2) {
+ // Capture
+ const startColumn = V.CoordToColumn(move.start.y);
+ notation = startColumn + "x" + finalSquare;
+ }
+ else notation = finalSquare;
+ return notation;
+ }
+ // Piece movement
+ return (
+ piece.toUpperCase() +
+ (move.vanish.length == 2 ? "x" : "") +
+ finalSquare
+ );
+ }
};
('Chess960', 'Standard rules'),
('Circular', 'Run forward'),
('Crazyhouse', 'Captures reborn'),
+ ('Cylinder', 'Neverending rows'),
('Dark', 'In the shadow'),
('Enpassant', 'Capture en passant'),
('Extinction', 'Capture all of a kind'),