Fix Cannibal variant. TODO: better kings images
[vchess.git] / client / src / variants / Cannibal.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2
3 export class CannibalRules extends ChessRules {
4 static get KING_CODE() {
5 return {
6 'p': 's',
7 'r': 'u',
8 'n': 'o',
9 'b': 'c',
10 'q': 't'
11 };
12 }
13
14 static get KING_DECODE() {
15 return {
16 's': 'p',
17 'u': 'r',
18 'o': 'n',
19 'c': 'b',
20 't': 'q'
21 };
22 }
23
24 // Kings may be disguised:
25 getPiece(x, y) {
26 const piece = this.board[x][y].charAt(1);
27 if (Object.keys(V.KING_DECODE).includes(piece))
28 return V.KING_DECODE[piece];
29 return piece;
30 }
31
32 getPpath(b) {
33 return (Object.keys(V.KING_DECODE).includes(b[1]) ? "Cannibal/" : "") + b;
34 }
35
36 // Kings may be disguised:
37 setOtherVariables(fen) {
38 super.setOtherVariables(fen);
39 const rows = V.ParseFen(fen).position.split("/");
40 if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0) {
41 for (let i = 0; i < rows.length; i++) {
42 let k = 0; //column index on board
43 for (let j = 0; j < rows[i].length; j++) {
44 const piece = rows[i].charAt(j);
45 if (Object.keys(V.KING_DECODE).includes(piece.toLowerCase())) {
46 const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b');
47 this.kingPos[color] = [i, k];
48 } else {
49 const num = parseInt(rows[i].charAt(j));
50 if (!isNaN(num)) k += num - 1;
51 }
52 k++;
53 }
54 }
55 }
56 }
57
58 // Trim all non-capturing moves
59 static KeepCaptures(moves) {
60 return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1);
61 }
62
63 // Stop at the first capture found (if any)
64 atLeastOneCapture() {
65 const color = this.turn;
66 const oppCol = V.GetOppCol(color);
67 for (let i = 0; i < V.size.x; i++) {
68 for (let j = 0; j < V.size.y; j++) {
69 if (
70 this.board[i][j] != V.EMPTY &&
71 this.getColor(i, j) != oppCol &&
72 this.filterValid(this.getPotentialMovesFrom([i, j])).some(m =>
73 // Warning: discard castle moves
74 m.vanish.length == 2 && m.appear.length == 1)
75 ) {
76 return true;
77 }
78 }
79 }
80 return false;
81 }
82
83 // Because of the disguised kings, getPiece() could be wrong:
84 // use board[x][y][1] instead (always valid).
85 getBasicMove([sx, sy], [ex, ey], tr) {
86 const initColor = this.getColor(sx, sy);
87 const initPiece = this.board[sx][sy].charAt(1);
88 let mv = new Move({
89 appear: [
90 new PiPo({
91 x: ex,
92 y: ey,
93 c: tr ? tr.c : initColor,
94 p: tr ? tr.p : initPiece
95 })
96 ],
97 vanish: [
98 new PiPo({
99 x: sx,
100 y: sy,
101 c: initColor,
102 p: initPiece
103 })
104 ]
105 });
106
107 // The opponent piece disappears if we take it
108 if (this.board[ex][ey] != V.EMPTY) {
109 mv.vanish.push(
110 new PiPo({
111 x: ex,
112 y: ey,
113 c: this.getColor(ex, ey),
114 p: this.board[ex][ey].charAt(1)
115 })
116 );
117
118 // If the captured piece has a different nature: take it as well
119 if (mv.vanish[0].p != mv.vanish[1].p) {
120 if (
121 mv.vanish[0].p == V.KING ||
122 Object.keys(V.KING_DECODE).includes(mv.vanish[0].p)
123 ) {
124 mv.appear[0].p = V.KING_CODE[mv.vanish[1].p];
125 } else mv.appear[0].p = mv.vanish[1].p;
126 }
127 }
128 else if (!!tr && mv.vanish[0].p != V.PAWN)
129 // Special case of a non-capturing king-as-pawn promotion
130 mv.appear[0].p = V.KING_CODE[tr.p];
131
132 return mv;
133 }
134
135 getPotentialMovesFrom([x, y]) {
136 const piece = this.board[x][y].charAt(1);
137 if (Object.keys(V.KING_DECODE).includes(piece))
138 return super.getPotentialMovesFrom([x, y], V.KING_DECODE[piece]);
139 return super.getPotentialMovesFrom([x, y], piece);
140 }
141
142 addPawnMoves([x1, y1], [x2, y2], moves) {
143 let finalPieces = [V.PAWN];
144 const color = this.turn;
145 const lastRank = (color == "w" ? 0 : V.size.x - 1);
146 if (x2 == lastRank) {
147 if (this.board[x2][y2] != V.EMPTY)
148 // Cannibal rules: no choice if capture
149 finalPieces = [this.getPiece(x2, y2)];
150 else finalPieces = V.PawnSpecs.promotions;
151 }
152 let tr = null;
153 for (let piece of finalPieces) {
154 tr = (piece != V.PAWN ? { c: color, p: piece } : null);
155 moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
156 }
157 }
158
159 getPossibleMovesFrom(sq) {
160 let moves = this.filterValid(this.getPotentialMovesFrom(sq));
161 const captureMoves = V.KeepCaptures(moves);
162 if (captureMoves.length > 0) return captureMoves;
163 if (this.atLeastOneCapture()) return [];
164 return moves;
165 }
166
167 getAllValidMoves() {
168 const moves = super.getAllValidMoves();
169 if (moves.some(m => m.vanish.length == 2)) return V.KeepCaptures(moves);
170 return moves;
171 }
172
173 postPlay(move) {
174 const c = V.GetOppCol(this.turn);
175 const piece = move.appear[0].p;
176 // Update king position + flags
177 if (piece == V.KING || Object.keys(V.KING_DECODE).includes(piece)) {
178 this.kingPos[c][0] = move.appear[0].x;
179 this.kingPos[c][1] = move.appear[0].y;
180 this.castleFlags[c] = [V.size.y, V.size.y];
181 return;
182 }
183 super.updateCastleFlags(move);
184 }
185
186 postUndo(move) {
187 // (Potentially) Reset king position
188 const c = this.getColor(move.start.x, move.start.y);
189 const piece = move.appear[0].p;
190 if (piece == V.KING || Object.keys(V.KING_DECODE).includes(piece))
191 this.kingPos[c] = [move.start.x, move.start.y];
192 }
193
194 static get VALUES() {
195 return {
196 p: 1,
197 r: 5,
198 n: 3,
199 b: 3,
200 q: 9,
201 k: 5
202 };
203 }
204
205 static get SEARCH_DEPTH() {
206 return 4;
207 }
208 };