1 import { ChessRules
} from "@/base_rules";
3 export class SpartanRules
extends ChessRules
{
5 static get HasEnpassant() {
9 static IsGoodFlags(flags
) {
10 // Only white can castle
11 return !!flags
.match(/^[a-z]{2,2}$/);
15 if ([V
.LIEUTENANT
, V
.GENERAL
, V
.CAPTAIN
, V
.WARLORD
].includes(b
[1]))
16 return "Spartan/" + b
;
20 static GenRandInitFen(randomness
) {
22 return "lgkcckwl/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ah";
24 // Mapping white --> black (first knight --> general; TODO):
34 const baseFen
= ChessRules
.GenRandInitFen(randomness
).replace('n', 'g');
36 baseFen
.substr(0, 8).split('').map(p
=> piecesMap
[p
]).join('') +
42 return this.castleFlags
['w'].map(V
.CoordToColumn
).join("");
46 this.castleFlags
= { 'w': [-1, -1] };
47 for (let i
= 0; i
< 2; i
++)
48 this.castleFlags
['w'][i
] = V
.ColumnToCoord(fenflags
.charAt(i
));
51 static IsGoodPosition(position
) {
52 if (position
.length
== 0) return false;
53 const rows
= position
.split("/");
54 if (rows
.length
!= V
.size
.x
) return false;
55 let kings
= { "k": 0, "K": 0 };
56 for (let row
of rows
) {
58 for (let i
= 0; i
< row
.length
; i
++) {
59 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
60 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
62 const num
= parseInt(row
[i
], 10);
63 if (isNaN(num
) || num
<= 0) return false;
67 if (sumElts
!= V
.size
.y
) return false;
69 // Both kings should be on board. One for white, 1 or 2 for black.
70 if (kings
['K'] != 1 || ![1, 2].includes(kings
['k'])) return false;
75 // Scan white king only:
76 this.kingPos
= { w: [-1, -1] };
77 const fenRows
= V
.ParseFen(fen
).position
.split("/");
78 for (let i
= 0; i
< fenRows
.length
; i
++) {
80 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
81 switch (fenRows
[i
].charAt(j
)) {
83 this.kingPos
["w"] = [i
, k
];
86 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
87 if (!isNaN(num
)) k
+= num
- 1;
95 static get LIEUTENANT() {
98 static get GENERAL() {
101 static get CAPTAIN() {
104 static get WARLORD() {
108 static get PIECES() {
110 ChessRules
.PIECES
.concat([V
.LIEUTENANT
, V
.GENERAL
, V
.CAPTAIN
, V
.WARLORD
])
114 getPotentialMovesFrom([x
, y
]) {
115 if (this.getColor(x
, y
) == 'w') return super.getPotentialMovesFrom([x
, y
]);
116 switch (this.getPiece(x
, y
)) {
118 const kings
= this.getKingsPos();
119 const moves
= this.getPotentialHopliteMoves([x
, y
]);
120 if (kings
.length
== 1) return moves
;
121 return moves
.filter(m
=> m
.appear
[0].p
!= V
.KING
);
123 case V
.KING: return this.getPotentialSpartanKingMoves([x
, y
]);
124 case V
.LIEUTENANT: return this.getPotentialLieutenantMoves([x
, y
]);
125 case V
.GENERAL: return this.getPotentialGeneralMoves([x
, y
]);
126 case V
.CAPTAIN: return this.getPotentialCaptainMoves([x
, y
]);
127 case V
.WARLORD: return this.getPotentialWarlordMoves([x
, y
]);
133 return Object
.assign(
155 getPotentialSpartanKingMoves(sq
) {
157 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
158 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
161 getPotentialHopliteMoves([x
, y
]) {
162 // Berolina pawn, with initial jumping option
166 [V
.LIEUTENANT
, V
.GENERAL
, V
.CAPTAIN
, V
.KING
, V
.WARLORD
];
167 for (let shiftY
of [-1, 0, 1]) {
168 const [i
, j
] = [7, y
+ shiftY
];
172 (shiftY
!= 0 && this.board
[i
][j
] == V
.EMPTY
) ||
173 (shiftY
== 0 && this.getColor(i
, j
) == 'w')
176 for (let p
of finalPieces
)
177 moves
.push(this.getBasicMove([x
, y
], [i
, j
], { c: 'b', p: p
}));
182 for (let shiftY
of [-1, 0, 1]) {
183 const [i
, j
] = [x
+ 1, y
+ shiftY
];
187 (shiftY
!= 0 && this.board
[i
][j
] == V
.EMPTY
) ||
188 (shiftY
== 0 && this.getColor(i
, j
) == 'w')
191 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
194 // Add initial 2 squares jumps:
196 for (let shiftY
of [-2, 2]) {
197 const [i
, j
] = [3, y
+ shiftY
];
198 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
199 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
206 getPotentialLieutenantMoves([x
, y
]) {
208 for (let shiftY
of [-1, 1]) {
209 const [i
, j
] = [x
, y
+ shiftY
];
210 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
211 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
213 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
['a']);
214 Array
.prototype.push
.apply(
216 super.getSlideNJumpMoves([x
, y
], steps
, "oneStep")
221 getPotentialCaptainMoves([x
, y
]) {
222 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
['d']);
223 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep")
226 getPotentialGeneralMoves([x
, y
]) {
228 super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.BISHOP
], "oneStep")
229 .concat(super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.ROOK
]))
233 getPotentialWarlordMoves([x
, y
]) {
235 super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.KNIGHT
], "oneStep")
236 .concat(super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.BISHOP
]))
240 isAttacked(sq
, color
) {
241 if (color
== 'w') return super.isAttacked(sq
, 'w');
243 this.isAttackedByHoplite(sq
) ||
244 super.isAttackedByKing(sq
, 'b') ||
245 this.isAttackedByLieutenant(sq
) ||
246 this.isAttackedByGeneral(sq
) ||
247 this.isAttackedByCaptain(sq
) ||
248 this.isAttackedByWarlord(sq
)
252 isAttackedByHoplite(sq
) {
253 return super.isAttackedBySlideNJump(sq
, 'b', V
.PAWN
, [[-1,0]], "oneStep");
256 isAttackedByLieutenant(sq
) {
257 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
['a']);
259 super.isAttackedBySlideNJump(sq
, 'b', V
.LIEUTENANT
, steps
, "oneStep")
263 isAttackedByCaptain(sq
) {
264 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
['d']);
265 return super.isAttackedBySlideNJump(sq
, 'b', V
.CAPTAIN
, steps
, "oneStep");
268 isAttackedByGeneral(sq
) {
270 super.isAttackedBySlideNJump(
271 sq
, 'b', V
.GENERAL
, V
.steps
[V
.BISHOP
], "oneStep") ||
272 super.isAttackedBySlideNJump(sq
, 'b', V
.GENERAL
, V
.steps
[V
.ROOK
])
276 isAttackedByWarlord(sq
) {
278 super.isAttackedBySlideNJump(sq
, 'b', V
.GENERAL
,
279 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep") ||
280 super.isAttackedBySlideNJump(sq
, 'b', V
.GENERAL
, V
.steps
[V
.ROOK
])
284 updateCastleFlags(move, piece
) {
285 // Only white can castle:
287 if (piece
== V
.KING
&& move.appear
[0].c
== 'w')
288 this.castleFlags
['w'] = [8, 8];
290 move.start
.x
== firstRank
&&
291 this.castleFlags
['w'].includes(move.start
.y
)
293 const flagIdx
= (move.start
.y
== this.castleFlags
['w'][0] ? 0 : 1);
294 this.castleFlags
['w'][flagIdx
] = 8;
297 move.end
.x
== firstRank
&&
298 this.castleFlags
['w'].includes(move.end
.y
)
300 const flagIdx
= (move.end
.y
== this.castleFlags
['w'][0] ? 0 : 1);
301 this.castleFlags
['w'][flagIdx
] = 8;
306 if (move.vanish
[0].c
== 'w') super.postPlay(move);
310 if (move.vanish
[0].c
== 'w') super.postUndo(move);
315 for (let i
=0; i
<8; i
++) {
316 for (let j
=0; j
<8; j
++) {
318 this.board
[i
][j
] != V
.EMPTY
&&
319 this.getColor(i
, j
) == 'b' &&
320 this.getPiece(i
, j
) == V
.KING
322 kings
.push({ x: i
, y: j
});
330 if (this.turn
== 'w') return super.getCheckSquares();
331 const kings
= this.getKingsPos();
333 for (let i
of [0, 1]) {
335 kings
.length
>= i
+1 &&
336 super.isAttacked([kings
[i
].x
, kings
[i
].y
], 'w')
338 res
.push([kings
[i
].x
, kings
[i
].y
]);
345 if (moves
.length
== 0) return [];
346 const color
= moves
[0].vanish
[0].c
;
347 if (color
== 'w') return super.filterValid(moves
);
348 // Black moves: check if both kings under attack
349 // If yes, moves must remove at least one attack.
350 const kings
= this.getKingsPos();
351 return moves
.filter(m
=> {
354 for (let k
of kings
) {
356 this.board
[k
.x
][k
.y
] == V
.EMPTY
357 ? [m
.appear
[0].x
, m
.appear
[0].y
] //king moved
359 if (super.isAttacked(curKingPos
, 'w')) attacks
++;
360 else break; //no need to check further
364 (kings
.length
== 2 && attacks
<= 1) ||
365 (kings
.length
== 1 && attacks
== 0)
371 if (this.turn
== 'w') return super.getCurrentScore();
372 if (super.atLeastOneMove()) return "*";
373 // Count kings on board
374 const kings
= this.getKingsPos();
376 super.isAttacked([kings
[0].x
, kings
[0].y
], 'w') ||
377 (kings
.length
== 2 && super.isAttacked([kings
[1].x
, kings
[1].y
], 'w'))
381 return "1/2"; //stalemate
384 static get VALUES() {
385 return Object
.assign(
397 static get SEARCH_DEPTH() {