Almost completed Empire Chess. Next is Synochess (+ rules translations)
[vchess.git] / client / src / variants / Sittuyin.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { randInt } from "@/utils/alea";
3
4 export class SittuyinRules extends ChessRules {
5
6 static get HasFlags() {
7 return false;
8 }
9
10 static get HasEnpassant() {
11 return false;
12 }
13
14 static get Monochrome() {
15 return true;
16 }
17
18 static get Notoodark() {
19 return true;
20 }
21
22 static get Lines() {
23 return ChessRules.Lines.concat([
24 [[0, 0], [8, 8]],
25 [[0, 8], [8, 0]]
26 ]);
27 }
28
29 static get PawnSpecs() {
30 return Object.assign(
31 {},
32 ChessRules.PawnSpecs,
33 {
34 // Promotions are handled differently here
35 promotions: [V.QUEEN]
36 }
37 );
38 }
39
40 static GenRandInitFen() {
41 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
42 }
43
44 re_setReserve(subTurn) {
45 const mc = this.movesCount;
46 const wc = (mc == 0 ? 1 : 0);
47 const bc = (mc <= 1 ? 1 : 0);
48 this.reserve = {
49 w: {
50 [V.ROOK]: wc * 2,
51 [V.KNIGHT]: wc * 2,
52 [V.BISHOP]: wc * 2,
53 [V.QUEEN]: wc,
54 [V.KING]: wc
55 },
56 b: {
57 [V.ROOK]: bc * 2,
58 [V.KNIGHT]: bc * 2,
59 [V.BISHOP]: bc * 2,
60 [V.QUEEN]: bc,
61 [V.KING]: bc
62 }
63 }
64 this.subTurn = subTurn || 1;
65 }
66
67 setOtherVariables(fen) {
68 super.setOtherVariables(fen);
69 if (this.movesCount <= 1) this.re_setReserve();
70 }
71
72 getPpath(b) {
73 return "Sittuyin/" + b;
74 }
75
76 getColor(i, j) {
77 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
78 return this.board[i][j].charAt(0);
79 }
80
81 getPiece(i, j) {
82 if (i >= V.size.x) return V.RESERVE_PIECES[j];
83 return this.board[i][j].charAt(1);
84 }
85
86 getReservePpath(index, color) {
87 return "Sittuyin/" + color + V.RESERVE_PIECES[index];
88 }
89
90 static get RESERVE_PIECES() {
91 return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
92 }
93
94 getPotentialMovesFrom([x, y]) {
95 if (this.movesCount >= 2) return super.getPotentialMovesFrom([x, y]);
96 // Only reserve moves are allowed for now:
97 if (V.OnBoard(x, y)) return [];
98 const color = this.turn;
99 const p = V.RESERVE_PIECES[y];
100 if (this.reserve[color][p] == 0) return [];
101 const iBound =
102 p != V.ROOK
103 ? (color == 'w' ? [4, 7] : [0, 3])
104 : (color == 'w' ? [7, 7] : [0, 0]);
105 const jBound = (i) => {
106 if (color == 'w' && i == 4) return [4, 7];
107 if (color == 'b' && i == 3) return [0, 3];
108 return [0, 7];
109 };
110 let moves = [];
111 for (let i = iBound[0]; i <= iBound[1]; i++) {
112 const jb = jBound(i);
113 for (let j = jb[0]; j <= jb[1]; j++) {
114 if (this.board[i][j] == V.EMPTY) {
115 let mv = new Move({
116 appear: [
117 new PiPo({
118 x: i,
119 y: j,
120 c: color,
121 p: p
122 })
123 ],
124 vanish: [],
125 start: { x: x, y: y },
126 end: { x: i, y: j }
127 });
128 moves.push(mv);
129 }
130 }
131 }
132 return moves;
133 }
134
135 getPotentialPawnMoves([x, y]) {
136 const color = this.turn;
137 const shiftX = V.PawnSpecs.directions[color];
138 let moves = [];
139 if (x + shiftX >= 0 && x + shiftX < 8) {
140 if (this.board[x + shiftX][y] == V.EMPTY)
141 // One square forward
142 moves.push(this.getBasicMove([x, y], [x + shiftX, y]));
143 // Captures
144 for (let shiftY of [-1, 1]) {
145 if (
146 y + shiftY >= 0 && y + shiftY < 8 &&
147 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
148 this.canTake([x, y], [x + shiftX, y + shiftY])
149 ) {
150 moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
151 }
152 }
153 }
154 let queenOnBoard = false;
155 let pawnsCount = 0;
156 outerLoop: for (let i=0; i<8; i++) {
157 for (let j=0; j<8; j++) {
158 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
159 const p = this.getPiece(i, j);
160 if (p == V.QUEEN) {
161 queenOnBoard = true;
162 break outerLoop;
163 }
164 else if (p == V.PAWN && pawnsCount <= 1) pawnsCount++;
165 }
166 }
167 }
168 if (
169 !queenOnBoard &&
170 (
171 pawnsCount == 1 ||
172 (color == 'w' && ((y <= 3 && x == y) || (y >= 4 && x == 7 - y))) ||
173 (color == 'b' && ((y >= 4 && x == y) || (y <= 3 && x == 7 - y)))
174 )
175 ) {
176 const addPromotion = ([xx, yy], moveTo) => {
177 // The promoted pawn shouldn't attack anything,
178 // and the promotion shouldn't discover a rook attack on anything.
179 const finalSquare = (!moveTo ? [x, y] : [xx, yy]);
180 let validP = true;
181 for (let step of V.steps[V.BISHOP]) {
182 const [i, j] = [finalSquare[0] + step[0], finalSquare[1] + step[1]];
183 if (
184 V.OnBoard(i, j) &&
185 this.board[i][j] != V.EMPTY &&
186 this.getColor(i, j) != color
187 ) {
188 validP = false;
189 break;
190 }
191 }
192 if (validP && !!moveTo) {
193 // Also check rook discovered attacks on the enemy king
194 let found = {
195 "0,-1": 0,
196 "0,1": 0,
197 "1,0": 0,
198 "-1,0": 0
199 };
200 // TODO: check opposite steps one after another, which could
201 // save some time (no need to explore the other line).
202 for (let step of V.steps[V.ROOK]) {
203 let [i, j] = [x + step[0], y + step[1]];
204 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
205 i += step[0];
206 j += step[1];
207 }
208 if (V.OnBoard(i, j)) {
209 const colIJ = this.getColor(i, j);
210 const pieceIJ = this.getPiece(i, j);
211 if (colIJ != color && pieceIJ == V.KING)
212 found[step[0] + "," + step[1]] = -1;
213 else if (colIJ == color && pieceIJ == V.ROOK)
214 found[step[0] + "," + step[1]] = 1;
215 }
216 }
217 if (
218 (found["0,-1"] * found["0,1"] < 0) ||
219 (found["-1,0"] * found["1,0"] < 0)
220 ) {
221 validP = false;
222 }
223 }
224 if (validP) {
225 moves.push(
226 new Move({
227 appear: [
228 new PiPo({
229 x: !!moveTo ? xx : x,
230 y: yy, //yy == y if !!moveTo
231 c: color,
232 p: V.QUEEN
233 })
234 ],
235 vanish: [
236 new PiPo({
237 x: x,
238 y: y,
239 c: color,
240 p: V.PAWN
241 })
242 ],
243 start: { x: x, y: y },
244 end: { x: xx, y: yy }
245 })
246 );
247 }
248 };
249 // In-place promotion always possible:
250 addPromotion([x - shiftX, y]);
251 for (let step of V.steps[V.BISHOP]) {
252 const [i, j] = [x + step[0], y + step[1]];
253 if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY)
254 addPromotion([i, j], "moveTo");
255 }
256 }
257 return moves;
258 }
259
260 getPotentialBishopMoves(sq) {
261 const forward = (this.turn == 'w' ? -1 : 1);
262 return this.getSlideNJumpMoves(
263 sq,
264 V.steps[V.BISHOP].concat([ [forward, 0] ]),
265 "oneStep"
266 );
267 }
268
269 getPotentialQueenMoves(sq) {
270 return this.getSlideNJumpMoves(
271 sq,
272 V.steps[V.BISHOP],
273 "oneStep"
274 );
275 }
276
277 getAllValidMoves() {
278 if (this.movesCount >= 2) return super.getAllValidMoves();
279 const color = this.turn;
280 let moves = [];
281 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
282 moves = moves.concat(
283 this.getPotentialMovesFrom([V.size.x + (color == "w" ? 0 : 1), i])
284 );
285 }
286 return this.filterValid(moves);
287 }
288
289 isAttackedByBishop(sq, color) {
290 const forward = (this.turn == 'w' ? 1 : -1);
291 return this.isAttackedBySlideNJump(
292 sq,
293 color,
294 V.BISHOP,
295 V.steps[V.BISHOP].concat([ [forward, 0] ]),
296 "oneStep"
297 );
298 }
299
300 isAttackedByQueen(sq, color) {
301 return this.isAttackedBySlideNJump(
302 sq,
303 color,
304 V.QUEEN,
305 V.steps[V.BISHOP],
306 "oneStep"
307 );
308 }
309
310 underCheck(color) {
311 if (this.movesCount <= 1) return false;
312 return super.underCheck(color);
313 }
314
315 play(move) {
316 const color = move.appear[0].c;
317 if (this.movesCount <= 1) {
318 V.PlayOnBoard(this.board, move);
319 const piece = move.appear[0].p;
320 this.reserve[color][piece]--;
321 if (piece == V.KING) this.kingPos[color] = [move.end.x, move.end.y];
322 if (this.subTurn == 8) {
323 // All placement moves are done
324 this.movesCount++;
325 this.turn = V.GetOppCol(color);
326 if (this.movesCount == 1) this.subTurn = 1;
327 else {
328 // Initial placement is over
329 delete this["reserve"];
330 delete this["subTurn"];
331 }
332 }
333 else this.subTurn++;
334 }
335 else super.play(move);
336 }
337
338 undo(move) {
339 const color = move.appear[0].c;
340 if (this.movesCount <= 2) {
341 V.UndoOnBoard(this.board, move);
342 const piece = move.appear[0].p;
343 if (piece == V.KING) this.kingPos[color] = [-1, -1];
344 if (!this.subTurn || this.subTurn == 1) {
345 // All placement moves are undone (if any)
346 if (!this.subTurn) this.re_setReserve(8);
347 else this.subTurn = 8;
348 this.movesCount--;
349 this.turn = color;
350 }
351 else this.subTurn--;
352 this.reserve[color][piece]++;
353 }
354 else super.undo(move);
355 }
356
357 getCheckSquares() {
358 if (this.movesCount <= 1) return [];
359 return super.getCheckSquares();
360 }
361
362 getCurrentScore() {
363 if (this.movesCount <= 1) return "*";
364 return super.getCurrentScore();
365 }
366
367 static get VALUES() {
368 return {
369 p: 1,
370 r: 5,
371 n: 3,
372 b: 3,
373 q: 2,
374 k: 1000
375 };
376 }
377
378 getComputerMove() {
379 if (this.movesCount >= 2) return super.getComputerMove();
380 // Play a random "initialization move"
381 let res = [];
382 for (let i=0; i<8; i++) {
383 const moves = this.getAllValidMoves();
384 const moveIdx = randInt(moves.length);
385 this.play(moves[moveIdx]);
386 res.push(moves[moveIdx]);
387 }
388 for (let i=7; i>=0; i--) this.undo(res[i]);
389 return res;
390 }
391
392 getNotation(move) {
393 // Do not note placement moves (complete move would be too long)
394 if (move.vanish.length == 0) return "";
395 if (move.appear[0].p != move.vanish[0].p) {
396 // Pawn promotion: indicate correct final square
397 const initSquare =
398 V.CoordsToSquare({ x: move.vanish[0].x, y: move.vanish[0].y })
399 const destSquare =
400 V.CoordsToSquare({ x: move.appear[0].x, y: move.appear[0].y })
401 const prefix = (initSquare != destSquare ? initSquare : "");
402 return prefix + destSquare + "=Q";
403 }
404 return super.getNotation(move);
405 }
406
407 };