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