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