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
{
7 static get PawnSpecs() {
12 initShift: { w: 2, b: 2 },
15 ChessRules
.PawnSpecs
.promotions
.concat([V
.CHAMPION
, V
.WIZARD
])
20 // For space between corners:
21 static get NOTHING() {
26 if (b
[0] == 'x') return 'x';
27 return ChessRules
.board2fen(b
);
31 if (f
== 'x') return V
.NOTHING
;
32 return ChessRules
.fen2board(f
);
36 if (b
[0] == 'x') return "Omega/nothing";
37 return ([V
.CHAMPION
, V
.WIZARD
].includes(b
[1]) ? "Omega/" : "") + b
;
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
) {
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
++;
52 const num
= parseInt(row
[i
], 10);
53 if (isNaN(num
)) return false;
57 if (sumElts
!= V
.size
.y
) return false;
59 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
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;
77 return { x: 12, y: 12 };
80 static OnBoard(x
, y
) {
82 (x
>= 1 && x
<= 10 && y
>= 1 && y
<= 10) ||
83 (x
== 11 && [0, 11].includes(y
)) ||
84 (x
== 0 && [0, 11].includes(y
))
88 // Dabbabah + alfil + wazir
89 static get CHAMPION() {
99 return ChessRules
.PIECES
.concat([V
.CHAMPION
, V
.WIZARD
]);
103 return Object
.assign(
139 static GenRandInitFen(randomness
) {
140 if (randomness
== 0) {
142 "wxxxxxxxxxxw/xcrnbqkbnrcx/xppppppppppx/x91x/x91x/x91x/" +
143 "x91x/x91x/x91x/xPPPPPPPPPPx/xCRNBQKBNRCx/WxxxxxxxxxxW " +
148 let pieces
= { w: new Array(10), b: new Array(10) };
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'];
158 let positions
= ArrayFun
.range(10);
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
];
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
];
176 let usedIndices
= [randIndex
, randIndex_tmp
, randIndexC
, randIndex_tmpC
];
178 for (let i
= 3; i
>= 0; i
--) positions
.splice(usedIndices
[i
], 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
+1) + V
.CoordToColumn(rook2Pos
+1);
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 // 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
)
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;
247 square
.split(",").forEach(sq
=> {
248 res
.push(V
.SquareToCoords(sq
));
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
);
263 if (sx
+ 2 * step
!= ex
) {
272 return undefined; //default
275 getPotentialMovesFrom([x
, y
]) {
276 switch (this.getPiece(x
, y
)) {
278 return this.getPotentialChampionMoves([x
, y
]);
280 return this.getPotentialWizardMoves([x
, y
]);
282 return super.getPotentialMovesFrom([x
, y
]);
286 getEnpassantCaptures([x
, y
], shiftX
) {
287 const Lep
= this.epSquares
.length
;
288 const epSquare
= this.epSquares
[Lep
- 1];
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({
302 c: this.getColor(px
, epsq
.y
)
304 moves
.push(enpassantMove
);
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
;
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
));
327 getPotentialChampionMoves(sq
) {
328 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.CHAMPION
], "oneStep");
331 getPotentialWizardMoves(sq
) {
332 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.WIZARD
], "oneStep");
335 getCastleMoves([x
, y
]) {
336 const finalSquares
= [
340 return super.getCastleMoves([x
, y
], finalSquares
);
343 isAttacked(sq
, color
) {
345 super.isAttacked(sq
, color
) ||
346 this.isAttackedByChampion(sq
, color
) ||
347 this.isAttackedByWizard(sq
, color
)
351 isAttackedByWizard(sq
, color
) {
353 this.isAttackedBySlideNJump(
354 sq
, color
, V
.WIZARD
, V
.steps
[V
.WIZARD
], "oneStep")
358 isAttackedByChampion(sq
, color
) {
360 this.isAttackedBySlideNJump(
361 sq
, color
, V
.CHAMPION
, V
.steps
[V
.CHAMPION
], "oneStep")
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
;
372 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
374 move.start
.x
== firstRank
&& //our rook moves?
375 this.castleFlags
[c
].includes(move.start
.y
)
377 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
378 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
380 // NOTE: not "else if" because a rook could take an opposing rook
382 move.end
.x
== oppFirstRank
&& //we took opponent rook?
383 this.castleFlags
[oppCol
].includes(move.end
.y
)
385 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
386 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
390 static get SEARCH_DEPTH() {
394 // Values taken from https://omegachess.com/strategy.htm
395 static get VALUES() {
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
)];