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