1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { ArrayFun
} from "@/utils/array";
3 import { randInt
} from "@/utils/alea";
5 export class OmegaRules
extends ChessRules
{
6 static get PawnSpecs() {
11 initShift: { w: 2, b: 2 },
14 ChessRules
.PawnSpecs
.promotions
.concat([V
.CHAMPION
, V
.WIZARD
])
19 // For space between corners:
20 static get NOTHING() {
25 if (b
[0] == 'x') return 'x';
26 return ChessRules
.board2fen(b
);
30 if (f
== 'x') return V
.NOTHING
;
31 return ChessRules
.fen2board(f
);
35 if (b
[0] == 'x') return "Omega/nothing";
36 return ([V
.CHAMPION
, V
.WIZARD
].includes(b
[1]) ? "Omega/" : "") + b
;
39 // TODO: the wall position should be checked too
40 static IsGoodPosition(position
) {
41 if (position
.length
== 0) return false;
42 const rows
= position
.split("/");
43 if (rows
.length
!= V
.size
.x
) return false;
44 let kings
= { "k": 0, "K": 0 };
45 for (let row
of rows
) {
47 for (let i
= 0; i
< row
.length
; i
++) {
48 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
49 if (['x'].concat(V
.PIECES
).includes(row
[i
].toLowerCase())) sumElts
++;
51 const num
= parseInt(row
[i
], 10);
52 if (isNaN(num
)) return false;
56 if (sumElts
!= V
.size
.y
) return false;
58 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
62 // NOTE: keep this extensive check because the board has holes
63 static IsGoodEnpassant(enpassant
) {
64 if (enpassant
!= "-") {
65 const squares
= enpassant
.split(",");
66 if (squares
.length
> 2) return false;
67 for (let sq
of squares
) {
68 const ep
= V
.SquareToCoords(sq
);
69 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
76 return { x: 12, y: 12 };
79 static OnBoard(x
, y
) {
81 (x
>= 1 && x
<= 10 && y
>= 1 && y
<= 10) ||
82 (x
== 11 && [0, 11].includes(y
)) ||
83 (x
== 0 && [0, 11].includes(y
))
87 // Dabbabah + alfil + wazir
88 static get CHAMPION() {
98 return ChessRules
.PIECES
.concat([V
.CHAMPION
, V
.WIZARD
]);
102 return Object
.assign(
138 static GenRandInitFen(randomness
) {
139 if (randomness
== 0) {
141 "wxxxxxxxxxxw/xcrnbqkbnrcx/xppppppppppx/x91x/x91x/x91x/" +
142 "x91x/x91x/x91x/xPPPPPPPPPPx/xCRNBQKBNRCx/WxxxxxxxxxxW " +
147 let pieces
= { w: new Array(10), b: new Array(10) };
149 // Shuffle pieces on first (and last rank if randomness == 2)
150 for (let c
of ["w", "b"]) {
151 if (c
== 'b' && randomness
== 1) {
152 pieces
['b'] = pieces
['w'];
157 let positions
= ArrayFun
.range(10);
159 // Get random squares for bishops
160 let randIndex
= 2 * randInt(5);
161 const bishop1Pos
= positions
[randIndex
];
162 // The second bishop must be on a square of different color
163 let randIndex_tmp
= 2 * randInt(5) + 1;
164 const bishop2Pos
= positions
[randIndex_tmp
];
166 // Get random squares for champions
167 let randIndexC
= 2 * randInt(4);
168 if (randIndexC
>= bishop1Pos
) randIndexC
+= 2;
169 const champion1Pos
= positions
[randIndexC
];
170 // The second champion must be on a square of different color
171 let randIndex_tmpC
= 2 * randInt(4) + 1;
172 if (randIndex_tmpC
>= bishop2Pos
) randIndex_tmpC
+= 2;
173 const champion2Pos
= positions
[randIndex_tmpC
];
175 let usedIndices
= [randIndex
, randIndex_tmp
, randIndexC
, randIndex_tmpC
];
177 for (let i
= 3; i
>= 0; i
--) positions
.splice(usedIndices
[i
], 1);
179 // Get random squares for other pieces
180 randIndex
= randInt(6);
181 const knight1Pos
= positions
[randIndex
];
182 positions
.splice(randIndex
, 1);
183 randIndex
= randInt(5);
184 const knight2Pos
= positions
[randIndex
];
185 positions
.splice(randIndex
, 1);
187 randIndex
= randInt(4);
188 const queenPos
= positions
[randIndex
];
189 positions
.splice(randIndex
, 1);
191 // Rooks and king positions are now fixed
192 const rook1Pos
= positions
[0];
193 const kingPos
= positions
[1];
194 const rook2Pos
= positions
[2];
196 pieces
[c
][champion1Pos
] = "c";
197 pieces
[c
][rook1Pos
] = "r";
198 pieces
[c
][knight1Pos
] = "n";
199 pieces
[c
][bishop1Pos
] = "b";
200 pieces
[c
][queenPos
] = "q";
201 pieces
[c
][kingPos
] = "k";
202 pieces
[c
][bishop2Pos
] = "b";
203 pieces
[c
][knight2Pos
] = "n";
204 pieces
[c
][rook2Pos
] = "r";
205 pieces
[c
][champion2Pos
] = "c";
206 flags
+= V
.CoordToColumn(rook1Pos
+1) + V
.CoordToColumn(rook2Pos
+1);
208 // Add turn + flags + enpassant
211 "x" + pieces
["b"].join("") +
212 "x/xppppppppppx/x91x/x91x/x91x/x91x/x91x/x91x/xPPPPPPPPPPx/x" +
213 pieces
["w"].join("").toUpperCase() + "x" +
215 "w 0 " + flags
+ " -"
219 // There may be 2 enPassant squares (if pawn jump 3 squares)
221 const L
= this.epSquares
.length
;
222 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
224 this.epSquares
[L
- 1].forEach(sq
=> {
225 res
+= V
.CoordsToSquare(sq
) + ",";
227 return res
.slice(0, -1); //remove last comma
230 canTake([x1
, y1
], [x2
, y2
]) {
232 // Cannot take wall :)
233 // NOTE: this check is useful only for pawns where OnBoard() isn't used
234 this.board
[x2
][y2
] != V
.NOTHING
&&
235 this.getColor(x1
, y1
) !== this.getColor(x2
, y2
)
239 // En-passant after 2-sq or 3-sq jumps
240 getEpSquare(moveOrSquare
) {
241 if (!moveOrSquare
) return undefined;
242 if (typeof moveOrSquare
=== "string") {
243 const square
= moveOrSquare
;
244 if (square
== "-") return undefined;
246 square
.split(",").forEach(sq
=> {
247 res
.push(V
.SquareToCoords(sq
));
251 // Argument is a move:
252 const move = moveOrSquare
;
253 const [sx
, sy
, ex
] = [move.start
.x
, move.start
.y
, move.end
.x
];
254 if (this.getPiece(sx
, sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2) {
255 const step
= (ex
- sx
) / Math
.abs(ex
- sx
);
262 if (sx
+ 2 * step
!= ex
) {
271 return undefined; //default
274 getPotentialMovesFrom([x
, y
]) {
275 switch (this.getPiece(x
, y
)) {
277 return this.getPotentialChampionMoves([x
, y
]);
279 return this.getPotentialWizardMoves([x
, y
]);
281 return super.getPotentialMovesFrom([x
, y
]);
285 getEnpassantCaptures([x
, y
], shiftX
) {
286 const Lep
= this.epSquares
.length
;
287 const epSquare
= this.epSquares
[Lep
- 1];
290 for (let epsq
of epSquare
) {
291 // TODO: some redundant checks
292 if (epsq
.x
== x
+ shiftX
&& Math
.abs(epsq
.y
- y
) == 1) {
293 let enpassantMove
= this.getBasicMove([x
, y
], [epsq
.x
, epsq
.y
]);
294 // WARNING: the captured pawn may be diagonally behind us,
295 // if it's a 3-squares jump and we take on 1st passing square
296 const px
= this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
;
297 enpassantMove
.vanish
.push({
301 c: this.getColor(px
, epsq
.y
)
303 moves
.push(enpassantMove
);
310 addPawnMoves([x1
, y1
], [x2
, y2
], moves
, promotions
) {
311 let finalPieces
= [V
.PAWN
];
312 const color
= this.turn
;
313 const lastRank
= (color
== "w" ? 1 : V
.size
.x
- 2);
314 if (x2
== lastRank
) {
315 // promotions arg: special override for Hiddenqueen variant
316 if (!!promotions
) finalPieces
= promotions
;
317 else if (!!V
.PawnSpecs
.promotions
) finalPieces
= V
.PawnSpecs
.promotions
;
320 for (let piece
of finalPieces
) {
321 tr
= (piece
!= V
.PAWN
? { c: color
, p: piece
} : null);
322 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
], tr
));
326 getPotentialChampionMoves(sq
) {
327 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.CHAMPION
], "oneStep");
330 getPotentialWizardMoves(sq
) {
331 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.WIZARD
], "oneStep");
334 getCastleMoves([x
, y
], castleInCheck
) {
335 const c
= this.getColor(x
, y
);
336 if (x
!= (c
== "w" ? V
.size
.x
- 2 : 1) || y
!= this.INIT_COL_KING
[c
])
337 return []; //x isn't first rank, or king has moved (shortcut)
340 const oppCol
= V
.GetOppCol(c
);
344 const finalSquares
= [
351 castleSide
++ //large, then small
353 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
354 // If this code is reached, rook and king are on initial position
356 // NOTE: in some variants this is not a rook
357 const rookPos
= this.castleFlags
[c
][castleSide
];
358 if (this.board
[x
][rookPos
] == V
.EMPTY
|| this.getColor(x
, rookPos
) != c
)
359 // Rook is not here, or changed color (see Benedict)
362 // Nothing on the path of the king ? (and no checks)
363 const castlingPiece
= this.getPiece(x
, rookPos
);
364 const finDist
= finalSquares
[castleSide
][0] - y
;
365 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
369 (!castleInCheck
&& this.isAttacked([x
, i
], oppCol
)) ||
370 (this.board
[x
][i
] != V
.EMPTY
&&
371 // NOTE: next check is enough, because of chessboard constraints
372 (this.getColor(x
, i
) != c
||
373 ![V
.KING
, castlingPiece
].includes(this.getPiece(x
, i
))))
375 continue castlingCheck
;
378 } while (i
!= finalSquares
[castleSide
][0]);
380 // Nothing on the path to the rook?
381 step
= castleSide
== 0 ? -1 : 1;
382 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
383 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
386 // Nothing on final squares, except maybe king and castling rook?
387 for (i
= 0; i
< 2; i
++) {
389 finalSquares
[castleSide
][i
] != rookPos
&&
390 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
392 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
||
393 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
396 continue castlingCheck
;
400 // If this code is reached, castle is valid
406 y: finalSquares
[castleSide
][0],
412 y: finalSquares
[castleSide
][1],
418 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
419 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: c
})
422 Math
.abs(y
- rookPos
) <= 2
423 ? { x: x
, y: rookPos
}
424 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
432 isAttacked(sq
, color
) {
434 super.isAttacked(sq
, color
) ||
435 this.isAttackedByChampion(sq
, color
) ||
436 this.isAttackedByWizard(sq
, color
)
440 isAttackedByWizard(sq
, color
) {
442 this.isAttackedBySlideNJump(
443 sq
, color
, V
.WIZARD
, V
.steps
[V
.WIZARD
], "oneStep")
447 isAttackedByChampion(sq
, color
) {
449 this.isAttackedBySlideNJump(
450 sq
, color
, V
.CHAMPION
, V
.steps
[V
.CHAMPION
], "oneStep")
454 updateCastleFlags(move, piece
) {
455 const c
= V
.GetOppCol(this.turn
);
456 const firstRank
= (c
== "w" ? V
.size
.x
- 2 : 1);
457 // Update castling flags if rooks are moved
458 const oppCol
= this.turn
;
459 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
461 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
463 move.start
.x
== firstRank
&& //our rook moves?
464 this.castleFlags
[c
].includes(move.start
.y
)
466 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
467 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
469 // NOTE: not "else if" because a rook could take an opposing rook
471 move.end
.x
== oppFirstRank
&& //we took opponent rook?
472 this.castleFlags
[oppCol
].includes(move.end
.y
)
474 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
475 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
479 static get SEARCH_DEPTH() {
483 // Values taken from https://omegachess.com/strategy.htm
484 static get VALUES() {
499 for (let i
= 0; i
< V
.size
.x
; i
++) {
500 for (let j
= 0; j
< V
.size
.y
; j
++) {
501 if (![V
.EMPTY
,V
.NOTHING
].includes(this.board
[i
][j
])) {
502 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
503 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];