Almost added TitanChess + EvolutionChess
[vchess.git] / client / src / variants / Omega.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export class OmegaRules extends ChessRules {
6
7 static get PawnSpecs() {
8 return Object.assign(
9 {},
10 ChessRules.PawnSpecs,
11 {
12 initShift: { w: 2, b: 2 },
13 threeSquares: true,
14 promotions:
15 ChessRules.PawnSpecs.promotions.concat([V.CHAMPION, V.WIZARD])
16 }
17 );
18 }
19
20 // For space between corners:
21 static get NOTHING() {
22 return "xx";
23 }
24
25 static board2fen(b) {
26 if (b[0] == 'x') return 'x';
27 return ChessRules.board2fen(b);
28 }
29
30 static fen2board(f) {
31 if (f == 'x') return V.NOTHING;
32 return ChessRules.fen2board(f);
33 }
34
35 getPpath(b) {
36 if (b[0] == 'x') return "Omega/nothing";
37 return ([V.CHAMPION, V.WIZARD].includes(b[1]) ? "Omega/" : "") + b;
38 }
39
40 // TODO: the wall position should be checked too
41 static IsGoodPosition(position) {
42 if (position.length == 0) return false;
43 const rows = position.split("/");
44 if (rows.length != V.size.x) return false;
45 let kings = { "k": 0, "K": 0 };
46 for (let row of rows) {
47 let sumElts = 0;
48 for (let i = 0; i < row.length; i++) {
49 if (['K','k'].includes(row[i])) kings[row[i]]++;
50 if (['x'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++;
51 else {
52 const num = parseInt(row[i], 10);
53 if (isNaN(num)) return false;
54 sumElts += num;
55 }
56 }
57 if (sumElts != V.size.y) return false;
58 }
59 if (Object.values(kings).some(v => v != 1)) return false;
60 return true;
61 }
62
63 // NOTE: keep this extensive check because the board has holes
64 static IsGoodEnpassant(enpassant) {
65 if (enpassant != "-") {
66 const squares = enpassant.split(",");
67 if (squares.length > 2) return false;
68 for (let sq of squares) {
69 const ep = V.SquareToCoords(sq);
70 if (isNaN(ep.x) || !V.OnBoard(ep)) return false;
71 }
72 }
73 return true;
74 }
75
76 static get size() {
77 return { x: 12, y: 12 };
78 }
79
80 static OnBoard(x, y) {
81 return (
82 (x >= 1 && x <= 10 && y >= 1 && y <= 10) ||
83 (x == 11 && [0, 11].includes(y)) ||
84 (x == 0 && [0, 11].includes(y))
85 );
86 }
87
88 // Dabbabah + alfil + wazir
89 static get CHAMPION() {
90 return "c";
91 }
92
93 // Camel + ferz
94 static get WIZARD() {
95 return "w";
96 }
97
98 static get PIECES() {
99 return ChessRules.PIECES.concat([V.CHAMPION, V.WIZARD]);
100 }
101
102 static get steps() {
103 return Object.assign(
104 {},
105 ChessRules.steps,
106 {
107 w: [
108 [-3, -1],
109 [-3, 1],
110 [-1, -3],
111 [-1, 3],
112 [1, -3],
113 [1, 3],
114 [3, -1],
115 [3, 1],
116 [-1, -1],
117 [-1, 1],
118 [1, -1],
119 [1, 1]
120 ],
121 c: [
122 [1, 0],
123 [-1, 0],
124 [0, 1],
125 [0, -1],
126 [2, 2],
127 [2, -2],
128 [-2, 2],
129 [-2, -2],
130 [-2, 0],
131 [0, -2],
132 [2, 0],
133 [0, 2]
134 ]
135 }
136 );
137 }
138
139 static GenRandInitFen(randomness) {
140 if (randomness == 0) {
141 return (
142 "wxxxxxxxxxxw/xcrnbqkbnrcx/xppppppppppx/x91x/x91x/x91x/" +
143 "x91x/x91x/x91x/xPPPPPPPPPPx/xCRNBQKBNRCx/WxxxxxxxxxxW " +
144 "w 0 cjcj -"
145 );
146 }
147
148 let pieces = { w: new Array(10), b: new Array(10) };
149 let flags = "";
150 // Shuffle pieces on first (and last rank if randomness == 2)
151 for (let c of ["w", "b"]) {
152 if (c == 'b' && randomness == 1) {
153 pieces['b'] = pieces['w'];
154 flags += flags;
155 break;
156 }
157
158 let positions = ArrayFun.range(10);
159
160 // Get random squares for bishops
161 let randIndex = 2 * randInt(5);
162 const bishop1Pos = positions[randIndex];
163 // The second bishop must be on a square of different color
164 let randIndex_tmp = 2 * randInt(5) + 1;
165 const bishop2Pos = positions[randIndex_tmp];
166
167 // Get random squares for champions
168 let randIndexC = 2 * randInt(4);
169 if (randIndexC >= bishop1Pos) randIndexC += 2;
170 const champion1Pos = positions[randIndexC];
171 // The second champion must be on a square of different color
172 let randIndex_tmpC = 2 * randInt(4) + 1;
173 if (randIndex_tmpC >= bishop2Pos) randIndex_tmpC += 2;
174 const champion2Pos = positions[randIndex_tmpC];
175
176 let usedIndices = [randIndex, randIndex_tmp, randIndexC, randIndex_tmpC];
177 usedIndices.sort();
178 for (let i = 3; i >= 0; i--) positions.splice(usedIndices[i], 1);
179
180 // Get random squares for other pieces
181 randIndex = randInt(6);
182 const knight1Pos = positions[randIndex];
183 positions.splice(randIndex, 1);
184 randIndex = randInt(5);
185 const knight2Pos = positions[randIndex];
186 positions.splice(randIndex, 1);
187
188 randIndex = randInt(4);
189 const queenPos = positions[randIndex];
190 positions.splice(randIndex, 1);
191
192 // Rooks and king positions are now fixed
193 const rook1Pos = positions[0];
194 const kingPos = positions[1];
195 const rook2Pos = positions[2];
196
197 pieces[c][champion1Pos] = "c";
198 pieces[c][rook1Pos] = "r";
199 pieces[c][knight1Pos] = "n";
200 pieces[c][bishop1Pos] = "b";
201 pieces[c][queenPos] = "q";
202 pieces[c][kingPos] = "k";
203 pieces[c][bishop2Pos] = "b";
204 pieces[c][knight2Pos] = "n";
205 pieces[c][rook2Pos] = "r";
206 pieces[c][champion2Pos] = "c";
207 flags += V.CoordToColumn(rook1Pos+1) + V.CoordToColumn(rook2Pos+1);
208 }
209 // Add turn + flags + enpassant
210 return (
211 "wxxxxxxxxxxw/" +
212 "x" + pieces["b"].join("") +
213 "x/xppppppppppx/x91x/x91x/x91x/x91x/x91x/x91x/xPPPPPPPPPPx/x" +
214 pieces["w"].join("").toUpperCase() + "x" +
215 "/WxxxxxxxxxxW " +
216 "w 0 " + flags + " -"
217 );
218 }
219
220 // There may be 2 enPassant squares (if pawn jump 3 squares)
221 getEnpassantFen() {
222 const L = this.epSquares.length;
223 if (!this.epSquares[L - 1]) return "-"; //no en-passant
224 let res = "";
225 this.epSquares[L - 1].forEach(sq => {
226 res += V.CoordsToSquare(sq) + ",";
227 });
228 return res.slice(0, -1); //remove last comma
229 }
230
231 canTake([x1, y1], [x2, y2]) {
232 return (
233 // Cannot take wall :)
234 // NOTE: this check is useful only for pawns where OnBoard() isn't used
235 this.board[x2][y2] != V.NOTHING &&
236 this.getColor(x1, y1) !== this.getColor(x2, y2)
237 );
238 }
239
240 // En-passant after 2-sq or 3-sq jumps
241 getEpSquare(moveOrSquare) {
242 if (!moveOrSquare) return undefined;
243 if (typeof moveOrSquare === "string") {
244 const square = moveOrSquare;
245 if (square == "-") return undefined;
246 let res = [];
247 square.split(",").forEach(sq => {
248 res.push(V.SquareToCoords(sq));
249 });
250 return res;
251 }
252 // Argument is a move:
253 const move = moveOrSquare;
254 const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x];
255 if (this.getPiece(sx, sy) == V.PAWN && Math.abs(sx - ex) >= 2) {
256 const step = (ex - sx) / Math.abs(ex - sx);
257 let res = [
258 {
259 x: sx + step,
260 y: sy
261 }
262 ];
263 if (sx + 2 * step != ex) {
264 // 3-squares jump
265 res.push({
266 x: sx + 2 * step,
267 y: sy
268 });
269 }
270 return res;
271 }
272 return undefined; //default
273 }
274
275 getPotentialMovesFrom([x, y]) {
276 switch (this.getPiece(x, y)) {
277 case V.CHAMPION:
278 return this.getPotentialChampionMoves([x, y]);
279 case V.WIZARD:
280 return this.getPotentialWizardMoves([x, y]);
281 default:
282 return super.getPotentialMovesFrom([x, y]);
283 }
284 }
285
286 getEnpassantCaptures([x, y], shiftX) {
287 const Lep = this.epSquares.length;
288 const epSquare = this.epSquares[Lep - 1];
289 let moves = [];
290 if (!!epSquare) {
291 for (let epsq of epSquare) {
292 // TODO: some redundant checks
293 if (epsq.x == x + shiftX && Math.abs(epsq.y - y) == 1) {
294 let enpassantMove = this.getBasicMove([x, y], [epsq.x, epsq.y]);
295 // WARNING: the captured pawn may be diagonally behind us,
296 // if it's a 3-squares jump and we take on 1st passing square
297 const px = this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX;
298 enpassantMove.vanish.push({
299 x: px,
300 y: epsq.y,
301 p: "p",
302 c: this.getColor(px, epsq.y)
303 });
304 moves.push(enpassantMove);
305 }
306 }
307 }
308 return moves;
309 }
310
311 addPawnMoves([x1, y1], [x2, y2], moves, promotions) {
312 let finalPieces = [V.PAWN];
313 const color = this.turn;
314 const lastRank = (color == "w" ? 1 : V.size.x - 2);
315 if (x2 == lastRank) {
316 // promotions arg: special override for Hiddenqueen variant
317 if (!!promotions) finalPieces = promotions;
318 else if (!!V.PawnSpecs.promotions) finalPieces = V.PawnSpecs.promotions;
319 }
320 let tr = null;
321 for (let piece of finalPieces) {
322 tr = (piece != V.PAWN ? { c: color, p: piece } : null);
323 moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
324 }
325 }
326
327 getPotentialChampionMoves(sq) {
328 return this.getSlideNJumpMoves(sq, V.steps[V.CHAMPION], "oneStep");
329 }
330
331 getPotentialWizardMoves(sq) {
332 return this.getSlideNJumpMoves(sq, V.steps[V.WIZARD], "oneStep");
333 }
334
335 getCastleMoves([x, y]) {
336 const finalSquares = [
337 [4, 5],
338 [8, 7]
339 ];
340 return super.getCastleMoves([x, y], finalSquares);
341 }
342
343 isAttacked(sq, color) {
344 return (
345 super.isAttacked(sq, color) ||
346 this.isAttackedByChampion(sq, color) ||
347 this.isAttackedByWizard(sq, color)
348 );
349 }
350
351 isAttackedByWizard(sq, color) {
352 return (
353 this.isAttackedBySlideNJump(
354 sq, color, V.WIZARD, V.steps[V.WIZARD], "oneStep")
355 );
356 }
357
358 isAttackedByChampion(sq, color) {
359 return (
360 this.isAttackedBySlideNJump(
361 sq, color, V.CHAMPION, V.steps[V.CHAMPION], "oneStep")
362 );
363 }
364
365 updateCastleFlags(move, piece) {
366 const c = V.GetOppCol(this.turn);
367 const firstRank = (c == "w" ? V.size.x - 2 : 1);
368 // Update castling flags if rooks are moved
369 const oppCol = this.turn;
370 const oppFirstRank = V.size.x - 1 - firstRank;
371 if (piece == V.KING)
372 this.castleFlags[c] = [V.size.y, V.size.y];
373 else if (
374 move.start.x == firstRank && //our rook moves?
375 this.castleFlags[c].includes(move.start.y)
376 ) {
377 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
378 this.castleFlags[c][flagIdx] = V.size.y;
379 }
380 // NOTE: not "else if" because a rook could take an opposing rook
381 if (
382 move.end.x == oppFirstRank && //we took opponent rook?
383 this.castleFlags[oppCol].includes(move.end.y)
384 ) {
385 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
386 this.castleFlags[oppCol][flagIdx] = V.size.y;
387 }
388 }
389
390 static get SEARCH_DEPTH() {
391 return 2;
392 }
393
394 // Values taken from https://omegachess.com/strategy.htm
395 static get VALUES() {
396 return {
397 p: 1,
398 n: 2,
399 b: 4,
400 r: 6,
401 q: 12,
402 w: 4,
403 c: 4,
404 k: 1000
405 };
406 }
407
408 evalPosition() {
409 let evaluation = 0;
410 for (let i = 0; i < V.size.x; i++) {
411 for (let j = 0; j < V.size.y; j++) {
412 if (![V.EMPTY,V.NOTHING].includes(this.board[i][j])) {
413 const sign = this.getColor(i, j) == "w" ? 1 : -1;
414 evaluation += sign * V.VALUES[this.getPiece(i, j)];
415 }
416 }
417 }
418 return evaluation;
419 }
420
421 };