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