Add Stealthbomb
[vchess.git] / client / src / variants / Stealthbomb.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2
3 export class StealthbombRules extends ChessRules {
4
5 static get CanAnalyze() {
6 return false;
7 }
8
9 static get SomeHiddenMoves() {
10 return true;
11 }
12
13 static get BOMB_DECODE() {
14 return {
15 s: "p",
16 t: "q",
17 u: "r",
18 c: "b",
19 o: "n",
20 l: "k"
21 };
22 }
23 static get BOMB_CODE() {
24 return {
25 p: "s",
26 q: "t",
27 r: "u",
28 b: "c",
29 n: "o",
30 k: "l"
31 };
32 }
33
34 static get PIECES() {
35 return ChessRules.PIECES.concat(Object.keys(V.BOMB_DECODE));
36 }
37
38 getPiece(i, j) {
39 const piece = this.board[i][j].charAt(1);
40 if (
41 ChessRules.PIECES.includes(piece) ||
42 // 'side' is used to determine what I see: normal or "loaded" piece?
43 this.getColor(i, j) == this.side
44 ) {
45 return piece;
46 }
47 // Loaded piece, but no right to view it
48 return V.BOMB_DECODE[piece];
49 }
50
51 getPpath(b, color, score) {
52 if (Object.keys(V.BOMB_DECODE).includes(b[1])) {
53 // Supposed to be hidden.
54 if (score == "*" && (!color || color != b[0]))
55 return b[0] + V.BOMB_DECODE[b[1]];
56 return "Stealthbomb/" + b;
57 }
58 return b;
59 }
60
61 hoverHighlight([x, y]) {
62 const c = this.turn;
63 return (
64 this.movesCount <= 1 &&
65 (
66 (c == 'w' && x >= 6) ||
67 (c == 'b' && x <= 1)
68 )
69 );
70 }
71
72 onlyClick([x, y]) {
73 return (
74 this.movesCount <= 1 ||
75 // TODO: next line theoretically shouldn't be required...
76 (this.movesCount == 2 && this.getColor(x, y) != this.turn)
77 );
78 }
79
80 // Initiate the game by choosing a square for the bomb:
81 doClick(square) {
82 const c = this.turn;
83 if (
84 this.movesCount >= 2 ||
85 (
86 (c == 'w' && square[0] < 6) ||
87 (c == 'b' && square[0] > 2)
88 )
89 ) {
90 return null;
91 }
92 const [x, y] = square;
93 const piece = super.getPiece(x, y);
94 return new Move({
95 appear: [ new PiPo({ x: x, y: y, c: c, p: V.BOMB_CODE[piece] }) ],
96 vanish: [ new PiPo({ x: x, y: y, c: c, p: piece }) ],
97 start: { x: -1, y: -1 }
98 });
99 }
100
101 getPotentialMovesFrom([x, y]) {
102 if (this.movesCount <= 1) {
103 const setup = this.doClick([x, y]);
104 return (!setup ? [] : [setup]);
105 }
106 let moves = super.getPotentialMovesFrom([x, y]);
107 const c = this.turn;
108 // Add bomb explosion
109 if (Object.keys(V.BOMB_DECODE).includes(this.board[x][y][1])) {
110 let mv = new Move({
111 appear: [ ],
112 vanish: [ new PiPo({ x: x, y: y, c: c, p: this.board[x][y][1] }) ],
113 end: { x: this.kingPos[c][0], y: this.kingPos[c][1] }
114 });
115 for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
116 let [i, j] = [x + s[0], y + s[1]];
117 if (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) {
118 mv.vanish.push(
119 new PiPo({
120 x: i,
121 y: j,
122 c: this.getColor(i, j),
123 p: this.board[i][j][1]
124 })
125 );
126 }
127 }
128 moves.push(mv);
129 }
130 return moves;
131 }
132
133 // NOTE: a lot of copy-paste from Atomic from here.
134 postPlay(move) {
135 if (this.movesCount >= 3) {
136 super.postPlay(move);
137 if (move.appear.length == 0) {
138 // Explosion
139 const firstRank = { w: 7, b: 0 };
140 for (let c of ["w", "b"]) {
141 // Did we explode king of color c ?
142 if (
143 Math.abs(this.kingPos[c][0] - move.start.x) <= 1 &&
144 Math.abs(this.kingPos[c][1] - move.start.y) <= 1
145 ) {
146 this.kingPos[c] = [-1, -1];
147 this.castleFlags[c] = [8, 8];
148 }
149 else {
150 // Now check if init rook(s) exploded
151 if (Math.abs(move.start.x - firstRank[c]) <= 1) {
152 if (Math.abs(move.start.y - this.castleFlags[c][0]) <= 1)
153 this.castleFlags[c][0] = 8;
154 if (Math.abs(move.start.y - this.castleFlags[c][1]) <= 1)
155 this.castleFlags[c][1] = 8;
156 }
157 }
158 }
159 }
160 }
161 }
162
163 postUndo(move) {
164 if (this.movesCount >= 2) {
165 super.postUndo(move);
166 const c = this.turn;
167 const oppCol = V.GetOppCol(c);
168 if ([this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)) {
169 // Last move exploded some king..
170 for (let psq of move.vanish) {
171 if (psq.p == "k")
172 this.kingPos[psq.c == c ? c : oppCol] = [psq.x, psq.y];
173 }
174 }
175 }
176 }
177
178 underCheck(color) {
179 const oppCol = V.GetOppCol(color);
180 let res = undefined;
181 // If our king disappeared, move is not valid
182 if (this.kingPos[color][0] < 0) res = true;
183 // If opponent king disappeared, move is valid
184 else if (this.kingPos[oppCol][0] < 0) res = false;
185 // Otherwise, if we remain under check, move is not valid
186 else res = this.isAttacked(this.kingPos[color], oppCol);
187 return res;
188 }
189
190 getCheckSquares() {
191 const color = this.turn;
192 let res = [];
193 if (
194 this.kingPos[color][0] >= 0 && //king might have exploded
195 this.isAttacked(this.kingPos[color], V.GetOppCol(color))
196 ) {
197 res = [JSON.parse(JSON.stringify(this.kingPos[color]))];
198 }
199 return res;
200 }
201
202 getCurrentScore() {
203 const color = this.turn;
204 const kp = this.kingPos[color];
205 if (kp[0] < 0)
206 // King disappeared
207 return color == "w" ? "0-1" : "1-0";
208 if (this.atLeastOneMove()) return "*";
209 if (!this.isAttacked(kp, V.GetOppCol(color))) return "1/2";
210 return color == "w" ? "0-1" : "1-0"; //checkmate
211 }
212
213 getNotation(move) {
214 if (this.movesCount <= 1) return "Bomb?";
215 const c = this.turn;
216 if (move.end.x == this.kingPos[c][0] && move.end.y == this.kingPos[c][1])
217 return V.CoordsToSquare(move.start) + "~X";
218 if (Object.keys(V.BOMB_DECODE).includes(move.vanish[0].p)) {
219 let cpMove = JSON.parse(JSON.stringify(move));
220 cpMove.vanish[0].p = V.BOMB_DECODE[move.vanish[0].p];
221 if (Object.keys(V.BOMB_DECODE).includes(move.appear[0].p))
222 cpMove.appear[0].p = V.BOMB_DECODE[move.appear[0].p];
223 return super.getNotation(cpMove);
224 }
225 return super.getNotation(move);
226 }
227
228 };