1f7cc7aa0533e1a172f84927a7b424914edf879b
1 import ChessRules
from "/base_rules.js";
2 import {ArrayFun
} from "/utils/array.js";
4 export default class ApocalypseRules
extends ChessRules
{
20 get pawnPromotions() {
28 setOtherVariables(fenParsed
) {
29 super.setOtherVariables(fenParsed
);
30 // Often a simple move, but sometimes an array (pawn relocation)
31 this.whiteMove
= fenParsed
.whiteMove
!= "-"
32 ? JSON
.parse(fenParsed
.whiteMove
)
34 this.firstMove
= null; //used if black turn pawn relocation
35 this.penalties
= ArrayFun
.toObject(
37 [0, 1].map(i
=> parseInt(fenParsed
.penalties
.charAt(i
), 10))
41 genRandInitBaseFen() {
43 fen: "npppn/p3p/5/P3P/NPPPN",
50 whiteMove: (o
.init
|| !this.whiteMove
) ? "-" : this.whiteMove
,
51 penalties: o
.init
? "00" : Object
.values(this.penalties
).join("")
56 if (this.whiteMove
.length
== 0)
58 if (this.whiteMove
.length
== 1)
59 return JSON
.stringify(this.whiteMove
[0]);
60 return JSON
.stringify(this.whiteMove
); //pawn relocation
63 // Allow pawns to move diagonally and capture vertically,
64 // because some of these moves might be valid a posteriori.
65 // They will be flagged as 'illegal' in a first time, however.
67 const pawnShift
= (color
== "w" ? -1 : 1);
73 steps: [[pawnShift
, 0], [pawnShift
, -1], [pawnShift
, 1]],
78 'n': super.pieces(color
, x
, y
)['n']
82 // Allow self-captures, because they might be valid
83 // if opponent takes on the same square (luck...)
88 getPotentialMovesFrom([x
, y
]) {
90 if (this.subTurn
== 2) {
91 const start
= this.firstMove
.end
;
92 if (x
== start
.x
&& y
== start
.y
) {
93 // Move the pawn to any empty square not on last rank (== x)
94 for (let i
=0; i
<this.size
.x
; i
++) {
97 for (let j
=0; j
<this.size
.y
; j
++) {
98 if (this.board
[i
][j
] == "")
99 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
105 const oppCol
= C
.GetOppCol(this.getColor(x
, y
));
106 moves
= super.getPotentialMovesFrom([x
, y
]).filter(m
=> {
107 // Remove pawn push toward own color (absurd)
109 m
.vanish
[0].p
!= 'p' ||
110 m
.end
.y
!= m
.start
.y
||
111 m
.vanish
.length
== 1 ||
112 m
.vanish
[1].c
== oppCol
115 // Flag a priori illegal moves
118 // Self-capture test:
119 (m
.vanish
.length
== 2 && m
.vanish
[1].c
== m
.vanish
[0].c
) ||
120 // Pawn going diagonaly to empty square, or vertically to occupied
122 m
.vanish
[0].p
== 'p' &&
124 (m
.end
.y
== m
.start
.y
&& m
.vanish
.length
== 2) ||
125 (m
.end
.y
!= m
.start
.y
&& m
.vanish
.length
== 1)
136 pawnPostProcess(moves
, color
, oppCol
) {
138 for (let i
=0; i
<this.size
.x
; i
++) {
139 for (let j
=0; j
<this.size
.y
; j
++) {
141 this.board
[i
][j
] != "" &&
142 this.getColor(i
, j
) == color
&&
143 this.getPiece(i
, j
) == 'n'
149 return super.pawnPostProcess(moves
, color
, oppCol
).filter(m
=> {
151 m
.vanish
[0].p
== 'p' &&
153 (color
== 'w' && m
.end
.x
== 0) ||
154 (color
== 'b' && m
.end
.x
== this.size
.x
- 1)
158 if (knightCount
<= 1 && m
.appear
[0].p
== 'p')
159 return false; //knight promotion mandatory
160 if (knightCount
== 2 && m
.appear
[0].p
== 'n')
161 m
.illegal
= true; //will be legal only if one knight is captured
172 // White and black (partial) moves were played: merge
173 resolveSynchroneMove(move) {
174 const condensate
= (mArr
) => {
175 const illegal
= (mArr
.length
== 1 && mArr
[0].illegal
) ||
176 (!mArr
[0] && mArr
[1].illegal
);
177 if (mArr
.length
== 1)
178 return Object
.assign({illegal: illegal
}, mArr
[0]);
180 return Object
.assign({illegal: illegal
}, mArr
[1]);
183 start: mArr
[0].start
,
185 vanish: mArr
[0].vanish
,
186 appear: mArr
[1].appear
,
188 [[mArr
[0].start
.x
, mArr
[0].start
.y
], [mArr
[0].end
.x
, mArr
[0].end
.y
]],
189 [[mArr
[1].start
.x
, mArr
[1].start
.y
], [mArr
[1].end
.x
, mArr
[1].end
.y
]]
193 const compatible
= (m1
, m2
) => {
197 if (m1
.appear
[0].p
!= m1
.vanish
[0].p
)
198 return m2
.vanish
.length
== 2 && m2
.vanish
[1].p
== 'n';
200 // Self-capture attempt?
201 (m1
.vanish
.length
== 2 && m1
.vanish
[1].c
== m1
.vanish
[0].c
) ||
202 // Pawn captures something by anticipation?
204 m1
.vanish
[0].p
== 'p' &&
205 m1
.vanish
.length
== 1 &&
206 m1
.start
.y
!= m1
.end
.y
209 return m2
.end
.x
== m1
.end
.x
&& m2
.end
.y
== m1
.end
.y
;
211 // Pawn push toward an enemy piece?
213 m1
.vanish
[0].p
== 'p' &&
214 m1
.vanish
.length
== 2 &&
215 m1
.start
.y
== m1
.end
.y
217 return m2
.start
.x
== m1
.end
.x
&& m2
.start
.y
== m1
.end
.y
;
221 const adjust
= (res
) => {
222 if (!res
.wm
|| !res
.bm
)
224 for (let c
of ['w', 'b']) {
225 const myMove
= res
[c
+ 'm'], oppMove
= res
[C
.GetOppCol(c
) + 'm'];
227 // More general test than checking moves ends,
228 // because of potential pawn relocation
229 myMove
.vanish
.length
== 2 &&
230 myMove
.vanish
[1].x
== oppMove
.start
.x
&&
231 myMove
.vanish
[1].y
== oppMove
.start
.y
233 // Whatever was supposed to vanish, finally doesn't vanish
237 if (res
.wm
.end
.y
== res
.bm
.end
.y
&& res
.wm
.end
.x
== res
.bm
.end
.x
) {
238 // Collision (necessarily on empty square)
239 if (!res
.wm
.illegal
&& !res
.bm
.illegal
) {
240 if (res
.wm
.vanish
[0].p
!= res
.bm
.vanish
[0].p
) {
241 const vanishColor
= (res
.wm
.vanish
[0].p
== 'n' ? 'b' : 'w');
242 res
[vanishColor
+ 'm'].appear
.shift();
245 // Collision of two pieces of same nature: both disappear
246 res
.wm
.appear
.shift();
247 res
.bm
.appear
.shift();
251 const c
= (!res
.wm
.illegal
? 'w' : 'b');
252 // Illegal move wins:
253 res
[c
+ 'm'].appear
.shift();
257 // Clone moves to avoid altering them:
258 let whiteMove
= JSON
.parse(JSON
.stringify(this.whiteMove
)),
259 blackMove
= JSON
.parse(JSON
.stringify([this.firstMove
, move]));
260 [whiteMove
, blackMove
] = [condensate(whiteMove
), condensate(blackMove
)];
263 (!whiteMove
.illegal
|| compatible(whiteMove
, blackMove
))
268 (!blackMove
.illegal
|| compatible(blackMove
, whiteMove
))
277 play(move, callback
) {
278 const color
= this.turn
;
280 this.whiteMove
.push(move);
282 move.vanish
[0].p
== 'p' && move.appear
[0].p
== 'p' &&
284 (color
== 'w' && move.end
.x
== 0) ||
285 (color
== 'b' && move.end
.x
== this.size
.x
- 1)
288 // Pawn on last rank : will relocate
290 this.firstMove
= move;
291 if (color
== this.playerColor
) {
292 this.playOnBoard(move);
293 this.playVisual(move);
298 if (color
== this.playerColor
&& this.firstMove
) {
299 // The move was played on board: undo it
300 this.undoOnBoard(this.firstMove
);
301 const revFirstMove
= {
302 start: this.firstMove
.end
,
303 end: this.firstMove
.start
,
304 appear: this.firstMove
.vanish
,
305 vanish: this.firstMove
.appear
307 this.playVisual(revFirstMove
);
309 this.turn
= C
.GetOppCol(color
);
312 this.firstMove
= null;
314 // A full turn just ended
315 const res
= this.resolveSynchroneMove(move);
316 const afterAnimate
= () => {
317 // start + end don't matter for playOnBoard() and playVisual().
318 // Merging is necessary because moves may overlap.
319 let toPlay
= {appear: [], vanish: []};
320 for (let c
of ['w', 'b']) {
322 Array
.prototype.push
.apply(toPlay
.vanish
, res
[c
+ 'm'].vanish
);
323 Array
.prototype.push
.apply(toPlay
.appear
, res
[c
+ 'm'].appear
);
326 this.playOnBoard(toPlay
);
327 this.playVisual(toPlay
);
331 this.animate(res
.wm
, () => {if (!res
.bm
) afterAnimate();});
333 this.animate(res
.bm
, afterAnimate
);
334 if (!res
.wm
&& !res
.bm
) {
335 this.displayIllegalInfo("both illegal");
336 ['w', 'b'].forEach(c
=> this.penalties
[c
]++);
339 this.displayIllegalInfo("white illegal");
340 this.penalties
['w']++;
343 this.displayIllegalInfo("black illegal");
344 this.penalties
['b']++;
352 displayIllegalInfo(msg
) {
353 super.displayMessage(null, msg
, "illegal-text", 2000);
356 atLeastOneLegalMove(color
) {
357 for (let i
=0; i
<this.size
.x
; i
++) {
358 for (let j
=0; j
<this.size
.y
; j
++) {
360 this.board
[i
][j
] != "" &&
361 this.getColor(i
, j
) == color
&&
362 this.getPotentialMovesFrom([i
, j
]).some(m
=> !m
.illegal
)
372 if (this.turn
== 'b') {
373 // Turn (white + black) not over yet.
374 // Could be stalemate if black cannot move (legally):
375 if (!this.atLeastOneLegalMove('b'))
379 // Count footmen: if a side has none, it loses
380 let fmCount
= {w: 0, b: 0};
381 for (let i
=0; i
<this.size
.x
; i
++) {
382 for (let j
=0; j
<this.size
.y
; j
++) {
383 if (this.board
[i
][j
] != "" && this.getPiece(i
, j
) == 'p')
384 fmCount
[this.getColor(i
, j
)]++;
387 if (Object
.values(fmCount
).some(v
=> v
== 0)) {
388 if (fmCount
['w'] == 0 && fmCount
['b'] == 0)
391 if (fmCount
['w'] == 0) return "0-1";
392 return "1-0"; //fmCount['b'] == 0
394 // Check penaltyFlags: if a side has 2 or more, it loses
395 if (Object
.values(this.penalties
).every(v
=> v
== 2)) return "1/2";
396 if (this.penalties
['w'] == 2) return "0-1";
397 if (this.penalties
['b'] == 2) return "1-0";
398 if (!this.atLeastOneLegalMove('w') || !this.atLeastOneLegalMove('b'))
399 // Stalemate (should be very rare)