Commit | Line | Data |
---|---|---|
55d3b31f BA |
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 | // Game modifiers (using "elementary variants"). Default: false | |
19 | styles: [ | |
20 | "balance", | |
21 | "capture", | |
22 | "cylinder", | |
23 | "doublemove", | |
24 | "madrasi", | |
25 | "progressive", | |
26 | "recycle", | |
27 | "teleport" | |
28 | ] | |
29 | }; | |
30 | } | |
31 | ||
32 | static board2fen(b) { | |
33 | const checkered_codes = { | |
34 | p: "s", | |
35 | q: "t", | |
36 | r: "u", | |
37 | b: "c", | |
38 | n: "o" | |
39 | }; | |
40 | if (b[0] == "c") return checkered_codes[b[1]]; | |
41 | return super.board2fen(b); | |
42 | } | |
43 | ||
44 | static fen2board(f) { | |
45 | // Tolerate upper-case versions of checkered pieces (why not?) | |
46 | const checkered_pieces = { | |
47 | s: "p", | |
48 | S: "p", | |
49 | t: "q", | |
50 | T: "q", | |
51 | u: "r", | |
52 | U: "r", | |
53 | c: "b", | |
54 | C: "b", | |
55 | o: "n", | |
56 | O: "n" | |
57 | }; | |
58 | if (Object.keys(checkered_pieces).includes(f)) | |
59 | return "c" + checkered_pieces[f]; | |
60 | return super.fen2board(f); | |
61 | } | |
62 | ||
63 | // TODO: pieces() | |
64 | ||
65 | setOtherVariables(fenParsed) { | |
66 | super.setOtherVariables(fenParsed); | |
67 | // Non-capturing last checkered move (if any) | |
68 | const cmove = fenParsed.cmove; | |
69 | if (cmove == "-") this.cmove = null; | |
70 | else { | |
71 | this.cmove = { | |
72 | start: C.SquareToCoords(cmove.substr(0, 2)), | |
73 | end: C.SquareToCoords(cmove.substr(2)) | |
74 | }; | |
75 | } | |
76 | // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous | |
77 | const stageInfo = fenParsed.stage; | |
78 | this.stage = parseInt(stageInfo[0], 10); | |
79 | this.canSwitch = (this.stage == 1 && stageInfo[1] != '-'); | |
80 | this.sideCheckered = (this.stage == 2 ? stageInfo[1] : undefined); | |
81 | } | |
82 | ||
83 | setFlags(fenflags) { | |
84 | super.setFlags(fenflags); //castleFlags | |
85 | this.pawnFlags = { | |
86 | w: [...Array(8)], //pawns can move 2 squares? | |
87 | b: [...Array(8)] | |
88 | }; | |
89 | const flags = fenflags.substr(4); //skip first 4 letters, for castle | |
90 | for (let c of ["w", "b"]) { | |
91 | for (let i = 0; i < 8; i++) | |
92 | this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1"; | |
93 | } | |
94 | } | |
95 | ||
96 | aggregateFlags() { | |
97 | return [this.castleFlags, this.pawnFlags]; | |
98 | } | |
99 | ||
100 | disaggregateFlags(flags) { | |
101 | this.castleFlags = flags[0]; | |
102 | this.pawnFlags = flags[1]; | |
103 | } | |
104 | ||
105 | getEpSquare(moveOrSquare) { | |
106 | // At stage 2, all pawns can be captured en-passant | |
107 | if ( | |
108 | this.stage == 2 || | |
109 | typeof moveOrSquare !== "object" || | |
110 | (moveOrSquare.appear.length > 0 && moveOrSquare.appear[0].c != 'c') | |
111 | ) | |
112 | return super.getEpSquare(moveOrSquare); | |
113 | // Checkered or switch move: no en-passant | |
114 | return undefined; | |
115 | } | |
116 | ||
117 | getCmove(move) { | |
118 | // No checkered move to undo at stage 2: | |
119 | if (this.stage == 1 && move.vanish.length == 1 && move.appear[0].c == "c") | |
120 | return { start: move.start, end: move.end }; | |
121 | return null; | |
122 | } | |
123 | ||
124 | canTake([x1, y1], [x2, y2]) { | |
125 | const color1 = this.getColor(x1, y1); | |
126 | const color2 = this.getColor(x2, y2); | |
127 | if (this.stage == 2) { | |
128 | // Black & White <-- takes --> Checkered | |
129 | const color1 = this.getColor(x1, y1); | |
130 | const color2 = this.getColor(x2, y2); | |
131 | return color1 != color2 && [color1, color2].includes('c'); | |
132 | } | |
133 | // Checkered aren't captured | |
134 | return ( | |
135 | color1 != color2 && | |
136 | color2 != "c" && | |
137 | (color1 != "c" || color2 != this.turn) | |
138 | ); | |
139 | } | |
140 | ||
141 | // TODO:::: | |
142 | getPotentialMovesFrom([x, y], noswitch) { | |
143 | let standardMoves = super.getPotentialMovesFrom([x, y]); | |
144 | if (this.stage == 1) { | |
145 | const color = this.turn; | |
146 | // Post-processing: apply "checkerization" of standard moves | |
147 | const lastRank = (color == "w" ? 0 : 7); | |
148 | let moves = []; | |
149 | // King is treated differently: it never turn checkered | |
150 | if (this.getPiece(x, y) == V.KING) { | |
151 | // If at least one checkered piece, allow switching: | |
152 | if ( | |
153 | this.canSwitch && !noswitch && | |
154 | this.board.some(b => b.some(cell => cell[0] == 'c')) | |
155 | ) { | |
156 | const oppCol = V.GetOppCol(color); | |
157 | moves.push( | |
158 | new Move({ | |
159 | start: { x: x, y: y }, | |
160 | end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] }, | |
161 | appear: [], | |
162 | vanish: [] | |
163 | }) | |
164 | ); | |
165 | } | |
166 | return standardMoves.concat(moves); | |
167 | } | |
168 | standardMoves.forEach(m => { | |
169 | if (m.vanish[0].p == V.PAWN) { | |
170 | if ( | |
171 | Math.abs(m.end.x - m.start.x) == 2 && | |
172 | !this.pawnFlags[this.turn][m.start.y] | |
173 | ) { | |
174 | return; //skip forbidden 2-squares jumps | |
175 | } | |
176 | if ( | |
177 | this.board[m.end.x][m.end.y] == V.EMPTY && | |
178 | m.vanish.length == 2 && | |
179 | this.getColor(m.start.x, m.start.y) == "c" | |
180 | ) { | |
181 | return; //checkered pawns cannot take en-passant | |
182 | } | |
183 | } | |
184 | if (m.vanish.length == 1) | |
185 | // No capture | |
186 | moves.push(m); | |
187 | else { | |
188 | // A capture occured (m.vanish.length == 2) | |
189 | m.appear[0].c = "c"; | |
190 | moves.push(m); | |
191 | if ( | |
192 | // Avoid promotions (already treated): | |
193 | m.appear[0].p != m.vanish[1].p && | |
194 | (m.vanish[0].p != V.PAWN || m.end.x != lastRank) | |
195 | ) { | |
196 | // Add transformation into captured piece | |
197 | let m2 = JSON.parse(JSON.stringify(m)); | |
198 | m2.appear[0].p = m.vanish[1].p; | |
199 | moves.push(m2); | |
200 | } | |
201 | } | |
202 | }); | |
203 | return moves; | |
204 | } | |
205 | return standardMoves; | |
206 | } | |
207 | ||
208 | // TODO: merge this into pieces() method | |
209 | getPotentialPawnMoves([x, y]) { | |
210 | const color = this.getColor(x, y); | |
211 | if (this.stage == 2) { | |
212 | const saveTurn = this.turn; | |
213 | if (this.sideCheckered == this.turn) { | |
214 | // Cannot change PawnSpecs.bidirectional, so cheat a little: | |
215 | this.turn = 'w'; | |
216 | const wMoves = super.getPotentialPawnMoves([x, y]); | |
217 | this.turn = 'b'; | |
218 | const bMoves = super.getPotentialPawnMoves([x, y]); | |
219 | this.turn = saveTurn; | |
220 | return wMoves.concat(bMoves); | |
221 | } | |
222 | // Playing with both colors: | |
223 | this.turn = color; | |
224 | const moves = super.getPotentialPawnMoves([x, y]); | |
225 | this.turn = saveTurn; | |
226 | return moves; | |
227 | } | |
228 | let moves = super.getPotentialPawnMoves([x, y]); | |
229 | // Post-process: set right color for checkered moves | |
230 | if (color == 'c') { | |
231 | moves.forEach(m => { | |
232 | m.appear[0].c = 'c'; //may be done twice if capture | |
233 | m.vanish[0].c = 'c'; | |
234 | }); | |
235 | } | |
236 | return moves; | |
237 | } | |
238 | ||
239 | canIplay(side, [x, y]) { | |
240 | if (this.stage == 2) { | |
241 | const color = this.getColor(x, y); | |
242 | return ( | |
243 | this.turn == this.sideCheckered | |
244 | ? color == 'c' | |
245 | : ['w', 'b'].includes(color) | |
246 | ); | |
247 | } | |
248 | return side == this.turn && [side, "c"].includes(this.getColor(x, y)); | |
249 | } | |
250 | ||
251 | // Does m2 un-do m1 ? (to disallow undoing checkered moves) | |
252 | oppositeMoves(m1, m2) { | |
253 | return ( | |
254 | !!m1 && | |
255 | m2.appear[0].c == "c" && | |
256 | m2.appear.length == 1 && | |
257 | m2.vanish.length == 1 && | |
258 | m1.start.x == m2.end.x && | |
259 | m1.end.x == m2.start.x && | |
260 | m1.start.y == m2.end.y && | |
261 | m1.end.y == m2.start.y | |
262 | ); | |
263 | } | |
264 | ||
265 | // TODO: adapt, merge | |
266 | filterValid(moves) { | |
267 | if (moves.length == 0) return []; | |
268 | const color = this.turn; | |
269 | const oppCol = V.GetOppCol(color); | |
270 | const L = this.cmoves.length; //at least 1: init from FEN | |
271 | const stage = this.stage; //may change if switch | |
272 | return moves.filter(m => { | |
273 | // Checkered cannot be under check (no king) | |
274 | if (stage == 2 && this.sideCheckered == color) return true; | |
275 | this.play(m); | |
276 | let res = true; | |
277 | if (stage == 1) { | |
278 | if (m.appear.length == 0 && m.vanish.length == 0) { | |
279 | // Special "switch" move: kings must not be attacked by checkered. | |
280 | // Not checking for oppositeMoves here: checkered are autonomous | |
281 | res = ( | |
282 | !this.isAttacked(this.kingPos['w'], ['c']) && | |
283 | !this.isAttacked(this.kingPos['b'], ['c']) && | |
284 | this.getAllPotentialMoves().length > 0 | |
285 | ); | |
286 | } | |
287 | else res = !this.oppositeMoves(this.cmoves[L - 1], m); | |
288 | } | |
289 | if (res && m.appear.length > 0) res = !this.underCheck(color); | |
290 | // At stage 2, side with B & W can be undercheck with both kings: | |
291 | if (res && stage == 2) res = !this.underCheck(oppCol); | |
292 | this.undo(m); | |
293 | return res; | |
294 | }); | |
295 | } | |
296 | ||
297 | atLeastOneMove() { | |
298 | const color = this.turn; | |
299 | const oppCol = V.GetOppCol(color); | |
300 | for (let i = 0; i < V.size.x; i++) { | |
301 | for (let j = 0; j < V.size.y; j++) { | |
302 | const colIJ = this.getColor(i, j); | |
303 | if ( | |
304 | this.board[i][j] != V.EMPTY && | |
305 | ( | |
306 | (this.stage == 1 && colIJ != oppCol) || | |
307 | (this.stage == 2 && | |
308 | ( | |
309 | (this.sideCheckered == color && colIJ == 'c') || | |
310 | (this.sideCheckered != color && ['w', 'b'].includes(colIJ)) | |
311 | ) | |
312 | ) | |
313 | ) | |
314 | ) { | |
315 | const moves = this.getPotentialMovesFrom([i, j], "noswitch"); | |
316 | if (moves.length > 0) { | |
317 | for (let k = 0; k < moves.length; k++) | |
318 | if (this.filterValid([moves[k]]).length > 0) return true; | |
319 | } | |
320 | } | |
321 | } | |
322 | } | |
323 | return false; | |
324 | } | |
325 | ||
326 | // TODO: adapt | |
327 | underCheck(color) { | |
328 | if (this.stage == 1) | |
329 | return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]); | |
330 | if (color == this.sideCheckered) return false; | |
331 | return ( | |
332 | this.isAttacked(this.kingPos['w'], ["c"]) || | |
333 | this.isAttacked(this.kingPos['b'], ["c"]) | |
334 | ); | |
335 | } | |
336 | ||
337 | play(move) { | |
338 | move.flags = JSON.stringify(this.aggregateFlags()); | |
339 | this.epSquares.push(this.getEpSquare(move)); | |
340 | V.PlayOnBoard(this.board, move); | |
341 | if (move.appear.length > 0 || move.vanish.length > 0) | |
342 | { | |
343 | this.turn = V.GetOppCol(this.turn); | |
344 | this.movesCount++; | |
345 | } | |
346 | this.postPlay(move); | |
347 | } | |
348 | ||
349 | postPlay(move) { | |
350 | if (move.appear.length == 0 && move.vanish.length == 0) { | |
351 | this.stage = 2; | |
352 | this.sideCheckered = this.turn; | |
353 | } | |
354 | else { | |
355 | const c = move.vanish[0].c; | |
356 | const piece = move.vanish[0].p; | |
357 | if (piece == V.KING) { | |
358 | this.kingPos[c][0] = move.appear[0].x; | |
359 | this.kingPos[c][1] = move.appear[0].y; | |
360 | } | |
361 | super.updateCastleFlags(move, piece); | |
362 | if ( | |
363 | [1, 6].includes(move.start.x) && | |
364 | move.vanish[0].p == V.PAWN && | |
365 | Math.abs(move.end.x - move.start.x) == 2 | |
366 | ) { | |
367 | // This move turns off a 2-squares pawn flag | |
368 | this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; | |
369 | } | |
370 | } | |
371 | this.cmove = this.getCmove(move); | |
372 | } | |
373 | ||
374 | getCurrentScore() { | |
375 | const color = this.turn; | |
376 | if (this.stage == 1) { | |
377 | if (this.atLeastOneMove()) return "*"; | |
378 | // Artifically change turn, for checkered pawns | |
379 | this.turn = V.GetOppCol(this.turn); | |
380 | const res = | |
381 | this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]) | |
382 | ? color == "w" | |
383 | ? "0-1" | |
384 | : "1-0" | |
385 | : "1/2"; | |
386 | this.turn = V.GetOppCol(this.turn); | |
387 | return res; | |
388 | } | |
389 | // Stage == 2: | |
390 | if (this.sideCheckered == this.turn) { | |
391 | // Check if remaining checkered pieces: if none, I lost | |
392 | if (this.board.some(b => b.some(cell => cell[0] == 'c'))) { | |
393 | if (!this.atLeastOneMove()) return "1/2"; | |
394 | return "*"; | |
395 | } | |
396 | return color == 'w' ? "0-1" : "1-0"; | |
397 | } | |
398 | if (this.atLeastOneMove()) return "*"; | |
399 | let res = this.isAttacked(this.kingPos['w'], ["c"]); | |
400 | if (!res) res = this.isAttacked(this.kingPos['b'], ["c"]); | |
401 | if (res) return color == 'w' ? "0-1" : "1-0"; | |
402 | return "1/2"; | |
403 | } | |
404 | ||
405 | // TODO: adapt | |
406 | static GenRandInitFen(options) { | |
407 | const baseFen = ChessRules.GenRandInitFen(options); | |
408 | return ( | |
409 | // Add 16 pawns flags + empty cmove + stage == 1: | |
410 | baseFen.slice(0, -2) + "1111111111111111 - - 1" + | |
411 | (!options["switch"] ? '-' : "") | |
412 | ); | |
413 | } | |
414 | { | |
415 | cmove: fenParts[5], | |
416 | stage: fenParts[6] | |
417 | } | |
418 | ||
419 | getCmoveFen() { | |
420 | const L = this.cmoves.length; | |
421 | return ( | |
422 | !this.cmoves[L - 1] | |
423 | ? "-" | |
424 | : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + | |
425 | ChessRules.CoordsToSquare(this.cmoves[L - 1].end) | |
426 | ); | |
427 | } | |
428 | ||
429 | getStageFen() { | |
430 | if (this.stage == 1) return "1" + (!this.canSwitch ? '-' : ""); | |
431 | // Stage == 2: | |
432 | return "2" + this.sideCheckered; | |
433 | } | |
434 | ||
435 | getFen() { | |
436 | return ( | |
437 | super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen() | |
438 | ); | |
439 | } | |
440 | ||
441 | getFlagsFen() { | |
442 | let fen = super.getFlagsFen(); | |
443 | // Add pawns flags | |
444 | for (let c of ["w", "b"]) | |
445 | for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0"); | |
446 | return fen; | |
447 | } | |
448 | ||
449 | }; |