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