2aa5b45fddf47f57c1853f40816e7aaef225db38
1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class Synchrone1Rules
extends ChessRules
{
6 static get CanAnalyze() {
10 static get ShowMoves() {
14 static get SomeHiddenMoves() {
18 static IsGoodFen(fen
) {
19 if (!ChessRules
.IsGoodFen(fen
)) return false;
20 const fenParsed
= V
.ParseFen(fen
);
24 fenParsed
.turn
== "b" &&
25 // NOTE: do not check really JSON stringified move...
26 (!fenParsed
.whiteMove
|| fenParsed
.whiteMove
== "-")
29 (fenParsed
.turn
== "w" && fenParsed
.whiteMove
!= "-")
36 static IsGoodEnpassant(enpassant
) {
37 const epArray
= enpassant
.split(",");
38 if (![2, 3].includes(epArray
.length
)) return false;
39 epArray
.forEach(epsq
=> {
41 const ep
= V
.SquareToCoords(epsq
);
42 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
48 static ParseFen(fen
) {
49 const fenParts
= fen
.split(" ");
51 { whiteMove: fenParts
[5] },
52 ChessRules
.ParseFen(fen
)
56 static GenRandInitFen(options
) {
57 return ChessRules
.GenRandInitFen(options
).slice(0, -1) + "-,- -";
61 return super.getFen() + " " + this.getWhitemoveFen();
65 return super.getFenForRepeat() + "_" + this.getWhitemoveFen();
68 setOtherVariables(fen
) {
69 const parsedFen
= V
.ParseFen(fen
);
70 this.setFlags(parsedFen
.flags
);
71 const epArray
= parsedFen
.enpassant
.split(",");
73 epArray
.forEach(epsq
=> this.epSquares
.push(this.getEpSquare(epsq
)));
75 // Also init whiteMove
77 parsedFen
.whiteMove
!= "-"
78 ? JSON
.parse(parsedFen
.whiteMove
)
83 this.kingPos
= { w: [-1, -1], b: [-1, -1] };
84 for (let i
= 0; i
< V
.size
.x
; i
++) {
85 for (let j
= 0; j
< V
.size
.y
; j
++) {
86 if (this.getPiece(i
, j
) == V
.KING
)
87 this.kingPos
[this.getColor(i
, j
)] = [i
, j
];
93 const L
= this.epSquares
.length
;
95 const start
= L
- 2 - (this.turn
== 'b' ? 1 : 0);
96 for (let i
=start
; i
< L
; i
++) {
97 if (!this.epSquares
[i
]) res
+= "-,";
98 else res
+= V
.CoordsToSquare(this.epSquares
[i
]) + ",";
100 return res
.slice(0, -1);
104 if (!this.whiteMove
) return "-";
105 return JSON
.stringify({
106 start: this.whiteMove
.start
,
107 end: this.whiteMove
.end
,
108 appear: this.whiteMove
.appear
,
109 vanish: this.whiteMove
.vanish
113 getPossibleMovesFrom([x
, y
]) {
114 let moves
= this.filterValid(super.getPotentialMovesFrom([x
, y
]));
115 if (!this.underCheck(this.getColor(x
, y
)))
116 // Augment with potential recaptures, except if we are under check
117 Array
.prototype.push
.apply(moves
, this.getRecaptures([x
, y
]));
121 // Aux function used to find opponent and self captures
122 getCaptures(from, to
, color
) {
123 const sliderAttack
= (xx
, yy
, allowedSteps
) => {
124 const deltaX
= xx
- to
[0],
125 absDeltaX
= Math
.abs(deltaX
);
126 const deltaY
= yy
- to
[1],
127 absDeltaY
= Math
.abs(deltaY
);
128 const step
= [ deltaX
/ absDeltaX
|| 0, deltaY
/ absDeltaY
|| 0 ];
130 // Check that the step is a priori valid:
131 (absDeltaX
!= absDeltaY
&& deltaX
!= 0 && deltaY
!= 0) ||
132 allowedSteps
.every(st
=> st
[0] != step
[0] || st
[1] != step
[1])
136 let sq
= [ to
[0] + step
[0], to
[1] + step
[1] ];
137 while (sq
[0] != xx
|| sq
[1] != yy
) {
138 // NOTE: no need to check OnBoard in this special case
139 if (this.board
[sq
[0]][sq
[1]] != V
.EMPTY
) return null;
143 return this.getBasicMove([xx
, yy
], [to
[0], to
[1]]);
145 // Can I take on the square 'to' ?
146 // If yes, return the (list of) capturing move(s)
147 const getTargetedCaptures
= ([i
, j
]) => {
150 switch (this.getPiece(i
, j
)) {
152 // Pushed pawns move as enemy pawns
153 const shift
= (color
== 'w' ? 1 : -1);
154 if (to
[0] + shift
== i
&& Math
.abs(to
[1] - j
) == 1)
155 move = this.getBasicMove([i
, j
], to
);
159 const deltaX
= Math
.abs(i
- to
[0]);
160 const deltaY
= Math
.abs(j
- to
[1]);
162 deltaX
+ deltaY
== 3 &&
163 [1, 2].includes(deltaX
) &&
164 [1, 2].includes(deltaY
)
166 move = this.getBasicMove([i
, j
], to
);
171 if (Math
.abs(i
- to
[0]) <= 1 && Math
.abs(j
- to
[1]) <= 1)
172 move = this.getBasicMove([i
, j
], to
);
175 move = sliderAttack(i
, j
, V
.steps
[V
.ROOK
]);
179 move = sliderAttack(i
, j
, V
.steps
[V
.BISHOP
]);
183 move = sliderAttack(i
, j
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]));
191 const theMove
= getTargetedCaptures(from);
192 if (!!theMove
) moves
.push(theMove
);
195 for (let i
=0; i
<8; i
++) {
196 for (let j
=0; j
<8; j
++) {
197 if (this.getColor(i
, j
) == color
) {
198 const newMove
= getTargetedCaptures([i
, j
]);
199 if (!!newMove
) moves
.push(newMove
);
204 return this.filterValid(moves
);
207 getRecaptures(from) {
208 // 1) Generate all opponent's capturing moves
209 let oppCaptureMoves
= [];
210 const color
= this.turn
;
211 const oppCol
= V
.GetOppCol(color
);
212 for (let i
=0; i
<8; i
++) {
213 for (let j
=0; j
<8; j
++) {
215 this.getColor(i
, j
) == color
&&
216 // Do not consider king captures: self-captures of king are forbidden
217 this.getPiece(i
, j
) != V
.KING
219 Array
.prototype.push
.apply(
221 this.getCaptures(null, [i
, j
], oppCol
)
226 // 2) Play each opponent's capture, and see if back-captures are possible:
227 // Lookup table to quickly decide if a move is already in list:
230 oppCaptureMoves
.forEach(m
=> {
231 // If another opponent capture with same endpoint already processed, skip
232 const mHash
= "m" + m
.end
.x
+ m
.end
.y
;
233 if (!moveSet
[mHash
]) {
234 moveSet
[mHash
] = true;
235 // Just make enemy piece disappear, to clear potential path:
236 const justDisappear
= {
238 vanish: [m
.vanish
[0]]
240 V
.PlayOnBoard(this.board
, justDisappear
);
241 // Can I take on [m.end.x, m.end.y] ? If yes, add to list:
242 this.getCaptures(from, [m
.end
.x
, m
.end
.y
], color
)
243 .forEach(cm
=> moves
.push(cm
));
244 V
.UndoOnBoard(this.board
, justDisappear
);
251 // Return possible moves + potential recaptures
252 return super.getAllValidMoves().concat(this.getRecaptures());
256 if (moves
.length
== 0) return [];
257 // filterValid can be called when it's "not our turn":
258 const color
= moves
[0].vanish
[0].c
;
259 return moves
.filter(m
=> {
260 const piece
= m
.vanish
[0].p
;
261 if (piece
== V
.KING
) {
262 this.kingPos
[color
][0] = m
.appear
[0].x
;
263 this.kingPos
[color
][1] = m
.appear
[0].y
;
265 V
.PlayOnBoard(this.board
, m
);
266 let res
= !this.underCheck(color
);
267 V
.UndoOnBoard(this.board
, m
);
268 if (piece
== V
.KING
) this.kingPos
[color
] = [m
.start
.x
, m
.start
.y
];
273 atLeastOneMove(color
) {
274 const curTurn
= this.turn
;
276 const res
= super.atLeastOneMove();
281 // White and black (partial) moves were played: merge
282 resolveSynchroneMove(move) {
283 const m1
= this.whiteMove
;
285 // For PlayOnBoard (no need for start / end, irrelevant)
293 if ((m1
.end
.x
!= m2
.end
.x
) || (m1
.end
.y
!= m2
.end
.y
)) {
294 // Easy case: two independant moves (which may (self-)capture)
295 smove
.appear
.push(m1
.appear
[0]);
296 smove
.appear
.push(m2
.appear
[0]);
297 // "Captured" pieces may have moved:
298 if (m1
.appear
.length
== 2) {
300 smove
.appear
.push(m1
.appear
[1]);
301 smove
.vanish
.push(m1
.vanish
[1]);
304 m1
.vanish
.length
== 2 &&
306 m1
.vanish
[1].x
!= m2
.start
.x
||
307 m1
.vanish
[1].y
!= m2
.start
.y
310 smove
.vanish
.push(m1
.vanish
[1]);
312 if (m2
.appear
.length
== 2) {
314 smove
.appear
.push(m2
.appear
[1]);
315 smove
.vanish
.push(m2
.vanish
[1]);
318 m2
.vanish
.length
== 2 &&
320 m2
.vanish
[1].x
!= m1
.start
.x
||
321 m2
.vanish
[1].y
!= m1
.start
.y
324 smove
.vanish
.push(m2
.vanish
[1]);
329 if (m1
.vanish
.length
== 1 && m2
.vanish
.length
== 1) {
330 // Easy case: both disappear except if one is a king
331 const p1
= m1
.vanish
[0].p
;
332 const p2
= m2
.vanish
[0].p
;
333 if ([p1
, p2
].includes(V
.KING
)) {
338 c: (p1
== V
.KING
? 'w' : 'b')
343 // One move is a self-capture and the other a normal capture:
344 // only the self-capture appears
345 const selfCaptureMove
=
346 m1
.vanish
[1].c
== m1
.vanish
[0].c
352 p: selfCaptureMove
.appear
[0].p
,
353 c: selfCaptureMove
.vanish
[0].c
358 p: selfCaptureMove
.vanish
[1].p
,
359 c: selfCaptureMove
.vanish
[0].c
368 move.flags
= JSON
.stringify(this.aggregateFlags());
369 this.epSquares
.push(this.getEpSquare(move));
371 // Do not play on board (would reveal the move...)
372 this.turn
= V
.GetOppCol(this.turn
);
377 updateCastleFlags(move) {
378 const firstRank
= { 'w': V
.size
.x
- 1, 'b': 0 };
379 move.appear
.concat(move.vanish
).forEach(av
=> {
380 for (let c
of ['w', 'b']) {
381 if (av
.x
== firstRank
[c
] && this.castleFlags
[c
].includes(av
.y
)) {
382 const flagIdx
= (av
.y
== this.castleFlags
[c
][0] ? 0 : 1);
383 this.castleFlags
[c
][flagIdx
] = 8;
390 if (this.turn
== 'b') {
391 // NOTE: whiteMove is used read-only, so no need to copy
392 this.whiteMove
= move;
396 // A full turn just ended:
397 const smove
= this.resolveSynchroneMove(move);
398 V
.PlayOnBoard(this.board
, smove
);
399 move.whiteMove
= this.whiteMove
; //for undo
400 this.whiteMove
= null;
402 // Update king position + flags
403 let kingAppear
= { 'w': false, 'b': false };
404 for (let i
= 0; i
< smove
.appear
.length
; i
++) {
405 if (smove
.appear
[i
].p
== V
.KING
) {
406 const c
= smove
.appear
[i
].c
;
407 kingAppear
[c
] = true;
408 this.kingPos
[c
][0] = smove
.appear
[i
].x
;
409 this.kingPos
[c
][1] = smove
.appear
[i
].y
;
412 for (let i
= 0; i
< smove
.vanish
.length
; i
++) {
413 if (smove
.vanish
[i
].p
== V
.KING
) {
414 const c
= smove
.vanish
[i
].c
;
415 if (!kingAppear
[c
]) {
416 this.kingPos
[c
][0] = -1;
417 this.kingPos
[c
][1] = -1;
422 this.updateCastleFlags(smove
);
428 this.epSquares
.pop();
429 this.disaggregateFlags(JSON
.parse(move.flags
));
431 if (this.turn
== 'w')
432 // Back to the middle of the move
433 V
.UndoOnBoard(this.board
, move.smove
);
434 this.turn
= V
.GetOppCol(this.turn
);
440 if (this.turn
== 'w') {
441 // Reset king positions: scan board (TODO: could be more efficient)
443 // Also reset whiteMove
444 this.whiteMove
= null;
446 else this.whiteMove
= move.whiteMove
;
450 const color
= this.turn
;
452 // kingPos must be reset for appropriate highlighting:
453 var lastMove
= JSON
.parse(JSON
.stringify(this.whiteMove
));
454 this.undo(lastMove
, "noFlag"); //will erase whiteMove, thus saved above
457 if (this.kingPos
['w'][0] >= 0 && this.underCheck('w'))
458 res
.push(JSON
.parse(JSON
.stringify(this.kingPos
['w'])));
459 if (this.kingPos
['b'][0] >= 0 && this.underCheck('b'))
460 res
.push(JSON
.parse(JSON
.stringify(this.kingPos
['b'])));
461 if (color
== 'b') this.play(lastMove
, "noFlag");
466 if (this.turn
== 'b')
467 // Turn (white + black) not over yet
469 // Was a king captured?
470 if (this.kingPos
['w'][0] < 0) return "0-1";
471 if (this.kingPos
['b'][0] < 0) return "1-0";
472 const whiteCanMove
= this.atLeastOneMove('w');
473 const blackCanMove
= this.atLeastOneMove('b');
474 if (whiteCanMove
&& blackCanMove
) return "*";
476 const whiteInCheck
= this.underCheck('w');
477 const blackInCheck
= this.underCheck('b');
479 (whiteCanMove
&& !this.underCheck('b')) ||
480 (blackCanMove
&& !this.underCheck('w'))
484 // Checkmate: could be mutual
485 if (!whiteCanMove
&& !blackCanMove
) return "1/2";
486 return (whiteCanMove
? "1-0" : "0-1");
490 const maxeval
= V
.INFINITY
;
491 const color
= this.turn
;
492 let moves
= this.getAllValidMoves();
493 if (moves
.length
== 0)
494 // TODO: this situation should not happen
497 if (Math
.random() < 0.5)
498 // Return a random move
499 return moves
[randInt(moves
.length
)];
501 // Rank moves at depth 1:
502 // try to capture something (not re-capturing)
504 V
.PlayOnBoard(this.board
, m
);
505 m
.eval
= this.evalPosition();
506 V
.UndoOnBoard(this.board
, m
);
508 moves
.sort((a
, b
) => {
509 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
511 let candidates
= [0];
512 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
514 return moves
[candidates
[randInt(candidates
.length
)]];
518 if (move.appear
.length
== 2 && move.appear
[0].p
== V
.KING
)
520 return move.end
.y
< move.start
.y
? "0-0-0" : "0-0";
521 // Basic system: piece + init + dest square
523 move.vanish
[0].p
.toUpperCase() +
524 V
.CoordsToSquare(move.start
) +
525 V
.CoordsToSquare(move.end
)