1 import { ChessRules
} from "@/base_rules";
3 export class EmpireRules
extends ChessRules
{
5 static get PawnSpecs() {
9 { promotions: [V
.QUEEN
] }
13 static get LoseOnRepetition() {
17 static IsGoodFlags(flags
) {
18 // Only black can castle
19 return !!flags
.match(/^[a-z]{2,2}$/);
23 return (b
[0] == 'w' ? "Empire/" : "") + b
;
26 static GenRandInitFen(options
) {
27 if (options
.randomness
== 0)
28 return "rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w 0 ah -";
30 // Mapping kingdom --> empire:
39 const baseFen
= ChessRules
.GenRandInitFen(options
);
41 baseFen
.substr(0, 24) + "PPPSSPPP/8/" +
42 baseFen
.substr(35, 8).split('').map(p
=> piecesMap
[p
]).join('') +
43 baseFen
.substr(43, 5) + baseFen
.substr(50)
48 return this.castleFlags
['b'].map(V
.CoordToColumn
).join("");
52 this.castleFlags
= { 'b': [-1, -1] };
53 for (let i
= 0; i
< 2; i
++)
54 this.castleFlags
['b'][i
] = V
.ColumnToCoord(fenflags
.charAt(i
));
63 static get CARDINAL() {
69 static get SOLDIER() {
72 // Kaiser is technically a King, so let's keep things simple.
75 return ChessRules
.PIECES
.concat(
76 [V
.TOWER
, V
.EAGLE
, V
.CARDINAL
, V
.DUKE
, V
.SOLDIER
]);
79 getPotentialMovesFrom(sq
) {
81 const piece
= this.getPiece(sq
[0], sq
[1]);
84 moves
= this.getPotentialTowerMoves(sq
);
87 moves
= this.getPotentialEagleMoves(sq
);
90 moves
= this.getPotentialCardinalMoves(sq
);
93 moves
= this.getPotentialDukeMoves(sq
);
96 moves
= this.getPotentialSoldierMoves(sq
);
99 moves
= super.getPotentialMovesFrom(sq
);
103 this.kingPos
['w'][0] != this.kingPos
['b'][0] &&
104 this.kingPos
['w'][1] != this.kingPos
['b'][1]
108 // TODO: factor two next "if" into one (rank/column...)
109 if (this.kingPos
['w'][1] == this.kingPos
['b'][1]) {
110 const colKing
= this.kingPos
['w'][1];
111 let intercept
= 0; //count intercepting pieces
112 let [kingPos1
, kingPos2
] = [this.kingPos
['w'][0], this.kingPos
['b'][0]];
113 if (kingPos1
> kingPos2
) [kingPos1
, kingPos2
] = [kingPos2
, kingPos1
];
114 for (let i
= kingPos1
+ 1; i
< kingPos2
; i
++) {
115 if (this.board
[i
][colKing
] != V
.EMPTY
) intercept
++;
117 if (intercept
>= 2) return moves
;
118 // intercept == 1 (0 is impossible):
119 // Any move not removing intercept is OK
120 return moves
.filter(m
=> {
122 // From another column?
123 m
.start
.y
!= colKing
||
124 // From behind a king? (including kings themselves!)
125 m
.start
.x
<= kingPos1
||
126 m
.start
.x
>= kingPos2
||
127 // Intercept piece moving: must remain in-between
129 m
.end
.y
== colKing
&&
130 m
.end
.x
> kingPos1
&&
136 if (this.kingPos
['w'][0] == this.kingPos
['b'][0]) {
137 const rowKing
= this.kingPos
['w'][0];
138 let intercept
= 0; //count intercepting pieces
139 let [kingPos1
, kingPos2
] = [this.kingPos
['w'][1], this.kingPos
['b'][1]];
140 if (kingPos1
> kingPos2
) [kingPos1
, kingPos2
] = [kingPos2
, kingPos1
];
141 for (let i
= kingPos1
+ 1; i
< kingPos2
; i
++) {
142 if (this.board
[rowKing
][i
] != V
.EMPTY
) intercept
++;
144 if (intercept
>= 2) return moves
;
145 // intercept == 1 (0 is impossible):
146 // Any move not removing intercept is OK
147 return moves
.filter(m
=> {
150 m
.start
.x
!= rowKing
||
151 // From "behind" a king? (including kings themselves!)
152 m
.start
.y
<= kingPos1
||
153 m
.start
.y
>= kingPos2
||
154 // Intercept piece moving: must remain in-between
156 m
.end
.x
== rowKing
&&
157 m
.end
.y
> kingPos1
&&
163 // piece == king: check only if move.end.y == enemy king column,
164 // or if move.end.x == enemy king rank.
165 const color
= this.getColor(sq
[0], sq
[1]);
166 const oppCol
= V
.GetOppCol(color
);
167 return moves
.filter(m
=> {
169 m
.end
.y
!= this.kingPos
[oppCol
][1] &&
170 m
.end
.x
!= this.kingPos
[oppCol
][0]
174 // check == -1 if (row, or col) unchecked, 1 if checked and occupied,
175 // 0 if checked and clear
176 let check
= [-1, -1];
177 // TODO: factor two next "if"...
178 if (m
.end
.x
== this.kingPos
[oppCol
][0]) {
182 let [kingPos1
, kingPos2
] = [m
.end
.y
, this.kingPos
[oppCol
][1]];
183 if (kingPos1
> kingPos2
) [kingPos1
, kingPos2
] = [kingPos2
, kingPos1
];
184 for (let i
= kingPos1
+ 1; i
< kingPos2
; i
++) {
185 if (this.board
[m
.end
.x
][i
] != V
.EMPTY
) {
190 return check
[0] == 1;
192 // Check already done:
193 return check
[0] == 1;
195 //if (m.end.y == this.kingPos[oppCol][1]) //true...
199 let [kingPos1
, kingPos2
] = [m
.end
.x
, this.kingPos
[oppCol
][0]];
200 if (kingPos1
> kingPos2
) [kingPos1
, kingPos2
] = [kingPos2
, kingPos1
];
201 for (let i
= kingPos1
+ 1; i
< kingPos2
; i
++) {
202 if (this.board
[i
][m
.end
.y
] != V
.EMPTY
) {
207 return check
[1] == 1;
209 // Check already done:
210 return check
[1] == 1;
214 // TODO: some merging to do with Orda method (and into base_rules.js)
215 getSlideNJumpMoves_([x
, y
], steps
, oneStep
) {
217 outerLoop: for (let step
of steps
) {
221 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
222 if (!step
.onlyTake
) moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
223 // NOTE: (bad) HACK here, since onlyTake is true only for Eagle
224 // capturing moves, which are oneStep...
225 if (oneStep
|| step
.onlyTake
) continue outerLoop
;
229 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]) && !step
.onlyMove
)
230 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
244 { s: [-1, -1], onlyMove: true },
245 { s: [-1, 1], onlyMove: true },
246 { s: [1, -1], onlyMove: true },
247 { s: [1, 1], onlyMove: true }
250 { s: [-1, 0], onlyMove: true },
251 { s: [1, 0], onlyMove: true },
252 { s: [0, -1], onlyMove: true },
253 { s: [0, 1], onlyMove: true },
260 { s: [-1, 0], onlyMove: true },
261 { s: [1, 0], onlyMove: true },
262 { s: [0, -1], onlyMove: true },
263 { s: [0, 1], onlyMove: true },
264 { s: [-1, -1], onlyMove: true },
265 { s: [-1, 1], onlyMove: true },
266 { s: [1, -1], onlyMove: true },
267 { s: [1, 1], onlyMove: true },
268 { s: [-2, -1], onlyTake: true },
269 { s: [-2, 1], onlyTake: true },
270 { s: [-1, -2], onlyTake: true },
271 { s: [-1, 2], onlyTake: true },
272 { s: [1, -2], onlyTake: true },
273 { s: [1, 2], onlyTake: true },
274 { s: [2, -1], onlyTake: true },
275 { s: [2, 1], onlyTake: true }
283 getPotentialTowerMoves(sq
) {
284 return this.getSlideNJumpMoves_(sq
, V
.steps
[V
.TOWER
]);
287 getPotentialCardinalMoves(sq
) {
288 return this.getSlideNJumpMoves_(sq
, V
.steps
[V
.CARDINAL
]);
291 getPotentialEagleMoves(sq
) {
292 return this.getSlideNJumpMoves_(sq
, V
.steps
[V
.EAGLE
]);
295 getPotentialDukeMoves([x
, y
]) {
296 // Anything to capture around? mark other steps to explore after
298 const oppCol
= V
.GetOppCol(this.getColor(x
, y
));
300 for (let s
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
301 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
304 this.board
[i
][j
] != V
.EMPTY
&&
305 this.getColor(i
, j
) == oppCol
307 moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
309 else steps
.push({ s: s
, onlyMove: true });
311 if (steps
.length
> 0) {
312 const noncapturingMoves
= this.getSlideNJumpMoves_([x
, y
], steps
);
313 Array
.prototype.push
.apply(moves
, noncapturingMoves
);
318 getPotentialKingMoves([x
, y
]) {
319 if (this.getColor(x
, y
) == 'b') return super.getPotentialKingMoves([x
, y
]);
320 // Empire doesn't castle:
321 return super.getSlideNJumpMoves(
322 [x
, y
], V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1);
325 getPotentialSoldierMoves([x
, y
]) {
326 const c
= this.getColor(x
, y
);
327 const shiftX
= (c
== 'w' ? -1 : 1);
328 const lastRank
= (c
== 'w' && x
== 0 || c
== 'b' && x
== 9);
330 if (!lastRank
) steps
.push([shiftX
, 0]);
331 if (y
> 0) steps
.push([0, -1]);
332 if (y
< 9) steps
.push([0, 1]);
333 return super.getSlideNJumpMoves([x
, y
], steps
, 1);
336 isAttacked(sq
, color
) {
337 if (color
== 'b') return super.isAttacked(sq
, color
);
338 // Empire: only pawn and king (+ queen if promotion) in common:
340 super.isAttackedByPawn(sq
, color
) ||
341 this.isAttackedByTower(sq
, color
) ||
342 this.isAttackedByEagle(sq
, color
) ||
343 this.isAttackedByCardinal(sq
, color
) ||
344 this.isAttackedByDuke(sq
, color
) ||
345 this.isAttackedBySoldier(sq
, color
) ||
346 super.isAttackedByKing(sq
, color
) ||
347 super.isAttackedByQueen(sq
, color
)
351 isAttackedByTower(sq
, color
) {
352 return super.isAttackedBySlideNJump(sq
, color
, V
.TOWER
, V
.steps
[V
.ROOK
]);
355 isAttackedByEagle(sq
, color
) {
356 return super.isAttackedBySlideNJump(
357 sq
, color
, V
.EAGLE
, V
.steps
[V
.KNIGHT
], 1);
360 isAttackedByCardinal(sq
, color
) {
361 return super.isAttackedBySlideNJump(
362 sq
, color
, V
.CARDINAL
, V
.steps
[V
.BISHOP
]);
365 isAttackedByDuke(sq
, color
) {
367 super.isAttackedBySlideNJump(
369 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), 1
374 isAttackedBySoldier([x
, y
], color
) {
375 const shiftX
= (color
== 'w' ? 1 : -1); //shift from king
376 return super.isAttackedBySlideNJump(
377 [x
, y
], color
, V
.SOLDIER
, [[shiftX
, 0], [0, 1], [0, -1]], 1);
380 updateCastleFlags(move, piece
) {
381 // Only black can castle:
383 if (piece
== V
.KING
&& move.appear
[0].c
== 'b')
384 this.castleFlags
['b'] = [8, 8];
386 move.start
.x
== firstRank
&&
387 this.castleFlags
['b'].includes(move.start
.y
)
389 const flagIdx
= (move.start
.y
== this.castleFlags
['b'][0] ? 0 : 1);
390 this.castleFlags
['b'][flagIdx
] = 8;
393 move.end
.x
== firstRank
&&
394 this.castleFlags
['b'].includes(move.end
.y
)
396 const flagIdx
= (move.end
.y
== this.castleFlags
['b'][0] ? 0 : 1);
397 this.castleFlags
['b'][flagIdx
] = 8;
403 const color
= V
.GetOppCol(this.turn
);
404 const lastRank
= (color
== 'w' ? 0 : 7);
405 if (this.kingPos
[color
][0] == lastRank
)
406 // The opposing edge is reached!
407 return color
== "w" ? "1-0" : "0-1";
408 if (this.atLeastOneMove()) return "*";
410 const oppCol
= this.turn
;
411 return (oppCol
== "w" ? "0-1" : "1-0");
414 static get VALUES() {
415 return Object
.assign(
428 static get SEARCH_DEPTH() {