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