Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Koopa.js
CommitLineData
6e0f2842 1import { ChessRules, PiPo } from "@/base_rules";
e88d69a8
BA
2
3export class KoopaRules extends ChessRules {
7e8a7ea1 4
e88d69a8
BA
5 static get HasEnpassant() {
6 return false;
7 }
8
6e0f2842 9 static get STUNNED() {
e88d69a8
BA
10 return ['s', 'u', 'o', 'c', 't', 'l'];
11 }
12
6e0f2842
BA
13 static get PIECES() {
14 return ChessRules.PIECES.concat(V.STUNNED);
e88d69a8
BA
15 }
16
6e0f2842
BA
17 static ParseFen(fen) {
18 let res = ChessRules.ParseFen(fen);
19 const fenParts = fen.split(" ");
20 res.stunned = fenParts[4];
21 return res;
22 }
23
24 static IsGoodFen(fen) {
25 if (!ChessRules.IsGoodFen(fen)) return false;
26 const fenParsed = V.ParseFen(fen);
27 // 5) Check "stunned"
28 if (
29 !fenParsed.stunned ||
30 (
31 fenParsed.stunned != "-" &&
32 !fenParsed.stunned.match(/^([a-h][1-8][1-4],?)*$/)
33 )
34 ) {
35 return false;
36 }
37 return true;
38 }
39
40 getPpath(b) {
41 return (V.STUNNED.includes(b[1]) ? "Koopa/" : "") + b;
42 }
43
44 getFen() {
45 return super.getFen() + " " + this.getStunnedFen();
46 }
47
48 getFenForRepeat() {
49 return super.getFenForRepeat() + "_" + this.getStunnedFen();
50 }
51
52 getStunnedFen() {
e50a8025
BA
53 const squares = Object.keys(this.stunned);
54 if (squares.length == 0) return "-";
55 return squares.map(square => square + this.stunned[square]).join(",");
6e0f2842
BA
56 }
57
58 // Base GenRandInitFen() is fine because en-passant indicator will
59 // stand for stunned indicator.
60
61 scanKings(fen) {
6e0f2842
BA
62 // Squares of white and black king:
63 this.kingPos = { w: [-1, -1], b: [-1, -1] };
64 const fenRows = V.ParseFen(fen).position.split("/");
65 const startRow = { 'w': V.size.x - 1, 'b': 0 };
66 for (let i = 0; i < fenRows.length; i++) {
67 let k = 0; //column index on board
68 for (let j = 0; j < fenRows[i].length; j++) {
69 switch (fenRows[i].charAt(j)) {
70 case "k":
71 case "l":
72 this.kingPos["b"] = [i, k];
6e0f2842
BA
73 break;
74 case "K":
75 case "L":
76 this.kingPos["w"] = [i, k];
6e0f2842
BA
77 break;
78 default: {
e50a8025 79 const num = parseInt(fenRows[i].charAt(j), 10);
6e0f2842
BA
80 if (!isNaN(num)) k += num - 1;
81 }
82 }
83 k++;
84 }
85 }
86 }
87
88 setOtherVariables(fen) {
89 super.setOtherVariables(fen);
90 let stunnedArray = [];
91 const stunnedFen = V.ParseFen(fen).stunned;
92 if (stunnedFen != "-") {
93 stunnedArray =
94 stunnedFen
95 .split(",")
96 .map(s => {
97 return {
98 square: s.substr(0, 2),
e50a8025 99 state: parseInt(s[2], 10)
6e0f2842
BA
100 };
101 });
102 }
103 this.stunned = {};
104 stunnedArray.forEach(s => {
105 this.stunned[s.square] = s.state;
106 });
e88d69a8
BA
107 }
108
109 getNormalizedStep(step) {
110 const [deltaX, deltaY] = [Math.abs(step[0]), Math.abs(step[1])];
111 if (deltaX == 0 || deltaY == 0 || deltaX == deltaY)
112 return [step[0] / deltaX || 0, step[1] / deltaY || 0];
113 // Knight:
114 const divisor = Math.min(deltaX, deltaY)
115 return [step[0] / divisor, step[1] / divisor];
116 }
117
118 getPotentialMovesFrom([x, y]) {
f9f67166
BA
119 let moves = super.getPotentialMovesFrom([x, y]).filter(m => {
120 if (
121 m.vanish[0].p != V.PAWN ||
122 m.appear[0].p == V.PAWN ||
123 m.vanish.length == 1
124 ) {
125 return true;
126 }
127 // Pawn promotion, "capturing": remove duplicates
128 return m.appear[0].p == V.QUEEN;
129 });
e88d69a8 130 // Complete moves: stuns & kicks
6e0f2842
BA
131 let promoteAfterStun = [];
132 const color = this.turn;
e88d69a8
BA
133 moves.forEach(m => {
134 if (m.vanish.length == 2 && m.appear.length == 1) {
135 const step =
136 this.getNormalizedStep([m.end.x - m.start.x, m.end.y - m.start.y]);
137 // "Capture" something: is target stunned?
6e0f2842 138 if (V.STUNNED.includes(m.vanish[1].p)) {
e88d69a8
BA
139 // Kick it: continue movement in the same direction,
140 // destroying all on its path.
141 let [i, j] = [m.end.x + step[0], m.end.y + step[1]];
142 while (V.OnBoard(i, j)) {
143 if (this.board[i][j] != V.EMPTY) {
144 m.vanish.push(
145 new PiPo({
146 x: i,
147 y: j,
148 c: this.getColor(i, j),
149 p: this.getPiece(i, j)
150 })
151 );
152 }
153 i += step[0];
154 j += step[1];
155 }
156 }
157 else {
158 // The piece is now stunned
6e0f2842 159 m.appear.push(JSON.parse(JSON.stringify(m.vanish[1])));
e88d69a8 160 const pIdx = ChessRules.PIECES.findIndex(p => p == m.appear[1].p);
6e0f2842 161 m.appear[1].p = V.STUNNED[pIdx];
e88d69a8
BA
162 // And the capturer continue in the same direction until an empty
163 // square or the edge of the board, maybe stunning other pieces.
164 let [i, j] = [m.end.x + step[0], m.end.y + step[1]];
165 while (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) {
166 const colIJ = this.getColor(i, j);
167 const pieceIJ = this.getPiece(i, j);
6e0f2842
BA
168 let pIdx = ChessRules.PIECES.findIndex(p => p == pieceIJ);
169 if (pIdx >= 0) {
170 // The piece isn't already stunned
171 m.vanish.push(
172 new PiPo({
173 x: i,
174 y: j,
175 c: colIJ,
176 p: pieceIJ
177 })
178 );
179 m.appear.push(
180 new PiPo({
181 x: i,
182 y: j,
183 c: colIJ,
184 p: V.STUNNED[pIdx]
185 })
186 );
187 }
e88d69a8
BA
188 i += step[0];
189 j += step[1];
190 }
191 if (V.OnBoard(i, j)) {
192 m.appear[0].x = i;
193 m.appear[0].y = j;
194 // Is it a pawn on last rank?
78a75d54
BA
195 if (
196 m.appear[0].p == V.PAWN &&
197 ((color == 'w' && i == 0) || (color == 'b' && i == 7))
198 ) {
6e0f2842
BA
199 m.appear[0].p = V.ROOK;
200 for (let ppiece of [V.KNIGHT, V.BISHOP, V.QUEEN]) {
201 let mp = JSON.parse(JSON.stringify(m));
202 mp.appear[0].p = ppiece;
203 promoteAfterStun.push(mp);
204 }
205 }
e88d69a8 206 }
6e0f2842 207 else
e88d69a8
BA
208 // The piece is out
209 m.appear.shift();
e88d69a8
BA
210 }
211 }
212 });
6e0f2842 213 return moves.concat(promoteAfterStun);
e88d69a8
BA
214 }
215
85a1dcba
BA
216 getPotentialKingMoves(sq) {
217 return (
218 this.getSlideNJumpMoves(
4313762d 219 sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1
7e8a7ea1 220 ).concat(super.getCastleMoves(sq, null, true, ['r']))
85a1dcba
BA
221 );
222 }
223
e88d69a8
BA
224 filterValid(moves) {
225 // Forbid kicking own king out
226 const color = this.turn;
227 return moves.filter(m => {
fccaa878
BA
228 const kingVanish =
229 m.vanish.some(v => v.c == color && ['k', 'l'].includes(v.p));
230 if (kingVanish) {
231 const kingAppear =
232 m.appear.some(a => a.c == color && ['k', 'l'].includes(a.p));
233 return kingAppear;
234 }
235 return true;
e88d69a8
BA
236 });
237 }
238
239 getCheckSquares() {
240 return [];
241 }
242
243 getCurrentScore() {
244 if (this.kingPos['w'][0] < 0) return "0-1";
245 if (this.kingPos['b'][0] < 0) return "1-0";
246 if (!this.atLeastOneMove()) return "1/2";
247 return "*";
248 }
249
250 postPlay(move) {
6e0f2842
BA
251 // Base method is fine because a stunned king (which won't be detected)
252 // can still castle after going back to normal.
253 super.postPlay(move);
6d596afc
BA
254 const color = this.turn;
255 const kp = this.kingPos[color];
256 if (
257 this.board[kp[0], kp[1]] == V.EMPTY ||
258 !['k', 'l'].includes(this.getPiece(kp[0], kp[1])) ||
259 this.getColor(kp[0], kp[1]) != color
260 ) {
261 // King didn't move by itself, and vanished => game over
262 this.kingPos[color] = [-1, -1];
263 }
6e0f2842
BA
264 move.stunned = JSON.stringify(this.stunned);
265 // Array of stunned stage 1 pieces (just back to normal then)
266 Object.keys(this.stunned).forEach(square => {
267 // All (formerly) stunned pieces progress by 1 level, if still on board
268 const coords = V.SquareToCoords(square);
269 const [x, y] = [coords.x, coords.y];
270 if (V.STUNNED.includes(this.board[x][y][1])) {
271 // Stunned piece still on board
272 this.stunned[square]--;
273 if (this.stunned[square] == 0) {
274 delete this.stunned[square];
275 const color = this.getColor(x, y);
276 const piece = this.getPiece(x, y);
277 const pIdx = V.STUNNED.findIndex(p => p == piece);
278 this.board[x][y] = color + ChessRules.PIECES[pIdx];
279 }
280 }
281 else delete this.stunned[square];
282 });
283 // Any new stunned pieces?
284 move.appear.forEach(a => {
285 if (V.STUNNED.includes(a.p))
286 // Set to maximum stun level:
287 this.stunned[V.CoordsToSquare({ x: a.x, y: a.y })] = 4;
288 });
e88d69a8
BA
289 }
290
291 postUndo(move) {
6e0f2842 292 super.postUndo(move);
6d596afc
BA
293 const oppCol = V.GetOppCol(this.turn);
294 if (this.kingPos[oppCol][0] < 0) {
295 // Opponent's king vanished
296 const psq =
297 move.vanish.find((v,i) => i >= 1 && ['k', 'l'].includes(v.p));
298 this.kingPos[oppCol] = [psq.x, psq.y];
6e0f2842
BA
299 }
300 this.stunned = JSON.parse(move.stunned);
301 for (let i=0; i<8; i++) {
302 for (let j=0; j<8; j++) {
303 const square = V.CoordsToSquare({ x: i, y: j });
304 const pieceIJ = this.getPiece(i, j);
305 if (!this.stunned[square]) {
306 const pIdx = V.STUNNED.findIndex(p => p == pieceIJ);
307 if (pIdx >= 0)
308 this.board[i][j] = this.getColor(i, j) + ChessRules.PIECES[pIdx];
309 }
310 else {
311 const pIdx = ChessRules.PIECES.findIndex(p => p == pieceIJ);
312 if (pIdx >= 0)
313 this.board[i][j] = this.getColor(i, j) + V.STUNNED[pIdx];
314 }
315 }
316 }
317 }
318
319 static get VALUES() {
320 return Object.assign(
321 {
322 s: 1,
323 u: 5,
324 o: 3,
325 c: 3,
326 t: 9,
327 l: 1000
328 },
329 ChessRules.VALUES
330 );
331 }
332
333 static get SEARCH_DEPTH() {
334 return 2;
335 }
336
337 getNotation(move) {
338 if (
339 move.appear.length == 2 &&
340 move.vanish.length == 2 &&
341 move.appear.concat(move.vanish).every(
342 av => ChessRules.PIECES.includes(av.p)) &&
343 move.appear[0].p == V.KING
344 ) {
345 if (move.end.y < move.start.y) return "0-0-0";
346 return "0-0";
347 }
348 const finalSquare = V.CoordsToSquare(move.end);
349 const piece = this.getPiece(move.start.x, move.start.y);
350 const captureMark = move.vanish.length >= 2 ? "x" : "";
351 let pawnMark = "";
352 if (piece == 'p' && captureMark.length == 1)
353 pawnMark = V.CoordToColumn(move.start.y); //start column
354 // Piece or pawn movement
355 let notation =
356 (piece == V.PAWN ? pawnMark : piece.toUpperCase()) +
357 captureMark + finalSquare;
358 if (
359 piece == 'p' &&
360 move.appear[0].c == move.vanish[0].c &&
361 move.appear[0].p != 'p'
362 ) {
363 // Promotion
364 notation += "=" + move.appear[0].p.toUpperCase();
365 }
366 return notation;
e88d69a8 367 }
7e8a7ea1 368
e88d69a8 369};