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