Some fixes, and add 2 variants: Checkless and Parachute
[vchess.git] / client / src / variants / Parachute.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2
3 export class ParachuteRules extends ChessRules {
4 static get HasFlags() {
5 return false;
6 }
7
8 static IsGoodFen(fen) {
9 if (!ChessRules.IsGoodFen(fen)) return false;
10 const fenParsed = V.ParseFen(fen);
11 // 5) Check reserves
12 if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{12,12}$/))
13 return false;
14 return true;
15 }
16
17 static ParseFen(fen) {
18 const fenParts = fen.split(" ");
19 return Object.assign(
20 ChessRules.ParseFen(fen),
21 { reserve: fenParts[4] }
22 );
23 }
24
25 static GenRandInitFen() {
26 // ChessRules.PIECES order is P, R, N, B, Q, K:
27 return "8/8/8/8/8/8/8/8 w 0 - 822211822211";
28 }
29
30 getFen() {
31 return super.getFen() + " " + this.getReserveFen();
32 }
33
34 getFenForRepeat() {
35 return super.getFenForRepeat() + "_" + this.getReserveFen();
36 }
37
38 getReserveFen() {
39 let counts = new Array(12);
40 for (let i = 0; i < V.PIECES.length; i++) {
41 counts[i] = this.reserve["w"][V.PIECES[i]];
42 counts[6 + i] = this.reserve["b"][V.PIECES[i]];
43 }
44 return counts.join("");
45 }
46
47 setOtherVariables(fen) {
48 super.setOtherVariables(fen);
49 const fenParsed = V.ParseFen(fen);
50 // Also init reserves (used by the interface to show landable pieces)
51 this.reserve = {
52 w: {
53 [V.PAWN]: parseInt(fenParsed.reserve[0]),
54 [V.ROOK]: parseInt(fenParsed.reserve[1]),
55 [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
56 [V.BISHOP]: parseInt(fenParsed.reserve[3]),
57 [V.QUEEN]: parseInt(fenParsed.reserve[4]),
58 [V.KING]: parseInt(fenParsed.reserve[5])
59 },
60 b: {
61 [V.PAWN]: parseInt(fenParsed.reserve[6]),
62 [V.ROOK]: parseInt(fenParsed.reserve[7]),
63 [V.KNIGHT]: parseInt(fenParsed.reserve[8]),
64 [V.BISHOP]: parseInt(fenParsed.reserve[9]),
65 [V.QUEEN]: parseInt(fenParsed.reserve[10]),
66 [V.KING]: parseInt(fenParsed.reserve[11])
67 }
68 };
69 }
70
71 getColor(i, j) {
72 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
73 return this.board[i][j].charAt(0);
74 }
75
76 getPiece(i, j) {
77 if (i >= V.size.x) return V.RESERVE_PIECES[j];
78 return this.board[i][j].charAt(1);
79 }
80
81 // Used by the interface:
82 getReservePpath(index, color) {
83 return color + V.RESERVE_PIECES[index];
84 }
85
86 // Ordering on reserve pieces (matching V.PIECES order)
87 static get RESERVE_PIECES() {
88 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
89 }
90
91 getReserveMoves([x, y]) {
92 const color = this.turn;
93 const oppCol = V.GetOppCol(color);
94 const p = V.RESERVE_PIECES[y];
95 if (this.reserve[color][p] == 0) return [];
96 let moves = [];
97 let boundary =
98 p == V.PAWN
99 // Pawns can land only on 4 first ranks:
100 ? (color == 'w' ? [4, 8] : [0, 4])
101 : [0, 8];
102 for (let i = boundary[0]; i < boundary[1]; i++) {
103 for (let j = 0; j < 8; j++) {
104 if (this.board[i][j] == V.EMPTY) {
105 let mv = new Move({
106 appear: [
107 new PiPo({
108 x: i,
109 y: j,
110 c: color,
111 p: p
112 })
113 ],
114 vanish: [],
115 start: { x: x, y: y }, //a bit artificial...
116 end: { x: i, y: j }
117 });
118 this.play(mv);
119 // Landing with check is forbidden:
120 if (!this.underCheck(oppCol)) moves.push(mv);
121 this.undo(mv);
122 }
123 }
124 }
125 return moves;
126 }
127
128 getPotentialMovesFrom([x, y]) {
129 let moves =
130 x >= 8
131 ? this.getReserveMoves([x, y])
132 : super.getPotentialMovesFrom([x, y]);
133 // Forbid captures if king not landed yet:
134 if (x < 8 && moves.length > 0 && this.kingPos[moves[0].appear[0].c][0] < 0)
135 moves = moves.filter(m => m.vanish.length == 1);
136 return moves;
137 }
138
139 getAllValidMoves() {
140 let moves = super.getAllValidMoves();
141 const color = this.turn;
142 for (let i = 0; i < V.RESERVE_PIECES.length; i++)
143 moves = moves.concat(
144 this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
145 );
146 return this.filterValid(moves);
147 }
148
149 isAttacked(sq, color) {
150 // While the king hasn't landed, nothing is attacked:
151 if (this.kingPos[color][0] < 0) return false;
152 return super.isAttacked(sq, color);
153 }
154
155 atLeastOneMove() {
156 if (!super.atLeastOneMove()) {
157 // Search one reserve move
158 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
159 let moves = this.filterValid(
160 this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
161 );
162 if (moves.length > 0) return true;
163 }
164 return false;
165 }
166 return true;
167 }
168
169 prePlay(move) {
170 super.prePlay(move);
171 if (move.vanish.length == 0) this.reserve[this.turn][move.appear[0].p]--;
172 }
173
174 postUndo(move) {
175 if (move.vanish.length == 0) this.reserve[this.turn][move.appear[0].p]++;
176 // (Potentially) Reset king position
177 if (move.appear[0].p == V.KING) {
178 const c = move.appear[0].c;
179 if (move.vanish.length == 0)
180 // Landing king
181 this.kingPos[c] = [-1, -1];
182 else
183 // King movement
184 this.kingPos[c] = [move.start.x, move.start.y];
185 }
186 }
187
188 static get SEARCH_DEPTH() {
189 return 1;
190 }
191
192 evalPosition() {
193 let evaluation = super.evalPosition();
194 // Add reserves:
195 for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
196 const p = V.RESERVE_PIECES[i];
197 evaluation += this.reserve["w"][p] * V.VALUES[p];
198 evaluation -= this.reserve["b"][p] * V.VALUES[p];
199 }
200 return evaluation;
201 }
202
203 getNotation(move) {
204 if (move.vanish.length > 0) return super.getNotation(move);
205 // Parachutage:
206 const piece =
207 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
208 return piece + "@" + V.CoordsToSquare(move.end);
209 }
210 };