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 const VariantRules
= class GrandRules
extends ChessRules
11 return ([V
.MARSHALL
,V
.CARDINAL
].includes(b
[1]) ? "Grand/" : "") + b
;
16 if (!ChessRules
.IsGoodFen(fen
))
18 const fenParsed
= V
.ParseFen(fen
);
20 if (!fenParsed
.captured
|| !fenParsed
.captured
.match(/^[0-9]{14,14}$/))
25 static IsGoodEnpassant(enpassant
)
29 const squares
= enpassant
.split(",");
30 if (squares
.length
> 2)
32 for (let sq
of squares
)
34 const ep
= V
.SquareToCoords(sq
);
35 if (isNaN(ep
.x
) || !V
.OnBoard(ep
))
44 const fenParts
= fen
.split(" ");
46 ChessRules
.ParseFen(fen
),
47 { captured: fenParts
[5] }
53 return super.getFen() + " " + this.getCapturedFen();
58 let counts
= [...Array(14).fill(0)];
60 for (let j
=0; j
<V
.PIECES
.length
; j
++)
62 if (V
.PIECES
[j
] == V
.KING
) //no king captured
64 counts
[i
] = this.captured
["w"][V
.PIECES
[i
]];
65 counts
[7+i
] = this.captured
["b"][V
.PIECES
[i
]];
68 return counts
.join("");
71 setOtherVariables(fen
)
73 super.setOtherVariables(fen
);
74 const fenParsed
= V
.ParseFen(fen
);
75 // Initialize captured pieces' counts from FEN
80 [V
.PAWN
]: parseInt(fenParsed
.captured
[0]),
81 [V
.ROOK
]: parseInt(fenParsed
.captured
[1]),
82 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[2]),
83 [V
.BISHOP
]: parseInt(fenParsed
.captured
[3]),
84 [V
.QUEEN
]: parseInt(fenParsed
.captured
[4]),
85 [V
.MARSHALL
]: parseInt(fenParsed
.captured
[5]),
86 [V
.CARDINAL
]: parseInt(fenParsed
.captured
[6]),
90 [V
.PAWN
]: parseInt(fenParsed
.captured
[7]),
91 [V
.ROOK
]: parseInt(fenParsed
.captured
[8]),
92 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[9]),
93 [V
.BISHOP
]: parseInt(fenParsed
.captured
[10]),
94 [V
.QUEEN
]: parseInt(fenParsed
.captured
[11]),
95 [V
.MARSHALL
]: parseInt(fenParsed
.captured
[12]),
96 [V
.CARDINAL
]: parseInt(fenParsed
.captured
[13]),
101 static get size() { return {x:10,y:10}; }
103 static get MARSHALL() { return 'm'; } //rook+knight
104 static get CARDINAL() { return 'c'; } //bishop+knight
108 return ChessRules
.PIECES
.concat([V
.MARSHALL
,V
.CARDINAL
]);
111 // There may be 2 enPassant squares (if pawn jump 3 squares)
114 const L
= this.epSquares
.length
;
115 if (!this.epSquares
[L
-1])
116 return "-"; //no en-passant
118 this.epSquares
[L
-1].forEach(sq
=> {
119 res
+= V
.CoordsToSquare(sq
) + ",";
121 return res
.slice(0,-1); //remove last comma
124 // En-passant after 2-sq or 3-sq jumps
125 getEpSquare(moveOrSquare
)
129 if (typeof moveOrSquare
=== "string")
131 const square
= moveOrSquare
;
135 square
.split(",").forEach(sq
=> {
136 res
.push(V
.SquareToCoords(sq
));
140 // Argument is a move:
141 const move = moveOrSquare
;
142 const [sx
,sy
,ex
] = [move.start
.x
,move.start
.y
,move.end
.x
];
143 if (this.getPiece(sx
,sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2)
145 const step
= (ex
-sx
) / Math
.abs(ex
-sx
);
150 if (sx
+ 2*step
!= ex
) //3-squares move
159 return undefined; //default
162 getPotentialMovesFrom([x
,y
])
164 switch (this.getPiece(x
,y
))
167 return this.getPotentialMarshallMoves([x
,y
]);
169 return this.getPotentialCardinalMoves([x
,y
]);
171 return super.getPotentialMovesFrom([x
,y
])
175 // Special pawn rules: promotions to captured friendly pieces,
176 // optional on ranks 8-9 and mandatory on rank 10.
177 getPotentialPawnMoves([x
,y
])
179 const color
= this.turn
;
181 const [sizeX
,sizeY
] = [V
.size
.x
,V
.size
.y
];
182 const shiftX
= (color
== "w" ? -1 : 1);
183 const startRanks
= (color
== "w" ? [sizeX
-2,sizeX
-3] : [1,2]);
184 const lastRanks
= (color
== "w" ? [0,1,2] : [sizeX
-1,sizeX
-2,sizeX
-3]);
185 const promotionPieces
=
186 [V
.ROOK
,V
.KNIGHT
,V
.BISHOP
,V
.QUEEN
,V
.MARSHALL
,V
.CARDINAL
];
188 // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
189 let finalPieces
= undefined;
190 if (lastRanks
.includes(x
+ shiftX
))
192 finalPieces
= promotionPieces
.filter(p
=> this.captured
[color
][p
] > 0);
193 if (x
+ shiftX
!= lastRanks
[0])
194 finalPieces
.push(V
.PAWN
);
197 finalPieces
= [V
.PAWN
];
198 if (this.board
[x
+shiftX
][y
] == V
.EMPTY
)
200 // One square forward
201 for (let piece
of finalPieces
)
202 moves
.push(this.getBasicMove([x
,y
], [x
+shiftX
,y
], {c:color
,p:piece
}));
203 if (startRanks
.includes(x
))
205 if (this.board
[x
+2*shiftX
][y
] == V
.EMPTY
)
208 moves
.push(this.getBasicMove([x
,y
], [x
+2*shiftX
,y
]));
209 if (x
==startRanks
[0] && this.board
[x
+3*shiftX
][y
] == V
.EMPTY
)
211 // Three squares jump
212 moves
.push(this.getBasicMove([x
,y
], [x
+3*shiftX
,y
]));
218 for (let shiftY
of [-1,1])
220 if (y
+ shiftY
>= 0 && y
+ shiftY
< sizeY
221 && this.board
[x
+shiftX
][y
+shiftY
] != V
.EMPTY
222 && this.canTake([x
,y
], [x
+shiftX
,y
+shiftY
]))
224 for (let piece
of finalPieces
)
226 moves
.push(this.getBasicMove([x
,y
], [x
+shiftX
,y
+shiftY
],
233 const Lep
= this.epSquares
.length
;
234 const epSquare
= this.epSquares
[Lep
-1];
237 for (let epsq
of epSquare
)
239 // TODO: some redundant checks
240 if (epsq
.x
== x
+shiftX
&& Math
.abs(epsq
.y
- y
) == 1)
242 var enpassantMove
= this.getBasicMove([x
,y
], [epsq
.x
,epsq
.y
]);
243 // WARNING: the captured pawn may be diagonally behind us,
244 // if it's a 3-squares jump and we take on 1st passing square
245 const px
= (this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
);
246 enpassantMove
.vanish
.push({
250 c: this.getColor(px
,epsq
.y
)
252 moves
.push(enpassantMove
);
260 // TODO: different castle?
262 getPotentialMarshallMoves(sq
)
264 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.ROOK
]).concat(
265 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
268 getPotentialCardinalMoves(sq
)
270 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.BISHOP
]).concat(
271 this.getSlideNJumpMoves(sq
, V
.steps
[V
.KNIGHT
], "oneStep"));
274 isAttacked(sq
, colors
)
276 return super.isAttacked(sq
, colors
)
277 || this.isAttackedByMarshall(sq
, colors
)
278 || this.isAttackedByCardinal(sq
, colors
);
281 isAttackedByMarshall(sq
, colors
)
283 return this.isAttackedBySlideNJump(sq
, colors
, V
.MARSHALL
, V
.steps
[V
.ROOK
])
284 || this.isAttackedBySlideNJump(
285 sq
, colors
, V
.MARSHALL
, V
.steps
[V
.KNIGHT
], "oneStep");
288 isAttackedByCardinal(sq
, colors
)
290 return this.isAttackedBySlideNJump(sq
, colors
, V
.CARDINAL
, V
.steps
[V
.BISHOP
])
291 || this.isAttackedBySlideNJump(
292 sq
, colors
, V
.CARDINAL
, V
.steps
[V
.KNIGHT
], "oneStep");
295 updateVariables(move)
297 super.updateVariables(move);
298 if (move.vanish
.length
== 2 && move.appear
.length
== 1)
300 // Capture: update this.captured
301 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
303 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
]--;
310 unupdateVariables(move)
312 super.unupdateVariables(move);
313 if (move.vanish
.length
== 2 && move.appear
.length
== 1)
314 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
315 if (move.vanish
[0].p
!= move.appear
[0].p
)
316 this.captured
[move.vanish
[0].c
][move.appear
[0].p
]++;
321 return Object
.assign(
323 {'c': 5, 'm': 7} //experimental
327 static get SEARCH_DEPTH() { return 2; }
329 // TODO: this function could be generalized and shared better (how ?!...)
330 static GenRandInitFen()
332 let pieces
= { "w": new Array(10), "b": new Array(10) };
333 // Shuffle pieces on first and last rank
334 for (let c
of ["w","b"])
336 let positions
= ArrayFun
.range(10);
338 // Get random squares for bishops
339 let randIndex
= 2 * randInt(5);
340 let bishop1Pos
= positions
[randIndex
];
341 // The second bishop must be on a square of different color
342 let randIndex_tmp
= 2 * randInt(5) + 1;
343 let bishop2Pos
= positions
[randIndex_tmp
];
344 // Remove chosen squares
345 positions
.splice(Math
.max(randIndex
,randIndex_tmp
), 1);
346 positions
.splice(Math
.min(randIndex
,randIndex_tmp
), 1);
348 // Get random squares for knights
349 randIndex
= randInt(8);
350 let knight1Pos
= positions
[randIndex
];
351 positions
.splice(randIndex
, 1);
352 randIndex
= randInt(7);
353 let knight2Pos
= positions
[randIndex
];
354 positions
.splice(randIndex
, 1);
356 // Get random square for queen
357 randIndex
= randInt(6);
358 let queenPos
= positions
[randIndex
];
359 positions
.splice(randIndex
, 1);
361 // ...random square for marshall
362 randIndex
= randInt(5);
363 let marshallPos
= positions
[randIndex
];
364 positions
.splice(randIndex
, 1);
366 // ...random square for cardinal
367 randIndex
= randInt(4);
368 let cardinalPos
= positions
[randIndex
];
369 positions
.splice(randIndex
, 1);
371 // Rooks and king positions are now fixed, because of the ordering rook-king-rook
372 let rook1Pos
= positions
[0];
373 let kingPos
= positions
[1];
374 let rook2Pos
= positions
[2];
376 // Finally put the shuffled pieces in the board array
377 pieces
[c
][rook1Pos
] = 'r';
378 pieces
[c
][knight1Pos
] = 'n';
379 pieces
[c
][bishop1Pos
] = 'b';
380 pieces
[c
][queenPos
] = 'q';
381 pieces
[c
][marshallPos
] = 'm';
382 pieces
[c
][cardinalPos
] = 'c';
383 pieces
[c
][kingPos
] = 'k';
384 pieces
[c
][bishop2Pos
] = 'b';
385 pieces
[c
][knight2Pos
] = 'n';
386 pieces
[c
][rook2Pos
] = 'r';
388 return pieces
["b"].join("") +
389 "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
390 pieces
["w"].join("").toUpperCase() +
391 " w 0 1111 - 00000000000000";