Randomize Pandemonium
[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 // If piece not in usual list, bishop or knight appears.
124 getPotentialMovesFrom([x, y]) {
125 if (this.movesCount <= 3) {
126 // Setup stage
127 const color = this.getColor(x, y);
128 const firstRank = (color == 'w' ? 7 : 0);
129 if (x != firstRank || V.AUGMENTED_PIECES.includes(this.board[x][y][1]))
130 return [];
131 const piece = this.getPiece(x, y);
132 const move = new Move({
133 appear: [
134 new PiPo({ x: x, y: y, c: color, p: this.getAugmented(piece) })
135 ],
136 vanish: [
137 new PiPo({ x: x, y: y, c: color, p: piece })
138 ],
139 start: { x: x, y: y },
140 end: { x: x, y: y }
141 });
142 return [move];
143 }
144 let moves = super.getPotentialMovesFrom([x, y]);
145 const initialPiece = this.getPiece(x, y);
146 const color = this.turn;
147 if (
148 ((color == 'w' && x == 7) || (color == "b" && x == 0)) &&
149 V.AUGMENTED_PIECES.includes(this.board[x][y][1])
150 ) {
151 const newPiece = this.getExtraPiece(this.board[x][y][1]);
152 moves.forEach(m => {
153 m.appear[0].p = initialPiece;
154 m.appear.push(
155 new PiPo({
156 p: newPiece,
157 c: color,
158 x: x,
159 y: y
160 })
161 );
162 });
163 moves.forEach(m => {
164 if (m.vanish.length <= 1) return;
165 const [vx, vy] = [m.vanish[1].x, m.vanish[1].y];
166 if (
167 m.appear.length >= 2 && //3 if the king was also augmented
168 m.vanish.length == 2 &&
169 m.vanish[1].c == color &&
170 V.AUGMENTED_PIECES.includes(this.board[vx][vy][1])
171 ) {
172 // Castle, rook is an "augmented piece"
173 m.appear[1].p = V.ROOK;
174 m.appear.push(
175 new PiPo({
176 p: this.getExtraPiece(this.board[vx][vy][1]),
177 c: color,
178 x: vx,
179 y: vy
180 })
181 );
182 }
183 });
184 }
185 return moves;
186 }
187
188 hoverHighlight([x, y]) {
189 const c = this.turn;
190 return (
191 this.movesCount <= 3 &&
192 ((c == 'w' && x == 7) || (c == 'b' && x == 0))
193 );
194 }
195
196 // Special case of move 1 = choose squares, knight first, then bishop
197 doClick(square) {
198 if (this.movesCount >= 4) return null;
199 const color = this.turn;
200 const [x, y] = [square[0], square[1]];
201 if ((color == 'w' && x != 7) || (color == 'b' && x != 0)) return null;
202 const selectedPiece = this.board[x][y][1];
203 return new Move({
204 appear: [
205 new PiPo({
206 x: x,
207 y: y,
208 c: color,
209 p: this.getAugmented(selectedPiece)
210 })
211 ],
212 vanish: [
213 new PiPo({
214 x: x,
215 y: y,
216 c: color,
217 p: selectedPiece
218 })
219 ],
220 start: { x: x, y: y },
221 end: { x: x, y: y }
222 });
223 }
224
225 postPlay(move) {
226 if (this.movesCount > 4) super.postPlay(move);
227 }
228
229 postUndo(move) {
230 if (this.movesCount >= 4) super.postUndo(move);
231 }
232
233 evalPosition() {
234 let evaluation = 0;
235 for (let i = 0; i < V.size.x; i++) {
236 for (let j = 0; j < V.size.y; j++) {
237 if (this.board[i][j] != V.EMPTY) {
238 const sign = this.getColor(i, j) == "w" ? 1 : -1;
239 const piece = this.getPiece(i, j);
240 evaluation += sign * V.VALUES[piece];
241 const symbol = this.board[i][j][1];
242 if (V.AUGMENTED_PIECES.includes(symbol)) {
243 const extraPiece = this.getExtraPiece(symbol);
244 evaluation += sign * V.VALUES[extraPiece]
245 }
246 }
247 }
248 }
249 return evaluation;
250 }
251
252 getNotation(move) {
253 if (
254 move.appear[0].x != move.vanish[0].x ||
255 move.appear[0].y != move.vanish[0].y
256 ) {
257 if (
258 V.AUGMENTED_PIECES.includes(move.vanish[0].p) ||
259 (
260 move.vanish.length >= 2 &&
261 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
262 )
263 ) {
264 // Simplify move before calling super.getNotation()
265 let smove = JSON.parse(JSON.stringify(move));
266 if (ChessRules.PIECES.includes(move.vanish[0].p)) {
267 // Castle with an augmented rook
268 smove.appear.pop();
269 smove.vanish[1].p = smove.appear[1].p;
270 }
271 else {
272 // Moving an augmented piece
273 smove.appear.pop();
274 smove.vanish[0].p = smove.appear[0].p;
275 if (
276 smove.vanish.length == 2 &&
277 smove.vanish[0].c == smove.vanish[1].c &&
278 V.AUGMENTED_PIECES.includes(move.vanish[1].p)
279 ) {
280 // Castle with an augmented rook
281 smove.appear.pop();
282 smove.vanish[1].p = smove.appear[1].p;
283 }
284 }
285 return super.getNotation(smove);
286 }
287 // Else, more common case:
288 return super.getNotation(move);
289 }
290 // First moves in game, placements:
291 const square = V.CoordsToSquare(move.appear[0]);
292 const reserve =
293 (['a','j','m','s','u'].includes(move.appear[0].p) ? 'N' : 'B');
294 return '+' + reserve + '@' + square;
295 }
296
297 };