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