Fix Screen variant: allow any setup even if king captured
[vchess.git] / client / src / variants / Screen.js
CommitLineData
39fe711a
BA
1import { ChessRules, Move, PiPo } from "@/base_rules";
2import { randInt } from "@/utils/alea";
3import { ArrayFun } from "@/utils/array";
4
5export class ScreenRules extends ChessRules {
6
7 static get HasFlags() {
8 return false;
9 }
10
11 static get HasEnpassant() {
12 return false;
13 }
14
39fe711a
BA
15 get canAnalyze() {
16 return this.movesCount >= 2;
17 }
18
19 get someHiddenMoves() {
20 return this.movesCount <= 1;
21 }
22
23 static GenRandInitFen() {
24 // Empty board
25 return "8/8/8/8/8/8/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.PAWN]: wc * 8,
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.PAWN]: bc * 8,
43 [V.ROOK]: bc * 2,
44 [V.KNIGHT]: bc * 2,
45 [V.BISHOP]: bc * 2,
46 [V.QUEEN]: bc,
47 [V.KING]: bc
48 }
49 }
50 this.subTurn = subTurn || 1;
51 }
52
53 re_setEnlightened(onOff) {
54 if (!onOff) delete this["enlightened"];
55 else {
56 // Turn on:
57 this.enlightened = {
58 'w': ArrayFun.init(8, 8, false),
59 'b': ArrayFun.init(8, 8, false)
60 };
61 for (let i=0; i<4; i++) {
62 for (let j=0; j<8; j++) this.enlightened['b'][i][j] = true;
63 }
ded43c88 64 for (let i=4; i<8; i++) {
39fe711a
BA
65 for (let j=0; j<8; j++) this.enlightened['w'][i][j] = true;
66 }
67 }
68 }
69
70 setOtherVariables(fen) {
71 super.setOtherVariables(fen);
72 if (this.movesCount <= 1) {
73 this.re_setReserve();
74 this.re_setEnlightened(true);
75 }
76 }
77
78 getColor(i, j) {
79 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
80 return this.board[i][j].charAt(0);
81 }
82
83 getPiece(i, j) {
84 if (i >= V.size.x) return V.RESERVE_PIECES[j];
85 return this.board[i][j].charAt(1);
86 }
87
88 getReservePpath(index, color) {
89 return color + V.RESERVE_PIECES[index];
90 }
91
92 static get RESERVE_PIECES() {
93 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
94 }
95
96 getPotentialMovesFrom([x, y]) {
97 if (this.movesCount >= 2) return super.getPotentialMovesFrom([x, y]);
98 // Only reserve moves are allowed for now:
99 if (V.OnBoard(x, y)) return [];
100 const color = this.turn;
101 const p = V.RESERVE_PIECES[y];
102 if (this.reserve[color][p] == 0) return [];
103 const shift = (p == V.PAWN ? 1 : 0);
104 let iBound = (color == 'w' ? [4, 7 - shift] : [shift, 3]);
105 let moves = [];
106
107 // Pawns cannot stack on files, one bishop per color
108 let forbiddenFiles = [];
109 if (p == V.PAWN) {
110 const colorShift = (color == 'w' ? 4 : 1);
111 forbiddenFiles =
112 ArrayFun.range(8).filter(jj => {
113 return ArrayFun.range(3).some(ii => {
114 return (
115 this.board[colorShift + ii][jj] != V.EMPTY &&
116 this.getPiece(colorShift + ii, jj) == V.PAWN
117 );
118 })
119 });
120 }
121 let forbiddenColor = -1;
122 if (p == V.BISHOP) {
123 const colorShift = (color == 'w' ? 4 : 0);
124 outerLoop: for (let ii = colorShift; ii < colorShift + 4; ii++) {
125 for (let jj = 0; jj < 8; jj++) {
126 if (
127 this.board[ii][jj] != V.EMPTY &&
128 this.getPiece(ii, jj) == V.BISHOP
129 ) {
130 forbiddenColor = (ii + jj) % 2;
131 break outerLoop;
132 }
133 }
134 }
135 }
136
137 for (let i = iBound[0]; i <= iBound[1]; i++) {
138 for (let j = 0; j < 8; j++) {
139 if (
140 this.board[i][j] == V.EMPTY &&
141 (p != V.PAWN || !forbiddenFiles.includes(j)) &&
142 (p != V.BISHOP || (i + j) % 2 != forbiddenColor)
143 ) {
144 // Ok, move is valid:
145 let mv = new Move({
146 appear: [
147 new PiPo({
148 x: i,
149 y: j,
150 c: color,
151 p: p
152 })
153 ],
154 vanish: [],
155 start: { x: x, y: y },
156 end: { x: i, y: j }
157 });
158 moves.push(mv);
159 }
160 }
161 }
162 moves.forEach(m => { m.end.noHighlight = true; });
163 return moves;
164 }
165
166 underCheck(color) {
167 if (this.movesCount <= 1) return false;
168 return super.underCheck(color);
169 }
170
171 getAllValidMoves() {
172 if (this.movesCount >= 2) return super.getAllValidMoves();
173 const color = this.turn;
174 let moves = [];
175 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
176 moves = moves.concat(
177 this.getPotentialMovesFrom([V.size.x + (color == "w" ? 0 : 1), i])
178 );
179 }
8d8a70d9
BA
180 // Some setup moves may let the king en prise (thus game would be lost)
181 return moves;
182 }
183
184 filterValid(moves) {
185 if (this.movesCount <= 1) return moves;
186 return super.filterValid(moves);
39fe711a
BA
187 }
188
189 play(move) {
190 const color = move.appear[0].c;
191 if (this.movesCount <= 1) {
192 V.PlayOnBoard(this.board, move);
193 const piece = move.appear[0].p;
194 this.reserve[color][piece]--;
195 if (piece == V.KING) this.kingPos[color] = [move.end.x, move.end.y];
196 if (this.subTurn == 16) {
197 // All placement moves are done
198 this.movesCount++;
199 this.turn = V.GetOppCol(color);
200 if (this.movesCount == 1) this.subTurn = 1;
201 else {
202 // Initial placement is over
203 delete this["reserve"];
204 delete this["subTurn"];
205 }
206 }
207 else this.subTurn++;
208 }
209 else {
210 if (this.movesCount == 2) this.re_setEnlightened(false);
211 super.play(move);
212 }
213 }
214
8d8a70d9
BA
215 postPlay(move) {
216 if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
217 // Only black king could be captured (theoretically...)
218 this.kingPos['b'] = [-1, -1];
219 super.postPlay(move);
220 }
221
39fe711a
BA
222 undo(move) {
223 const color = move.appear[0].c;
224 if (this.movesCount <= 2) {
225 V.UndoOnBoard(this.board, move);
226 const piece = move.appear[0].p;
227 if (piece == V.KING) this.kingPos[color] = [-1, -1];
228 if (!this.subTurn || this.subTurn == 1) {
229 // All placement moves are undone (if any)
230 if (!this.subTurn) this.re_setReserve(16);
231 else this.subTurn = 16;
232 this.movesCount--;
233 if (this.movesCount == 1) this.re_setEnlightened(true);
234 this.turn = color;
235 }
236 else this.subTurn--;
237 this.reserve[color][piece]++;
238 }
239 else super.undo(move);
240 }
241
8d8a70d9
BA
242 postUndo(move) {
243 if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
244 this.kingPos['b'] = [move.vanish[1].x, move.vanish[1].y];
245 super.postUndo(move);
246 }
247
39fe711a
BA
248 getCheckSquares() {
249 if (this.movesCount <= 1) return [];
250 return super.getCheckSquares();
251 }
252
253 getCurrentScore() {
254 if (this.movesCount <= 1) return "*";
8d8a70d9
BA
255 // Only black king could be eaten on move 2:
256 if (this.kingPos['b'][0] < 0) return "1-0";
39fe711a
BA
257 return super.getCurrentScore();
258 }
259
260 getComputerMove() {
261 if (this.movesCount >= 2) return super.getComputerMove();
262 // Play a random "initialization move"
263 let res = [];
264 for (let i=0; i<16; i++) {
265 const moves = this.getAllValidMoves();
266 const moveIdx = randInt(moves.length);
267 this.play(moves[moveIdx]);
268 res.push(moves[moveIdx]);
269 }
270 for (let i=15; i>=0; i--) this.undo(res[i]);
271 return res;
272 }
273
274 getNotation(move) {
275 // Do not note placement moves (complete move would be too long)
276 if (move.vanish.length == 0) return "";
277 return super.getNotation(move);
278 }
279
280};