1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class AvalancheRules
extends ChessRules
{
6 static get PawnSpecs() {
9 { promotions: [V
.PAWN
] },
15 static get HasEnpassant() {
19 static IsGoodFen(fen
) {
20 if (!ChessRules
.IsGoodFen(fen
)) return false;
21 const fenParts
= fen
.split(" ");
22 if (fenParts
.length
!= 5) return false;
23 if (!fenParts
[4].match(/^[0-8]$/)) return false;
27 canIplay(side
, [x
, y
]) {
28 if (this.subTurn
== 0) return (x
>= V
.size
.x
);
29 const c
= this.getColor(x
, y
);
31 (this.subTurn
== 1 && c
== side
) ||
32 (this.subTurn
== 2 && c
!= side
&& this.getPiece(x
, y
) == V
.PAWN
)
36 static ParseFen(fen
) {
37 const fenParts
= fen
.split(" ");
39 ChessRules
.ParseFen(fen
),
40 { promoteFile: fenParts
[4] }
45 const L
= this.promoteFile
.length
;
46 return (this.promoteFile
[L
-1] + 1);
50 return super.getFen() + " " + this.getPromoteFen();
54 return super.getFenForRepeat() + "_" + this.getPromoteFen();
57 static GenRandInitFen(randomness
) {
58 return ChessRules
.GenRandInitFen(randomness
).slice(0, -1) + "0";
62 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
63 return this.board
[i
][j
].charAt(1);
66 static get RESERVE_PIECES() {
68 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
71 setOtherVariables(fen
) {
72 super.setOtherVariables(fen
);
73 const fenPromoteFile
= V
.ParseFen(fen
).promoteFile
;
74 this.promoteFile
= [parseInt(fenPromoteFile
, 10) - 1];
75 this.reserve
= { 'w': null, 'b': null };
76 if (this.promoteFile
[0] >= 0) {
87 else this.subTurn
= 1;
90 getReservePpath(index
, color
) {
91 return color
+ V
.RESERVE_PIECES
[index
];
95 // Send a new piece piece to our first rank
96 const color
= this.turn
;
97 const L
= this.promoteFile
.length
;
98 const [rank
, file
] = [color
== 'w' ? 0 : 7, this.promoteFile
[L
-1]];
101 new PiPo({ x: rank
, y: file
, c: color
, p: V
.RESERVE_PIECES
[y
] })
104 new PiPo({ x: rank
, y: file
, c: color
, p: V
.PAWN
})
106 start: { x: 8, y: y
},
107 end: { x: rank
, y: file
}
111 getPotentialMovesFrom([x
, y
]) {
112 if (this.subTurn
== 0)
113 // Reserves, outside of board: x == sizeX(+1)
114 return (x
>= 8 ? [this.getReserveMove(y
)] : []);
115 if (this.subTurn
== 1)
117 return super.getPotentialMovesFrom([x
, y
]);
118 // subTurn == 2: only allowed to push an opponent's pawn (if possible)
119 const oppPawnShift
= (this.turn
== 'w' ? 1 : -1);
121 V
.OnBoard(x
+ oppPawnShift
, y
) &&
122 this.board
[x
+ oppPawnShift
][y
] == V
.EMPTY
124 return [this.getBasicMove([x
, y
], [x
+ oppPawnShift
, y
])];
130 if (this.subTurn
== 0) {
132 for (let y
= 0; y
< V
.RESERVE_PIECES
.length
; y
++)
133 moves
.push(this.getReserveMove(y
));
136 if (this.subTurn
== 1)
137 return this.filterValid(super.getAllPotentialMoves());
138 // subTurn == 2: move opponent's pawn only
140 const oppCol
= V
.GetOppCol(this.turn
);
141 for (let i
= 0; i
< 8; i
++) {
142 for (let j
= 0; j
< 8; j
++) {
144 this.board
[i
][j
] != V
.EMPTY
&&
145 this.getColor(i
, j
) == oppCol
&&
146 this.getPiece(i
, j
) == V
.PAWN
148 Array
.prototype.push
.apply(
149 moves
, this.getPotentialMovesFrom([i
, j
]));
157 if (this.subTurn
!= 1) return moves
; //self-checks by pawns are allowed
158 return super.filterValid(moves
);
162 if (this.subTurn
== 0) return true; //TODO: never called in this situation
163 if (this.subTurn
== 1) {
164 // Cannot use super method: infinite recursive calls
165 const color
= this.turn
;
166 for (let i
= 0; i
< 8; i
++) {
167 for (let j
= 0; j
< 8; j
++) {
168 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
169 const moves
= this.getPotentialMovesFrom([i
, j
]);
170 if (moves
.length
> 0) {
171 for (let k
= 0; k
< moves
.length
; k
++) {
172 const piece
= moves
[k
].vanish
[0].p
;
173 if (piece
== V
.KING
) {
174 this.kingPos
[color
] =
175 [moves
[k
].appear
[0].x
, moves
[k
].appear
[0].y
];
177 V
.PlayOnBoard(this.board
, moves
[k
]);
178 const res
= !this.underCheck(color
);
179 V
.UndoOnBoard(this.board
, moves
[k
]);
180 if (piece
== V
.KING
) {
181 this.kingPos
[color
] =
182 [moves
[k
].vanish
[0].x
, moves
[k
].vanish
[0].y
];
184 if (res
) return true;
192 // subTurn == 2: need to find an enemy pawn which can advance
193 const oppCol
= V
.GetOppCol(this.turn
);
194 const oppPawnShift
= (oppCol
== 'w' ? -1 : 1);
195 for (let i
= 0; i
< 8; i
++) {
196 for (let j
= 0; j
< 8; j
++) {
198 this.board
[i
][j
] != V
.EMPTY
&&
199 this.getColor(i
, j
) == oppCol
&&
200 this.getPiece(i
, j
) == V
.PAWN
&&
201 V
.OnBoard(i
+ oppPawnShift
, j
) &&
202 this.board
[i
+ oppPawnShift
][j
] == V
.EMPTY
212 if (this.kingPos
[this.turn
][0] < 0) return [];
213 return super.getCheckSquares();
217 // If my king disappeared: I lost!
219 if (this.kingPos
[c
][0] < 0) return (c
== 'w' ? "0-1" : "1-0");
220 return super.getCurrentScore();
224 if (this.subTurn
!= 1) return;
225 const c
= move.vanish
[0].c
;
226 const piece
= move.vanish
[0].p
;
227 const firstRank
= c
== "w" ? V
.size
.x
- 1 : 0;
228 if (piece
== V
.KING
) {
229 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
230 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
233 const oppCol
= V
.GetOppCol(c
);
234 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
) {
235 // Opponent's king is captured, game over
236 this.kingPos
[oppCol
] = [-1, -1];
237 move.captureKing
= true;
239 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
241 move.start
.x
== firstRank
&& //our rook moves?
242 this.castleFlags
[c
].includes(move.start
.y
)
244 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
245 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
248 move.end
.x
== oppFirstRank
&& //we took opponent rook?
249 this.castleFlags
[oppCol
].includes(move.end
.y
)
251 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
252 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
257 move.flags
= JSON
.stringify(this.aggregateFlags());
259 V
.PlayOnBoard(this.board
, move);
261 move.turn
= [c
, this.subTurn
];
262 const oppCol
= V
.GetOppCol(c
);
263 const oppLastRank
= (c
== 'w' ? 7 : 0);
264 if (this.subTurn
<= 1) this.reserve
[oppCol
] = null;
265 if (this.subTurn
== 0) {
267 this.reserve
[c
] = null;
269 else if (this.subTurn
== 1) {
272 this.movesCount
== 0 ||
273 !!move.captureKing
||
274 !this.atLeastOneMove()
279 this.promoteFile
.push(-1);
280 move.pushPromote
= true;
286 if (move.end
.x
== oppLastRank
) {
287 this.promoteFile
.push(move.end
.y
);
288 this.reserve
[oppCol
] = {
298 this.promoteFile
.push(-1);
300 move.pushPromote
= true;
306 this.disaggregateFlags(JSON
.parse(move.flags
));
307 V
.UndoOnBoard(this.board
, move);
308 const changeTurn
= (this.turn
!= move.turn
[0]);
309 this.turn
= move.turn
[0];
310 this.subTurn
= move.turn
[1];
311 if (!!move.pushPromote
) {
312 const promoteFile
= this.promoteFile
.pop();
313 if (promoteFile
>= 0) this.reserve
[V
.GetOppCol(this.turn
)] = null;
315 else if (this.subTurn
== 0) {
316 this.reserve
[this.turn
] = {
323 if (changeTurn
) this.movesCount
--;
328 if (this.subTurn
!= 1) return;
329 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
330 // Opponent's king was captured
331 this.kingPos
[move.vanish
[1].c
] = [move.vanish
[1].x
, move.vanish
[1].y
];
332 super.postUndo(move);
336 // Just try to capture as much material as possible (1-half move)
337 const moves
= this.getAllValidMoves();
338 if (this.subTurn
== 0) {
339 this.play(moves
[3]); //HACK... 3 = queen index
340 const res
= this.getComputerMove();
342 return [moves
[3], res
];
344 // subTurn == 1 (necessarily)
346 let maxValue
= -V
.INFINITY
;
347 for (let m
of moves
) {
349 if (m
.vanish
.length
== 2) {
350 // Compute delta value, to not give all material on pawns... (TODO)
351 // 0.5 to favor captures (if same type of piece).
353 ChessRules
.VALUES
[m
.vanish
[1].p
] - ChessRules
.VALUES
[m
.vanish
[0].p
];
355 if (value
> maxValue
) {
359 else if (value
== maxValue
) candidates
.push(m
);
361 const m1
= candidates
[randInt(candidates
.length
)];
364 if (this.subTurn
== 2) {
365 // Just pick a pawn at random
366 const moves2
= this.getAllValidMoves();
367 m2
= moves2
[randInt(moves2
.length
)];
375 if (this.subTurn
== 0)
376 return move.appear
[0].p
.toUpperCase() + "@" + V
.CoordsToSquare(move.end
);
377 if (this.subTurn
== 1) return super.getNotation(move);
378 // subTurn == 2: just indicate final square
379 return V
.CoordsToSquare(move.end
);