Fix Titan (king tracking)
[vchess.git] / client / src / variants / Titan.js
CommitLineData
7e8a7ea1 1import { ChessRules, Move, PiPo } from "@/base_rules";
b0d55a05
BA
2
3export class TitanRules extends ChessRules {
7e8a7ea1
BA
4
5 static get IMAGE_EXTENSION() {
6 // Temporarily, for the time SVG pieces are being designed:
7 return ".png";
8 }
b0d55a05 9
31c53595 10 // Decode if normal piece, or + bishop or knight
7e8a7ea1
BA
11 getPiece(x, y) {
12 const piece = this.board[x][y].charAt(1);
31c53595
BA
13 if (ChessRules.PIECES.includes(piece)) return piece;
14 // Augmented piece:
15 switch (piece) {
16 case 'a':
17 case 'c':
18 return 'b';
19 case 'j':
20 case 'l':
21 return 'k';
22 case 'm':
23 case 'o':
24 return 'n';
25 case 's':
26 case 't':
27 return 'q';
28 case 'u':
29 case 'v':
30 return 'r';
31 }
32 }
33
b0d55a05
BA
34 // Code: a/c = bishop + knight/bishop j/l for king,
35 // m/o for knight, s/t for queen, u/v for rook
36 static get AUGMENTED_PIECES() {
31c53595
BA
37 return [
38 'a',
39 'c',
40 'j',
41 'l',
42 'm',
43 'o',
44 's',
45 't',
46 'u',
47 'v'
48 ];
b0d55a05 49 }
31c53595 50
7e8a7ea1
BA
51 getPpath(b) {
52 return "Titan/" + b;
53 }
54
31c53595 55 // Decode above notation into additional piece
b0d55a05 56 getExtraPiece(symbol) {
31c53595
BA
57 if (['a','j','m','s','u'].includes(symbol))
58 return 'n';
59 return 'b';
b0d55a05
BA
60 }
61
7e8a7ea1
BA
62 // Inverse operation: augment piece
63 getAugmented(piece) {
64 const knight = this.movesCount <= 1;
65 switch (piece) {
66 case V.ROOK: return (knight ? 'u' : 'v');
67 case V.KNIGHT: return (knight ? 'm' : 'o');
68 case V.BISHOP: return (knight ? 'a' : 'c');
69 case V.QUEEN: return (knight ? 's' : 't');
70 case V.KING: return (knight ? 'j' : 'l');
71 }
72 return '_'; //never reached
73 }
74
75 static IsGoodPosition(position) {
76 if (position.length == 0) return false;
77 const rows = position.split("/");
78 if (rows.length != V.size.x) return false;
79 let kings = { "w": 0, "b": 0 };
80 const allPiecesCodes = V.PIECES.concat(V.AUGMENTED_PIECES);
81 const kingBlackCodes = ['j','k','l'];
82 const kingWhiteCodes = ['J','K','L'];
83 for (let row of rows) {
84 let sumElts = 0;
85 for (let i = 0; i < row.length; i++) {
86 if (kingBlackCodes.includes(row[i])) kings['b']++;
87 else if (kingWhiteCodes.includes(row[i])) kings['w']++;
88 if (allPiecesCodes.includes(row[i].toLowerCase())) sumElts++;
89 else {
90 const num = parseInt(row[i], 10);
91 if (isNaN(num)) return false;
92 sumElts += num;
93 }
94 }
95 if (sumElts != V.size.y) return false;
96 }
97 // Both kings should be on board, only one of each color:
98 if (Object.values(kings).some(v => v != 1)) return false;
99 return true;
100 }
101
102 // Kings may be augmented:
103 scanKings(fen) {
104 this.kingPos = { w: [-1, -1], b: [-1, -1] };
105 const rows = V.ParseFen(fen).position.split("/");
106 for (let i = 0; i < rows.length; i++) {
107 let k = 0; //column index on board
108 for (let j = 0; j < rows[i].length; j++) {
109 const piece = rows[i].charAt(j);
110 if (['j','k','l'].includes(piece.toLowerCase())) {
111 const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b');
112 this.kingPos[color] = [i, k];
113 }
114 else {
115 const num = parseInt(rows[i].charAt(j), 10);
116 if (!isNaN(num)) k += num - 1;
117 }
118 k++;
119 }
120 }
121 }
122
e9437f4b
BA
123 canIplay(side, [x, y]) {
124 if (this.movesCount >= 4) return super.canIplay(side, [x, y]);
125 return (
126 this.turn == side &&
127 (
128 (side == 'w' && x == 7) ||
129 (side == 'b' && x == 0)
130 )
131 );
132 }
133
134 hoverHighlight([x, y]) {
135 const c = this.turn;
136 return (
137 this.movesCount <= 3 &&
138 ((c == 'w' && x == 7) || (c == 'b' && x == 0))
139 );
140 }
141
142 onlyClick([x, y]) {
143 return (
144 this.movesCount <= 3 ||
145 // TODO: next line theoretically shouldn't be required...
146 (this.movesCount == 4 && this.getColor(x, y) != this.turn)
147 );
148 }
149
150 // Special case of move 1 = choose squares, knight first, then bishop
151 doClick(square) {
152 if (this.movesCount >= 4) return null;
153 const color = this.turn;
154 const [x, y] = [square[0], square[1]];
155 if ((color == 'w' && x != 7) || (color == 'b' && x != 0)) return null;
156 const selectedPiece = this.board[x][y][1];
157 return new Move({
158 appear: [
159 new PiPo({
160 x: x,
161 y: y,
162 c: color,
163 p: this.getAugmented(selectedPiece)
164 })
165 ],
166 vanish: [
167 new PiPo({
168 x: x,
169 y: y,
170 c: color,
171 p: selectedPiece
172 })
173 ],
174 start: { x: x, y: y },
175 end: { x: x, y: y }
176 });
177 }
178
31c53595
BA
179 // If piece not in usual list, bishop or knight appears.
180 getPotentialMovesFrom([x, y]) {
7e8a7ea1
BA
181 if (this.movesCount <= 3) {
182 // Setup stage
e9437f4b
BA
183 const move = this.doClick([x, y]);
184 return (!move ? [] : [move]);
7e8a7ea1
BA
185 }
186 let moves = super.getPotentialMovesFrom([x, y]);
187 const initialPiece = this.getPiece(x, y);
b0d55a05
BA
188 const color = this.turn;
189 if (
3208c667
BA
190 ((color == 'w' && x == 7) || (color == "b" && x == 0)) &&
191 V.AUGMENTED_PIECES.includes(this.board[x][y][1])
b0d55a05 192 ) {
31c53595 193 const newPiece = this.getExtraPiece(this.board[x][y][1]);
b0d55a05 194 moves.forEach(m => {
7e8a7ea1 195 m.appear[0].p = initialPiece;
b0d55a05
BA
196 m.appear.push(
197 new PiPo({
198 p: newPiece,
199 c: color,
31c53595
BA
200 x: x,
201 y: y
b0d55a05
BA
202 })
203 );
204 });
3208c667
BA
205 moves.forEach(m => {
206 if (m.vanish.length <= 1) return;
207 const [vx, vy] = [m.vanish[1].x, m.vanish[1].y];
208 if (
209 m.appear.length >= 2 && //3 if the king was also augmented
210 m.vanish.length == 2 &&
211 m.vanish[1].c == color &&
212 V.AUGMENTED_PIECES.includes(this.board[vx][vy][1])
213 ) {
214 // Castle, rook is an "augmented piece"
215 m.appear[1].p = V.ROOK;
216 m.appear.push(
217 new PiPo({
218 p: this.getExtraPiece(this.board[vx][vy][1]),
219 c: color,
220 x: vx,
221 y: vy
222 })
223 );
224 }
225 });
b0d55a05
BA
226 }
227 return moves;
228 }
229
7e8a7ea1 230 postPlay(move) {
70c94aa7
BA
231 if (this.movesCount > 4) {
232 let piece = move.vanish[0].p;
233 if (['j', 'l'].includes(piece)) piece = V.KING;
234 if (piece == V.KING && move.appear.length > 0)
235 this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
236 this.updateCastleFlags(move, piece);
237 }
7e8a7ea1
BA
238 }
239
240 postUndo(move) {
70c94aa7
BA
241 if (this.movesCount >= 4) {
242 const c = this.getColor(move.start.x, move.start.y);
243 if (['j', 'k', 'l'].includes(this.getPiece(move.start.x, move.start.y)))
244 this.kingPos[c] = [move.start.x, move.start.y];
245 }
7e8a7ea1
BA
246 }
247
248 evalPosition() {
249 let evaluation = 0;
250 for (let i = 0; i < V.size.x; i++) {
251 for (let j = 0; j < V.size.y; j++) {
252 if (this.board[i][j] != V.EMPTY) {
253 const sign = this.getColor(i, j) == "w" ? 1 : -1;
254 const piece = this.getPiece(i, j);
255 evaluation += sign * V.VALUES[piece];
256 const symbol = this.board[i][j][1];
257 if (V.AUGMENTED_PIECES.includes(symbol)) {
258 const extraPiece = this.getExtraPiece(symbol);
259 evaluation += sign * V.VALUES[extraPiece]
260 }
261 }
262 }
263 }
264 return evaluation;
265 }
266
267 getNotation(move) {
268 if (
269 move.appear[0].x != move.vanish[0].x ||
270 move.appear[0].y != move.vanish[0].y
271 ) {
272 if (
273 V.AUGMENTED_PIECES.includes(move.vanish[0].p) ||
274 (
58327965 275 move.appear.length >= 2 &&
7e8a7ea1
BA
276 move.vanish.length >= 2 &&
277 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
278 )
279 ) {
280 // Simplify move before calling super.getNotation()
281 let smove = JSON.parse(JSON.stringify(move));
282 if (ChessRules.PIECES.includes(move.vanish[0].p)) {
283 // Castle with an augmented rook
284 smove.appear.pop();
285 smove.vanish[1].p = smove.appear[1].p;
286 }
287 else {
288 // Moving an augmented piece
289 smove.appear.pop();
290 smove.vanish[0].p = smove.appear[0].p;
291 if (
292 smove.vanish.length == 2 &&
293 smove.vanish[0].c == smove.vanish[1].c &&
294 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
295 ) {
296 // Castle with an augmented rook
297 smove.appear.pop();
298 smove.vanish[1].p = smove.appear[1].p;
299 }
300 }
301 return super.getNotation(smove);
302 }
303 // Else, more common case:
304 return super.getNotation(move);
305 }
306 // First moves in game, placements:
307 const square = V.CoordsToSquare(move.appear[0]);
308 const reserve =
309 (['a','j','m','s','u'].includes(move.appear[0].p) ? 'N' : 'B');
310 return '+' + reserve + '@' + square;
311 }
312
b0d55a05 313};