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