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 static get DarkBottomRight() {
24 // For space between corners:
25 static get NOTHING() {
30 if (b
[0] == 'x') return 'x';
31 return ChessRules
.board2fen(b
);
35 if (f
== 'x') return V
.NOTHING
;
36 return ChessRules
.fen2board(f
);
40 if (b
[0] == 'x') return "Omega/nothing";
41 return ([V
.CHAMPION
, V
.WIZARD
].includes(b
[1]) ? "Omega/" : "") + b
;
44 // TODO: the wall position should be checked too
45 static IsGoodPosition(position
) {
46 if (position
.length
== 0) return false;
47 const rows
= position
.split("/");
48 if (rows
.length
!= V
.size
.x
) return false;
49 let kings
= { "k": 0, "K": 0 };
50 for (let row
of rows
) {
52 for (let i
= 0; i
< row
.length
; i
++) {
53 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
54 if (['x'].concat(V
.PIECES
).includes(row
[i
].toLowerCase())) sumElts
++;
56 const num
= parseInt(row
[i
], 10);
57 if (isNaN(num
)) return false;
61 if (sumElts
!= V
.size
.y
) return false;
63 if (Object
.values(kings
).some(v
=> v
!= 1)) return false;
67 // NOTE: keep this extensive check because the board has holes
68 static IsGoodEnpassant(enpassant
) {
69 if (enpassant
!= "-") {
70 const squares
= enpassant
.split(",");
71 if (squares
.length
> 2) return false;
72 for (let sq
of squares
) {
73 const ep
= V
.SquareToCoords(sq
);
74 if (isNaN(ep
.x
) || !V
.OnBoard(ep
)) return false;
81 return { x: 12, y: 12 };
84 static OnBoard(x
, y
) {
86 (x
>= 1 && x
<= 10 && y
>= 1 && y
<= 10) ||
87 (x
== 11 && [0, 11].includes(y
)) ||
88 (x
== 0 && [0, 11].includes(y
))
92 // Dabbabah + alfil + wazir
93 static get CHAMPION() {
102 static get PIECES() {
103 return ChessRules
.PIECES
.concat([V
.CHAMPION
, V
.WIZARD
]);
107 return Object
.assign(
143 static GenRandInitFen(randomness
) {
144 if (randomness
== 0) {
146 "wxxxxxxxxxxw/xcrnbqkbnrcx/xppppppppppx/x91x/x91x/x91x/" +
147 "x91x/x91x/x91x/xPPPPPPPPPPx/xCRNBQKBNRCx/WxxxxxxxxxxW " +
152 let pieces
= { w: new Array(10), b: new Array(10) };
154 // Shuffle pieces on first (and last rank if randomness == 2)
155 for (let c
of ["w", "b"]) {
156 if (c
== 'b' && randomness
== 1) {
157 pieces
['b'] = pieces
['w'];
162 let positions
= ArrayFun
.range(10);
164 // Get random squares for bishops
165 let randIndex
= 2 * randInt(5);
166 const bishop1Pos
= positions
[randIndex
];
167 // The second bishop must be on a square of different color
168 let randIndex_tmp
= 2 * randInt(5) + 1;
169 const bishop2Pos
= positions
[randIndex_tmp
];
171 // Get random squares for champions
172 let randIndexC
= 2 * randInt(4);
173 if (randIndexC
>= bishop1Pos
) randIndexC
+= 2;
174 const champion1Pos
= positions
[randIndexC
];
175 // The second champion must be on a square of different color
176 let randIndex_tmpC
= 2 * randInt(4) + 1;
177 if (randIndex_tmpC
>= bishop2Pos
) randIndex_tmpC
+= 2;
178 const champion2Pos
= positions
[randIndex_tmpC
];
180 let usedIndices
= [randIndex
, randIndex_tmp
, randIndexC
, randIndex_tmpC
];
182 for (let i
= 3; i
>= 0; i
--) positions
.splice(usedIndices
[i
], 1);
184 // Get random squares for other pieces
185 randIndex
= randInt(6);
186 const knight1Pos
= positions
[randIndex
];
187 positions
.splice(randIndex
, 1);
188 randIndex
= randInt(5);
189 const knight2Pos
= positions
[randIndex
];
190 positions
.splice(randIndex
, 1);
192 randIndex
= randInt(4);
193 const queenPos
= positions
[randIndex
];
194 positions
.splice(randIndex
, 1);
196 // Rooks and king positions are now fixed
197 const rook1Pos
= positions
[0];
198 const kingPos
= positions
[1];
199 const rook2Pos
= positions
[2];
201 pieces
[c
][champion1Pos
] = "c";
202 pieces
[c
][rook1Pos
] = "r";
203 pieces
[c
][knight1Pos
] = "n";
204 pieces
[c
][bishop1Pos
] = "b";
205 pieces
[c
][queenPos
] = "q";
206 pieces
[c
][kingPos
] = "k";
207 pieces
[c
][bishop2Pos
] = "b";
208 pieces
[c
][knight2Pos
] = "n";
209 pieces
[c
][rook2Pos
] = "r";
210 pieces
[c
][champion2Pos
] = "c";
211 flags
+= V
.CoordToColumn(rook1Pos
+1) + V
.CoordToColumn(rook2Pos
+1);
213 // Add turn + flags + enpassant
216 "x" + pieces
["b"].join("") +
217 "x/xppppppppppx/x91x/x91x/x91x/x91x/x91x/x91x/xPPPPPPPPPPx/x" +
218 pieces
["w"].join("").toUpperCase() + "x" +
220 "w 0 " + flags
+ " -"
224 // There may be 2 enPassant squares (if pawn jump 3 squares)
226 const L
= this.epSquares
.length
;
227 if (!this.epSquares
[L
- 1]) return "-"; //no en-passant
229 this.epSquares
[L
- 1].forEach(sq
=> {
230 res
+= V
.CoordsToSquare(sq
) + ",";
232 return res
.slice(0, -1); //remove last comma
235 canTake([x1
, y1
], [x2
, y2
]) {
237 // Cannot take wall :)
238 // NOTE: this check is useful only for pawns where OnBoard() isn't used
239 this.board
[x2
][y2
] != V
.NOTHING
&&
240 this.getColor(x1
, y1
) !== this.getColor(x2
, y2
)
244 // En-passant after 2-sq or 3-sq jumps
245 getEpSquare(moveOrSquare
) {
246 if (!moveOrSquare
) return undefined;
247 if (typeof moveOrSquare
=== "string") {
248 const square
= moveOrSquare
;
249 if (square
== "-") return undefined;
251 square
.split(",").forEach(sq
=> {
252 res
.push(V
.SquareToCoords(sq
));
256 // Argument is a move:
257 const move = moveOrSquare
;
258 const [sx
, sy
, ex
] = [move.start
.x
, move.start
.y
, move.end
.x
];
259 if (this.getPiece(sx
, sy
) == V
.PAWN
&& Math
.abs(sx
- ex
) >= 2) {
260 const step
= (ex
- sx
) / Math
.abs(ex
- sx
);
267 if (sx
+ 2 * step
!= ex
) {
276 return undefined; //default
279 getPotentialMovesFrom([x
, y
]) {
280 switch (this.getPiece(x
, y
)) {
281 case V
.CHAMPION: return this.getPotentialChampionMoves([x
, y
]);
282 case V
.WIZARD: return this.getPotentialWizardMoves([x
, y
]);
283 default: return super.getPotentialMovesFrom([x
, y
]);
287 getEnpassantCaptures([x
, y
], shiftX
) {
288 const Lep
= this.epSquares
.length
;
289 const epSquare
= this.epSquares
[Lep
- 1];
292 for (let epsq
of epSquare
) {
293 // TODO: some redundant checks
294 if (epsq
.x
== x
+ shiftX
&& Math
.abs(epsq
.y
- y
) == 1) {
295 let enpassantMove
= this.getBasicMove([x
, y
], [epsq
.x
, epsq
.y
]);
296 // WARNING: the captured pawn may be diagonally behind us,
297 // if it's a 3-squares jump and we take on 1st passing square
298 const px
= this.board
[x
][epsq
.y
] != V
.EMPTY
? x : x
- shiftX
;
299 enpassantMove
.vanish
.push({
303 c: this.getColor(px
, epsq
.y
)
305 moves
.push(enpassantMove
);
312 addPawnMoves([x1
, y1
], [x2
, y2
], moves
) {
313 const color
= this.turn
;
314 const lastRank
= (color
== "w" ? 1 : V
.size
.x
- 2);
315 const finalPieces
= (x2
== lastRank
? V
.PawnSpecs
.promotions : [V
.PAWN
]);
316 for (let piece
of finalPieces
) {
317 const tr
= (piece
!= V
.PAWN
? { c: color
, p: piece
} : null);
318 moves
.push(this.getBasicMove([x1
, y1
], [x2
, y2
], tr
));
322 getPotentialChampionMoves(sq
) {
323 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.CHAMPION
], "oneStep");
326 getPotentialWizardMoves(sq
) {
327 return this.getSlideNJumpMoves(sq
, V
.steps
[V
.WIZARD
], "oneStep");
330 getCastleMoves([x
, y
]) {
331 const finalSquares
= [
335 return super.getCastleMoves([x
, y
], finalSquares
);
338 isAttacked(sq
, color
) {
340 super.isAttacked(sq
, color
) ||
341 this.isAttackedByChampion(sq
, color
) ||
342 this.isAttackedByWizard(sq
, color
)
346 isAttackedByWizard(sq
, color
) {
348 this.isAttackedBySlideNJump(
349 sq
, color
, V
.WIZARD
, V
.steps
[V
.WIZARD
], "oneStep")
353 isAttackedByChampion(sq
, color
) {
355 this.isAttackedBySlideNJump(
356 sq
, color
, V
.CHAMPION
, V
.steps
[V
.CHAMPION
], "oneStep")
360 updateCastleFlags(move, piece
) {
361 const c
= V
.GetOppCol(this.turn
);
362 const firstRank
= (c
== "w" ? V
.size
.x
- 2 : 1);
363 // Update castling flags if rooks are moved
364 const oppCol
= this.turn
;
365 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
367 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
369 move.start
.x
== firstRank
&& //our rook moves?
370 this.castleFlags
[c
].includes(move.start
.y
)
372 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
373 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
375 // NOTE: not "else if" because a rook could take an opposing rook
377 move.end
.x
== oppFirstRank
&& //we took opponent rook?
378 this.castleFlags
[oppCol
].includes(move.end
.y
)
380 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
381 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
385 static get SEARCH_DEPTH() {
389 // Values taken from https://omegachess.com/strategy.htm
390 static get VALUES() {
405 for (let i
= 0; i
< V
.size
.x
; i
++) {
406 for (let j
= 0; j
< V
.size
.y
; j
++) {
407 if (![V
.EMPTY
,V
.NOTHING
].includes(this.board
[i
][j
])) {
408 const sign
= this.getColor(i
, j
) == "w" ? 1 : -1;
409 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];