Add Antiking v1
[vchess.git] / client / src / variants / Antiking1.js
1 import { ChessRules } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt } from "@/utils/alea";
4
5 export const VariantRules = class Antiking1Rules extends ChessRules {
6 static get HasEnpassant() {
7 return false;
8 }
9
10 static get HasCastle() {
11 return false;
12 }
13
14 static get ANTIKING() {
15 return "a";
16 }
17
18 static get PIECES() {
19 return ChessRules.PIECES.concat([V.ANTIKING]);
20 }
21
22 getPpath(b) {
23 return b[1] == "a" ? "Antiking/" + b : b;
24 }
25
26 setOtherVariables(fen) {
27 super.setOtherVariables(fen);
28 this.antikingPos = { w: [-1, -1], b: [-1, -1] };
29 const rows = V.ParseFen(fen).position.split("/");
30 for (let i = 0; i < rows.length; i++) {
31 let k = 0;
32 for (let j = 0; j < rows[i].length; j++) {
33 switch (rows[i].charAt(j)) {
34 case "a":
35 this.antikingPos["b"] = [i, k];
36 break;
37 case "A":
38 this.antikingPos["w"] = [i, k];
39 break;
40 default: {
41 const num = parseInt(rows[i].charAt(j));
42 if (!isNaN(num)) k += num - 1;
43 }
44 }
45 k++;
46 }
47 }
48 }
49
50 // (Anti)King flags at 1 (true) if they can knight-jump
51 setFlags(fenflags) {
52 this.kingFlags = {
53 // King then antiking
54 w: [...Array(2).fill(false)],
55 b: [...Array(2).fill(false)]
56 };
57 for (let c of ["w", "b"]) {
58 for (let i = 0; i < 2; i++)
59 this.kingFlags[c][i] = fenflags.charAt((c == "w" ? 0 : 2) + i) == "1";
60 }
61 }
62
63 aggregateFlags() {
64 return this.kingFlags;
65 }
66
67 disaggregateFlags(flags) {
68 this.kingFlags = flags;
69 }
70
71 getFlagsFen() {
72 // Return kings flags
73 let flags = "";
74 for (let c of ["w", "b"]) {
75 for (let i = 0; i < 2; i++) flags += this.kingFlags[c][i] ? "1" : "0";
76 }
77 return flags;
78 }
79
80 canTake([x1, y1], [x2, y2]) {
81 const piece1 = this.getPiece(x1, y1);
82 const piece2 = this.getPiece(x2, y2);
83 const color1 = this.getColor(x1, y1);
84 const color2 = this.getColor(x2, y2);
85 return (
86 piece2 != "a" &&
87 ((piece1 != "a" && color1 != color2) ||
88 (piece1 == "a" && color1 == color2))
89 );
90 }
91
92 getPotentialMovesFrom([x, y]) {
93 let moves = [];
94 let addKnightJumps = false;
95 const piece = this.getPiece(x, y);
96 const color = this.getColor(x, y);
97 if (piece == V.ANTIKING) {
98 moves = this.getPotentialAntikingMoves([x, y]);
99 addKnightJumps = this.kingFlags[color][1];
100 } else {
101 moves = super.getPotentialMovesFrom([x, y]);
102 if (piece == V.KING) addKnightJumps = this.kingFlags[color][0];
103 }
104 if (addKnightJumps) {
105 // Add potential knight jump to (anti)kings
106 const knightJumps = super.getPotentialKnightMoves([x, y]);
107 // Remove captures (TODO: could be done more efficiently...)
108 moves = moves.concat(knightJumps.filter(m => m.vanish.length == 1));
109 }
110 return moves;
111 }
112
113 getPotentialPawnMoves([x, y]) {
114 const color = this.turn;
115 let moves = [];
116 const [sizeX, sizeY] = [V.size.x, V.size.y];
117 const shiftX = color == "w" ? -1 : 1;
118 const startRank = color == "w" ? sizeX - 2 : 1;
119 const lastRank = color == "w" ? 0 : sizeX - 1;
120 const finalPieces =
121 x + shiftX == lastRank ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] : [V.PAWN];
122
123 // One square diagonally
124 for (let shiftY of [-1, 1]) {
125 if (this.board[x + shiftX][y + shiftY] == V.EMPTY) {
126 for (let piece of finalPieces) {
127 moves.push(
128 this.getBasicMove([x, y], [x + shiftX, y + shiftY], {
129 c: color,
130 p: piece
131 })
132 );
133 }
134 }
135 }
136 // Capture
137 if (
138 this.board[x + shiftX][y] != V.EMPTY &&
139 this.canTake([x, y], [x + shiftX, y])
140 ) {
141 for (let piece of finalPieces)
142 moves.push(
143 this.getBasicMove([x, y], [x + shiftX, y], { c: color, p: piece })
144 );
145 }
146
147 return moves;
148 }
149
150 getPotentialAntikingMoves(sq) {
151 // The antiking moves like a king (only captured colors differ)
152 return this.getSlideNJumpMoves(
153 sq,
154 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
155 "oneStep"
156 );
157 }
158
159 isAttacked(sq, color) {
160 return (
161 super.isAttacked(sq, color) ||
162 this.isAttackedByAntiking(sq, color)
163 );
164 }
165
166 isAttackedByPawn([x, y], color) {
167 let pawnShift = (color == "w" ? 1 : -1);
168 if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
169 if (
170 this.getPiece(x + pawnShift, y) == V.PAWN &&
171 this.getColor(x + pawnShift, y) == color
172 ) {
173 return true;
174 }
175 }
176 return false;
177 }
178
179 isAttackedByKing([x, y], color) {
180 // Antiking is not attacked by king:
181 if (this.getPiece(x, y) == V.ANTIKING) return false;
182 return this.isAttackedBySlideNJump(
183 [x, y],
184 color,
185 V.KING,
186 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
187 "oneStep"
188 );
189 }
190
191 isAttackedByAntiking([x, y], color) {
192 // (Anti)King is not attacked by antiking
193 if ([V.KING, V.ANTIKING].includes(this.getPiece(x, y))) return false;
194 return this.isAttackedBySlideNJump(
195 [x, y],
196 color,
197 V.ANTIKING,
198 V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
199 "oneStep"
200 );
201 }
202
203 underCheck(color) {
204 const oppCol = V.GetOppCol(color);
205 let res =
206 this.isAttacked(this.kingPos[color], oppCol) ||
207 !this.isAttacked(this.antikingPos[color], oppCol);
208 return res;
209 }
210
211 getCheckSquares(color) {
212 let res = [];
213 const oppCol = V.GetOppCol(color);
214 if (this.isAttacked(this.kingPos[color], oppCol))
215 res.push(JSON.parse(JSON.stringify(this.kingPos[color])));
216 if (!this.isAttacked(this.antikingPos[color], oppCol))
217 res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
218 return res;
219 }
220
221 postPlay(move) {
222 super.postPlay(move);
223 const piece = move.vanish[0].p;
224 const c = move.vanish[0].c;
225 // Update antiking position, and kings flags
226 if (piece == V.ANTIKING) {
227 this.antikingPos[c][0] = move.appear[0].x;
228 this.antikingPos[c][1] = move.appear[0].y;
229 this.kingFlags[c][1] = false;
230 } else if (piece == V.KING) this.kingFlags[c][0] = false;
231 }
232
233 postUndo(move) {
234 super.postUndo(move);
235 const c = move.vanish[0].c;
236 if (move.vanish[0].p == V.ANTIKING)
237 this.antikingPos[c] = [move.start.x, move.start.y];
238 }
239
240 getCurrentScore() {
241 if (this.atLeastOneMove())
242 return "*";
243
244 const color = this.turn;
245 const oppCol = V.GetOppCol(color);
246 if (
247 !this.isAttacked(this.kingPos[color], oppCol) &&
248 this.isAttacked(this.antikingPos[color], oppCol)
249 ) {
250 return "1/2";
251 }
252 return color == "w" ? "0-1" : "1-0";
253 }
254
255 static get VALUES() {
256 return Object.assign(
257 { a: 1000 },
258 ChessRules.VALUES
259 );
260 }
261
262 static GenRandInitFen() {
263 // Always deterministic setup
264 return "2prbkqA/2p1nnbr/2pppppp/8/8/PPPPPP2/RBNN1P2/aQKBRP2 w 0 1111";
265 }
266
267 static get SEARCH_DEPTH() {
268 return 2;
269 }
270 };