Draft Fullcavalry and Atomic2 (pawn removal). Fix Grand
[vchess.git] / client / src / variants / Fullcavalry.js
1 import { ArrayFun } from "@/utils/array";
2 import { randInt } from "@/utils/alea";
3 import { ChessRules, PiPo, Move } from "@/base_rules";
4
5 export class FullcavalryRules extends ChessRules {
6
7 static get LANCER() {
8 return "l";
9 }
10
11 static get IMAGE_EXTENSION() {
12 // Temporarily, for the time SVG pieces are being designed:
13 return ".png";
14 }
15
16 // Lancer directions *from white perspective*
17 static get LANCER_DIRS() {
18 return {
19 'c': [-1, 0], //north
20 'd': [-1, 1], //N-E
21 'e': [0, 1], //east
22 'f': [1, 1], //S-E
23 'g': [1, 0], //south
24 'h': [1, -1], //S-W
25 'm': [0, -1], //west
26 'o': [-1, -1] //N-W
27 };
28 }
29
30 static get PIECES() {
31 return ChessRules.PIECES.concat(Object.keys(V.LANCER_DIRS));
32 }
33
34 getPiece(i, j) {
35 const piece = this.board[i][j].charAt(1);
36 // Special lancer case: 8 possible orientations
37 if (Object.keys(V.LANCER_DIRS).includes(piece)) return V.LANCER;
38 return piece;
39 }
40
41 getPpath(b, color, score, orientation) {
42 if (Object.keys(V.LANCER_DIRS).includes(b[1])) {
43 if (orientation == 'w') return "Eightpieces/tmp_png/" + b;
44 // Find opposite direction for adequate display:
45 let oppDir = '';
46 switch (b[1]) {
47 case 'c':
48 oppDir = 'g';
49 break;
50 case 'g':
51 oppDir = 'c';
52 break;
53 case 'd':
54 oppDir = 'h';
55 break;
56 case 'h':
57 oppDir = 'd';
58 break;
59 case 'e':
60 oppDir = 'm';
61 break;
62 case 'm':
63 oppDir = 'e';
64 break;
65 case 'f':
66 oppDir = 'o';
67 break;
68 case 'o':
69 oppDir = 'f';
70 break;
71 }
72 return "Eightpieces/tmp_png/" + b[0] + oppDir;
73 }
74 // TODO: after we have SVG pieces, remove the folder and next prefix:
75 return "Eightpieces/tmp_png/" + b;
76 }
77
78 getPPpath(m, orientation) {
79 return (
80 this.getPpath(
81 m.appear[0].c + m.appear[0].p,
82 null,
83 null,
84 orientation
85 )
86 );
87 }
88
89 static GenRandInitFen(randomness) {
90 if (randomness == 0)
91 // Deterministic:
92 return "efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM w 0 ahah -";
93
94 const baseFen = ChessRules.GenRandInitFen(randomness);
95 // Replace black rooks by lancers oriented south,
96 // and white rooks by lancers oriented north:
97 return baseFen.replace(/r/g, 'g').replace(/R/g, 'C');
98 }
99
100 // Because of the lancers, getPiece() could be wrong:
101 // use board[x][y][1] instead (always valid).
102 // TODO: base implementation now uses this too (no?)
103 getBasicMove([sx, sy], [ex, ey], tr) {
104 const initColor = this.getColor(sx, sy);
105 const initPiece = this.board[sx][sy].charAt(1);
106 let mv = new Move({
107 appear: [
108 new PiPo({
109 x: ex,
110 y: ey,
111 c: tr ? tr.c : initColor,
112 p: tr ? tr.p : initPiece
113 })
114 ],
115 vanish: [
116 new PiPo({
117 x: sx,
118 y: sy,
119 c: initColor,
120 p: initPiece
121 })
122 ]
123 });
124
125 // The opponent piece disappears if we take it
126 if (this.board[ex][ey] != V.EMPTY) {
127 mv.vanish.push(
128 new PiPo({
129 x: ex,
130 y: ey,
131 c: this.getColor(ex, ey),
132 p: this.board[ex][ey].charAt(1)
133 })
134 );
135 }
136
137 return mv;
138 }
139
140 getPotentialMovesFrom([x, y]) {
141 if (this.getPiece(x, y) == V.LANCER)
142 return this.getPotentialLancerMoves([x, y]);
143 return super.getPotentialMovesFrom([x, y]);
144 }
145
146 // Obtain all lancer moves in "step" direction
147 getPotentialLancerMoves_aux([x, y], step, tr) {
148 let moves = [];
149 // Add all moves to vacant squares until opponent is met:
150 const color = this.getColor(x, y);
151 const oppCol = V.GetOppCol(color)
152 let sq = [x + step[0], y + step[1]];
153 while (V.OnBoard(sq[0], sq[1]) && this.getColor(sq[0], sq[1]) != oppCol) {
154 if (this.board[sq[0]][sq[1]] == V.EMPTY)
155 moves.push(this.getBasicMove([x, y], sq, tr));
156 sq[0] += step[0];
157 sq[1] += step[1];
158 }
159 if (V.OnBoard(sq[0], sq[1]))
160 // Add capturing move
161 moves.push(this.getBasicMove([x, y], sq, tr));
162 return moves;
163 }
164
165 getPotentialLancerMoves([x, y]) {
166 let moves = [];
167 // Add all lancer possible orientations, similar to pawn promotions.
168 const color = this.getColor(x, y);
169 const dirCode = this.board[x][y][1];
170 const curDir = V.LANCER_DIRS[dirCode];
171 const monodirMoves =
172 this.getPotentialLancerMoves_aux([x, y], V.LANCER_DIRS[dirCode]);
173 monodirMoves.forEach(m => {
174 Object.keys(V.LANCER_DIRS).forEach(k => {
175 const newDir = V.LANCER_DIRS[k];
176 // Prevent orientations toward outer board:
177 if (V.OnBoard(m.end.x + newDir[0], m.end.y + newDir[1])) {
178 let mk = JSON.parse(JSON.stringify(m));
179 mk.appear[0].p = k;
180 moves.push(mk);
181 }
182 });
183 });
184 return moves;
185 }
186
187 isAttacked(sq, color) {
188 return (
189 super.isAttacked(sq, color) ||
190 this.isAttackedByLancer(sq, color)
191 );
192 }
193
194 isAttackedByLancer([x, y], color) {
195 for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
196 // If in this direction there are only enemy pieces and empty squares,
197 // and we meet a lancer: can he reach us?
198 // NOTE: do not stop at first lancer, there might be several!
199 let coord = { x: x + step[0], y: y + step[1] };
200 let lancerPos = [];
201 while (
202 V.OnBoard(coord.x, coord.y) &&
203 (
204 this.board[coord.x][coord.y] == V.EMPTY ||
205 this.getColor(coord.x, coord.y) == color
206 )
207 ) {
208 if (
209 this.getPiece(coord.x, coord.y) == V.LANCER &&
210 !this.isImmobilized([coord.x, coord.y])
211 ) {
212 lancerPos.push({x: coord.x, y: coord.y});
213 }
214 coord.x += step[0];
215 coord.y += step[1];
216 }
217 for (let xy of lancerPos) {
218 const dir = V.LANCER_DIRS[this.board[xy.x][xy.y].charAt(1)];
219 if (dir[0] == -step[0] && dir[1] == -step[1]) return true;
220 }
221 }
222 return false;
223 }
224
225 static get VALUES() {
226 return Object.assign(
227 { l: 4.8 }, //Jeff K. estimation (for Eightpieces)
228 ChessRules.VALUES
229 );
230 }
231
232 // For moves notation:
233 static get LANCER_DIRNAMES() {
234 return {
235 'c': "N",
236 'd': "NE",
237 'e': "E",
238 'f': "SE",
239 'g': "S",
240 'h': "SW",
241 'm': "W",
242 'o': "NW"
243 };
244 }
245
246 filterValid(moves) {
247 // At move 1, forbid captures (in case of...):
248 if (this.movesCount >= 2) return moves;
249 return moves.filter(m => m.vanish.length == 1);
250 }
251
252 getNotation(move) {
253 let notation = super.getNotation(move);
254 if (Object.keys(V.LANCER_DIRNAMES).includes(move.vanish[0].p))
255 // Lancer: add direction info
256 notation += "=" + V.LANCER_DIRNAMES[move.appear[0].p];
257 else if (
258 move.vanish[0].p == V.PAWN &&
259 Object.keys(V.LANCER_DIRNAMES).includes(move.appear[0].p)
260 ) {
261 // Fix promotions in lancer:
262 notation = notation.slice(0, -1) +
263 "L:" + V.LANCER_DIRNAMES[move.appear[0].p];
264 }
265 return notation;
266 }
267
268 };