Change castle flags. Eightpieces still not OK, but almost
[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 getPotentialKingMoves(sq) {
162 return this.getSlideNJumpMoves(
163 sq,
164 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
165 "oneStep"
166 );
167 }
168
169 filterValid(moves) {
170 const filteredMoves = super.filterValid(moves);
171 // If at least one full move made, everything is allowed:
172 if (this.movesCount >= 2) return filteredMoves;
173 // Else, forbid check:
174 const oppCol = V.GetOppCol(this.turn);
175 return filteredMoves.filter(m => {
176 this.play(m);
177 const res = !this.underCheck(oppCol);
178 this.undo(m);
179 return res;
180 });
181 }
182
183 isAttackedByPawn([x, y], colors) {
184 const pawnShift = 1;
185 const attackerRow = V.ComputeX(x + pawnShift);
186 for (let c of colors) {
187 for (let i of [-1, 1]) {
188 if (
189 y + i >= 0 &&
190 y + i < V.size.y &&
191 this.getPiece(attackerRow, y + i) == V.PAWN &&
192 this.getColor(attackerRow, y + i) == c
193 ) {
194 return true;
195 }
196 }
197 }
198 return false;
199 }
200
201 isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
202 for (let step of steps) {
203 let rx = V.ComputeX(x + step[0]),
204 ry = y + step[1];
205 while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
206 rx = V.ComputeX(rx + step[0]);
207 ry += step[1];
208 }
209 if (
210 V.OnBoard(rx, ry) &&
211 this.getPiece(rx, ry) === piece &&
212 colors.includes(this.getColor(rx, ry))
213 ) {
214 return true;
215 }
216 }
217 return false;
218 }
219
220 getFlagsFen() {
221 // Return pawns flags
222 let flags = "";
223 for (let c of ["w", "b"]) {
224 for (let i = 0; i < 8; i++) flags += this.pawnFlags[c][i] ? "1" : "0";
225 }
226 return flags;
227 }
228
229 postPlay(move) {
230 super.postPlay(move);
231 const c = move.vanish[0].c;
232 const secondRank = { "w": 6, "b": 2 };
233 if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
234 // This move turns off a 2-squares pawn flag
235 this.pawnFlags[c][move.start.y] = false;
236 }
237
238 static get VALUES() {
239 return {
240 p: 1,
241 r: 5,
242 n: 3,
243 b: 4,
244 q: 10,
245 k: 1000
246 };
247 }
248
249 static get SEARCH_DEPTH() {
250 return 2;
251 }
252 };