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