A few fixes (for updateCastleFlags()) + add Madhouse and Pocketknight variants
[vchess.git] / client / src / variants / Shako.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt, sample } from "@/utils/alea";
4
5 export class ShakoRules extends ChessRules {
6 static get PawnSpecs() {
7 return Object.assign(
8 {},
9 ChessRules.PawnSpecs,
10 {
11 initShift: { w: 2, b: 2 },
12 promotions:
13 ChessRules.PawnSpecs.promotions.concat([V.ELEPHANT, V.CANNON])
14 }
15 );
16 }
17
18 static get ELEPHANT() {
19 return "e";
20 }
21
22 static get CANNON() {
23 return "c";
24 }
25
26 static get PIECES() {
27 return ChessRules.PIECES.concat([V.ELEPHANT, V.CANNON]);
28 }
29
30 getPpath(b) {
31 const prefix = [V.ELEPHANT, V.CANNON].includes(b[1]) ? "Shako/" : "";
32 return prefix + b;
33 }
34
35 static get steps() {
36 return Object.assign(
37 {},
38 ChessRules.steps,
39 {
40 e: [
41 [-1, -1],
42 [-1, 1],
43 [1, -1],
44 [1, 1],
45 [-2, -2],
46 [-2, 2],
47 [2, -2],
48 [2, 2]
49 ]
50 }
51 );
52 }
53
54 static get size() {
55 return { x: 10, y: 10};
56 }
57
58 getPotentialMovesFrom([x, y]) {
59 switch (this.getPiece(x, y)) {
60 case V.ELEPHANT:
61 return this.getPotentialElephantMoves([x, y]);
62 case V.CANNON:
63 return this.getPotentialCannonMoves([x, y]);
64 default:
65 return super.getPotentialMovesFrom([x, y]);
66 }
67 }
68
69 getPotentialElephantMoves([x, y]) {
70 return this.getSlideNJumpMoves([x, y], V.steps[V.ELEPHANT], "oneStep");
71 }
72
73 getPotentialCannonMoves([x, y]) {
74 const oppCol = V.GetOppCol(this.turn);
75 let moves = [];
76 // Look in every direction until an obstacle (to jump) is met
77 for (const step of V.steps[V.ROOK]) {
78 let i = x + step[0];
79 let j = y + step[1];
80 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
81 moves.push(this.getBasicMove([x, y], [i, j]));
82 i += step[0];
83 j += step[1];
84 }
85 // Then, search for an enemy
86 i += step[0];
87 j += step[1];
88 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
89 i += step[0];
90 j += step[1];
91 }
92 if (V.OnBoard(i, j) && this.getColor(i, j) == oppCol)
93 moves.push(this.getBasicMove([x, y], [i, j]));
94 }
95 return moves;
96 }
97
98 getCastleMoves([x, y]) {
99 const c = this.getColor(x, y);
100 if (x != (c == "w" ? V.size.x - 2 : 1) || y != this.INIT_COL_KING[c])
101 return []; //x isn't second rank, or king has moved (shortcut)
102
103 // Castling ?
104 const oppCol = V.GetOppCol(c);
105 let moves = [];
106 let i = 0;
107 // King, then rook:
108 const finalSquares = [
109 [3, 4],
110 [7, 6]
111 ];
112 castlingCheck: for (
113 let castleSide = 0;
114 castleSide < 2;
115 castleSide++ //large, then small
116 ) {
117 if (this.castleFlags[c][castleSide] >= V.size.y) continue;
118 // If this code is reached, rook and king are on initial position
119
120 const rookPos = this.castleFlags[c][castleSide];
121
122 // Nothing on the path of the king ? (and no checks)
123 const castlingPiece = this.getPiece(x, rookPos);
124 const finDist = finalSquares[castleSide][0] - y;
125 let step = finDist / Math.max(1, Math.abs(finDist));
126 i = y;
127 do {
128 if (
129 this.isAttacked([x, i], oppCol) ||
130 (this.board[x][i] != V.EMPTY &&
131 // NOTE: next check is enough, because of chessboard constraints
132 (this.getColor(x, i) != c ||
133 ![V.KING, castlingPiece].includes(this.getPiece(x, i))))
134 ) {
135 continue castlingCheck;
136 }
137 i += step;
138 } while (i != finalSquares[castleSide][0]);
139
140 // Nothing on the path to the rook?
141 step = castleSide == 0 ? -1 : 1;
142 for (i = y + step; i != rookPos; i += step) {
143 if (this.board[x][i] != V.EMPTY) continue castlingCheck;
144 }
145
146 // Nothing on final squares, except maybe king and castling rook?
147 for (i = 0; i < 2; i++) {
148 if (
149 finalSquares[castleSide][i] != rookPos &&
150 this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
151 (
152 this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
153 this.getColor(x, finalSquares[castleSide][i]) != c
154 )
155 ) {
156 continue castlingCheck;
157 }
158 }
159
160 // If this code is reached, castle is valid
161 moves.push(
162 new Move({
163 appear: [
164 new PiPo({
165 x: x,
166 y: finalSquares[castleSide][0],
167 p: V.KING,
168 c: c
169 }),
170 new PiPo({
171 x: x,
172 y: finalSquares[castleSide][1],
173 p: castlingPiece,
174 c: c
175 })
176 ],
177 vanish: [
178 new PiPo({ x: x, y: y, p: V.KING, c: c }),
179 new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c })
180 ],
181 end:
182 Math.abs(y - rookPos) <= 2
183 ? { x: x, y: rookPos }
184 : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
185 })
186 );
187 }
188
189 return moves;
190 }
191
192 isAttacked(sq, color) {
193 return (
194 super.isAttacked(sq, color) ||
195 this.isAttackedByElephant(sq, color) ||
196 this.isAttackedByCannon(sq, color)
197 );
198 }
199
200 isAttackedByElephant(sq, color) {
201 return (
202 this.isAttackedBySlideNJump(
203 sq, color, V.ELEPHANT, V.steps[V.ELEPHANT], "oneStep"
204 )
205 );
206 }
207
208 isAttackedByCannon([x, y], color) {
209 // Reversed process: is there an obstacle in line,
210 // and a cannon next in the same line?
211 for (const step of V.steps[V.ROOK]) {
212 let [i, j] = [x+step[0], y+step[1]];
213 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
214 i += step[0];
215 j += step[1];
216 }
217 if (V.OnBoard(i, j)) {
218 // Keep looking in this direction
219 i += step[0];
220 j += step[1];
221 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
222 i += step[0];
223 j += step[1];
224 }
225 if (
226 V.OnBoard(i, j) &&
227 this.getPiece(i, j) == V.CANNON &&
228 this.getColor(i, j) == color
229 ) {
230 return true;
231 }
232 }
233 }
234 return false;
235 }
236
237 updateCastleFlags(move, piece) {
238 const c = V.GetOppCol(this.turn);
239 const firstRank = (c == "w" ? V.size.x - 2 : 1);
240 // Update castling flags if rooks are moved
241 const oppCol = this.turn;
242 const oppFirstRank = V.size.x - 1 - firstRank;
243 if (piece == V.KING)
244 this.castleFlags[c] = [V.size.y, V.size.y];
245 else if (
246 move.start.x == firstRank && //our rook moves?
247 this.castleFlags[c].includes(move.start.y)
248 ) {
249 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
250 this.castleFlags[c][flagIdx] = V.size.y;
251 }
252 // NOTE: not "else if" because a rook could take an opposing rook
253 if (
254 move.end.x == oppFirstRank && //we took opponent rook?
255 this.castleFlags[oppCol].includes(move.end.y)
256 ) {
257 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
258 this.castleFlags[oppCol][flagIdx] = V.size.y;
259 }
260 }
261
262 static get VALUES() {
263 return Object.assign(
264 { e: 3, c: 5 },
265 ChessRules.VALUES
266 );
267 }
268
269 static get SEARCH_DEPTH() {
270 return 2;
271 }
272
273 static GenRandInitFen(randomness) {
274 if (randomness == 0) {
275 return (
276 "c8c/ernbqkbnre/pppppppppp/91/91/91/91/PPPPPPPPPP/ERNBQKBNRE/C8C " +
277 "w 0 bibi -"
278 );
279 }
280
281 let pieces = { w: new Array(10), b: new Array(10) };
282 let flags = "";
283 // Shuffle pieces on second (and before-last rank if randomness == 2)
284 for (let c of ["w", "b"]) {
285 if (c == 'b' && randomness == 1) {
286 pieces['b'] = pieces['w'];
287 flags += flags;
288 break;
289 }
290
291 let positions = ArrayFun.range(10);
292
293 // Get random squares for bishops + elephants
294 const be1Pos = sample([0, 2, 4, 6, 8], 2);
295 const be2Pos = sample([1, 3, 5, 7, 9], 2);
296 const bishop1Pos = be1Pos[0];
297 const bishop2Pos = be2Pos[0];
298 const elephant1Pos = be1Pos[1];
299 const elephant2Pos = be2Pos[1];
300 // Remove chosen squares
301 (be1Pos.concat(be2Pos)).sort((x, y) => y - x).forEach(pos => {
302 positions.splice(pos, 1);
303 });
304
305 let randIndex = randInt(6);
306 const knight1Pos = positions[randIndex];
307 positions.splice(randIndex, 1);
308 randIndex = randInt(5);
309 const knight2Pos = positions[randIndex];
310 positions.splice(randIndex, 1);
311
312 randIndex = randInt(4);
313 const queenPos = positions[randIndex];
314 positions.splice(randIndex, 1);
315
316 const rook1Pos = positions[0];
317 const kingPos = positions[1];
318 const rook2Pos = positions[2];
319
320 pieces[c][elephant1Pos] = "e";
321 pieces[c][rook1Pos] = "r";
322 pieces[c][knight1Pos] = "n";
323 pieces[c][bishop1Pos] = "b";
324 pieces[c][queenPos] = "q";
325 pieces[c][kingPos] = "k";
326 pieces[c][bishop2Pos] = "b";
327 pieces[c][knight2Pos] = "n";
328 pieces[c][rook2Pos] = "r";
329 pieces[c][elephant2Pos] = "e";
330 flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
331 }
332 // Add turn + flags + enpassant
333 return (
334 "c8c/" + pieces["b"].join("") +
335 "/pppppppppp/91/91/91/91/PPPPPPPPPP/" +
336 pieces["w"].join("").toUpperCase() + "/C8C" +
337 " w 0 " + flags + " -"
338 );
339 }
340 };