Fix Koopa promotions with captures, and Balakhlava: pawns move forward
[vchess.git] / client / src / variants / Suction.js
1 import { ChessRules, PiPo, Move } from "@/base_rules";
2 import { SuicideRules } from "@/variants/Suicide";
3
4 export class SuctionRules extends ChessRules {
5 static get PawnSpecs() {
6 return Object.assign(
7 {},
8 ChessRules.PawnSpecs,
9 // No promotions:
10 { promotions: [V.PAWN] }
11 );
12 }
13
14 static get HasFlags() {
15 return false;
16 }
17
18 setOtherVariables(fen) {
19 super.setOtherVariables(fen);
20 // Local stack of "captures"
21 this.cmoves = [];
22 const cmove = V.ParseFen(fen).cmove;
23 if (cmove == "-") this.cmoves.push(null);
24 else {
25 this.cmoves.push({
26 start: ChessRules.SquareToCoords(cmove.substr(0, 2)),
27 end: ChessRules.SquareToCoords(cmove.substr(2))
28 });
29 }
30 }
31
32 static ParseFen(fen) {
33 return Object.assign(
34 ChessRules.ParseFen(fen),
35 { cmove: fen.split(" ")[4] }
36 );
37 }
38
39 static IsGoodFen(fen) {
40 if (!ChessRules.IsGoodFen(fen)) return false;
41 const fenParts = fen.split(" ");
42 if (fenParts.length != 5) return false;
43 if (fenParts[4] != "-" && !fenParts[4].match(/^([a-h][1-8]){2}$/))
44 return false;
45 return true;
46 }
47
48 getCmove(move) {
49 if (move.vanish.length == 2)
50 return { start: move.start, end: move.end };
51 return null;
52 }
53
54 getBasicMove([sx, sy], [ex, ey]) {
55 const startColor = this.getColor(sx, sy);
56 const startPiece = this.getPiece(sx, sy);
57 let mv = new Move({
58 appear: [
59 new PiPo({
60 x: ex,
61 y: ey,
62 c: startColor,
63 p: startPiece
64 })
65 ],
66 vanish: [
67 new PiPo({
68 x: sx,
69 y: sy,
70 c: startColor,
71 p: startPiece
72 })
73 ]
74 });
75
76 if (this.board[ex][ey] != V.EMPTY) {
77 const endColor = this.getColor(ex, ey);
78 const endPiece = this.getPiece(ex, ey);
79 mv.vanish.push(
80 new PiPo({
81 x: ex,
82 y: ey,
83 c: endColor,
84 p: endPiece
85 })
86 );
87 mv.appear.push(
88 new PiPo({
89 x: sx,
90 y: sy,
91 c: endColor,
92 p: endPiece
93 })
94 );
95 }
96 return mv;
97 }
98
99 getEnpassantCaptures([x, y], shiftX) {
100 let moves = [];
101 const Lep = this.epSquares.length;
102 const epSquare = this.epSquares[Lep - 1]; //always at least one element
103 if (
104 !!epSquare &&
105 epSquare.x == x + shiftX &&
106 Math.abs(epSquare.y - y) == 1
107 ) {
108 let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
109 const oppCol = V.GetOppCol(this.turn);
110 enpassantMove.vanish.push({
111 x: x,
112 y: epSquare.y,
113 p: "p",
114 c: oppCol
115 });
116 enpassantMove.appear.push({
117 x: x,
118 y: y,
119 p: "p",
120 c: oppCol
121 });
122 moves.push(enpassantMove);
123 }
124 return moves;
125 }
126
127 getPotentialKingMoves() {
128 return [];
129 }
130
131 // Does m2 un-do m1 ? (to disallow undoing captures)
132 oppositeMoves(m1, m2) {
133 return (
134 !!m1 &&
135 m2.vanish.length == 2 &&
136 m1.start.x == m2.start.x &&
137 m1.end.x == m2.end.x &&
138 m1.start.y == m2.start.y &&
139 m1.end.y == m2.end.y
140 );
141 }
142
143 filterValid(moves) {
144 if (moves.length == 0) return [];
145 return moves.filter(m => {
146 const L = this.cmoves.length; //at least 1: init from FEN
147 return !this.oppositeMoves(this.cmoves[L - 1], m);
148 });
149 }
150
151 static GenRandInitFen(randomness) {
152 // Add empty cmove:
153 return SuicideRules.GenRandInitFen(randomness) + " -";
154 }
155
156 getCmoveFen() {
157 const L = this.cmoves.length;
158 return (
159 !this.cmoves[L - 1]
160 ? "-"
161 : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) +
162 ChessRules.CoordsToSquare(this.cmoves[L - 1].end)
163 );
164 }
165
166 getFen() {
167 return super.getFen() + " " + this.getCmoveFen();
168 }
169
170 getFenForRepeat() {
171 return super.getFenForRepeat() + "_" + this.getCmoveFen();
172 }
173
174 postPlay(move) {
175 super.postPlay(move);
176 if (move.vanish.length == 2) {
177 // Was opponent king swapped?
178 if (move.vanish[1].p == V.KING)
179 this.kingPos[this.turn] = [move.appear[1].x, move.appear[1].y];
180 }
181 this.cmoves.push(this.getCmove(move));
182 }
183
184 postUndo(move) {
185 super.postUndo(move);
186 if (move.appear.length == 2) {
187 if (move.appear[1].p == V.KING)
188 this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
189 }
190 this.cmoves.pop();
191 }
192
193 atLeastOneMove() {
194 return true;
195 }
196
197 getCheckSquares() {
198 return [];
199 }
200
201 getCurrentScore() {
202 const color = this.turn;
203 const kp = this.kingPos[color];
204 if (color == "w" && kp[0] == 0) return "0-1";
205 if (color == "b" && kp[0] == V.size.x - 1) return "1-0";
206 // King is not on the opposite edge: game not over
207 return "*";
208 }
209
210 evalPosition() {
211 // Very simple criterion for now: kings position
212 return this.kingPos["w"][0] + this.kingPos["b"][0];
213 }
214
215 getNotation(move) {
216 // Translate final square
217 const finalSquare = V.CoordsToSquare(move.end);
218
219 const piece = this.getPiece(move.start.x, move.start.y);
220 if (piece == V.PAWN) {
221 // Pawn move
222 let notation = "";
223 if (move.vanish.length == 2) {
224 // Capture
225 const startColumn = V.CoordToColumn(move.start.y);
226 notation = startColumn + "x" + finalSquare;
227 }
228 else notation = finalSquare;
229 return notation;
230 }
231 // Piece movement
232 return (
233 piece.toUpperCase() +
234 (move.vanish.length == 2 ? "x" : "") +
235 finalSquare
236 );
237 }
238 };