Bug fixes
[vchess.git] / client / src / variants / Circular.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt, shuffle } from "@/utils/alea";
4
5 export const VariantRules = class CircularRules extends ChessRules {
6 static get HasEnpassant() {
7 return false;
8 }
9
10 static get CanFlip() {
11 return false;
12 }
13
14 setFlags(fenflags) {
15 this.pawnFlags = {
16 w: [...Array(8).fill(true)], //pawns can move 2 squares?
17 b: [...Array(8).fill(true)]
18 };
19 for (let c of ["w", "b"]) {
20 for (let i = 0; i < 8; i++)
21 this.pawnFlags[c][i] = fenflags.charAt((c == "w" ? 0 : 8) + i) == "1";
22 }
23 }
24
25 aggregateFlags() {
26 return this.pawnFlags;
27 }
28
29 disaggregateFlags(flags) {
30 this.pawnFlags = flags;
31 }
32
33 static GenRandInitFen(randomness) {
34 if (randomness == 0)
35 return "8/8/pppppppp/rnbqkbnr/8/8/PPPPPPPP/RNBQKBNR w 0 1111111111111111";
36
37 let pieces = { w: new Array(8), b: new Array(8) };
38 // Shuffle pieces on first and fifth rank
39 for (let c of ["w", "b"]) {
40 if (c == 'b' && randomness == 1) {
41 pieces['b'] = pieces['w'];
42 break;
43 }
44
45 let positions = ArrayFun.range(8);
46
47 // Get random squares for bishops
48 let randIndex = 2 * randInt(4);
49 const bishop1Pos = positions[randIndex];
50 // The second bishop must be on a square of different color
51 let randIndex_tmp = 2 * randInt(4) + 1;
52 const bishop2Pos = positions[randIndex_tmp];
53 // Remove chosen squares
54 positions.splice(Math.max(randIndex, randIndex_tmp), 1);
55 positions.splice(Math.min(randIndex, randIndex_tmp), 1);
56
57 // Get random squares for knights
58 randIndex = randInt(6);
59 const knight1Pos = positions[randIndex];
60 positions.splice(randIndex, 1);
61 randIndex = randInt(5);
62 const knight2Pos = positions[randIndex];
63 positions.splice(randIndex, 1);
64
65 // Get random square for queen
66 randIndex = randInt(4);
67 const queenPos = positions[randIndex];
68 positions.splice(randIndex, 1);
69
70 // Rooks and king positions are now fixed,
71 // because of the ordering rook-king-rook
72 const rook1Pos = positions[0];
73 const kingPos = positions[1];
74 const rook2Pos = positions[2];
75
76 // Finally put the shuffled pieces in the board array
77 pieces[c][rook1Pos] = "r";
78 pieces[c][knight1Pos] = "n";
79 pieces[c][bishop1Pos] = "b";
80 pieces[c][queenPos] = "q";
81 pieces[c][kingPos] = "k";
82 pieces[c][bishop2Pos] = "b";
83 pieces[c][knight2Pos] = "n";
84 pieces[c][rook2Pos] = "r";
85 }
86 return (
87 "8/8/pppppppp/" +
88 pieces["b"].join("") +
89 "/8/8/PPPPPPPP/" +
90 pieces["w"].join("").toUpperCase() +
91 // 16 flags: can pawns advance 2 squares?
92 " w 0 1111111111111111"
93 );
94 }
95
96 // Output basically x % 8 (circular board)
97 static ComputeX(x) {
98 let res = x % V.size.x;
99 if (res < 0)
100 res += V.size.x;
101 return res;
102 }
103
104 getSlideNJumpMoves([x, y], steps, oneStep) {
105 let moves = [];
106 outerLoop: for (let step of steps) {
107 let i = V.ComputeX(x + step[0]);
108 let j = y + step[1];
109 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
110 moves.push(this.getBasicMove([x, y], [i, j]));
111 if (oneStep !== undefined) continue outerLoop;
112 i = V.ComputeX(i + step[0]);
113 j += step[1];
114 }
115 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
116 moves.push(this.getBasicMove([x, y], [i, j]));
117 }
118 return moves;
119 }
120
121 getPotentialPawnMoves([x, y]) {
122 const color = this.turn;
123 let moves = [];
124 const [sizeX, sizeY] = [V.size.x, V.size.y];
125 // All pawns go in the same direction!
126 const shiftX = -1;
127 const startRank = color == "w" ? sizeX - 2 : 2;
128
129 // One square forward
130 const nextRow = V.ComputeX(x + shiftX);
131 if (this.board[nextRow][y] == V.EMPTY) {
132 moves.push(this.getBasicMove([x, y], [nextRow, y]));
133 if (
134 x == startRank &&
135 this.pawnFlags[color][y] &&
136 this.board[x + 2 * shiftX][y] == V.EMPTY
137 ) {
138 // Two squares jump
139 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
140 }
141 }
142 // Captures
143 for (let shiftY of [-1, 1]) {
144 if (
145 y + shiftY >= 0 &&
146 y + shiftY < sizeY &&
147 this.board[nextRow][y + shiftY] != V.EMPTY &&
148 this.canTake([x, y], [nextRow, y + shiftY])
149 ) {
150 moves.push(this.getBasicMove([x, y], [nextRow, y + shiftY]));
151 }
152 }
153
154 return moves;
155 }
156
157 getPotentialKingMoves(sq) {
158 return this.getSlideNJumpMoves(
159 sq,
160 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
161 "oneStep"
162 );
163 }
164
165 filterValid(moves) {
166 const filteredMoves = super.filterValid(moves);
167 // If at least one full move made, everything is allowed:
168 if (this.movesCount >= 2)
169 return filteredMoves;
170 // Else, forbid check:
171 const oppCol = V.GetOppCol(this.turn);
172 return filteredMoves.filter(m => {
173 this.play(m);
174 const res = !this.underCheck(oppCol);
175 this.undo(m);
176 return res;
177 });
178 }
179
180 isAttackedByPawn([x, y], colors) {
181 const pawnShift = 1;
182 const attackerRow = V.ComputeX(x + pawnShift);
183 for (let c of colors) {
184 for (let i of [-1, 1]) {
185 if (
186 y + i >= 0 &&
187 y + i < V.size.y &&
188 this.getPiece(attackerRow, y + i) == V.PAWN &&
189 this.getColor(attackerRow, y + i) == c
190 ) {
191 return true;
192 }
193 }
194 }
195 return false;
196 }
197
198 isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
199 for (let step of steps) {
200 let rx = V.ComputeX(x + step[0]),
201 ry = y + step[1];
202 while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
203 rx = V.ComputeX(rx + step[0]);
204 ry += step[1];
205 }
206 if (
207 V.OnBoard(rx, ry) &&
208 this.getPiece(rx, ry) === piece &&
209 colors.includes(this.getColor(rx, ry))
210 ) {
211 return true;
212 }
213 }
214 return false;
215 }
216
217 getFlagsFen() {
218 // Return pawns flags
219 let flags = "";
220 for (let c of ["w", "b"]) {
221 for (let i = 0; i < 8; i++) flags += this.pawnFlags[c][i] ? "1" : "0";
222 }
223 return flags;
224 }
225
226 updateVariables(move) {
227 const c = move.vanish[0].c;
228 const secondRank = {"w":6, "b":2};
229 // Update king position + flags
230 if (move.vanish[0].p == V.KING && move.appear.length > 0) {
231 this.kingPos[c][0] = move.appear[0].x;
232 this.kingPos[c][1] = move.appear[0].y;
233 }
234 else if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
235 // This move turns off a 2-squares pawn flag
236 this.pawnFlags[c][move.start.y] = false;
237 }
238
239 static get VALUES() {
240 return {
241 p: 1,
242 r: 5,
243 n: 3,
244 b: 4,
245 q: 10,
246 k: 1000
247 };
248 }
249
250 static get SEARCH_DEPTH() {
251 return 2;
252 }
253 };