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 (side
!= this.turn
) return false;
29 if (this.subTurn
== 0) return (x
>= V
.size
.x
);
30 const c
= this.getColor(x
, y
);
32 (this.subTurn
== 1 && c
== side
) ||
33 (this.subTurn
== 2 && c
!= side
&& this.getPiece(x
, y
) == V
.PAWN
)
37 static ParseFen(fen
) {
38 const fenParts
= fen
.split(" ");
40 ChessRules
.ParseFen(fen
),
41 { promoteFile: fenParts
[4] }
46 const L
= this.promoteFile
.length
;
47 return (this.promoteFile
[L
-1] + 1);
51 return super.getFen() + " " + this.getPromoteFen();
55 return super.getFenForRepeat() + "_" + this.getPromoteFen();
58 static GenRandInitFen(randomness
) {
59 return ChessRules
.GenRandInitFen(randomness
).slice(0, -1) + "0";
63 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
64 return this.board
[i
][j
].charAt(1);
67 static get RESERVE_PIECES() {
69 return [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
72 setOtherVariables(fen
) {
73 super.setOtherVariables(fen
);
74 const fenPromoteFile
= V
.ParseFen(fen
).promoteFile
;
75 this.promoteFile
= [parseInt(fenPromoteFile
, 10) - 1];
76 this.reserve
= { 'w': null, 'b': null };
77 if (this.promoteFile
[0] >= 0) {
88 else this.subTurn
= 1;
91 getReservePpath(index
, color
) {
92 return color
+ V
.RESERVE_PIECES
[index
];
96 // Send a new piece piece to our first rank
97 const color
= this.turn
;
98 const L
= this.promoteFile
.length
;
99 const [rank
, file
] = [color
== 'w' ? 0 : 7, this.promoteFile
[L
-1]];
102 new PiPo({ x: rank
, y: file
, c: color
, p: V
.RESERVE_PIECES
[y
] })
105 new PiPo({ x: rank
, y: file
, c: color
, p: V
.PAWN
})
107 start: { x: 8, y: y
},
108 end: { x: rank
, y: file
}
112 getPotentialMovesFrom([x
, y
]) {
113 if (this.subTurn
== 0)
114 // Reserves, outside of board: x == sizeX(+1)
115 return (x
>= 8 ? [this.getReserveMove(y
)] : []);
116 if (this.subTurn
== 1)
118 return super.getPotentialMovesFrom([x
, y
]);
119 // subTurn == 2: only allowed to push an opponent's pawn (if possible)
120 const oppPawnShift
= (this.turn
== 'w' ? 1 : -1);
122 V
.OnBoard(x
+ oppPawnShift
, y
) &&
123 this.board
[x
+ oppPawnShift
][y
] == V
.EMPTY
125 return [this.getBasicMove([x
, y
], [x
+ oppPawnShift
, y
])];
131 if (this.subTurn
== 0) {
133 for (let y
= 0; y
< V
.RESERVE_PIECES
.length
; y
++)
134 moves
.push(this.getReserveMove(y
));
137 if (this.subTurn
== 1)
138 return this.filterValid(super.getAllPotentialMoves());
139 // subTurn == 2: move opponent's pawn only
141 const oppCol
= V
.GetOppCol(this.turn
);
142 for (let i
= 0; i
< 8; i
++) {
143 for (let j
= 0; j
< 8; j
++) {
145 this.board
[i
][j
] != V
.EMPTY
&&
146 this.getColor(i
, j
) == oppCol
&&
147 this.getPiece(i
, j
) == V
.PAWN
149 Array
.prototype.push
.apply(
150 moves
, this.getPotentialMovesFrom([i
, j
]));
158 if (this.subTurn
!= 1) return moves
; //self-checks by pawns are allowed
159 return super.filterValid(moves
);
163 if (this.subTurn
== 0) return true; //TODO: never called in this situation
164 if (this.subTurn
== 1) {
165 // Cannot use super method: infinite recursive calls
166 const color
= this.turn
;
167 for (let i
= 0; i
< 8; i
++) {
168 for (let j
= 0; j
< 8; j
++) {
169 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
170 const moves
= this.getPotentialMovesFrom([i
, j
]);
171 if (moves
.length
> 0) {
172 for (let k
= 0; k
< moves
.length
; k
++) {
173 const piece
= moves
[k
].vanish
[0].p
;
174 if (piece
== V
.KING
) {
175 this.kingPos
[color
] =
176 [moves
[k
].appear
[0].x
, moves
[k
].appear
[0].y
];
178 V
.PlayOnBoard(this.board
, moves
[k
]);
179 const res
= !this.underCheck(color
);
180 V
.UndoOnBoard(this.board
, moves
[k
]);
181 if (piece
== V
.KING
) {
182 this.kingPos
[color
] =
183 [moves
[k
].vanish
[0].x
, moves
[k
].vanish
[0].y
];
185 if (res
) return true;
193 // subTurn == 2: need to find an enemy pawn which can advance
194 const oppCol
= V
.GetOppCol(this.turn
);
195 const oppPawnShift
= (oppCol
== 'w' ? -1 : 1);
196 for (let i
= 0; i
< 8; i
++) {
197 for (let j
= 0; j
< 8; j
++) {
199 this.board
[i
][j
] != V
.EMPTY
&&
200 this.getColor(i
, j
) == oppCol
&&
201 this.getPiece(i
, j
) == V
.PAWN
&&
202 V
.OnBoard(i
+ oppPawnShift
, j
) &&
203 this.board
[i
+ oppPawnShift
][j
] == V
.EMPTY
213 if (this.kingPos
[this.turn
][0] < 0) return [];
214 return super.getCheckSquares();
218 // If my king disappeared: I lost!
220 if (this.kingPos
[c
][0] < 0) return (c
== 'w' ? "0-1" : "1-0");
221 return super.getCurrentScore();
225 if (this.subTurn
!= 1) return;
226 const c
= move.vanish
[0].c
;
227 const piece
= move.vanish
[0].p
;
228 const firstRank
= c
== "w" ? V
.size
.x
- 1 : 0;
229 if (piece
== V
.KING
) {
230 this.kingPos
[c
] = [move.appear
[0].x
, move.appear
[0].y
];
231 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
234 const oppCol
= V
.GetOppCol(c
);
235 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
) {
236 // Opponent's king is captured, game over
237 this.kingPos
[oppCol
] = [-1, -1];
238 move.captureKing
= true;
240 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
242 move.start
.x
== firstRank
&& //our rook moves?
243 this.castleFlags
[c
].includes(move.start
.y
)
245 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
246 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
249 move.end
.x
== oppFirstRank
&& //we took opponent rook?
250 this.castleFlags
[oppCol
].includes(move.end
.y
)
252 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
253 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
258 move.flags
= JSON
.stringify(this.aggregateFlags());
260 V
.PlayOnBoard(this.board
, move);
262 move.turn
= [c
, this.subTurn
];
263 const oppCol
= V
.GetOppCol(c
);
264 const oppLastRank
= (c
== 'w' ? 7 : 0);
265 if (this.subTurn
<= 1) this.reserve
[oppCol
] = null;
266 if (this.subTurn
== 0) {
268 this.reserve
[c
] = null;
270 else if (this.subTurn
== 1) {
273 this.movesCount
== 0 ||
274 !!move.captureKing
||
275 !this.atLeastOneMove()
280 this.promoteFile
.push(-1);
281 move.pushPromote
= true;
287 if (move.end
.x
== oppLastRank
) {
288 this.promoteFile
.push(move.end
.y
);
289 this.reserve
[oppCol
] = {
299 this.promoteFile
.push(-1);
301 move.pushPromote
= true;
307 this.disaggregateFlags(JSON
.parse(move.flags
));
308 V
.UndoOnBoard(this.board
, move);
309 const changeTurn
= (this.turn
!= move.turn
[0]);
310 this.turn
= move.turn
[0];
311 this.subTurn
= move.turn
[1];
312 if (!!move.pushPromote
) {
313 const promoteFile
= this.promoteFile
.pop();
314 if (promoteFile
>= 0) this.reserve
[V
.GetOppCol(this.turn
)] = null;
316 else if (this.subTurn
== 0) {
317 this.reserve
[this.turn
] = {
324 if (changeTurn
) this.movesCount
--;
329 if (this.subTurn
!= 1) return;
330 if (move.vanish
.length
== 2 && move.vanish
[1].p
== V
.KING
)
331 // Opponent's king was captured
332 this.kingPos
[move.vanish
[1].c
] = [move.vanish
[1].x
, move.vanish
[1].y
];
333 super.postUndo(move);
337 // Just try to capture as much material as possible (1-half move)
338 const moves
= this.getAllValidMoves();
339 if (this.subTurn
== 0) {
340 this.play(moves
[3]); //HACK... 3 = queen index
341 const res
= this.getComputerMove();
343 return [moves
[3], res
];
345 // subTurn == 1 (necessarily)
347 let maxValue
= -V
.INFINITY
;
348 for (let m
of moves
) {
350 if (m
.vanish
.length
== 2) {
351 // Compute delta value, to not give all material on pawns... (TODO)
352 // 0.5 to favor captures (if same type of piece).
354 ChessRules
.VALUES
[m
.vanish
[1].p
] - ChessRules
.VALUES
[m
.vanish
[0].p
];
356 if (value
> maxValue
) {
360 else if (value
== maxValue
) candidates
.push(m
);
362 const m1
= candidates
[randInt(candidates
.length
)];
365 if (this.subTurn
== 2) {
366 // Just pick a pawn at random
367 const moves2
= this.getAllValidMoves();
368 m2
= moves2
[randInt(moves2
.length
)];
376 if (this.subTurn
== 0)
377 return move.appear
[0].p
.toUpperCase() + "@" + V
.CoordsToSquare(move.end
);
378 if (this.subTurn
== 1) return super.getNotation(move);
379 // subTurn == 2: just indicate final square
380 return V
.CoordsToSquare(move.end
);