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 static IsGoodPosition(position
) {
40 if (position
.length
== 0) return false;
41 const rows
= position
.split("/");
42 if (rows
.length
!= V
.size
.x
) return false;
43 let kings
= { "k": 0, "K": 0 };
44 for (let row
of rows
) {
46 for (let i
= 0; i
< row
.length
; i
++) {
47 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
48 if (['x'].concat(V
.PIECES
).includes(row
[i
].toLowerCase())) sumElts
++;
50 const num
= parseInt(row
[i
]);
51 if (isNaN(num
)) return false;
55 if (sumElts
!= V
.size
.y
) return false;
57 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
61 // NOTE: keep this extensive check because the board has holes
62 static IsGoodEnpassant(enpassant
) {
63 if (enpassant
!= "-") {
64 const squares
= enpassant
.split(",");
65 if (squares
.length
> 2) return false;
66 for (let sq
of squares
) {
67 const ep
= V
.SquareToCoords(sq
);
68 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
75 return { x: 12, y: 12 };
78 static OnBoard(x
, y
) {
80 (x
>= 1 && x
<= 10 && y
>= 1 && y
<= 10) ||
81 (x
== 11 && [0, 11].includes(y
)) ||
82 (x
== 0 && [0, 11].includes(y
))
86 // Dabbabah + alfil + wazir
87 static get CHAMPION() {
97 return ChessRules
.PIECES
.concat([V
.CHAMPION
, V
.WIZARD
]);
101 return Object
.assign(
137 static GenRandInitFen(randomness
) {
138 if (randomness
== 0) {
140 "wxxxxxxxxxxw/xcrnbqkbnrcx/xppppppppppx/x91x/x91x/x91x/" +
141 "x91x/x91x/x91x/xPPPPPPPPPPx/xCRNBQKBNRCx/WxxxxxxxxxxW " +
146 let pieces
= { w: new Array(10), b: new Array(10) };
148 // Shuffle pieces on first (and last rank if randomness == 2)
149 for (let c
of ["w", "b"]) {
150 if (c
== 'b' && randomness
== 1) {
151 pieces
['b'] = pieces
['w'];
156 let positions
= ArrayFun
.range(10);
158 // Get random squares for bishops
159 let randIndex
= 2 * randInt(5);
160 const bishop1Pos
= positions
[randIndex
];
161 // The second bishop must be on a square of different color
162 let randIndex_tmp
= 2 * randInt(5) + 1;
163 const bishop2Pos
= positions
[randIndex_tmp
];
164 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
165 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
167 // Get random squares for champions
168 randIndex
= 2 * randInt(4);
169 let bishopSameColorPos
= (bishop1Pos
% 2 == 0 ? bishop1Pos : bishop2Pos
);
170 if (randIndex
>= bishopSameColorPos
) randIndex
+= 2;
171 const champion1Pos
= positions
[randIndex
];
172 // The second champion must be on a square of different color
173 randIndex_tmp
= 2 * randInt(4) + 1;
174 bishopSameColorPos
= (bishop1Pos
% 2 == 0 ? bishop1Pos : bishop2Pos
);
175 if (randIndex_tmp
>= bishopSameColorPos
) randIndex_tmp
+= 2;
176 const champion2Pos
= positions
[randIndex_tmp
];
177 positions
.splice(Math
.max(randIndex
, randIndex_tmp
), 1);
178 positions
.splice(Math
.min(randIndex
, randIndex_tmp
), 1);
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);
188 randIndex
= randInt(4);
189 const queenPos
= positions
[randIndex
];
190 positions
.splice(randIndex
, 1);
192 // Rooks and king positions are now fixed
193 const rook1Pos
= positions
[0];
194 const kingPos
= positions
[1];
195 const rook2Pos
= positions
[2];
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
) + V
.CoordToColumn(rook2Pos
);
209 // Add turn + flags + enpassant
212 "x" + pieces
["b"].join("") +
213 "x/xppppppppppx/x91x/x91x/x91x/x91x/x91x/x91x/xPPPPPPPPPPx/x" +
214 pieces
["w"].join("").toUpperCase() + "x" +
216 "w 0 " + flags
+ " -"
220 // There may be 2 enPassant squares (if pawn jump 3 squares)
222 const L
= this.epSquares
.length
;
223 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
225 this.epSquares
[L
- 1].forEach(sq
=> {
226 res
+= V
.CoordsToSquare(sq
) + ",";
228 return res
.slice(0, -1); //remove last comma
231 canTake([x1
, y1
], [x2
, y2
]) {
233 // Cannot take wall :)
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 getPotentialChampionMoves(sq
) {
311 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.CHAMPION
], "oneStep");
314 getPotentialWizardMoves(sq
) {
315 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.WIZARD
], "oneStep");
318 getCastleMoves([x
, y
], castleInCheck
) {
319 const c
= this.getColor(x
, y
);
320 if (x
!= (c
== "w" ? V
.size
.x
- 2 : 1) || y
!= this.INIT_COL_KING
[c
])
321 return []; //x isn't first rank, or king has moved (shortcut)
324 const oppCol
= V
.GetOppCol(c
);
328 const finalSquares
= [
335 castleSide
++ //large, then small
337 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
338 // If this code is reached, rook and king are on initial position
340 // NOTE: in some variants this is not a rook
341 const rookPos
= this.castleFlags
[c
][castleSide
];
342 if (this.board
[x
][rookPos
] == V
.EMPTY
|| this.getColor(x
, rookPos
) != c
)
343 // Rook is not here, or changed color (see Benedict)
346 // Nothing on the path of the king ? (and no checks)
347 const castlingPiece
= this.getPiece(x
, rookPos
);
348 const finDist
= finalSquares
[castleSide
][0] - y
;
349 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
353 (!castleInCheck
&& this.isAttacked([x
, i
], oppCol
)) ||
354 (this.board
[x
][i
] != V
.EMPTY
&&
355 // NOTE: next check is enough, because of chessboard constraints
356 (this.getColor(x
, i
) != c
||
357 ![V
.KING
, castlingPiece
].includes(this.getPiece(x
, i
))))
359 continue castlingCheck
;
362 } while (i
!= finalSquares
[castleSide
][0]);
364 // Nothing on the path to the rook?
365 step
= castleSide
== 0 ? -1 : 1;
366 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
367 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
370 // Nothing on final squares, except maybe king and castling rook?
371 for (i
= 0; i
< 2; i
++) {
373 finalSquares
[castleSide
][i
] != rookPos
&&
374 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
376 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
||
377 this.getColor(x
, finalSquares
[castleSide
][i
]) != c
380 continue castlingCheck
;
384 // If this code is reached, castle is valid
390 y: finalSquares
[castleSide
][0],
396 y: finalSquares
[castleSide
][1],
402 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
403 new PiPo({ x: x
, y: rookPos
, p: castlingPiece
, c: c
})
406 Math
.abs(y
- rookPos
) <= 2
407 ? { x: x
, y: rookPos
}
408 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
416 isAttacked(sq
, color
) {
418 super.isAttacked(sq
, color
) ||
419 this.isAttackedByChampion(sq
, color
) ||
420 this.isAttackedByWizard(sq
, color
)
424 isAttackedByWizard(sq
, color
) {
426 this.isAttackedBySlideNJump(
427 sq
, color
, V
.WIZARD
, V
.steps
[V
.WIZARD
], "oneStep")
431 isAttackedByChampion(sq
, color
) {
433 this.isAttackedBySlideNJump(
434 sq
, color
, V
.CHAMPION
, V
.steps
[V
.CHAMPION
], "oneStep")
438 updateCastleFlags(move, piece
) {
439 const c
= V
.GetOppCol(this.turn
);
440 const firstRank
= (c
== "w" ? V
.size
.x
- 2 : 1);
441 // Update castling flags if rooks are moved
442 const oppCol
= this.turn
;
443 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
445 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
447 move.start
.x
== firstRank
&& //our rook moves?
448 this.castleFlags
[c
].includes(move.start
.y
)
450 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
451 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
453 // NOTE: not "else if" because a rook could take an opposing rook
455 move.end
.x
== oppFirstRank
&& //we took opponent rook?
456 this.castleFlags
[oppCol
].includes(move.end
.y
)
458 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
459 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
463 static get SEARCH_DEPTH() {
467 // Values taken from https://omegachess.com/strategy.htm
468 static get VALUES() {
483 for (let i
= 0; i
< V
.size
.x
; i
++) {
484 for (let j
= 0; j
< V
.size
.y
; j
++) {
485 if (![V
.EMPTY
,V
.NOTHING
].includes(this.board
[i
][j
])) {
486 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
487 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];