"Enter the disco": "Enter the disco",
"Exchange pieces' positions": "Exchange pieces' positions",
"Exotic captures": "Exotic captures",
- "Explosive captures": "Explosive captures",
+ "Explosive captures (v1)": "Explosive captures (v1)",
+ "Explosive captures (v2)": "Explosive captures (v2)",
"Extra bishops and knights": "Extra bishops and knights",
"Faster development": "Faster development",
"Four new pieces": "Four new pieces",
"Kings cross the 11x11 board": "Kings cross the 11x11 board",
"Knight in pocket": "Knight in pocket",
"Knight versus pawns": "Knight versus pawns",
+ "Lancers everywhere": "Lancers everywhere",
"Landing on the board": "Landing on the board",
"Laws of attraction": "Laws of attraction",
"Long jumps over pieces": "Long jumps over pieces",
"Enter the disco": "Entrar en la discoteca",
"Exchange pieces' positions": "Intercambiar posiciones de piezas",
"Exotic captures": "Capturas exóticas",
- "Explosive captures": "Capturas explosivas",
+ "Explosive captures (v1)": "Capturas explosivas (v1)",
+ "Explosive captures (v2)": "Capturas explosivas (v2)",
"Extra bishops and knights": "Alfiles y caballos adicionales",
"Faster development": "Desarrollo acelerado",
"Four new pieces": "Quatro nuevas piezas",
"Kings cross the 11x11 board": "Los reyes cruzan el 11x11 tablero",
"Knight in pocket": "Caballo en bolsillo",
"Knight versus pawns": "Caballo contra peones",
+ "Lancers everywhere": "Lanceros por todas partes",
"Landing on the board": "Aterrizando en el tablero",
"Laws of attraction": "Las leyes de las atracciones",
"Long jumps over pieces": "Saltos largos sobre las piezas",
"Enter the disco": "Entrez dans la boîte",
"Exchange pieces' positions": "Échangez les positions des pièces",
"Exotic captures": "Captures exotiques",
- "Explosive captures": "Captures explosives",
+ "Explosive captures (v1)": "Captures explosives (v1)",
+ "Explosive captures (v2)": "Captures explosives (v2)",
"Extra bishops and knights": "Fous et cavaliers supplémentaires",
"Faster development": "Développement accéléré",
"Four new pieces": "Quatre nouvelles pièces",
"Kings cross the 11x11 board": "Les rois traversent l'échiquier 11x11",
"Knight in pocket": "Cavalier en poche",
"Knight versus pawns": "Cavalier contre pions",
+ "Lancers everywhere": "Lanciers à tous les coins",
"Landing on the board": "Débarquement sur l'échiquier",
"Laws of attraction": "Les lois de l'attraction",
"Long jumps over pieces": "Sauts longs par dessus les pièces",
--- /dev/null
+p.boxed.
+ Atomic1, but white's first move is a pawn removal.
+
+p.
+ At the very first move of the game, white must remove a pawn,
+ from either side.
+ Black then replies, and the game continues normally.
+
+figure.diagram-container
+ .diagram
+ | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPP1PPP/RNBQKBNR:
+ figcaption.
+ After 1.e2X, pawn removal at e2.
+
+h3 More information
+
+p
+ | This variant aims at balancing the big white's advantage in Atomic1.
+ | It was suggested (relayed?) and analyzed recently (2020) by a strong
+ | Atomic player (Gannet on Discord). See
+ a(href="https://discord.com/channels/686736099959504907/687076744095858762/762398439043498046")
+ | the messages
+ | on Discord vchess server.
--- /dev/null
+p.boxed TODO
--- /dev/null
+p.boxed TODO
--- /dev/null
+p.boxed
+ | Rooks are replaced by lancers from Eightpieces variant.
+
+p
+ | Everything is the same as in orthodox rules, except that rooks are
+ | replaced by lancers, as defined in the
+ a(href="/variants/Eightpieces") Eightpieces variant
+ | .
+
+p.
+ White lancers are facing diagonally upward, while black lancers point
+ diagonally down in the deterministic initial setup.
+ In the randomized version, to avoid complex setup rules,
+ all lancers are facing up.
+
+figure.diagram-container
+ .diagram
+ | fen:efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM:
+ figcaption Initial deterministic position.
+
+h3 More information
+
+p TODO
--- /dev/null
+p.boxed TODO
--- /dev/null
+p.boxed
+ | Les tours sont remplacées par des lanciers de la variante Eightpieces.
+
+p
+ | Tout se déroule comme aux échecs orthodoxes, sauf en ce qui concerne
+ | les tours qui sont remplacées par des lanciers comme défini dans la
+ a(href="variants/Eightpieces") variante Eightpieces
+ | .
+
+p.
+ Les lanciers blancs regarde vers l'avant en diagonale, tandis que les
+ lanciers noirs pointent vers le sud (toujours en diagonale) dans
+ l'arrangement initial déterministe.
+ Dans la version aléatoire, tous les lanciers regardent droit devant afin
+ d'éviter des règles d'initialisation complexes.
+
+figure.diagram-container
+ .diagram
+ | fen:efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM:
+ figcaption Position initiale déterministe.
+
+h3 Plus d'information
+
+p TODO
h3 Special moves
-p.
- Castling is possible as in orthodox 8x8 game. The white king move to c1 or
- i1 (one square to the left of bottom-right corner) for large (resp. small)
- castle. Same for black on the other side.
+p Castling is not permitted.
p.
Promotion is mandatory for a pawn reaching the last rank. However, they can
Pawn promotion is possible (but not forced) on the two ranks before last,
only in an already captured (friendly) piece.
-p.
- Note: I changed the author's starting position, to increase randomness
- (and uniformize with other variants).
- Thus the castling rule was introduced compared to the rules described
- on chessvariants.com.
-
h3 Source
p
h3 Movimientos especiales
-p.
- El enroque es posible como en un juego con un tablero 8x8.
- El rey blanco va a c1 o i1 (un cuadro a la izquierda de la esquina
- inferior derecha) para un enroque grande (resp. pequeño).
- Lo mismo para las negras del otro lado.
+p No se permite el enroque.
p.
La promoción es obligatoria para los peones que llegan a la última fila,
La promoción es posible (pero no obligatoria) en las octava y novena
filas, siempre en una pieza amiga capturada.
-p.
- Nota: cambié la posición inicial planificada por el autor, a
- aumentar el alea de la situación inicial (y estandarizar con los demás
- variantes). De repente, la regla de enroque se agregó en comparación con las
- reglas inicialmente descrito.
-
h3 Fuente
p
h3 Coups spéciaux
-p.
- Le roque est possible comme dans une partie sur un échiquier 8x8.
- Le roi blanc va en c1 ou i1 (une case à gauche du coin inférieur droit)
- pour un grand (resp. petit) roque. Pareil pour les noirs de l'autre côté.
+p Le roque n'est pas autorisé.
p.
La promotion est obligatoire pour les pions arrivant sur la dernière rangée,
La promotion est possible (mais non obligatoire) sur les huitième et neuvième
rangées, toujours en une pièce amie déjà capturée.
-p.
- Note : j'ai changé la position de départ prévue par l'auteur, pour
- augmenter l'alea de la situation initiale (et uniformiser avec les autres
- variantes). Du coup la règle du roque a été ajoutée comparée aux règles
- décrites initialement.
-
h3 Source
p
-
var varlist = [
"Eightpieces",
+ "Fullcavalry",
"Grand",
"Grasshopper",
"Omega",
-
var varlist = [
"Antimatter",
- "Atomic",
+ "Atomic1",
+ "Atomic2",
"Checkless"
]
ul
-
var varlist = [
"Eightpieces",
+ "Fullcavalry",
"Grand",
"Grasshopper",
"Omega",
-
var varlist = [
"Antimatter",
- "Atomic",
+ "Atomic1",
+ "Atomic2",
"Checkless"
]
ul
-
var varlist = [
"Eightpieces",
+ "Fullcavalry",
"Grand",
"Grasshopper",
"Omega",
-
var varlist = [
"Antimatter",
- "Atomic",
+ "Atomic1",
+ "Atomic2",
"Checkless"
]
ul
import { ChessRules, PiPo } from "@/base_rules";
-export class AtomicRules extends ChessRules {
+export class Atomic1Rules extends ChessRules {
getPotentialMovesFrom([x, y]) {
let moves = super.getPotentialMovesFrom([x, y]);
postPlay(move) {
super.postPlay(move);
- if (move.appear.length == 0) {
+ // NOTE: (harmless) condition on movesCount for Atomic2
+ if (move.appear.length == 0 && this.movesCount >= 2) {
// Capture
const firstRank = { w: 7, b: 0 };
for (let c of ["w", "b"]) {
super.postUndo(move);
const c = this.turn;
const oppCol = V.GetOppCol(c);
- if ([this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)) {
+ // NOTE: condition on movesCount for Atomic2
+ if (
+ this.movesCount >= 1 &&
+ [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)
+ ) {
// There is a chance that last move blowed some king away..
for (let psq of move.vanish) {
if (psq.p == "k")
--- /dev/null
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { Atomic1Rules } from "@/variants/Atomic1";
+
+export class Atomic2Rules extends Atomic1Rules {
+
+ getPotentialMovesFrom([x, y]) {
+ if (this.movesCount == 0) {
+ if ([1, 6].includes(x)) {
+ const c = this.getColor(x, y);
+ return [
+ new Move({
+ appear: [],
+ vanish: [
+ new PiPo({ x: x, y: y, p: V.PAWN, c: c })
+ ],
+ start: { x: x, y: y },
+ end: { x: x, y: y }
+ })
+ ];
+ }
+ return [];
+ }
+ return super.getPotentialMovesFrom([x, y]);
+ }
+
+ hoverHighlight(x, y) {
+ return this.movesCount == 0 && [1, 6].includes(x);
+ }
+
+ doClick(square) {
+ if (this.movesCount >= 1) return null;
+ const [x, y] = [square[0], square[1]];
+ if (![1, 6].includes(x)) return null;
+ return new Move({
+ appear: [],
+ vanish: [
+ new PiPo({
+ x: x,
+ y: y,
+ c: this.getColor(x, y),
+ p: V.PAWN
+ })
+ ],
+ start: { x: x, y: y },
+ end: { x: x, y: y }
+ });
+ }
+
+ getNotation(move) {
+ if (move.appear.length == 0 && move.vanish.length == 1)
+ // First move in game
+ return V.CoordsToSquare(move.start) + "X";
+ return super.getNotation(move);
+ }
+
+};
-import { ArrayFun } from "@/utils/array";
import { randInt } from "@/utils/alea";
import { ChessRules, PiPo, Move } from "@/base_rules";
static GenRandInitFen(randomness) {
if (randomness == 0)
- // Deterministic:
return "jfsqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JDSQKBNR w 0 ahah - -";
- let pieces = { w: new Array(8), b: new Array(8) };
- let flags = "";
- // Shuffle pieces on first (and last rank if randomness == 2)
- for (let c of ["w", "b"]) {
- if (c == 'b' && randomness == 1) {
- const lancerIdx = pieces['w'].findIndex(p => {
- return Object.keys(V.LANCER_DIRS).includes(p);
- });
- pieces['b'] =
- pieces['w'].slice(0, lancerIdx)
- .concat(['g'])
- .concat(pieces['w'].slice(lancerIdx + 1));
- flags += flags;
- break;
- }
-
- let positions = ArrayFun.range(8);
-
- // Get random squares for bishop and sentry
- let randIndex = 2 * randInt(4);
- let bishopPos = positions[randIndex];
- // The sentry must be on a square of different color
- let randIndex_tmp = 2 * randInt(4) + 1;
- let sentryPos = positions[randIndex_tmp];
- if (c == 'b') {
- // Check if white sentry is on the same color as ours.
- // If yes: swap bishop and sentry positions.
- // NOTE: test % 2 == 1 because there are 7 slashes.
- if ((pieces['w'].indexOf('s') - sentryPos) % 2 == 1)
- [bishopPos, sentryPos] = [sentryPos, bishopPos];
+ const baseFen = ChessRules.GenRandInitFen(randomness);
+ const fenParts = baseFen.split(' ');
+ const posParts = fenParts[0].split('/');
+
+ // Replace one bishop by sentry, so that sentries on different colors
+ // Also replace one random rook by jailer,
+ // and one random knight by lancer (facing north/south)
+
+ // "replaced" array contains -2 initially, then either -1 if skipped,
+ // or (eventually) the index of replacement:
+ let newPos = { 0: "", 7: "" };
+ let sentryOddity = -1;
+ for (let rank of [0, 7]) {
+ let replaced = { 'b': -2, 'n': -2, 'r': -2 };
+ for (let i = 0; i < 8; i++) {
+ const curChar = posParts[rank].charAt(i).toLowerCase();
+ if (['b', 'n', 'r'].includes(curChar)) {
+ if (
+ replaced[curChar] == -1 ||
+ (curChar == 'b' && rank == 7 && i % 2 == sentryOddity) ||
+ (
+ (curChar != 'b' || rank == 0) &&
+ replaced[curChar] == -2 &&
+ randInt(2) == 0
+ )
+ ) {
+ replaced[curChar] = i;
+ if (curChar == 'b') {
+ if (sentryOddity < 0) sentryOddity = i % 2;
+ newPos[rank] += 's';
+ }
+ else if (curChar == 'r') newPos[rank] += 'j';
+ else
+ // Lancer: orientation depends on side
+ newPos[rank] += (rank == 0 ? 'g' : 'c');
+ }
+ else {
+ if (replaced[curChar] == -2) replaced[curChar]++;
+ newPos[rank] += curChar;
+ }
+ }
+ else newPos[rank] += curChar;
}
- positions.splice(Math.max(randIndex, randIndex_tmp), 1);
- positions.splice(Math.min(randIndex, randIndex_tmp), 1);
-
- // Get random squares for knight and lancer
- randIndex = randInt(6);
- const knightPos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = randInt(5);
- const lancerPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Get random square for queen
- randIndex = randInt(4);
- const queenPos = positions[randIndex];
- positions.splice(randIndex, 1);
-
- // Rook, jailer and king positions are now almost fixed,
- // only the ordering rook->jailer or jailer->rook must be decided.
- let rookPos = positions[0];
- let jailerPos = positions[2];
- const kingPos = positions[1];
- flags += V.CoordToColumn(rookPos) + V.CoordToColumn(jailerPos);
- if (Math.random() < 0.5) [rookPos, jailerPos] = [jailerPos, rookPos];
-
- pieces[c][rookPos] = "r";
- pieces[c][knightPos] = "n";
- pieces[c][bishopPos] = "b";
- pieces[c][queenPos] = "q";
- pieces[c][kingPos] = "k";
- pieces[c][sentryPos] = "s";
- // Lancer faces north for white, and south for black:
- pieces[c][lancerPos] = c == 'w' ? 'c' : 'g';
- pieces[c][jailerPos] = "j";
}
+
return (
- pieces["b"].join("") +
- "/pppppppp/8/8/8/8/PPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0 " + flags + " - -"
+ newPos[0] + "/" + posParts.slice(1, 7).join('/') + "/" +
+ newPos[7].toUpperCase() + " " + fenParts.slice(1, 5).join(' ') + " -"
);
}
}
return true;
});
- } else if (this.subTurn == 2) {
+ }
+ else if (this.subTurn == 2) {
// Put back the sentinel on board:
const color = this.turn;
moves.forEach(m => {
--- /dev/null
+import { ArrayFun } from "@/utils/array";
+import { randInt } from "@/utils/alea";
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export class FullcavalryRules extends ChessRules {
+
+ static get LANCER() {
+ return "l";
+ }
+
+ static get IMAGE_EXTENSION() {
+ // Temporarily, for the time SVG pieces are being designed:
+ return ".png";
+ }
+
+ // Lancer directions *from white perspective*
+ static get LANCER_DIRS() {
+ return {
+ 'c': [-1, 0], //north
+ 'd': [-1, 1], //N-E
+ 'e': [0, 1], //east
+ 'f': [1, 1], //S-E
+ 'g': [1, 0], //south
+ 'h': [1, -1], //S-W
+ 'm': [0, -1], //west
+ 'o': [-1, -1] //N-W
+ };
+ }
+
+ static get PIECES() {
+ return ChessRules.PIECES.concat(Object.keys(V.LANCER_DIRS));
+ }
+
+ getPiece(i, j) {
+ const piece = this.board[i][j].charAt(1);
+ // Special lancer case: 8 possible orientations
+ if (Object.keys(V.LANCER_DIRS).includes(piece)) return V.LANCER;
+ return piece;
+ }
+
+ getPpath(b, color, score, orientation) {
+ if (Object.keys(V.LANCER_DIRS).includes(b[1])) {
+ if (orientation == 'w') return "Eightpieces/tmp_png/" + b;
+ // Find opposite direction for adequate display:
+ let oppDir = '';
+ switch (b[1]) {
+ case 'c':
+ oppDir = 'g';
+ break;
+ case 'g':
+ oppDir = 'c';
+ break;
+ case 'd':
+ oppDir = 'h';
+ break;
+ case 'h':
+ oppDir = 'd';
+ break;
+ case 'e':
+ oppDir = 'm';
+ break;
+ case 'm':
+ oppDir = 'e';
+ break;
+ case 'f':
+ oppDir = 'o';
+ break;
+ case 'o':
+ oppDir = 'f';
+ break;
+ }
+ return "Eightpieces/tmp_png/" + b[0] + oppDir;
+ }
+ // TODO: after we have SVG pieces, remove the folder and next prefix:
+ return "Eightpieces/tmp_png/" + b;
+ }
+
+ getPPpath(m, orientation) {
+ return (
+ this.getPpath(
+ m.appear[0].c + m.appear[0].p,
+ null,
+ null,
+ orientation
+ )
+ );
+ }
+
+ static GenRandInitFen(randomness) {
+ if (randomness == 0)
+ // Deterministic:
+ return "efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM w 0 ahah -";
+
+ const baseFen = ChessRules.GenRandInitFen(randomness);
+ // Replace black rooks by lancers oriented south,
+ // and white rooks by lancers oriented north:
+ return baseFen.replace(/r/g, 'g').replace(/R/g, 'C');
+ }
+
+ // Because of the lancers, getPiece() could be wrong:
+ // use board[x][y][1] instead (always valid).
+ // TODO: base implementation now uses this too (no?)
+ getBasicMove([sx, sy], [ex, ey], tr) {
+ const initColor = this.getColor(sx, sy);
+ const initPiece = this.board[sx][sy].charAt(1);
+ let mv = new Move({
+ appear: [
+ new PiPo({
+ x: ex,
+ y: ey,
+ c: tr ? tr.c : initColor,
+ p: tr ? tr.p : initPiece
+ })
+ ],
+ vanish: [
+ new PiPo({
+ x: sx,
+ y: sy,
+ c: initColor,
+ p: initPiece
+ })
+ ]
+ });
+
+ // The opponent piece disappears if we take it
+ if (this.board[ex][ey] != V.EMPTY) {
+ mv.vanish.push(
+ new PiPo({
+ x: ex,
+ y: ey,
+ c: this.getColor(ex, ey),
+ p: this.board[ex][ey].charAt(1)
+ })
+ );
+ }
+
+ return mv;
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ if (this.getPiece(x, y) == V.LANCER)
+ return this.getPotentialLancerMoves([x, y]);
+ return super.getPotentialMovesFrom([x, y]);
+ }
+
+ // Obtain all lancer moves in "step" direction
+ getPotentialLancerMoves_aux([x, y], step, tr) {
+ let moves = [];
+ // Add all moves to vacant squares until opponent is met:
+ const color = this.getColor(x, y);
+ const oppCol = V.GetOppCol(color)
+ let sq = [x + step[0], y + step[1]];
+ while (V.OnBoard(sq[0], sq[1]) && this.getColor(sq[0], sq[1]) != oppCol) {
+ if (this.board[sq[0]][sq[1]] == V.EMPTY)
+ moves.push(this.getBasicMove([x, y], sq, tr));
+ sq[0] += step[0];
+ sq[1] += step[1];
+ }
+ if (V.OnBoard(sq[0], sq[1]))
+ // Add capturing move
+ moves.push(this.getBasicMove([x, y], sq, tr));
+ return moves;
+ }
+
+ getPotentialLancerMoves([x, y]) {
+ let moves = [];
+ // Add all lancer possible orientations, similar to pawn promotions.
+ const color = this.getColor(x, y);
+ const dirCode = this.board[x][y][1];
+ const curDir = V.LANCER_DIRS[dirCode];
+ const monodirMoves =
+ this.getPotentialLancerMoves_aux([x, y], V.LANCER_DIRS[dirCode]);
+ monodirMoves.forEach(m => {
+ Object.keys(V.LANCER_DIRS).forEach(k => {
+ const newDir = V.LANCER_DIRS[k];
+ // Prevent orientations toward outer board:
+ if (V.OnBoard(m.end.x + newDir[0], m.end.y + newDir[1])) {
+ let mk = JSON.parse(JSON.stringify(m));
+ mk.appear[0].p = k;
+ moves.push(mk);
+ }
+ });
+ });
+ return moves;
+ }
+
+ isAttacked(sq, color) {
+ return (
+ super.isAttacked(sq, color) ||
+ this.isAttackedByLancer(sq, color)
+ );
+ }
+
+ isAttackedByLancer([x, y], color) {
+ for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+ // If in this direction there are only enemy pieces and empty squares,
+ // and we meet a lancer: can he reach us?
+ // NOTE: do not stop at first lancer, there might be several!
+ let coord = { x: x + step[0], y: y + step[1] };
+ let lancerPos = [];
+ while (
+ V.OnBoard(coord.x, coord.y) &&
+ (
+ this.board[coord.x][coord.y] == V.EMPTY ||
+ this.getColor(coord.x, coord.y) == color
+ )
+ ) {
+ if (
+ this.getPiece(coord.x, coord.y) == V.LANCER &&
+ !this.isImmobilized([coord.x, coord.y])
+ ) {
+ lancerPos.push({x: coord.x, y: coord.y});
+ }
+ coord.x += step[0];
+ coord.y += step[1];
+ }
+ for (let xy of lancerPos) {
+ const dir = V.LANCER_DIRS[this.board[xy.x][xy.y].charAt(1)];
+ if (dir[0] == -step[0] && dir[1] == -step[1]) return true;
+ }
+ }
+ return false;
+ }
+
+ static get VALUES() {
+ return Object.assign(
+ { l: 4.8 }, //Jeff K. estimation (for Eightpieces)
+ ChessRules.VALUES
+ );
+ }
+
+ // For moves notation:
+ static get LANCER_DIRNAMES() {
+ return {
+ 'c': "N",
+ 'd': "NE",
+ 'e': "E",
+ 'f': "SE",
+ 'g': "S",
+ 'h': "SW",
+ 'm': "W",
+ 'o': "NW"
+ };
+ }
+
+ filterValid(moves) {
+ // At move 1, forbid captures (in case of...):
+ if (this.movesCount >= 2) return moves;
+ return moves.filter(m => m.vanish.length == 1);
+ }
+
+ getNotation(move) {
+ let notation = super.getNotation(move);
+ if (Object.keys(V.LANCER_DIRNAMES).includes(move.vanish[0].p))
+ // Lancer: add direction info
+ notation += "=" + V.LANCER_DIRNAMES[move.appear[0].p];
+ else if (
+ move.vanish[0].p == V.PAWN &&
+ Object.keys(V.LANCER_DIRNAMES).includes(move.appear[0].p)
+ ) {
+ // Fix promotions in lancer:
+ notation = notation.slice(0, -1) +
+ "L:" + V.LANCER_DIRNAMES[move.appear[0].p];
+ }
+ return notation;
+ }
+
+};
import { ArrayFun } from "@/utils/array";
import { randInt } from "@/utils/alea";
-// NOTE: initial setup differs from the original; see
-// https://www.chessvariants.com/large.dir/freeling.html
export class GrandRules extends ChessRules {
+ static get HasCastle() {
+ return false;
+ }
+
static IsGoodFen(fen) {
if (!ChessRules.IsGoodFen(fen)) return false;
const fenParsed = V.ParseFen(fen);
const fenParts = fen.split(" ");
return Object.assign(
ChessRules.ParseFen(fen),
- { captured: fenParts[5] }
+ { captured: fenParts[4] }
);
}
return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]);
}
- // There may be 2 enPassant squares (if pawn jump 3 squares)
- getEnpassantFen() {
- const L = this.epSquares.length;
- if (!this.epSquares[L - 1]) return "-"; //no en-passant
- let res = "";
- this.epSquares[L - 1].forEach(sq => {
- res += V.CoordsToSquare(sq) + ",";
- });
- return res.slice(0, -1); //remove last comma
- }
-
- // En-passant after 2-sq or 3-sq jumps
- getEpSquare(moveOrSquare) {
- if (!moveOrSquare) return undefined;
- if (typeof moveOrSquare === "string") {
- const square = moveOrSquare;
- if (square == "-") return undefined;
- let res = [];
- square.split(",").forEach(sq => {
- res.push(V.SquareToCoords(sq));
- });
- return res;
- }
- // Argument is a move:
- const move = moveOrSquare;
- const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x];
- if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) {
- const step = (ex - sx) / Math.abs(ex - sx);
- let res = [
- {
- x: sx + step,
- y: sy
- }
- ];
- if (sx + 2 * step != ex) {
- // 3-squares jump
- res.push({
- x: sx + 2 * step,
- y: sy
- });
- }
- return res;
- }
- return undefined; //default
- }
-
getPotentialMovesFrom([x, y]) {
switch (this.getPiece(x, y)) {
case V.MARSHALL:
let moves = [];
const [sizeX, sizeY] = [V.size.x, V.size.y];
const shiftX = color == "w" ? -1 : 1;
- const startRanks = color == "w" ? [sizeX - 2, sizeX - 3] : [1, 2];
+ const startRank = (color == "w" ? sizeX - 3 : 2);
const lastRanks =
color == "w" ? [0, 1, 2] : [sizeX - 1, sizeX - 2, sizeX - 3];
const promotionPieces = [
if (lastRanks.includes(x + shiftX)) {
finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
if (x + shiftX != lastRanks[0]) finalPieces.push(V.PAWN);
- } else finalPieces = [V.PAWN];
+ }
+ else finalPieces = [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 (startRanks.includes(x)) {
- if (this.board[x + 2 * shiftX][y] == V.EMPTY) {
- // Two squares jump
- moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
- if (x == startRanks[0] && this.board[x + 3 * shiftX][y] == V.EMPTY) {
- // Three squares jump
- moves.push(this.getBasicMove([x, y], [x + 3 * shiftX, y]));
- }
- }
- }
+ 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]) {
}
// En passant
- const Lep = this.epSquares.length;
- const epSquare = this.epSquares[Lep - 1];
- if (!!epSquare) {
- for (let epsq of epSquare) {
- // TODO: some redundant checks
- if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) {
- let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]);
- // WARNING: the captured pawn may be diagonally behind us,
- // if it's a 3-squares jump and we take on 1st passing square
- const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX;
- enpassantMove.vanish.push({
- x: px,
- y: epsq.y,
- p: "p",
- c: this.getColor(px, epsq.y)
- });
- moves.push(enpassantMove);
- }
- }
- }
+ Array.prototype.push.apply(
+ moves,
+ this.getEnpassantCaptures([x, y], shiftX)
+ );
return moves;
}
- // TODO: different castle?
-
getPotentialMarshallMoves(sq) {
return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep")
if (randomness == 0) {
return (
"r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " +
- // No castling in the official initial setup
- "w 0 zzzz - 00000000000000"
+ "w 0 - 00000000000000"
);
}
- let pieces = { w: new Array(10), b: new Array(10) };
- let flags = "";
- // Shuffle pieces on first and last rank
+ let pieces = { w: new Array(8), b: new Array(8) };
+ // Shuffle pieces on second and before-last rank
for (let c of ["w", "b"]) {
if (c == 'b' && randomness == 1) {
pieces['b'] = pieces['w'];
- flags += flags;
break;
}
- let positions = ArrayFun.range(10);
+ let positions = ArrayFun.range(8);
// Get random squares for bishops
- let randIndex = 2 * randInt(5);
+ let randIndex = 2 * randInt(4);
let bishop1Pos = positions[randIndex];
// The second bishop must be on a square of different color
- let randIndex_tmp = 2 * randInt(5) + 1;
+ let randIndex_tmp = 2 * randInt(4) + 1;
let bishop2Pos = positions[randIndex_tmp];
// Remove chosen squares
positions.splice(Math.max(randIndex, randIndex_tmp), 1);
positions.splice(Math.min(randIndex, randIndex_tmp), 1);
// Get random squares for knights
- randIndex = randInt(8);
+ randIndex = randInt(6);
let knight1Pos = positions[randIndex];
positions.splice(randIndex, 1);
- randIndex = randInt(7);
+ randIndex = randInt(5);
let knight2Pos = positions[randIndex];
positions.splice(randIndex, 1);
// Get random square for queen
- randIndex = randInt(6);
+ randIndex = randInt(4);
let queenPos = positions[randIndex];
positions.splice(randIndex, 1);
// ...random square for marshall
- randIndex = randInt(5);
+ randIndex = randInt(3);
let marshallPos = positions[randIndex];
positions.splice(randIndex, 1);
// ...random square for cardinal
- randIndex = randInt(4);
+ randIndex = randInt(2);
let cardinalPos = positions[randIndex];
positions.splice(randIndex, 1);
- // Rooks and king positions are now fixed,
- // because of the ordering rook-king-rook
- let rook1Pos = positions[0];
- let kingPos = positions[1];
- let rook2Pos = positions[2];
+ // King position is now fixed,
+ let kingPos = positions[0];
// Finally put the shuffled pieces in the board array
- pieces[c][rook1Pos] = "r";
pieces[c][knight1Pos] = "n";
pieces[c][bishop1Pos] = "b";
pieces[c][queenPos] = "q";
pieces[c][kingPos] = "k";
pieces[c][bishop2Pos] = "b";
pieces[c][knight2Pos] = "n";
- pieces[c][rook2Pos] = "r";
- flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
}
return (
- pieces["b"].join("") +
- "/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/" +
- pieces["w"].join("").toUpperCase() +
- " w 0 " + flags + " - 00000000000000"
+ "r8r/1" + pieces["b"].join("") + "1/" +
+ "pppppppppp/91/91/91/91/PPPPPPPPPP/" +
+ "1" + pieces["w"].join("").toUpperCase() + "1/R8R" +
+ " w 0 " + " - 00000000000000"
);
}
getPossibleMovesFrom(sq) {
let moves = this.filterValid(this.getPotentialMovesFrom(sq));
const captureMoves = V.KeepCaptures(moves);
-
-console.log(this.atLeastOneCapture());
-
if (captureMoves.length > 0) return captureMoves;
if (this.atLeastOneCapture()) return [];
return moves;
return moves;
}
+ hoverHighlight(x, y) {
+ const c = this.turn;
+ return (
+ this.movesCount <= 3 &&
+ ((c == 'w' && x == 7) || (c == 'b' && x == 0))
+ );
+ }
+
// Special case of move 1 = choose squares, knight first, then bishop
doClick(square) {
if (this.movesCount >= 4) return null;
this.alertAndQuit("Analysis disabled for this variant");
else this.loadGame(orientation);
})
- .catch((err) => { this.alertAndQuit("Mispelled variant name", true); });
+ //.catch((err) => { this.alertAndQuit("Mispelled variant name", true); });
this.rulesContent =
afterRawLoad(
require(
('Antiking2', 'Keep antiking in check (v2)'),
('Antimatter', 'Dangerous collisions'),
('Arena', 'Middle battle'),
- ('Atomic', 'Explosive captures'),
+ ('Atomic1', 'Explosive captures (v1)'),
+ ('Atomic2', 'Explosive captures (v2)'),
('Ball', 'Score a goal'),
('Balaklava', 'Meet the Mammoth'),
('Baroque', 'Exotic captures'),
('Football', 'Score a goal'),
('Forward', 'Moving forward'),
('Freecapture', 'Capture both colors'),
+ ('Fullcavalry', 'Lancers everywhere'),
('Grand', 'Big board'),
('Grasshopper', 'Long jumps over pieces'),
('Gridolina', 'Jump the borders'),