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 // NOTE: keep this extensive check because the board has holes
40 static IsGoodEnpassant(enpassant
) {
41 if (enpassant
!= "-") {
42 const squares
= enpassant
.split(",");
43 if (squares
.length
> 2) return false;
44 for (let sq
of squares
) {
45 const ep
= V
.SquareToCoords(sq
);
46 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
53 return { x: 12, y: 12 };
56 static OnBoard(x
, y
) {
58 (x
>= 1 && x
<= 10 && y
>= 1 && y
<= 10) ||
59 (x
== 11 && [0, 11].includes(y
)) ||
60 (x
== 0 && [0, 11].includes(y
))
64 // Dabbabah + alfil + wazir
65 static get CHAMPION() {
75 return ChessRules
.PIECES
.concat([V
.CHAMPION
, V
.WIZARD
]);
115 static GenRandInitFen(randomness
) {
116 if (randomness
== 0) {
118 "wxxxxxxxxxxw/xcrnbqkbnrcx/xppppppppppx/x91x/x91x/x91x/" +
119 "x91x/x91x/x91x/xPPPPPPPPPPx/xCRNBQKBNRCx/WxxxxxxxxxxW " +
124 let pieces
= { w: new Array(10), b: new Array(10) };
126 // Shuffle pieces on first (and last rank if randomness == 2)
127 for (let c
of ["w", "b"]) {
128 if (c
== 'b' && randomness
== 1) {
129 pieces
['b'] = pieces
['w'];
134 let positions
= ArrayFun
.range(10);
136 // Get random squares for bishops
137 let randIndex
= 2 * randInt(5);
138 const bishop1Pos
= positions
[randIndex
];
139 // The second bishop must be on a square of different color
140 let randIndex_tmp
= 2 * randInt(5) + 1;
141 const bishop2Pos
= positions
[randIndex_tmp
];
142 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
143 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
145 // Get random squares for champions
146 randIndex
= 2 * randInt(4);
147 let bishopSameColorPos
= (bishop1Pos
% 2 == 0 ? bishop1Pos : bishop2Pos
);
148 if (randIndex
>= bishopSameColorPos
) randIndex
+= 2;
149 const champion1Pos
= positions
[randIndex
];
150 // The second champion must be on a square of different color
151 randIndex_tmp
= 2 * randInt(4) + 1;
152 bishopSameColorPos
= (bishop1Pos
% 2 == 0 ? bishop1Pos : bishop2Pos
);
153 if (randIndex_tmp
>= bishopSameColorPos
) randIndex_tmp
+= 2;
154 const champion2Pos
= positions
[randIndex_tmp
];
155 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
156 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
158 // Get random squares for other pieces
159 randIndex
= randInt(6);
160 const knight1Pos
= positions
[randIndex
];
161 positions
.splice(randIndex
, 1);
162 randIndex
= randInt(5);
163 const knight2Pos
= positions
[randIndex
];
164 positions
.splice(randIndex
, 1);
166 randIndex
= randInt(4);
167 const queenPos
= positions
[randIndex
];
168 positions
.splice(randIndex
, 1);
170 // Rooks and king positions are now fixed
171 const rook1Pos
= positions
[0];
172 const kingPos
= positions
[1];
173 const rook2Pos
= positions
[2];
175 pieces
[c
][champion1Pos
] = "c";
176 pieces
[c
][rook1Pos
] = "r";
177 pieces
[c
][knight1Pos
] = "n";
178 pieces
[c
][bishop1Pos
] = "b";
179 pieces
[c
][queenPos
] = "q";
180 pieces
[c
][kingPos
] = "k";
181 pieces
[c
][bishop2Pos
] = "b";
182 pieces
[c
][knight2Pos
] = "n";
183 pieces
[c
][rook2Pos
] = "r";
184 pieces
[c
][champion2Pos
] = "c";
185 flags
+= V
.CoordToColumn(rook1Pos
) + V
.CoordToColumn(rook2Pos
);
187 // Add turn + flags + enpassant
190 "x" + pieces
["b"].join("") +
191 "x/xppppppppppx/x91x/x91x/x91x/x91x/x91x/x91x/xPPPPPPPPPPx/x" +
192 pieces
["w"].join("").toUpperCase() + "x" +
194 "w 0 " + flags
+ " -"
198 // There may be 2 enPassant squares (if pawn jump 3 squares)
200 const L
= this.epSquares
.length
;
201 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
203 this.epSquares
[L
- 1].forEach(sq
=> {
204 res
+= V
.CoordsToSquare(sq
) + ",";
206 return res
.slice(0, -1); //remove last comma
209 // En-passant after 2-sq or 3-sq jumps
210 getEpSquare(moveOrSquare
) {
211 if (!moveOrSquare
) return undefined;
212 if (typeof moveOrSquare
=== "string") {
213 const square
= moveOrSquare
;
214 if (square
== "-") return undefined;
216 square
.split(",").forEach(sq
=> {
217 res
.push(V
.SquareToCoords(sq
));
221 // Argument is a move:
222 const move = moveOrSquare
;
223 const [sx
, sy
, ex
] = [move.start
.x
, move.start
.y
, move.end
.x
];
224 if (this.getPiece(sx
, sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2) {
225 const step
= (ex
- sx
) / Math
.abs(ex
- sx
);
232 if (sx
+ 2 * step
!= ex
) {
241 return undefined; //default
244 getPotentialMovesFrom([x
, y
]) {
245 switch (this.getPiece(x
, y
)) {
247 return this.getPotentialChampionMoves([x
, y
]);
249 return this.getPotentialWizardMoves([x
, y
]);
251 return super.getPotentialMovesFrom([x
, y
]);
255 getEnpassanCaptures([x
, y
], shiftX
) {
256 const Lep
= this.epSquares
.length
;
257 const epSquare
= this.epSquares
[Lep
- 1];
260 for (let epsq
of epSquare
) {
261 // TODO: some redundant checks
262 if (epsq
.x
== x
+ shiftX
&& Math
.abs(epsq
.y
- y
) == 1) {
263 let enpassantMove
= this.getBasicMove([x
, y
], [epsq
.x
, epsq
.y
]);
264 // WARNING: the captured pawn may be diagonally behind us,
265 // if it's a 3-squares jump and we take on 1st passing square
266 const px
= this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
;
267 enpassantMove
.vanish
.push({
271 c: this.getColor(px
, epsq
.y
)
273 moves
.push(enpassantMove
);
280 getPotentialChampionMoves(sq
) {
281 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.CHAMPION
], "oneStep");
284 getPotentialWizardMoves(sq
) {
285 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.WIZARD
], "oneStep");
288 getCastleMoves([x
, y
], castleInCheck
) {
289 const c
= this.getColor(x
, y
);
290 if (x
!= (c
== "w" ? V
.size
.x
- 2 : 1) || y
!= this.INIT_COL_KING
[c
])
291 return []; //x isn't first rank, or king has moved (shortcut)
294 const oppCol
= V
.GetOppCol(c
);
298 const finalSquares
= [
305 castleSide
++ //large, then small
307 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
308 // If this code is reached, rook and king are on initial position
310 // NOTE: in some variants this is not a rook
311 const rookPos
= this.castleFlags
[c
][castleSide
];
312 if (this.board
[x
][rookPos
] == V
.EMPTY
|| this.getColor(x
, rookPos
) != c
)
313 // Rook is not here, or changed color (see Benedict)
316 // Nothing on the path of the king ? (and no checks)
317 const castlingPiece
= this.getPiece(x
, rookPos
);
318 const finDist
= finalSquares
[castleSide
][0] - y
;
319 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
323 (!castleInCheck
&& this.isAttacked([x
, i
], oppCol
)) ||
324 (this.board
[x
][i
] != V
.EMPTY
&&
325 // NOTE: next check is enough, because of chessboard constraints
326 (this.getColor(x
, i
) != c
||
327 ![V
.KING
, castlingPiece
].includes(this.getPiece(x
, i
))))
329 continue castlingCheck
;
332 } while (i
!= finalSquares
[castleSide
][0]);
334 // Nothing on the path to the rook?
335 step
= castleSide
== 0 ? -1 : 1;
336 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
337 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
340 // Nothing on final squares, except maybe king and castling rook?
341 for (i
= 0; i
< 2; i
++) {
343 finalSquares
[castleSide
][i
] != rookPos
&&
344 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
346 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
||
347 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
350 continue castlingCheck
;
354 // If this code is reached, castle is valid
360 y: finalSquares
[castleSide
][0],
366 y: finalSquares
[castleSide
][1],
372 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
373 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: c
})
376 Math
.abs(y
- rookPos
) <= 2
377 ? { x: x
, y: rookPos
}
378 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
386 isAttacked(sq
, color
) {
388 super.isAttacked(sq
, color
) ||
389 this.isAttackedByChampion(sq
, color
) ||
390 this.isAttackedByWizard(sq
, color
)
394 isAttackedByWizard(sq
, color
) {
396 this.isAttackedBySlideNJump(
397 sq
, color
, V
.WIZARD
, V
.steps
[V
.WIZARD
], "oneStep")
401 isAttackedByChampion(sq
, color
) {
403 this.isAttackedBySlideNJump(
404 sq
, color
, V
.CHAMPION
, V
.steps
[V
.CHAMPION
], "oneStep")
408 updateCastleFlags(move, piece
) {
409 const c
= V
.GetOppCol(this.turn
);
410 const firstRank
= (c
== "w" ? V
.size
.x
- 2 : 1);
411 // Update castling flags if rooks are moved
412 const oppCol
= this.turn
;
413 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
415 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
417 move.start
.x
== firstRank
&& //our rook moves?
418 this.castleFlags
[c
].includes(move.start
.y
)
420 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
421 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
423 // NOTE: not "else if" because a rook could take an opposing rook
425 move.end
.x
== oppFirstRank
&& //we took opponent rook?
426 this.castleFlags
[oppCol
].includes(move.end
.y
)
428 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
429 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
433 static get SEARCH_DEPTH() {
437 // Values taken from https://omegachess.com/strategy.htm
438 static get VALUES() {
453 for (let i
= 0; i
< V
.size
.x
; i
++) {
454 for (let j
= 0; j
< V
.size
.y
; j
++) {
455 if (![V
.EMPTY
,V
.NOTHING
].includes(this.board
[i
][j
])) {
456 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
457 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];