Filter out Eightpieces from variants list for now
[vchess.git] / client / src / variants / Eightpieces.js
CommitLineData
2a8a94c9
BA
1import { ArrayFun } from "@/utils/array";
2import { randInt, shuffle } from "@/utils/alea";
3import { ChessRules, PiPo, Move } from "@/base_rules";
4
5export const VariantRules = class EightpiecesRules extends ChessRules {
6 static get JAILER() {
7 return "j";
8 }
9 static get SENTRY() {
10 return "s";
11 }
12 static get LANCER() {
13 return "l";
14 }
15
16 static get PIECES() {
17 return ChessRules.PIECES.concat([V.JAILER, V.SENTRY, V.LANCER]);
18 }
19
28b32b4f
BA
20 static get LANCER_DIRS() {
21 return {
22 'c': [-1, 0], //north
23 'd': [-1, 1], //N-E
24 'e': [0, 1], //east
25 'f': [1, 1], //S-E
26 'g': [1, 0], //south
27 'h': [1, -1], //S-W
28 'm': [0, -1], //west
29 'o': [-1, -1] //N-W
30 };
31 }
32
33 getPiece(i, j) {
34 const piece = this.board[i][j].charAt(1);
35 // Special lancer case: 8 possible orientations
36 if (Object.keys(V.LANCER_DIRS).includes(piece)) return V.LANCER;
37 return piece;
38 }
39
2a8a94c9 40 getPpath(b) {
90e814b6
BA
41 if ([V.JAILER, V.SENTRY].concat(Object.keys(V.LANCER_DIRS)).includes(b[1]))
42 return "Eightpieces/" + b;
43 return b;
44 }
45
46 static ParseFen(fen) {
47 const fenParts = fen.split(" ");
48 return Object.assign(ChessRules.ParseFen(fen), {
6b7b2cf7 49 sentrypush: fenParts[5]
90e814b6
BA
50 });
51 }
52
53 getFen() {
6b7b2cf7 54 return super.getFen() + " " + this.getSentrypushFen();
90e814b6
BA
55 }
56
57 getFenForRepeat() {
6b7b2cf7 58 return super.getFenForRepeat() + "_" + this.getSentrypushFen();
90e814b6
BA
59 }
60
6b7b2cf7
BA
61 getSentrypushFen() {
62 const L = this.sentryPush.length;
63 if (!this.sentryPush[L-1]) return "-";
90e814b6 64 let res = "";
6b7b2cf7 65 this.sentryPush[L-1].forEach(coords =>
90e814b6
BA
66 res += V.CoordsToSquare(coords) + ",");
67 return res.slice(0, -1);
2a8a94c9
BA
68 }
69
28b32b4f
BA
70 setOtherVariables(fen) {
71 super.setOtherVariables(fen);
72 // subTurn == 2 only when a sentry moved, and is about to push something
73 this.subTurn = 1;
74 // Stack pieces' forbidden squares after a sentry move at each turn
90e814b6 75 const parsedFen = V.ParseFen(fen);
6b7b2cf7 76 if (parsedFen.sentrypush == "-") this.sentryPush = [null];
90e814b6 77 else {
6b7b2cf7
BA
78 this.sentryPush = [
79 parsedFen.sentrypush.split(",").map(sq => {
90e814b6
BA
80 return V.SquareToCoords(sq);
81 })
82 ];
83 }
2a8a94c9
BA
84 }
85
28b32b4f
BA
86 canTake([x1,y1], [x2, y2]) {
87 if (this.subTurn == 2)
88 // Sentry push: pieces can capture own color (only)
89 return this.getColor(x1, y1) == this.getColor(x2, y2);
90 return super.canTake([x1,y1], [x2, y2]);
2a8a94c9
BA
91 }
92
93 static GenRandInitFen(randomness) {
6b7b2cf7 94 // TODO: special conditions for 960
90e814b6 95 return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -";
2a8a94c9
BA
96 }
97
90e814b6 98 // Scan kings, rooks and jailers
28b32b4f 99 scanKingsRooks(fen) {
90e814b6
BA
100 this.INIT_COL_KING = { w: -1, b: -1 };
101 this.INIT_COL_ROOK = { w: -1, b: -1 };
102 this.INIT_COL_JAILER = { w: -1, b: -1 };
28b32b4f
BA
103 this.kingPos = { w: [-1, -1], b: [-1, -1] };
104 const fenRows = V.ParseFen(fen).position.split("/");
90e814b6 105 const startRow = { 'w': V.size.x - 1, 'b': 0 };
28b32b4f 106 for (let i = 0; i < fenRows.length; i++) {
90e814b6 107 let k = 0;
28b32b4f
BA
108 for (let j = 0; j < fenRows[i].length; j++) {
109 switch (fenRows[i].charAt(j)) {
110 case "k":
28b32b4f 111 this.kingPos["b"] = [i, k];
90e814b6 112 this.INIT_COL_KING["b"] = k;
28b32b4f
BA
113 break;
114 case "K":
28b32b4f 115 this.kingPos["w"] = [i, k];
90e814b6
BA
116 this.INIT_COL_KING["w"] = k;
117 break;
118 case "r":
119 if (i == startRow['b'] && this.INIT_COL_ROOK["b"] < 0)
120 this.INIT_COL_ROOK["b"] = k;
121 break;
122 case "R":
123 if (i == startRow['w'] && this.INIT_COL_ROOK["w"] < 0)
124 this.INIT_COL_ROOK["w"] = k;
125 break;
126 case "j":
127 if (i == startRow['b'] && this.INIT_COL_JAILER["b"] < 0)
128 this.INIT_COL_JAILER["b"] = k;
129 break;
130 case "J":
131 if (i == startRow['w'] && this.INIT_COL_JAILER["w"] < 0)
132 this.INIT_COL_JAILER["w"] = k;
28b32b4f
BA
133 break;
134 default: {
135 const num = parseInt(fenRows[i].charAt(j));
136 if (!isNaN(num)) k += num - 1;
137 }
138 }
139 k++;
140 }
141 }
2a8a94c9
BA
142 }
143
6b7b2cf7
BA
144 // Is piece on square (x,y) immobilized?
145 isImmobilized([x, y]) {
146 const color = this.getColor(x, y);
147 const oppCol = V.GetOppCol(color);
148 for (let step of V.steps[V.ROOK]) {
149 const [i, j] = [x + step[0], y + step[1]];
150 if (
151 V.OnBoard(i, j) &&
152 this.board[i][j] != V.EMPTY &&
153 this.getColor(i, j) == oppCol
154 ) {
155 const oppPiece = this.getPiece(i, j);
156 if (oppPiece == V.JAILER) return [i, j];
157 }
158 }
159 return null;
160 }
161
162 getPotentialMovesFrom_aux([x, y]) {
163 switch (this.getPiece(x, y)) {
164 case V.JAILER:
165 return this.getPotentialJailerMoves([x, y]);
166 case V.SENTRY:
167 return this.getPotentialSentryMoves([x, y]);
168 case V.LANCER
169 return this.getPotentialLancerMoves([x, y]);
170 default:
171 return super.getPotentialMovesFrom([x, y]);
172 }
173 }
174
28b32b4f 175 getPotentialMovesFrom([x,y]) {
6b7b2cf7
BA
176 if (this.subTurn == 1) {
177 if (!!this.isImmobilized([x, y])) return [];
178 return this.getPotentialMovesFrom_aux([x, y]);
179 }
180 // subTurn == 2: only the piece pushed by the sentry is allowed to move,
181 // as if the sentry didn't exist
182 if (x != this.sentryPos.x && y != this.sentryPos.y) return [];
183 return this.getPotentialMovesFrom_aux([x,y]);
184 }
185
186 getAllValidMoves() {
187 let moves = super.getAllValidMoves().filter(m =>
188 // Remove jailer captures
189 m.vanish[0].p != V.JAILER || m.vanish.length == 1;
190 );
191 const L = this.sentryPush.length;
192 if (!!this.sentryPush[L-1] && this.subTurn == 1) {
193 // Delete moves walking back on sentry push path
194 moves = moves.filter(m => {
195 if (
196 m.vanish[0].p != V.PAWN &&
197 this.sentryPush[L-1].some(sq => sq.x == m.end.x && sq.y == m.end.y)
198 ) {
199 return false;
200 }
201 return true;
202 });
203 }
204 return moves;
205 }
206
207 filterValid(moves) {
208 // Disable check tests when subTurn == 2, because the move isn't finished
209 if (this.subTurn == 2) return moves;
210 return super.filterValid(moves);
211 }
212
213 getPotentialLancerMoves([x, y]) {
214 // TODO: add all lancer possible orientations same as pawn promotions,
215 // except if just after a push: allow all movements from init square then
216 return [];
217 }
218
219 getPotentialSentryMoves([x, y]) {
220 // The sentry moves a priori like a bishop:
221 let moves = super.getPotentialBishopMoves([x, y]);
222 // ...but captures are replaced by special move
223 // "appear = [], vanish = init square" to let the pushed piece move then.
224 // TODO
225 }
226
227 getPotentialJailerMoves([x, y]) {
228 // Captures are removed afterward:
229 return super.getPotentialRookMoves([x, y]);
230 }
231
232 getPotentialKingMoves([x, y]) {
233 let moves = super.getPotentialKingMoves([x, y]);
234 // Augment with pass move is the king is immobilized:
235 const jsq = this.isImmobilized([x, y]);
236 if (!!jsq) {
237 moves.push(new Move({
238 appear: [],
239 vanish: [],
240 start: { x: x, y: y },
241 end: { x: jsq[0], y: jsq[1] }
242 });
243 }
244 return moves;
2a8a94c9
BA
245 }
246
90e814b6
BA
247 // Adapted: castle with jailer possible
248 getCastleMoves([x, y]) {
249 const c = this.getColor(x, y);
250 const firstRank = (c == "w" ? V.size.x - 1 : 0);
251 if (x != firstRank || y != this.INIT_COL_KING[c])
252 return [];
253
254 const oppCol = V.GetOppCol(c);
255 let moves = [];
256 let i = 0;
257 // King, then rook or jailer:
258 const finalSquares = [
259 [2, 3],
260 [V.size.y - 2, V.size.y - 3]
261 ];
262 castlingCheck: for (
263 let castleSide = 0;
264 castleSide < 2;
265 castleSide++
266 ) {
267 if (!this.castleFlags[c][castleSide]) continue;
268 // Rook (or jailer) and king are on initial position
269
270 const finDist = finalSquares[castleSide][0] - y;
271 let step = finDist / Math.max(1, Math.abs(finDist));
272 i = y;
273 do {
274 if (
275 this.isAttacked([x, i], [oppCol]) ||
276 (this.board[x][i] != V.EMPTY &&
277 (this.getColor(x, i) != c ||
278 ![V.KING, V.ROOK].includes(this.getPiece(x, i))))
279 ) {
280 continue castlingCheck;
281 }
282 i += step;
283 } while (i != finalSquares[castleSide][0]);
284
285 step = castleSide == 0 ? -1 : 1;
286 const rookOrJailerPos =
287 castleSide == 0
288 ? Math.min(this.INIT_COL_ROOK[c], this.INIT_COL_JAILER[c])
289 : Math.max(this.INIT_COL_ROOK[c], this.INIT_COL_JAILER[c]);
290 for (i = y + step; i != rookOrJailerPos; i += step)
291 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
292
293 // Nothing on final squares, except maybe king and castling rook or jailer?
294 for (i = 0; i < 2; i++) {
295 if (
296 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
297 this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
298 finalSquares[castleSide][i] != rookOrJailerPos
299 ) {
300 continue castlingCheck;
301 }
302 }
303
304 // If this code is reached, castle is valid
305 const castlingPiece = this.getPiece(firstRank, rookOrJailerPos);
306 moves.push(
307 new Move({
308 appear: [
309 new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
310 new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c })
311 ],
312 vanish: [
313 new PiPo({ x: x, y: y, p: V.KING, c: c }),
314 new PiPo({ x: x, y: rookOrJailerPos, p: castlingPiece, c: c })
315 ],
316 end:
317 Math.abs(y - rookOrJailerPos) <= 2
318 ? { x: x, y: rookOrJailerPos }
319 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
320 })
321 );
322 }
323
324 return moves;
2a8a94c9
BA
325 }
326
28b32b4f 327 updateVariables(move) {
90e814b6
BA
328 super.updateVariables(move);
329 if (this.subTurn == 2) {
330 // A piece is pushed:
331 // TODO: push array of squares between start and end of move, included
332 // (except if it's a pawn)
6b7b2cf7 333 this.sentryPush.push([]); //TODO
90e814b6
BA
334 this.subTurn = 1;
335 } else {
6b7b2cf7 336 if (move.appear.length == 0 && move.vanish.length == 1) {
90e814b6
BA
337 // Special sentry move: subTurn <- 2, and then move pushed piece
338 this.subTurn = 2;
339 }
340 // Else: normal move.
341 }
2a8a94c9
BA
342 }
343
90e814b6
BA
344 play(move) {
345 move.flags = JSON.stringify(this.aggregateFlags());
346 this.epSquares.push(this.getEpSquare(move));
347 V.PlayOnBoard(this.board, move);
348 // TODO: turn changes only if not a sentry push or subTurn == 2
349 //this.turn = V.GetOppCol(this.turn);
350 this.movesCount++;
351 this.updateVariables(move);
352 }
2a8a94c9 353
90e814b6
BA
354 undo(move) {
355 this.epSquares.pop();
356 this.disaggregateFlags(JSON.parse(move.flags));
357 V.UndoOnBoard(this.board, move);
358 // TODO: here too, take care of turn. If undoing when subTurn == 2,
359 // do not change turn (this shouldn't happen anyway).
360 // ==> normal undo() should be ok.
361 //this.turn = V.GetOppCol(this.turn);
362 this.movesCount--;
363 this.unupdateVariables(move);
364 }
2a8a94c9
BA
365
366 static get VALUES() {
367 return Object.assign(
28b32b4f 368 { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations
2a8a94c9
BA
369 ChessRules.VALUES
370 );
371 }
6b7b2cf7
BA
372
373 getNotation(move) {
374 // Special case "king takes jailer" is a pass move
375 if (move.appear.length == 0 && move.vanish.length == 0) return "pass";
376 return super.getNotation(move);
377 }
2a8a94c9 378};