Fix Arena diag
[vchess.git] / client / src / variants / Titan.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2
3 export class TitanRules extends ChessRules {
4
5 static get IMAGE_EXTENSION() {
6 // Temporarily, for the time SVG pieces are being designed:
7 return ".png";
8 }
9
10 // Decode if normal piece, or + bishop or knight
11 getPiece(x, y) {
12 const piece = this.board[x][y].charAt(1);
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
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() {
37 return [
38 'a',
39 'c',
40 'j',
41 'l',
42 'm',
43 'o',
44 's',
45 't',
46 'u',
47 'v'
48 ];
49 }
50
51 getPpath(b) {
52 return "Titan/" + b;
53 }
54
55 // Decode above notation into additional piece
56 getExtraPiece(symbol) {
57 if (['a','j','m','s','u'].includes(symbol))
58 return 'n';
59 return 'b';
60 }
61
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
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 // Prevent re-augmenting a chosen piece:
158 if (!ChessRules.PIECES.includes(selectedPiece)) return null;
159 return new Move({
160 appear: [
161 new PiPo({
162 x: x,
163 y: y,
164 c: color,
165 p: this.getAugmented(selectedPiece)
166 })
167 ],
168 vanish: [
169 new PiPo({
170 x: x,
171 y: y,
172 c: color,
173 p: selectedPiece
174 })
175 ],
176 start: { x: x, y: y },
177 end: { x: x, y: y }
178 });
179 }
180
181 // If piece not in usual list, bishop or knight appears.
182 getPotentialMovesFrom([x, y]) {
183 if (this.movesCount <= 3) {
184 // Setup stage
185 const move = this.doClick([x, y]);
186 return (!move ? [] : [move]);
187 }
188 let moves = super.getPotentialMovesFrom([x, y]);
189 const initialPiece = this.getPiece(x, y);
190 const color = this.turn;
191 if (
192 ((color == 'w' && x == 7) || (color == "b" && x == 0)) &&
193 V.AUGMENTED_PIECES.includes(this.board[x][y][1])
194 ) {
195 const newPiece = this.getExtraPiece(this.board[x][y][1]);
196 moves.forEach(m => {
197 m.appear[0].p = initialPiece;
198 m.appear.push(
199 new PiPo({
200 p: newPiece,
201 c: color,
202 x: x,
203 y: y
204 })
205 );
206 });
207 moves.forEach(m => {
208 if (m.vanish.length <= 1) return;
209 const [vx, vy] = [m.vanish[1].x, m.vanish[1].y];
210 if (
211 m.appear.length >= 2 && //3 if the king was also augmented
212 m.vanish.length == 2 &&
213 m.vanish[1].c == color &&
214 V.AUGMENTED_PIECES.includes(this.board[vx][vy][1])
215 ) {
216 // Castle, rook is an "augmented piece"
217 m.appear[1].p = V.ROOK;
218 m.appear.push(
219 new PiPo({
220 p: this.getExtraPiece(this.board[vx][vy][1]),
221 c: color,
222 x: vx,
223 y: vy
224 })
225 );
226 }
227 });
228 }
229 return moves;
230 }
231
232 postPlay(move) {
233 if (this.movesCount > 4) {
234 let piece = move.vanish[0].p;
235 if (['j', 'l'].includes(piece)) piece = V.KING;
236 if (piece == V.KING)
237 this.kingPos[move.appear[0].c] = [move.appear[0].x, move.appear[0].y];
238 this.updateCastleFlags(move, piece);
239 }
240 }
241
242 postUndo(move) {
243 if (this.movesCount >= 4) {
244 if (['j', 'k', 'l'].includes(this.getPiece(move.start.x, move.start.y)))
245 this.kingPos[move.vanish[0].c] = [move.start.x, move.start.y];
246 }
247 }
248
249 evalPosition() {
250 let evaluation = 0;
251 for (let i = 0; i < V.size.x; i++) {
252 for (let j = 0; j < V.size.y; j++) {
253 if (this.board[i][j] != V.EMPTY) {
254 const sign = this.getColor(i, j) == "w" ? 1 : -1;
255 const piece = this.getPiece(i, j);
256 evaluation += sign * V.VALUES[piece];
257 const symbol = this.board[i][j][1];
258 if (V.AUGMENTED_PIECES.includes(symbol)) {
259 const extraPiece = this.getExtraPiece(symbol);
260 evaluation += sign * V.VALUES[extraPiece]
261 }
262 }
263 }
264 }
265 return evaluation;
266 }
267
268 getNotation(move) {
269 if (
270 move.appear[0].x != move.vanish[0].x ||
271 move.appear[0].y != move.vanish[0].y
272 ) {
273 if (
274 V.AUGMENTED_PIECES.includes(move.vanish[0].p) ||
275 (
276 move.appear.length >= 2 &&
277 move.vanish.length >= 2 &&
278 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
279 )
280 ) {
281 // Simplify move before calling super.getNotation()
282 let smove = JSON.parse(JSON.stringify(move));
283 if (ChessRules.PIECES.includes(move.vanish[0].p)) {
284 // Castle with an augmented rook
285 smove.appear.pop();
286 smove.vanish[1].p = smove.appear[1].p;
287 }
288 else {
289 // Moving an augmented piece
290 smove.appear.pop();
291 smove.vanish[0].p = smove.appear[0].p;
292 if (
293 smove.vanish.length == 2 &&
294 smove.vanish[0].c == smove.vanish[1].c &&
295 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
296 ) {
297 // Castle with an augmented rook
298 smove.appear.pop();
299 smove.vanish[1].p = smove.appear[1].p;
300 }
301 }
302 return super.getNotation(smove);
303 }
304 // Else, more common case:
305 return super.getNotation(move);
306 }
307 // First moves in game, placements:
308 const square = V.CoordsToSquare(move.appear[0]);
309 const reserve =
310 (['a','j','m','s','u'].includes(move.appear[0].p) ? 'N' : 'B');
311 return '+' + reserve + '@' + square;
312 }
313
314 };