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