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 (b
[0] == 'b' && b
[1] != 'k') return "Spartan/" + b
;
19 static GenRandInitFen(randomness
) {
21 return "lgkcckwl/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ah";
23 // Mapping white --> black (first knight --> general; TODO):
33 const baseFen
= ChessRules
.GenRandInitFen(randomness
).replace('n', 'g');
35 baseFen
.substr(0, 8).split('').map(p
=> piecesMap
[p
]).join('') +
41 return this.castleFlags
['w'].map(V
.CoordToColumn
).join("");
45 this.castleFlags
= { 'w': [-1, -1] };
46 for (let i
= 0; i
< 2; i
++)
47 this.castleFlags
['w'][i
] = V
.ColumnToCoord(fenflags
.charAt(i
));
50 static IsGoodPosition(position
) {
51 if (position
.length
== 0) return false;
52 const rows
= position
.split("/");
53 if (rows
.length
!= V
.size
.x
) return false;
54 let kings
= { "k": 0, "K": 0 };
55 for (let row
of rows
) {
57 for (let i
= 0; i
< row
.length
; i
++) {
58 if (['K','k'].includes(row
[i
])) kings
[row
[i
]]++;
59 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
61 const num
= parseInt(row
[i
], 10);
62 if (isNaN(num
) || num
<= 0) return false;
66 if (sumElts
!= V
.size
.y
) return false;
68 // Both kings should be on board. One for white, 1 or 2 for black.
69 if (kings
['K'] != 1 || ![1, 2].includes(kings
['k'])) return false;
74 // Scan white king only:
75 this.kingPos
= { w: [-1, -1] };
76 const fenRows
= V
.ParseFen(fen
).position
.split("/");
77 for (let i
= 0; i
< fenRows
.length
; i
++) {
79 for (let j
= 0; j
< fenRows
[i
].length
; j
++) {
80 switch (fenRows
[i
].charAt(j
)) {
82 this.kingPos
["w"] = [i
, k
];
85 const num
= parseInt(fenRows
[i
].charAt(j
), 10);
86 if (!isNaN(num
)) k
+= num
- 1;
94 static get LIEUTENANT() {
97 static get GENERAL() {
100 static get CAPTAIN() {
103 static get WARLORD() {
107 static get PIECES() {
109 ChessRules
.PIECES
.concat([V
.LIEUTENANT
, V
.GENERAL
, V
.CAPTAIN
, V
.WARLORD
])
113 getPotentialMovesFrom([x
, y
]) {
114 if (this.getColor(x
, y
) == 'w') return super.getPotentialMovesFrom([x
, y
]);
115 switch (this.getPiece(x
, y
)) {
117 const kings
= this.getKingsPos();
118 const moves
= this.getPotentialHopliteMoves([x
, y
]);
119 if (kings
.length
== 1) return moves
;
120 return moves
.filter(m
=> m
.appear
[0].p
!= V
.KING
);
122 case V
.KING: return this.getPotentialSpartanKingMoves([x
, y
]);
123 case V
.LIEUTENANT: return this.getPotentialLieutenantMoves([x
, y
]);
124 case V
.GENERAL: return this.getPotentialGeneralMoves([x
, y
]);
125 case V
.CAPTAIN: return this.getPotentialCaptainMoves([x
, y
]);
126 case V
.WARLORD: return this.getPotentialWarlordMoves([x
, y
]);
132 return Object
.assign(
154 getPotentialSpartanKingMoves(sq
) {
156 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]);
157 return super.getSlideNJumpMoves(sq
, steps
, "oneStep");
160 getPotentialHopliteMoves([x
, y
]) {
161 // Berolina pawn, with initial jumping option
165 [V
.LIEUTENANT
, V
.GENERAL
, V
.CAPTAIN
, V
.KING
, V
.WARLORD
];
166 for (let shiftY
of [-1, 0, 1]) {
167 const [i
, j
] = [7, y
+ shiftY
];
171 (shiftY
!= 0 && this.board
[i
][j
] == V
.EMPTY
) ||
172 (shiftY
== 0 && this.getColor(i
, j
) == 'w')
175 for (let p
of finalPieces
)
176 moves
.push(this.getBasicMove([x
, y
], [i
, j
], { c: 'b', p: p
}));
181 for (let shiftY
of [-1, 0, 1]) {
182 const [i
, j
] = [x
+ 1, y
+ shiftY
];
186 (shiftY
!= 0 && this.board
[i
][j
] == V
.EMPTY
) ||
187 (shiftY
== 0 && this.getColor(i
, j
) == 'w')
190 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
193 // Add initial 2 squares jumps:
195 for (let shiftY
of [-2, 2]) {
196 const [i
, j
] = [3, y
+ shiftY
];
197 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
198 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
205 getPotentialLieutenantMoves([x
, y
]) {
207 for (let shiftY
of [-1, 1]) {
208 const [i
, j
] = [x
, y
+ shiftY
];
209 if (V
.OnBoard(i
, j
) && this.board
[i
][j
] == V
.EMPTY
)
210 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
212 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
['a']);
213 Array
.prototype.push
.apply(
215 super.getSlideNJumpMoves([x
, y
], steps
, "oneStep")
220 getPotentialCaptainMoves([x
, y
]) {
221 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
['d']);
222 return super.getSlideNJumpMoves([x
, y
], steps
, "oneStep")
225 getPotentialGeneralMoves([x
, y
]) {
227 super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.BISHOP
], "oneStep")
228 .concat(super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.ROOK
]))
232 getPotentialWarlordMoves([x
, y
]) {
234 super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.KNIGHT
], "oneStep")
235 .concat(super.getSlideNJumpMoves([x
, y
], V
.steps
[V
.BISHOP
]))
239 isAttacked(sq
, color
) {
240 if (color
== 'w') return super.isAttacked(sq
, 'w');
242 this.isAttackedByHoplite(sq
) ||
243 super.isAttackedByKing(sq
, 'b') ||
244 this.isAttackedByLieutenant(sq
) ||
245 this.isAttackedByGeneral(sq
) ||
246 this.isAttackedByCaptain(sq
) ||
247 this.isAttackedByWarlord(sq
)
251 isAttackedByHoplite(sq
) {
252 return super.isAttackedBySlideNJump(sq
, 'b', V
.PAWN
, [[-1,0]], "oneStep");
255 isAttackedByLieutenant(sq
) {
256 const steps
= V
.steps
[V
.BISHOP
].concat(V
.steps
['a']);
258 super.isAttackedBySlideNJump(sq
, 'b', V
.LIEUTENANT
, steps
, "oneStep")
262 isAttackedByCaptain(sq
) {
263 const steps
= V
.steps
[V
.ROOK
].concat(V
.steps
['d']);
264 return super.isAttackedBySlideNJump(sq
, 'b', V
.CAPTAIN
, steps
, "oneStep");
267 isAttackedByGeneral(sq
) {
269 super.isAttackedBySlideNJump(
270 sq
, 'b', V
.GENERAL
, V
.steps
[V
.BISHOP
], "oneStep") ||
271 super.isAttackedBySlideNJump(sq
, 'b', V
.GENERAL
, V
.steps
[V
.ROOK
])
275 isAttackedByWarlord(sq
) {
277 super.isAttackedBySlideNJump(
278 sq
, 'b', V
.WARLORD
, V
.steps
[V
.KNIGHT
], "oneStep") ||
279 super.isAttackedBySlideNJump(sq
, 'b', V
.WARLORD
, V
.steps
[V
.BISHOP
])
283 updateCastleFlags(move, piece
) {
284 // Only white can castle:
286 if (piece
== V
.KING
&& move.appear
[0].c
== 'w')
287 this.castleFlags
['w'] = [8, 8];
289 move.start
.x
== firstRank
&&
290 this.castleFlags
['w'].includes(move.start
.y
)
292 const flagIdx
= (move.start
.y
== this.castleFlags
['w'][0] ? 0 : 1);
293 this.castleFlags
['w'][flagIdx
] = 8;
296 move.end
.x
== firstRank
&&
297 this.castleFlags
['w'].includes(move.end
.y
)
299 const flagIdx
= (move.end
.y
== this.castleFlags
['w'][0] ? 0 : 1);
300 this.castleFlags
['w'][flagIdx
] = 8;
305 if (move.vanish
[0].c
== 'w') super.postPlay(move);
309 if (move.vanish
[0].c
== 'w') super.postUndo(move);
314 for (let i
=0; i
<8; i
++) {
315 for (let j
=0; j
<8; j
++) {
317 this.board
[i
][j
] != V
.EMPTY
&&
318 this.getColor(i
, j
) == 'b' &&
319 this.getPiece(i
, j
) == V
.KING
321 kings
.push({ x: i
, y: j
});
329 if (this.turn
== 'w') return super.getCheckSquares();
330 const kings
= this.getKingsPos();
332 for (let i
of [0, 1]) {
334 kings
.length
>= i
+1 &&
335 super.isAttacked([kings
[i
].x
, kings
[i
].y
], 'w')
337 res
.push([kings
[i
].x
, kings
[i
].y
]);
344 if (moves
.length
== 0) return [];
345 const color
= moves
[0].vanish
[0].c
;
346 if (color
== 'w') return super.filterValid(moves
);
347 // Black moves: check if both kings under attack
348 // If yes, moves must remove at least one attack.
349 const kings
= this.getKingsPos();
350 return moves
.filter(m
=> {
353 for (let k
of kings
) {
355 this.board
[k
.x
][k
.y
] == V
.EMPTY
356 ? [m
.appear
[0].x
, m
.appear
[0].y
] //king moved
358 if (super.isAttacked(curKingPos
, 'w')) attacks
++;
359 else break; //no need to check further
363 (kings
.length
== 2 && attacks
<= 1) ||
364 (kings
.length
== 1 && attacks
== 0)
370 if (this.turn
== 'w') return super.getCurrentScore();
371 if (super.atLeastOneMove()) return "*";
372 // Count kings on board
373 const kings
= this.getKingsPos();
375 super.isAttacked([kings
[0].x
, kings
[0].y
], 'w') ||
376 (kings
.length
== 2 && super.isAttacked([kings
[1].x
, kings
[1].y
], 'w'))
380 return "1/2"; //stalemate
383 static get VALUES() {
384 return Object
.assign(
396 static get SEARCH_DEPTH() {
401 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
402 if (piece
== V
.PAWN
) {
404 const finalSquare
= V
.CoordsToSquare(move.end
);
406 if (move.vanish
.length
== 2)
408 notation
= "Px" + finalSquare
;
410 // No capture: indicate the initial square for potential ambiguity
411 const startSquare
= V
.CoordsToSquare(move.start
);
412 notation
= startSquare
+ finalSquare
;
414 if (move.appear
[0].p
!= V
.PAWN
)
416 notation
+= "=" + move.appear
[0].p
.toUpperCase();
419 return super.getNotation(move); //OK for all other pieces