96431c3513b5299380da490d7d4d7ccdee791d43
1 import { ChessRules
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class ApocalypseRules
extends ChessRules
{
5 static get PawnSpecs() {
11 promotions: [V
.KNIGHT
]
16 static get HasCastle() {
20 static get HasEnpassant() {
24 static get CanAnalyze() {
28 static get ShowMoves() {
33 return [V
.PAWN
, V
.KNIGHT
];
36 static IsGoodPosition(position
) {
37 if (position
.length
== 0) return false;
38 const rows
= position
.split("/");
39 if (rows
.length
!= V
.size
.x
) return false;
40 // At least one pawn per color
41 let pawns
= { "p": 0, "P": 0 };
42 for (let row
of rows
) {
44 for (let i
= 0; i
< row
.length
; i
++) {
45 if (['P','p'].includes(row
[i
])) pawns
[row
[i
]]++;
46 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
48 const num
= parseInt(row
[i
]);
49 if (isNaN(num
)) return false;
53 if (sumElts
!= V
.size
.y
) return false;
55 if (Object
.values(pawns
).some(v
=> v
== 0))
60 static IsGoodFen(fen
) {
61 if (!ChessRules
.IsGoodFen(fen
)) return false;
62 const fenParsed
= V
.ParseFen(fen
);
66 fenParsed
.turn
== "w" &&
67 // NOTE: do not check really JSON stringified move...
68 (!fenParsed
.whiteMove
|| fenParsed
.whiteMove
== "-")
71 (fenParsed
.turn
== "b" && fenParsed
.whiteMove
!= "-")
78 static IsGoodFlags(flags
) {
79 return !!flags
.match(/^[0-2]{2,2}$/);
83 return this.penaltyFlags
;
86 disaggregateFlags(flags
) {
87 this.penaltyFlags
= flags
;
90 static ParseFen(fen
) {
91 const fenParts
= fen
.split(" ");
93 ChessRules
.ParseFen(fen
),
94 { whiteMove: fenParts
[4] }
99 return { x: 5, y: 5 };
102 static GenRandInitFen() {
103 return "npppn/p3p/5/P3P/NPPPN w 0 00 -";
107 return super.getFen() + " " + this.getWhitemoveFen();
111 return super.getFenForRepeat() + "_" + this.getWhitemoveFen();
115 return this.penaltyFlags
.join("");
118 setOtherVariables(fen
) {
119 const parsedFen
= V
.ParseFen(fen
);
120 this.setFlags(parsedFen
.flags
);
121 // Also init whiteMove
123 parsedFen
.whiteMove
!= "-"
124 ? JSON
.parse(parsedFen
.whiteMove
)
129 this.penaltyFlags
= [0, 1].map(i
=> parseInt(fenflags
[i
]));
133 if (!this.whiteMove
) return "-";
134 return JSON
.stringify({
135 start: this.whiteMove
.start
,
136 end: this.whiteMove
.end
,
137 appear: this.whiteMove
.appear
,
138 vanish: this.whiteMove
.vanish
142 getSpeculations(moves
, sq
) {
145 const mHash
= "m" + m
.start
.x
+ m
.start
.y
+ m
.end
.x
+ m
.end
.y
;
146 moveSet
[mHash
] = true;
148 const color
= this.turn
;
149 this.turn
= V
.GetOppCol(color
);
150 const oppMoves
= super.getAllValidMoves();
152 // For each opponent's move, generate valid moves [from sq]
153 let speculations
= [];
154 oppMoves
.forEach(m
=> {
155 V
.PlayOnBoard(this.board
, m
);
156 const newValidMoves
=
158 ? super.getPotentialMovesFrom(sq
)
159 : super.getAllValidMoves();
160 newValidMoves
.forEach(vm
=> {
161 const mHash
= "m" + vm
.start
.x
+ vm
.start
.y
+ vm
.end
.x
+ vm
.end
.y
;
162 if (!moveSet
[mHash
]) {
163 moveSet
[mHash
] = true;
164 vm
.illegal
= true; //potentially illegal!
165 speculations
.push(vm
);
168 V
.UndoOnBoard(this.board
, m
);
173 getPossibleMovesFrom([x
, y
]) {
174 const possibleMoves
= super.getPotentialMovesFrom([x
, y
])
175 // Augment potential moves with opponent's moves speculation:
176 return possibleMoves
.concat(this.getSpeculations(possibleMoves
, [x
, y
]));
180 // Return possible moves + potentially valid moves
181 const validMoves
= super.getAllValidMoves();
182 return validMoves
.concat(this.getSpeculations(validMoves
));
185 addPawnMoves([x1
, y1
], [x2
, y2
], moves
) {
186 let finalPieces
= [V
.PAWN
];
187 const color
= this.turn
;
188 const lastRank
= (color
== "w" ? 0 : V
.size
.x
- 1);
189 if (x2
== lastRank
) {
190 // If 0 or 1 horsemen, promote in knight
191 let knightCounter
= 0;
192 let emptySquares
= [];
193 for (let i
=0; i
<V
.size
.x
; i
++) {
194 for (let j
=0; j
<V
.size
.y
; j
++) {
195 if (this.board
[i
][j
] == V
.EMPTY
) emptySquares
.push([i
, j
]);
197 this.getColor(i
, j
) == color
&&
198 this.getPiece(i
, j
) == V
.KNIGHT
204 if (knightCounter
<= 1) finalPieces
= [V
.KNIGHT
];
206 // Generate all possible landings
207 emptySquares
.forEach(sq
=> {
208 if (sq
[0] != lastRank
)
209 moves
.push(this.getBasicMove([x1
, y1
], [sq
[0], sq
[1]]));
215 for (let piece
of finalPieces
) {
216 tr
= (piece
!= V
.PAWN
? { c: color
, p: piece
} : null);
217 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
], tr
));
226 atLeastOneMove(color
) {
227 const curTurn
= this.turn
;
229 const res
= super.atLeastOneMove();
234 // White and black (partial) moves were played: merge
235 resolveSynchroneMove(move) {
236 let m
= [this.whiteMove
, move];
237 for (let i
of [0, 1]) {
238 if (!!m
[i
].illegal
) {
239 // Either an anticipated capture of something which didn't move
240 // (or not to the right square), or a push through blocus.
244 m
[i
].start
.y
== m
[i
].end
.y
&&
245 (m
[1-i
].start
.x
!= m
[i
].end
.x
|| m
[1-i
].start
.y
!= m
[i
].end
.y
)
250 Math
.abs(m
[i
].start
.y
- m
[i
].end
.y
) == 1 &&
251 (m
[1-i
].end
.x
!= m
[i
].end
.x
|| m
[1-i
].end
.y
!= m
[i
].end
.y
)
254 // Just discard the move, and add a penalty point
255 this.penaltyFlags
[m
[i
].vanish
[0].c
]++;
261 // For PlayOnBoard (no need for start / end, irrelevant)
268 // If one move is illegal, just execute the other
269 if (!m1
&& !!m2
) return m2
;
270 if (!m2
&& !!m1
) return m1
;
271 if (!m1
&& !m2
) return smove
;
272 // Both move are now legal:
273 smove
.vanish
.push(m1
.vanish
[0]);
274 smove
.vanish
.push(m2
.vanish
[0]);
275 if ((m1
.end
.x
!= m2
.end
.x
) || (m1
.end
.y
!= m2
.end
.y
)) {
276 // Easy case: two independant moves
277 smove
.appear
.push(m1
.appear
[0]);
278 smove
.appear
.push(m2
.appear
[0]);
279 // "Captured" pieces may have moved:
281 m1
.vanish
.length
== 2 &&
283 m1
.vanish
[1].x
!= m2
.start
.x
||
284 m1
.vanish
[1].y
!= m2
.start
.y
287 smove
.vanish
.push(m1
.vanish
[1]);
290 m2
.vanish
.length
== 2 &&
292 m2
.vanish
[1].x
!= m1
.start
.x
||
293 m2
.vanish
[1].y
!= m1
.start
.y
296 smove
.vanish
.push(m2
.vanish
[1]);
299 // Collision: both disappear except if different kinds (knight remains)
300 const p1
= m1
.vanish
[0].p
;
301 const p2
= m2
.vanish
[0].p
;
302 if ([p1
, p2
].includes(V
.KNIGHT
) && [p1
, p2
].includes(V
.PAWN
)) {
307 c: (p1
== V
.KNIGHT
? 'w' : 'b')
315 // Do not play on board (would reveal the move...)
316 move.flags
= JSON
.stringify(this.aggregateFlags());
317 this.turn
= V
.GetOppCol(this.turn
);
323 if (this.turn
== 'b') {
324 // NOTE: whiteMove is used read-only, so no need to copy
325 this.whiteMove
= move;
329 // A full turn just ended:
330 const smove
= this.resolveSynchroneMove(move);
331 V
.PlayOnBoard(this.board
, smove
);
332 move.whiteMove
= this.whiteMove
; //for undo
333 this.whiteMove
= null;
338 this.disaggregateFlags(JSON
.parse(move.flags
));
339 if (this.turn
== 'w')
340 // Back to the middle of the move
341 V
.UndoOnBoard(this.board
, move.smove
);
342 this.turn
= V
.GetOppCol(this.turn
);
348 if (this.turn
== 'w') this.whiteMove
= null;
349 else this.whiteMove
= move.whiteMove
;
352 getCheckSquares(color
) {
357 if (this.turn
== 'b')
358 // Turn (white + black) not over yet
360 // Count footmen: if a side has none, it loses
361 let fmCount
= { 'w': 0, 'b': 0 };
362 for (let i
=0; i
<5; i
++) {
363 for (let j
=0; j
<5; j
++) {
364 if (this.board
[i
][j
] != V
.EMPTY
&& this.getPiece(i
, j
) == V
.PAWN
)
365 fmCount
[this.getColor(i
, j
)]++;
368 if (Object
.values(fmCount
).some(v
=> v
== 0)) {
369 if (fmCount
['w'] == 0 && fmCount
['b'] == 0)
372 if (fmCount
['w'] == 0) return "0-1";
373 return "1-0"; //fmCount['b'] == 0
375 // Check penaltyFlags: if a side has 2 or more, it loses
376 if (this.penaltyFlags
.every(f
=> f
== 2)) return "1/2";
377 if (this.penaltyFlags
[0] == 2) return "0-1";
378 if (this.penaltyFlags
[1] == 2) return "1-0";
379 if (!this.atLeastOneMove('w') || !this.atLeastOneMove('b'))
380 // Stalemate (should be very rare)
386 const maxeval
= V
.INFINITY
;
387 const color
= this.turn
;
388 let moves
= this.getAllValidMoves();
389 if (moves
.length
== 0)
390 // TODO: this situation should not happen
393 if (Math
.random() < 0.5)
394 // Return a random move
395 return moves
[randInt(moves
.length
)];
397 // Rank moves at depth 1:
398 // try to capture something (not re-capturing)
400 V
.PlayOnBoard(this.board
, m
);
401 m
.eval
= this.evalPosition();
402 V
.UndoOnBoard(this.board
, m
);
404 moves
.sort((a
, b
) => {
405 return (color
== "w" ? 1 : -1) * (b
.eval
- a
.eval
);
407 let candidates
= [0];
408 for (let i
= 1; i
< moves
.length
&& moves
[i
].eval
== moves
[0].eval
; i
++)
410 return moves
[candidates
[randInt(candidates
.length
)]];
414 // Basic system: piece + init + dest square
416 move.vanish
[0].p
.toUpperCase() +
417 V
.CoordsToSquare(move.start
) +
418 V
.CoordsToSquare(move.end
)