Fix Sittuyin promotions notation
[vchess.git] / client / src / variants / Sittuyin.js
CommitLineData
b406466b
BA
1import { ChessRules, Move, PiPo } from "@/base_rules";
2
3export class SittuyinRules extends ChessRules {
4 static get HasFlags() {
5 return false;
6 }
7
8 static get HasEnpassant() {
9 return false;
10 }
11
12 static get PawnSpecs() {
13 return Object.assign(
14 {},
15 ChessRules.PawnSpecs,
16 {
17 twoSquares: false,
18 // Promotions are handled differently here
19 promotions: [V.QUEEN]
20 }
21 );
22 }
23
24 static GenRandInitFen() {
25 return "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8 w 0";
26 }
27
28 re_setReserve(subTurn) {
29 const mc = this.movesCount;
30 const wc = (mc == 0 ? 1 : 0);
31 const bc = (mc <= 1 ? 1 : 0);
32 this.reserve = {
33 w: {
34 [V.ROOK]: wc * 2,
35 [V.KNIGHT]: wc * 2,
36 [V.BISHOP]: wc * 2,
37 [V.QUEEN]: wc,
38 [V.KING]: wc
39 },
40 b: {
41 [V.ROOK]: bc * 2,
42 [V.KNIGHT]: bc * 2,
43 [V.BISHOP]: bc * 2,
44 [V.QUEEN]: bc,
45 [V.KING]: bc
46 }
47 }
48 this.subTurn = subTurn || 1;
49 }
50
51 setOtherVariables(fen) {
52 super.setOtherVariables(fen);
53 if (this.movesCount <= 1) this.re_setReserve();
54 }
55
56 getPpath(b) {
57 return "Sittuyin/" + b;
58 }
59
60 getColor(i, j) {
61 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
62 return this.board[i][j].charAt(0);
63 }
64
65 getPiece(i, j) {
66 if (i >= V.size.x) return V.RESERVE_PIECES[j];
67 return this.board[i][j].charAt(1);
68 }
69
70 getReservePpath(index, color) {
71 return "Sittuyin/" + color + V.RESERVE_PIECES[index];
72 }
73
74 static get RESERVE_PIECES() {
75 return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
76 }
77
78 getPotentialMovesFrom([x, y]) {
79 if (this.movesCount <= 1) {
80 const color = this.turn;
81 const p = V.RESERVE_PIECES[y];
82 if (this.reserve[color][p] == 0) return [];
83 const iBound =
84 p != V.ROOK
85 ? (color == 'w' ? [4, 7] : [0, 3])
86 : (color == 'w' ? [7, 7] : [0, 0]);
87 const jBound = (i) => {
88 if (color == 'w' && i == 4) return [4, 7];
89 if (color == 'b' && i == 3) return [0, 3];
90 return [0, 7];
91 };
92 let moves = [];
93 for (let i = iBound[0]; i <= iBound[1]; i++) {
94 const jb = jBound(i);
95 for (let j = jb[0]; j <= jb[1]; j++) {
96 if (this.board[i][j] == V.EMPTY) {
97 let mv = new Move({
98 appear: [
99 new PiPo({
100 x: i,
101 y: j,
102 c: color,
103 p: p
104 })
105 ],
106 vanish: [],
107 start: { x: x, y: y },
108 end: { x: i, y: j }
109 });
110 moves.push(mv);
111 }
112 }
113 }
114 return moves;
115 }
116 return super.getPotentialMovesFrom([x, y]);
117 }
118
119 getPotentialPawnMoves([x, y]) {
120 const color = this.turn;
121 const [sizeX, sizeY] = [V.size.x, V.size.y];
122 const shiftX = V.PawnSpecs.directions[color];
123 let moves = [];
124 // NOTE: next condition is generally true (no pawn on last rank)
125 if (x + shiftX >= 0 && x + shiftX < sizeX) {
126 if (this.board[x + shiftX][y] == V.EMPTY) {
127 // One square forward
128 moves.push(this.getBasicMove([x, y], [x + shiftX, y]));
129 }
130 // Captures
131 if (V.PawnSpecs.canCapture) {
132 for (let shiftY of [-1, 1]) {
133 if (
134 y + shiftY >= 0 &&
135 y + shiftY < sizeY
136 ) {
137 if (
138 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
139 this.canTake([x, y], [x + shiftX, y + shiftY])
140 ) {
141 moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
142 }
143 }
144 }
145 }
146 }
147 let queenOnBoard = false;
148 let pawnsCount = 0;
149 outerLoop: for (let i=0; i<8; i++) {
150 for (let j=0; j<8; j++) {
151 if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
152 const p = this.getPiece(i, j);
153 if (p == V.QUEEN) {
154 queenOnBoard = true;
155 break outerLoop;
156 }
157 else if (p == V.PAWN && pawnsCount <= 1) pawnsCount++;
158 }
159 }
160 }
161 if (
162 !queenOnBoard &&
163 (
164 pawnsCount == 1 ||
165 (color == 'w' && ((y <= 3 && x == y) || (y >= 4 && x == 7 - y))) ||
166 (color == 'b' && ((y >= 4 && x == y) || (y <= 3 && x == 7 - y)))
167 )
168 ) {
169 // Add potential promotions
170 const addPromotion = ([xx, yy], moveTo) => {
171 moves.push(
172 new Move({
173 appear: [
174 new PiPo({
175 x: !!moveTo ? xx : x,
176 y: yy, //yy == y if !!moveTo
177 c: color,
178 p: V.QUEEN
179 })
180 ],
181 vanish: [
182 new PiPo({
183 x: x,
184 y: y,
185 c: color,
186 p: V.PAWN
187 })
188 ],
189 start: { x: x, y: y },
190 end: { x: xx, y: yy }
191 })
192 );
193 };
194 // In-place promotion always possible:
195 addPromotion([x - shiftX, y]);
196 for (let step of V.steps[V.BISHOP]) {
197 const [i, j] = [x + step[0], y + step[1]];
8a44494d 198 if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY)
b406466b
BA
199 addPromotion([i, j], "moveTo");
200 }
201 }
202 return moves;
203 }
204
205 getPotentialBishopMoves(sq) {
206 const forward = (this.turn == 'w' ? -1 : 1);
207 return this.getSlideNJumpMoves(
208 sq,
209 V.steps[V.BISHOP].concat([ [forward, 0] ]),
210 "oneStep"
211 );
212 }
213
214 getPotentialQueenMoves(sq) {
215 return this.getSlideNJumpMoves(
216 sq,
217 V.steps[V.BISHOP],
218 "oneStep"
219 );
220 }
221
222 isAttackedByBishop(sq, color) {
223 const forward = (this.turn == 'w' ? 1 : -1);
224 return this.isAttackedBySlideNJump(
225 sq,
226 color,
227 V.BISHOP,
228 V.steps[V.BISHOP].concat([ [forward, 0] ]),
229 "oneStep"
230 );
231 }
232
233 isAttackedByQueen(sq, color) {
234 return this.isAttackedBySlideNJump(
235 sq,
236 color,
237 V.QUEEN,
238 V.steps[V.BISHOP],
239 "oneStep"
240 );
241 }
242
243 underCheck(color) {
244 if (this.movesCount <= 1) return false;
245 return super.underCheck(color);
246 }
247
248 play(move) {
249 const color = move.appear[0].c;
250 if (this.movesCount <= 1) {
251 V.PlayOnBoard(this.board, move);
252 const piece = move.appear[0].p;
253 this.reserve[color][piece]--;
254 if (piece == V.KING) this.kingPos[color] = [move.end.x, move.end.y];
255 if (this.subTurn == 8) {
256 // All placement moves are done
257 this.movesCount++;
258 this.turn = V.GetOppCol(color);
259 if (this.movesCount == 1) this.subTurn = 1;
260 else {
261 // Initial placement is over
262 delete this["reserve"];
263 delete this["subTurn"];
264 }
265 }
266 else this.subTurn++;
267 }
268 else super.play(move);
269 }
270
271 undo(move) {
272 const color = move.appear[0].c;
273 if (this.movesCount <= 2) {
274 V.UndoOnBoard(this.board, move);
275 const piece = move.appear[0].p;
276 if (piece == V.KING) this.kingPos[color] = [-1, -1];
277 if (!this.subTurn || this.subTurn == 1) {
278 // All placement moves are undone (if any)
279 if (!this.subTurn) this.re_setReserve(8);
280 else this.subTurn = 8;
281 this.movesCount--;
282 this.turn = color;
283 }
284 else this.subTurn--;
285 this.reserve[color][piece]++;
286 }
287 else super.undo(move);
288 }
289
290 getCheckSquares() {
291 if (this.movesCount <= 1) return [];
292 return super.getCheckSquares();
293 }
294
295 getCurrentScore() {
296 if (this.movesCount <= 1) return "*";
297 return super.getCurrentScore();
298 }
299
300 static get VALUES() {
301 return {
302 p: 1,
303 r: 5,
304 n: 3,
305 b: 3,
306 q: 2,
307 k: 1000
308 };
309 }
310
311 getNotation(move) {
312 // Do not note placement moves (complete move would be too long)
313 if (move.vanish.length == 0) return "";
fcffb2da
BA
314 if (move.appear[0].p != move.vanish[0].p) {
315 // Pawn promotion: indicate correct final square
316 const initSquare =
317 V.CoordsToSquare({ x: move.vanish[0].x, y: move.vanish[0].y })
318 const destSquare =
319 V.CoordsToSquare({ x: move.vanish[0].x, y: move.vanish[0].y })
320 const prefix = (initSquare != destSquare ? initSquare : "");
321 return prefix + destSquare + "=Q";
322 }
b406466b
BA
323 return super.getNotation(move);
324 }
325};