1 import { ChessRules
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 // NOTE: initial setup differs from the original; see
6 // https://www.chessvariants.com/large.dir/freeling.html
7 export class GrandRules
extends ChessRules
{
8 static IsGoodFen(fen
) {
9 if (!ChessRules
.IsGoodFen(fen
)) return false;
10 const fenParsed
= V
.ParseFen(fen
);
12 if (!fenParsed
.captured
|| !fenParsed
.captured
.match(/^[0-9]{14,14}$/))
17 static IsGoodEnpassant(enpassant
) {
18 if (enpassant
!= "-") {
19 const squares
= enpassant
.split(",");
20 if (squares
.length
> 2) return false;
21 for (let sq
of squares
) {
22 const ep
= V
.SquareToCoords(sq
);
23 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
29 static ParseFen(fen
) {
30 const fenParts
= fen
.split(" ");
32 ChessRules
.ParseFen(fen
),
33 { captured: fenParts
[5] }
38 return ([V
.MARSHALL
, V
.CARDINAL
].includes(b
[1]) ? "Grand/" : "") + b
;
42 return super.getFen() + " " + this.getCapturedFen();
46 return super.getFenForRepeat() + "_" + this.getCapturedFen();
50 let counts
= [...Array(14).fill(0)];
52 for (let j
= 0; j
< V
.PIECES
.length
; j
++) {
53 if (V
.PIECES
[j
] == V
.KING
)
56 counts
[i
] = this.captured
["w"][V
.PIECES
[i
]];
57 counts
[7 + i
] = this.captured
["b"][V
.PIECES
[i
]];
60 return counts
.join("");
63 setOtherVariables(fen
) {
64 super.setOtherVariables(fen
);
65 const fenParsed
= V
.ParseFen(fen
);
66 // Initialize captured pieces' counts from FEN
69 [V
.PAWN
]: parseInt(fenParsed
.captured
[0]),
70 [V
.ROOK
]: parseInt(fenParsed
.captured
[1]),
71 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[2]),
72 [V
.BISHOP
]: parseInt(fenParsed
.captured
[3]),
73 [V
.QUEEN
]: parseInt(fenParsed
.captured
[4]),
74 [V
.MARSHALL
]: parseInt(fenParsed
.captured
[5]),
75 [V
.CARDINAL
]: parseInt(fenParsed
.captured
[6])
78 [V
.PAWN
]: parseInt(fenParsed
.captured
[7]),
79 [V
.ROOK
]: parseInt(fenParsed
.captured
[8]),
80 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[9]),
81 [V
.BISHOP
]: parseInt(fenParsed
.captured
[10]),
82 [V
.QUEEN
]: parseInt(fenParsed
.captured
[11]),
83 [V
.MARSHALL
]: parseInt(fenParsed
.captured
[12]),
84 [V
.CARDINAL
]: parseInt(fenParsed
.captured
[13])
90 return { x: 10, y: 10 };
94 static get MARSHALL() {
99 static get CARDINAL() {
103 static get PIECES() {
104 return ChessRules
.PIECES
.concat([V
.MARSHALL
, V
.CARDINAL
]);
107 // There may be 2 enPassant squares (if pawn jump 3 squares)
109 const L
= this.epSquares
.length
;
110 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
112 this.epSquares
[L
- 1].forEach(sq
=> {
113 res
+= V
.CoordsToSquare(sq
) + ",";
115 return res
.slice(0, -1); //remove last comma
118 // En-passant after 2-sq or 3-sq jumps
119 getEpSquare(moveOrSquare
) {
120 if (!moveOrSquare
) return undefined;
121 if (typeof moveOrSquare
=== "string") {
122 const square
= moveOrSquare
;
123 if (square
== "-") return undefined;
125 square
.split(",").forEach(sq
=> {
126 res
.push(V
.SquareToCoords(sq
));
130 // Argument is a move:
131 const move = moveOrSquare
;
132 const [sx
, sy
, ex
] = [move.start
.x
, move.start
.y
, move.end
.x
];
133 if (this.getPiece(sx
, sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2) {
134 const step
= (ex
- sx
) / Math
.abs(ex
- sx
);
141 if (sx
+ 2 * step
!= ex
) {
150 return undefined; //default
153 getPotentialMovesFrom([x
, y
]) {
154 switch (this.getPiece(x
, y
)) {
156 return this.getPotentialMarshallMoves([x
, y
]);
158 return this.getPotentialCardinalMoves([x
, y
]);
160 return super.getPotentialMovesFrom([x
, y
]);
164 // Special pawn rules: promotions to captured friendly pieces,
165 // optional on ranks 8-9 and mandatory on rank 10.
166 getPotentialPawnMoves([x
, y
]) {
167 const color
= this.turn
;
169 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
170 const shiftX
= color
== "w" ? -1 : 1;
171 const startRanks
= color
== "w" ? [sizeX
- 2, sizeX
- 3] : [1, 2];
173 color
== "w" ? [0, 1, 2] : [sizeX
- 1, sizeX
- 2, sizeX
- 3];
174 const promotionPieces
= [
183 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
184 let finalPieces
= undefined;
185 if (lastRanks
.includes(x
+ shiftX
)) {
186 finalPieces
= promotionPieces
.filter(p
=> this.captured
[color
][p
] > 0);
187 if (x
+ shiftX
!= lastRanks
[0]) finalPieces
.push(V
.PAWN
);
188 } else finalPieces
= [V
.PAWN
];
189 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
190 // One square forward
191 for (let piece
of finalPieces
)
193 this.getBasicMove([x
, y
], [x
+ shiftX
, y
], { c: color
, p: piece
})
195 if (startRanks
.includes(x
)) {
196 if (this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
) {
198 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
199 if (x
== startRanks
[0] && this.board
[x
+ 3 * shiftX
][y
] == V
.EMPTY
) {
200 // Three squares jump
201 moves
.push(this.getBasicMove([x
, y
], [x
+ 3 * shiftX
, y
]));
207 for (let shiftY
of [-1, 1]) {
210 y
+ shiftY
< sizeY
&&
211 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
212 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
214 for (let piece
of finalPieces
) {
216 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
226 const Lep
= this.epSquares
.length
;
227 const epSquare
= this.epSquares
[Lep
- 1];
229 for (let epsq
of epSquare
) {
230 // TODO: some redundant checks
231 if (epsq
.x
== x
+ shiftX
&& Math
.abs(epsq
.y
- y
) == 1) {
232 let enpassantMove
= this.getBasicMove([x
, y
], [epsq
.x
, epsq
.y
]);
233 // WARNING: the captured pawn may be diagonally behind us,
234 // if it's a 3-squares jump and we take on 1st passing square
235 const px
= this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
;
236 enpassantMove
.vanish
.push({
240 c: this.getColor(px
, epsq
.y
)
242 moves
.push(enpassantMove
);
250 // TODO: different castle?
252 getPotentialMarshallMoves(sq
) {
253 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
254 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep")
258 getPotentialCardinalMoves(sq
) {
259 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
260 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep")
264 isAttacked(sq
, color
) {
266 super.isAttacked(sq
, color
) ||
267 this.isAttackedByMarshall(sq
, color
) ||
268 this.isAttackedByCardinal(sq
, color
)
272 isAttackedByMarshall(sq
, color
) {
274 this.isAttackedBySlideNJump(sq
, color
, V
.MARSHALL
, V
.steps
[V
.ROOK
]) ||
275 this.isAttackedBySlideNJump(
285 isAttackedByCardinal(sq
, color
) {
287 this.isAttackedBySlideNJump(sq
, color
, V
.CARDINAL
, V
.steps
[V
.BISHOP
]) ||
288 this.isAttackedBySlideNJump(
299 super.postPlay(move);
300 if (move.vanish
.length
== 2 && move.appear
.length
== 1) {
301 // Capture: update this.captured
302 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
304 if (move.vanish
[0].p
!= move.appear
[0].p
) {
305 // Promotion: update this.captured
306 this.captured
[move.vanish
[0].c
][move.appear
[0].p
]--;
311 super.postUndo(move);
312 if (move.vanish
.length
== 2 && move.appear
.length
== 1)
313 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
314 if (move.vanish
[0].p
!= move.appear
[0].p
)
315 this.captured
[move.vanish
[0].c
][move.appear
[0].p
]++;
318 static get VALUES() {
319 return Object
.assign(
320 { c: 5, m: 7 }, //experimental
325 static get SEARCH_DEPTH() {
329 static GenRandInitFen(randomness
) {
330 if (randomness
== 0) {
331 // No castling in the official initial setup
332 return "r8r/1nbqkmcbn1/pppppppppp/91/91/91/91/PPPPPPPPPP/1NBQKMCBN1/R8R " +
333 "w 0 zzzz - 00000000000000";
336 let pieces
= { w: new Array(10), b: new Array(10) };
338 // Shuffle pieces on first and last rank
339 for (let c
of ["w", "b"]) {
340 if (c
== 'b' && randomness
== 1) {
341 pieces
['b'] = pieces
['w'];
346 let positions
= ArrayFun
.range(10);
348 // Get random squares for bishops
349 let randIndex
= 2 * randInt(5);
350 let bishop1Pos
= positions
[randIndex
];
351 // The second bishop must be on a square of different color
352 let randIndex_tmp
= 2 * randInt(5) + 1;
353 let bishop2Pos
= positions
[randIndex_tmp
];
354 // Remove chosen squares
355 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
356 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
358 // Get random squares for knights
359 randIndex
= randInt(8);
360 let knight1Pos
= positions
[randIndex
];
361 positions
.splice(randIndex
, 1);
362 randIndex
= randInt(7);
363 let knight2Pos
= positions
[randIndex
];
364 positions
.splice(randIndex
, 1);
366 // Get random square for queen
367 randIndex
= randInt(6);
368 let queenPos
= positions
[randIndex
];
369 positions
.splice(randIndex
, 1);
371 // ...random square for marshall
372 randIndex
= randInt(5);
373 let marshallPos
= positions
[randIndex
];
374 positions
.splice(randIndex
, 1);
376 // ...random square for cardinal
377 randIndex
= randInt(4);
378 let cardinalPos
= positions
[randIndex
];
379 positions
.splice(randIndex
, 1);
381 // Rooks and king positions are now fixed, because of the ordering rook-king-rook
382 let rook1Pos
= positions
[0];
383 let kingPos
= positions
[1];
384 let rook2Pos
= positions
[2];
386 // Finally put the shuffled pieces in the board array
387 pieces
[c
][rook1Pos
] = "r";
388 pieces
[c
][knight1Pos
] = "n";
389 pieces
[c
][bishop1Pos
] = "b";
390 pieces
[c
][queenPos
] = "q";
391 pieces
[c
][marshallPos
] = "m";
392 pieces
[c
][cardinalPos
] = "c";
393 pieces
[c
][kingPos
] = "k";
394 pieces
[c
][bishop2Pos
] = "b";
395 pieces
[c
][knight2Pos
] = "n";
396 pieces
[c
][rook2Pos
] = "r";
397 flags
+= V
.CoordToColumn(rook1Pos
) + V
.CoordToColumn(rook2Pos
);
400 pieces
["b"].join("") +
401 "/pppppppppp/91/91/91/91/91/91/PPPPPPPPPP/" +
402 pieces
["w"].join("").toUpperCase() +
403 " w 0 " + flags
+ " - 00000000000000"