Some more debug on Checkered (still not ready: turn/color issues..)
[xogo.git] / variants / Checkered / class.js
CommitLineData
55d3b31f
BA
1import ChessRules from "/base_rules.js";
2import PiPo from "/utils/PiPo.js";
3import Move from "/utils/Move.js";
4
5export default class CheckeredRules extends ChessRules {
6
7 static get Options() {
8 return {
9 select: C.Options.select,
10 input: [
11 {
12 label: "Allow switching",
13 variable: "withswitch",
14 type: "checkbox",
15 defaut: false
16 }
17 ],
55d3b31f
BA
18 styles: [
19 "balance",
20 "capture",
21 "cylinder",
22 "doublemove",
23 "madrasi",
24 "progressive",
25 "recycle",
26 "teleport"
27 ]
28 };
29 }
30
616a8d7a
BA
31 static GetColorClass(c) {
32 if (c == 'c')
33 return "checkered";
34 return C.GetColorClass(c);
35 }
36
b57b726c 37 board2fen(b) {
55d3b31f
BA
38 const checkered_codes = {
39 p: "s",
40 q: "t",
41 r: "u",
42 b: "c",
43 n: "o"
44 };
8d19ea48
BA
45 if (b[0] == "c")
46 return checkered_codes[b[1]];
55d3b31f
BA
47 return super.board2fen(b);
48 }
49
b57b726c 50 fen2board(f) {
55d3b31f
BA
51 // Tolerate upper-case versions of checkered pieces (why not?)
52 const checkered_pieces = {
53 s: "p",
54 S: "p",
55 t: "q",
56 T: "q",
57 u: "r",
58 U: "r",
59 c: "b",
60 C: "b",
61 o: "n",
62 O: "n"
63 };
64 if (Object.keys(checkered_pieces).includes(f))
65 return "c" + checkered_pieces[f];
66 return super.fen2board(f);
67 }
68
616a8d7a
BA
69 genRandInitBaseFen() {
70 let res = super.genRandInitBaseFen();
71 res.o.flags += "1".repeat(16); //pawns flags
72 return res;
73 }
74
5006aaca 75 getPartFen(o) {
ceeac4e8
BA
76 return Object.assign(
77 {
78 "cmove": o.init ? "-" : this.getCmoveFen(),
79 "stage": o.init ? "1" : this.getStageFen()
80 },
81 super.getPartFen(o)
82 );
5006aaca
BA
83 }
84
85 getCmoveFen() {
86 if (!this.cmove)
87 return "-";
88 return (
89 C.CoordsToSquare(this.cmove.start) + C.CoordsToSquare(this.cmove.end)
90 );
91 }
92
93 getStageFen() {
94 return (this.stage + this.sideCheckered);
95 }
96
97 getFlagsFen() {
98 let fen = super.getFlagsFen();
99 // Add pawns flags
ceeac4e8 100 for (let c of ["w", "b"]) {
5006aaca
BA
101 for (let i = 0; i < 8; i++)
102 fen += (this.pawnFlags[c][i] ? "1" : "0");
ceeac4e8 103 }
5006aaca
BA
104 return fen;
105 }
106
107 getPawnShift(color) {
108 return super.getPawnShift(color == 'c' ? this.turn : color);
109 }
110
ceeac4e8
BA
111 getOppCols(color) {
112 if (this.stage == 1)
113 return super.getOppCols(color).concat(['c']);
114 // Stage 2: depends if color is w+b or checkered
115 if (color == this.sideCheckered)
116 return ['w', 'b'];
117 return ['c'];
118 }
119
8d19ea48
BA
120 pieces(color, x, y) {
121 let baseRes = super.pieces(color, x, y);
b57b726c
BA
122 if (this.getPiece(x, y) == 'p' && color == 'c') {
123 const pawnShift = this.getPawnShift(this.turn); //cannot trust color
124 const initRank = (
125 (this.stage == 2 && [1, 6].includes(x)) ||
126 (this.stage == 1 &&
127 ((x == 1 && this.turn == 'b') || (x == 6 && this.turn == 'w'))
128 )
129 );
8d19ea48 130 // Checkered pawns on stage 2 are bidirectional
b57b726c
BA
131 let moveSteps = [[pawnShift, 0]],
132 attackSteps = [[pawnShift, 1], [pawnShift, -1]];
133 if (this.stage == 2) {
134 moveSteps.push([-pawnShift, 0]);
135 Array.prototype.push.apply(attackSteps,
136 [[-pawnShift, 1], [-pawnShift, -1]]);
137 }
8d19ea48
BA
138 baseRes['p'] = {
139 "class": "pawn",
140 moves: [
141 {
b57b726c 142 steps: moveSteps,
8d19ea48
BA
143 range: (initRank ? 2 : 1)
144 }
145 ],
146 attack: [
147 {
b57b726c 148 steps: attackSteps,
8d19ea48
BA
149 range: 1
150 }
151 ]
152 };
153 }
154 const checkered = {
155 's': {"class": "checkered-pawn", moveas: 'p'},
156 'u': {"class": "checkered-rook", moveas: 'r'},
157 'o': {"class": "checkered-knight", moveas: 'n'},
158 'c': {"class": "checkered-bishop", moveas: 'b'},
159 't': {"class": "checkered-queen", moveas: 'q'}
160 };
161 return Object.assign(baseRes, checkered);
162 }
55d3b31f
BA
163
164 setOtherVariables(fenParsed) {
165 super.setOtherVariables(fenParsed);
166 // Non-capturing last checkered move (if any)
167 const cmove = fenParsed.cmove;
168 if (cmove == "-") this.cmove = null;
169 else {
170 this.cmove = {
171 start: C.SquareToCoords(cmove.substr(0, 2)),
172 end: C.SquareToCoords(cmove.substr(2))
173 };
174 }
175 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
176 const stageInfo = fenParsed.stage;
177 this.stage = parseInt(stageInfo[0], 10);
5006aaca 178 this.sideCheckered = (this.stage == 2 ? stageInfo[1] : "");
55d3b31f
BA
179 }
180
181 setFlags(fenflags) {
182 super.setFlags(fenflags); //castleFlags
183 this.pawnFlags = {
184 w: [...Array(8)], //pawns can move 2 squares?
185 b: [...Array(8)]
186 };
187 const flags = fenflags.substr(4); //skip first 4 letters, for castle
188 for (let c of ["w", "b"]) {
189 for (let i = 0; i < 8; i++)
190 this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1";
191 }
192 }
193
55d3b31f
BA
194 getEpSquare(moveOrSquare) {
195 // At stage 2, all pawns can be captured en-passant
196 if (
197 this.stage == 2 ||
198 typeof moveOrSquare !== "object" ||
199 (moveOrSquare.appear.length > 0 && moveOrSquare.appear[0].c != 'c')
200 )
201 return super.getEpSquare(moveOrSquare);
202 // Checkered or switch move: no en-passant
203 return undefined;
204 }
205
206 getCmove(move) {
207 // No checkered move to undo at stage 2:
208 if (this.stage == 1 && move.vanish.length == 1 && move.appear[0].c == "c")
5006aaca 209 return {start: move.start, end: move.end};
55d3b31f
BA
210 return null;
211 }
212
213 canTake([x1, y1], [x2, y2]) {
214 const color1 = this.getColor(x1, y1);
215 const color2 = this.getColor(x2, y2);
216 if (this.stage == 2) {
217 // Black & White <-- takes --> Checkered
218 const color1 = this.getColor(x1, y1);
219 const color2 = this.getColor(x2, y2);
220 return color1 != color2 && [color1, color2].includes('c');
221 }
55d3b31f
BA
222 return (
223 color1 != color2 &&
8d19ea48 224 color2 != "c" && //checkered aren't captured
55d3b31f
BA
225 (color1 != "c" || color2 != this.turn)
226 );
227 }
228
8d19ea48
BA
229 postProcessPotentialMoves(moves) {
230 if (this.stage == 2 || moves.length == 0)
231 return moves;
232 const color = this.turn;
233 // Apply "checkerization" of standard moves
234 const lastRank = (color == "w" ? 0 : 7);
235 const [x, y] = [moves[0].start.x, moves[0].start.y];
8d19ea48
BA
236 const piece = this.getPiece(x, y);
237 // King is treated differently: it never turn checkered
ceeac4e8 238 if (piece == 'k' && this.stage == 1) {
8d19ea48
BA
239 // If at least one checkered piece, allow switching:
240 if (
5006aaca 241 this.options["withswitch"] &&
8d19ea48
BA
242 this.board.some(b => b.some(cell => cell[0] == 'c'))
243 ) {
ceeac4e8 244 const oppKingPos = this.searchKingPos(C.GetOppTurn(this.turn))[0];
8d19ea48
BA
245 moves.push(
246 new Move({
247 start: { x: x, y: y },
ceeac4e8 248 end: {x: oppKingPos[0], y: oppKingPos[1]},
8d19ea48
BA
249 appear: [],
250 vanish: []
251 })
252 );
55d3b31f 253 }
8d19ea48
BA
254 return moves;
255 }
256 if (piece == 'p') {
257 // Filter out forbidden pawn moves
258 moves = moves.filter(m => {
ceeac4e8 259 if (m.vanish.length > 0 && m.vanish[0].p == 'p') {
55d3b31f
BA
260 if (
261 Math.abs(m.end.x - m.start.x) == 2 &&
262 !this.pawnFlags[this.turn][m.start.y]
263 ) {
8d19ea48 264 return false; //forbidden 2-squares jumps
55d3b31f
BA
265 }
266 if (
616a8d7a 267 this.board[m.end.x][m.end.y] == "" &&
55d3b31f
BA
268 m.vanish.length == 2 &&
269 this.getColor(m.start.x, m.start.y) == "c"
270 ) {
8d19ea48 271 return false; //checkered pawns cannot take en-passant
55d3b31f
BA
272 }
273 }
8d19ea48 274 return true;
55d3b31f 275 });
55d3b31f 276 }
8d19ea48
BA
277 let extraMoves = [];
278 moves.forEach(m => {
ceeac4e8 279 if (m.vanish.length == 2 && m.appear.length == 1) {
8d19ea48
BA
280 // A capture occured
281 m.appear[0].c = "c";
282 if (
283 m.appear[0].p != m.vanish[1].p &&
284 // No choice if promotion:
285 (m.vanish[0].p != 'p' || m.end.x != lastRank)
286 ) {
287 // Add transformation into captured piece
288 let m2 = JSON.parse(JSON.stringify(m));
289 m2.appear[0].p = m.vanish[1].p;
290 extraMoves.push(m2);
291 }
55d3b31f 292 }
8d19ea48
BA
293 });
294 return moves.concat(extraMoves);
55d3b31f
BA
295 }
296
ceeac4e8 297 canIplay(x, y) {
55d3b31f
BA
298 if (this.stage == 2) {
299 const color = this.getColor(x, y);
300 return (
301 this.turn == this.sideCheckered
302 ? color == 'c'
303 : ['w', 'b'].includes(color)
304 );
305 }
ceeac4e8
BA
306 return (
307 this.playerColor == this.turn &&
308 [this.turn, "c"].includes(this.getColor(x, y))
309 );
55d3b31f
BA
310 }
311
312 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
313 oppositeMoves(m1, m2) {
314 return (
315 !!m1 &&
55d3b31f
BA
316 m2.appear.length == 1 &&
317 m2.vanish.length == 1 &&
5006aaca 318 m2.appear[0].c == "c" &&
55d3b31f
BA
319 m1.start.x == m2.end.x &&
320 m1.end.x == m2.start.x &&
321 m1.start.y == m2.end.y &&
322 m1.end.y == m2.start.y
323 );
324 }
325
55d3b31f 326 filterValid(moves) {
55d3b31f 327 const color = this.turn;
616a8d7a 328 if (this.stage == 2 && this.sideCheckered == color)
5006aaca
BA
329 // Checkered cannot be under check (no king)
330 return moves;
331 let kingPos = super.searchKingPos(color);
5006aaca
BA
332 if (this.stage == 2)
333 // Must consider both kings (attacked by checkered side)
ceeac4e8
BA
334 kingPos = [kingPos, super.searchKingPos(C.GetOppTurn(this.turn))];
335 const oppCols = this.getOppCols(color);
b57b726c
BA
336 // Artificial turn change required, because canTake uses turn info.
337 // canTake is called from underCheck --> ... --> findDestSquares
338 this.turn = C.GetOppTurn(this.turn);
339 const filteredMoves = moves.filter(m => {
8d19ea48 340 this.playOnBoard(m);
b57b726c
BA
341 if (m.vanish[0].p == 'k')
342 kingPos[0] = [m.appear[0].x, m.appear[0].y];
55d3b31f 343 let res = true;
616a8d7a 344 if (this.stage == 1)
5006aaca
BA
345 res = !this.oppositeMoves(this.cmove, m);
346 if (res && m.appear.length > 0)
ceeac4e8 347 res = !this.underCheck(kingPos, oppCols);
b57b726c
BA
348 if (m.vanish[0].p == 'k')
349 kingPos[0] = [m.vanish[0].x, m.vanish[0].y];
5006aaca 350 this.undoOnBoard(m);
55d3b31f
BA
351 return res;
352 });
b57b726c
BA
353 this.turn = C.GetOppTurn(this.turn);
354 return filteredMoves;
55d3b31f
BA
355 }
356
5006aaca 357 atLeastOneMove(color) {
ceeac4e8 358 const myCols = [color, 'c'];
5006aaca
BA
359 for (let i = 0; i < this.size.x; i++) {
360 for (let j = 0; j < this.size.y; j++) {
55d3b31f
BA
361 const colIJ = this.getColor(i, j);
362 if (
5006aaca 363 this.board[i][j] != "" &&
55d3b31f 364 (
ceeac4e8 365 (this.stage == 1 && myCols.includes(colIJ)) ||
55d3b31f
BA
366 (this.stage == 2 &&
367 (
368 (this.sideCheckered == color && colIJ == 'c') ||
369 (this.sideCheckered != color && ['w', 'b'].includes(colIJ))
370 )
371 )
372 )
373 ) {
8d19ea48 374 const moves = this.getPotentialMovesFrom([i, j]);
5006aaca
BA
375 if (moves.some(m => this.filterValid([m]).length >= 1))
376 return true;
55d3b31f
BA
377 }
378 }
379 }
380 return false;
381 }
382
ceeac4e8
BA
383 underCheck(square_s, oppCols) {
384 if (this.stage == 2 && oppCol != this.sideCheckered)
5006aaca 385 return false; //checkered pieces is me, I'm not under check
616a8d7a 386 return square_s.some(sq => super.underAttack(sq, oppCols));
55d3b31f
BA
387 }
388
5006aaca
BA
389 prePlay(move) {
390 if (move.appear.length > 0 && move.vanish.length > 0) {
391 super.prePlay(move);
55d3b31f
BA
392 if (
393 [1, 6].includes(move.start.x) &&
616a8d7a 394 move.vanish[0].p == 'p' &&
55d3b31f
BA
395 Math.abs(move.end.x - move.start.x) == 2
396 ) {
397 // This move turns off a 2-squares pawn flag
398 this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
399 }
400 }
5006aaca
BA
401 }
402
403 postPlay(move) {
404 if (move.appear.length == 0 && move.vanish.length == 0) {
405 this.stage = 2;
406 this.sideCheckered = this.turn;
407 }
408 else
409 super.postPlay(move);
55d3b31f
BA
410 this.cmove = this.getCmove(move);
411 }
412
5006aaca
BA
413 tryChangeTurn(move) {
414 if (move.appear.length > 0 && move.vanish.length > 0)
415 super.tryChangeTurn(move);
416 }
417
55d3b31f
BA
418 getCurrentScore() {
419 const color = this.turn;
420 if (this.stage == 1) {
5006aaca
BA
421 if (this.atLeastOneMove(color))
422 return "*";
55d3b31f 423 // Artifically change turn, for checkered pawns
ceeac4e8
BA
424 const oppTurn = C.GetOppTurn(color);
425 this.turn = oppTurn;
5006aaca
BA
426 const kingPos = super.searchKingPos(color)[0];
427 let res = "1/2";
ceeac4e8 428 if (super.underAttack(kingPos, [oppTurn, 'c']))
5006aaca
BA
429 res = (color == "w" ? "0-1" : "1-0");
430 this.turn = color;
55d3b31f
BA
431 return res;
432 }
433 // Stage == 2:
5006aaca 434 if (this.sideCheckered == color) {
55d3b31f
BA
435 // Check if remaining checkered pieces: if none, I lost
436 if (this.board.some(b => b.some(cell => cell[0] == 'c'))) {
5006aaca
BA
437 if (!this.atLeastOneMove(color))
438 return "1/2";
55d3b31f
BA
439 return "*";
440 }
5006aaca 441 return (color == 'w' ? "0-1" : "1-0");
55d3b31f 442 }
5006aaca
BA
443 if (this.atLeastOneMove(color))
444 return "*";
ceeac4e8 445 let res = super.underAttack(super.searchKingPos(color)[0], ['c']);
5006aaca 446 if (!res)
ceeac4e8 447 res = super.underAttack(super.searchKingPos(oppCol)[0], ['c']);
5006aaca
BA
448 if (res)
449 return (color == 'w' ? "0-1" : "1-0");
55d3b31f
BA
450 return "1/2";
451 }
452
55d3b31f 453};