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(randomness
) {
28 return "rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w 0 ah -";
30 // Mapping kingdom --> empire:
39 const baseFen
= ChessRules
.GenRandInitFen(randomness
);
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 // check == -1 if (row, or col) unchecked, 1 if checked and occupied,
168 // 0 if checked and clear
169 let check
= [-1, -1];
170 return moves
.filter(m
=> {
172 m
.end
.y
!= this.kingPos
[oppCol
][1] &&
173 m
.end
.x
!= this.kingPos
[oppCol
][0]
177 // TODO: factor two next "if"...
178 if (m
.end
.x
== this.kingPos
[oppCol
][0]) {
182 let [kingPos1
, kingPos2
] =
183 [this.kingPos
[color
][1], this.kingPos
[oppCol
][1]];
184 if (kingPos1
> kingPos2
) [kingPos1
, kingPos2
] = [kingPos2
, kingPos1
];
185 for (let i
= kingPos1
+ 1; i
< kingPos2
; i
++) {
186 if (this.board
[m
.end
.x
][i
] != V
.EMPTY
) {
191 return check
[0] == 1;
193 // Check already done:
194 return check
[0] == 1;
196 //if (m.end.y == this.kingPos[oppCol][1]) //true...
200 let [kingPos1
, kingPos2
] =
201 [this.kingPos
[color
][0], this.kingPos
[oppCol
][0]];
202 if (kingPos1
> kingPos2
) [kingPos1
, kingPos2
] = [kingPos2
, kingPos1
];
203 for (let i
= kingPos1
+ 1; i
< kingPos2
; i
++) {
204 if (this.board
[i
][m
.end
.y
] != V
.EMPTY
) {
209 return check
[1] == 1;
211 // Check already done:
212 return check
[1] == 1;
216 getSlideNJumpMoves_([x
, y
], steps
, oneStep
) {
218 outerLoop: for (let step
of steps
) {
222 while (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
) {
223 if (!step
.onlyTake
) moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
224 // NOTE: (bad) HACK here, since onlyTake is true only for Eagle
225 // capturing moves, which are oneStep...
226 if (!!oneStep
|| !!step
.onlyTake
) continue outerLoop
;
230 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]) && !step
.onlyMove
)
231 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
245 { s: [-1, -1], onlyMove: true },
246 { s: [-1, 1], onlyMove: true },
247 { s: [1, -1], onlyMove: true },
248 { s: [1, 1], onlyMove: true }
251 { s: [-1, 0], onlyMove: true },
252 { s: [1, 0], onlyMove: true },
253 { s: [0, -1], onlyMove: true },
254 { s: [0, 1], onlyMove: true },
261 { s: [-1, 0], onlyMove: true },
262 { s: [1, 0], onlyMove: true },
263 { s: [0, -1], onlyMove: true },
264 { s: [0, 1], onlyMove: true },
265 { s: [-1, -1], onlyMove: true },
266 { s: [-1, 1], onlyMove: true },
267 { s: [1, -1], onlyMove: true },
268 { s: [1, 1], onlyMove: true },
269 { s: [-2, -1], onlyTake: true },
270 { s: [-2, 1], onlyTake: true },
271 { s: [-1, -2], onlyTake: true },
272 { s: [-1, 2], onlyTake: true },
273 { s: [1, -2], onlyTake: true },
274 { s: [1, 2], onlyTake: true },
275 { s: [2, -1], onlyTake: true },
276 { s: [2, 1], onlyTake: true }
284 getPotentialTowerMoves(sq
) {
285 return this.getSlideNJumpMoves_(sq
, V
.steps
[V
.TOWER
]);
288 getPotentialCardinalMoves(sq
) {
289 return this.getSlideNJumpMoves_(sq
, V
.steps
[V
.CARDINAL
]);
292 getPotentialEagleMoves(sq
) {
293 return this.getSlideNJumpMoves_(sq
, V
.steps
[V
.EAGLE
]);
296 getPotentialDukeMoves([x
, y
]) {
297 // Anything to capture around? mark other steps to explore after
299 const oppCol
= V
.GetOppCol(this.getColor(x
, y
));
301 for (let s
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
302 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
305 this.board
[i
][j
] != V
.EMPTY
&&
306 this.getColor(i
, j
) == oppCol
308 moves
.push(super.getBasicMove([x
, y
], [i
, j
]));
310 else steps
.push({ s: s
, onlyMove: true });
312 if (steps
.length
> 0) {
313 const noncapturingMoves
= this.getSlideNJumpMoves_([x
, y
], steps
);
314 Array
.prototype.push
.apply(moves
, noncapturingMoves
);
319 getPotentialKingMoves([x
, y
]) {
320 if (this.getColor(x
, y
) == 'b') return super.getPotentialKingMoves([x
, y
]);
321 // Empire doesn't castle:
322 return super.getSlideNJumpMoves(
324 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
329 getPotentialSoldierMoves([x
, y
]) {
330 const c
= this.getColor(x
, y
);
331 const shiftX
= (c
== 'w' ? -1 : 1);
332 const lastRank
= (c
== 'w' && x
== 0 || c
== 'b' && x
== 9);
334 if (!lastRank
) steps
.push([shiftX
, 0]);
335 if (y
> 0) steps
.push([0, -1]);
336 if (y
< 9) steps
.push([0, 1]);
337 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep");
340 isAttacked(sq
, color
) {
341 if (color
== 'b') return super.isAttacked(sq
, color
);
342 // Empire: only pawn and king (+ queen if promotion) in common:
344 super.isAttackedByPawn(sq
, color
) ||
345 this.isAttackedByTower(sq
, color
) ||
346 this.isAttackedByEagle(sq
, color
) ||
347 this.isAttackedByCardinal(sq
, color
) ||
348 this.isAttackedByDuke(sq
, color
) ||
349 this.isAttackedBySoldier(sq
, color
) ||
350 super.isAttackedByKing(sq
, color
) ||
351 super.isAttackedByQueen(sq
, color
)
355 isAttackedByTower(sq
, color
) {
356 return super.isAttackedBySlideNJump(sq
, color
, V
.TOWER
, V
.steps
[V
.ROOK
]);
359 isAttackedByEagle(sq
, color
) {
360 return super.isAttackedBySlideNJump(
361 sq
, color
, V
.EAGLE
, V
.steps
[V
.KNIGHT
], "oneStep");
364 isAttackedByCardinal(sq
, color
) {
365 return super.isAttackedBySlideNJump(
366 sq
, color
, V
.CARDINAL
, V
.steps
[V
.BISHOP
]);
369 isAttackedByDuke(sq
, color
) {
371 super.isAttackedBySlideNJump(
373 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep"
378 isAttackedBySoldier([x
, y
], color
) {
379 const shiftX
= (color
== 'w' ? 1 : -1); //shift from king
380 return super.isAttackedBySlideNJump(
381 [x
, y
], color
, V
.SOLDIER
, [[shiftX
, 0], [0, 1], [0, -1]], "oneStep");
384 updateCastleFlags(move, piece
) {
385 // Only black can castle:
387 if (piece
== V
.KING
&& move.appear
[0].c
== 'b')
388 this.castleFlags
['b'] = [8, 8];
390 move.start
.x
== firstRank
&&
391 this.castleFlags
['b'].includes(move.start
.y
)
393 const flagIdx
= (move.start
.y
== this.castleFlags
['b'][0] ? 0 : 1);
394 this.castleFlags
['b'][flagIdx
] = 8;
397 move.end
.x
== firstRank
&&
398 this.castleFlags
['b'].includes(move.end
.y
)
400 const flagIdx
= (move.end
.y
== this.castleFlags
['b'][0] ? 0 : 1);
401 this.castleFlags
['b'][flagIdx
] = 8;
407 const color
= V
.GetOppCol(this.turn
);
408 const lastRank
= (color
== 'w' ? 0 : 7);
409 if (this.kingPos
[color
][0] == lastRank
)
410 // The opposing edge is reached!
411 return color
== "w" ? "1-0" : "0-1";
412 if (this.atLeastOneMove()) return "*";
414 const oppCol
= this.turn
;
415 return (oppCol
== "w" ? "0-1" : "1-0");
418 static get VALUES() {
419 return Object
.assign(
432 static get SEARCH_DEPTH() {