Finish Chakart. Debug now
[vchess.git] / client / src / variants / Circular.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { shuffle } from "@/utils/alea";
4
5 export 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 " +
40 "w 0 1111111111111111";
41 }
42
43 let pieces = { w: new Array(8), b: new Array(8) };
44 // Shuffle pieces on first and last rank
45 for (let c of ["w", "b"]) {
46 if (c == 'b' && randomness == 1) {
47 pieces['b'] = pieces['w'];
48 break;
49 }
50
51 // Get random squares for every piece, totally freely
52 let positions = shuffle(ArrayFun.range(8));
53 const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
54 const rem2 = positions[0] % 2;
55 if (rem2 == positions[1] % 2) {
56 // Fix bishops (on different colors)
57 for (let i=2; i<8; i++) {
58 if (positions[i] % 2 != rem2)
59 [positions[1], positions[i]] = [positions[i], positions[1]];
60 }
61 }
62 for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i];
63 }
64 return (
65 "8/8/pppppppp/" +
66 pieces["b"].join("") +
67 "/8/8/PPPPPPPP/" +
68 pieces["w"].join("").toUpperCase() +
69 // 16 flags: can pawns advance 2 squares?
70 " w 0 1111111111111111"
71 );
72 }
73
74 // Output basically x % 8 (circular board)
75 static ComputeX(x) {
76 let res = x % V.size.x;
77 if (res < 0)
78 res += V.size.x;
79 return res;
80 }
81
82 getSlideNJumpMoves([x, y], steps, oneStep) {
83 let moves = [];
84 // Don't add move twice when running on an infinite file:
85 let infiniteSteps = {};
86 outerLoop: for (let step of steps) {
87 if (!!infiniteSteps[(-step[0]) + "." + (-step[1])]) continue;
88 let i = V.ComputeX(x + step[0]);
89 let j = y + step[1];
90 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
91 moves.push(this.getBasicMove([x, y], [i, j]));
92 if (oneStep !== undefined) continue outerLoop;
93 i = V.ComputeX(i + step[0]);
94 j += step[1];
95 }
96 if (V.OnBoard(i, j)) {
97 if (i == x && j == y)
98 // Looped back onto initial square
99 infiniteSteps[step[0] + "." + step[1]] = true;
100 else if (this.canTake([x, y], [i, j]))
101 moves.push(this.getBasicMove([x, y], [i, j]));
102 }
103 }
104 return moves;
105 }
106
107 getPotentialPawnMoves([x, y]) {
108 const color = this.turn;
109 let moves = [];
110 const [sizeX, sizeY] = [V.size.x, V.size.y];
111 // All pawns go in the same direction!
112 const shiftX = -1;
113 const startRank = color == "w" ? sizeX - 2 : 2;
114
115 // One square forward
116 const nextRow = V.ComputeX(x + shiftX);
117 if (this.board[nextRow][y] == V.EMPTY) {
118 moves.push(this.getBasicMove([x, y], [nextRow, y]));
119 if (
120 x == startRank &&
121 this.pawnFlags[color][y] &&
122 this.board[x + 2 * shiftX][y] == V.EMPTY
123 ) {
124 // Two squares jump
125 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
126 }
127 }
128 // Captures
129 for (let shiftY of [-1, 1]) {
130 if (
131 y + shiftY >= 0 &&
132 y + shiftY < sizeY &&
133 this.board[nextRow][y + shiftY] != V.EMPTY &&
134 this.canTake([x, y], [nextRow, y + shiftY])
135 ) {
136 moves.push(this.getBasicMove([x, y], [nextRow, y + shiftY]));
137 }
138 }
139
140 return moves;
141 }
142
143 filterValid(moves) {
144 const filteredMoves = super.filterValid(moves);
145 // If at least one full move made, everything is allowed:
146 if (this.movesCount >= 2) return filteredMoves;
147 // Else, forbid check:
148 const oppCol = V.GetOppCol(this.turn);
149 return filteredMoves.filter(m => {
150 this.play(m);
151 const res = !this.underCheck(oppCol);
152 this.undo(m);
153 return res;
154 });
155 }
156
157 isAttackedByPawn([x, y], color) {
158 // pawn shift is always 1 (all pawns go the same way)
159 const attackerRow = V.ComputeX(x + 1);
160 for (let i of [-1, 1]) {
161 if (
162 y + i >= 0 &&
163 y + i < V.size.y &&
164 this.getPiece(attackerRow, y + i) == V.PAWN &&
165 this.getColor(attackerRow, y + i) == color
166 ) {
167 return true;
168 }
169 }
170 return false;
171 }
172
173 isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
174 for (let step of steps) {
175 let rx = V.ComputeX(x + step[0]),
176 ry = y + step[1];
177 while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
178 rx = V.ComputeX(rx + step[0]);
179 ry += step[1];
180 }
181 if (
182 V.OnBoard(rx, ry) &&
183 this.getPiece(rx, ry) == piece &&
184 this.getColor(rx, ry) == color
185 ) {
186 return true;
187 }
188 }
189 return false;
190 }
191
192 getFlagsFen() {
193 // Return pawns flags
194 let flags = "";
195 for (let c of ["w", "b"]) {
196 for (let i = 0; i < 8; i++) flags += this.pawnFlags[c][i] ? "1" : "0";
197 }
198 return flags;
199 }
200
201 postPlay(move) {
202 super.postPlay(move);
203 const c = move.vanish[0].c;
204 const secondRank = { "w": 6, "b": 2 };
205 if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
206 // This move turns off a 2-squares pawn flag
207 this.pawnFlags[c][move.start.y] = false;
208 }
209
210 static get VALUES() {
211 return {
212 p: 1,
213 r: 5,
214 n: 3,
215 b: 4,
216 q: 10,
217 k: 1000
218 };
219 }
220
221 static get SEARCH_DEPTH() {
222 return 2;
223 }
224 };