Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Shako.js
1 import { ChessRules, Move, PiPo } from "@/base_rules";
2 import { ArrayFun } from "@/utils/array";
3 import { randInt, sample } from "@/utils/alea";
4
5 export class ShakoRules extends ChessRules {
6
7 static get PawnSpecs() {
8 return Object.assign(
9 {},
10 ChessRules.PawnSpecs,
11 {
12 initShift: { w: 2, b: 2 },
13 promotions:
14 ChessRules.PawnSpecs.promotions.concat([V.ELEPHANT, V.CANNON])
15 }
16 );
17 }
18
19 static get ELEPHANT() {
20 return "e";
21 }
22
23 static get CANNON() {
24 return "c";
25 }
26
27 static get PIECES() {
28 return ChessRules.PIECES.concat([V.ELEPHANT, V.CANNON]);
29 }
30
31 getPpath(b) {
32 const prefix = [V.ELEPHANT, V.CANNON].includes(b[1]) ? "Shako/" : "";
33 return prefix + b;
34 }
35
36 static get steps() {
37 return Object.assign(
38 {},
39 ChessRules.steps,
40 {
41 e: [
42 [-1, -1],
43 [-1, 1],
44 [1, -1],
45 [1, 1],
46 [-2, -2],
47 [-2, 2],
48 [2, -2],
49 [2, 2]
50 ]
51 }
52 );
53 }
54
55 static get size() {
56 return { x: 10, y: 10};
57 }
58
59 getPotentialMovesFrom([x, y]) {
60 switch (this.getPiece(x, y)) {
61 case V.ELEPHANT:
62 return this.getPotentialElephantMoves([x, y]);
63 case V.CANNON:
64 return this.getPotentialCannonMoves([x, y]);
65 default:
66 return super.getPotentialMovesFrom([x, y]);
67 }
68 }
69
70 getPotentialElephantMoves([x, y]) {
71 return this.getSlideNJumpMoves([x, y], V.steps[V.ELEPHANT], 1);
72 }
73
74 getPotentialCannonMoves([x, y]) {
75 const oppCol = V.GetOppCol(this.turn);
76 let moves = [];
77 // Look in every direction until an obstacle (to jump) is met
78 for (const step of V.steps[V.ROOK]) {
79 let i = x + step[0];
80 let j = y + step[1];
81 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
82 moves.push(this.getBasicMove([x, y], [i, j]));
83 i += step[0];
84 j += step[1];
85 }
86 // Then, search for an enemy
87 i += step[0];
88 j += step[1];
89 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
90 i += step[0];
91 j += step[1];
92 }
93 if (V.OnBoard(i, j) && this.getColor(i, j) == oppCol)
94 moves.push(this.getBasicMove([x, y], [i, j]));
95 }
96 return moves;
97 }
98
99 getCastleMoves([x, y]) {
100 const finalSquares = [
101 [3, 4],
102 [7, 6]
103 ];
104 return super.getCastleMoves([x, y], finalSquares);
105 }
106
107 isAttacked(sq, color) {
108 return (
109 super.isAttacked(sq, color) ||
110 this.isAttackedByElephant(sq, color) ||
111 this.isAttackedByCannon(sq, color)
112 );
113 }
114
115 isAttackedByElephant(sq, color) {
116 return (
117 this.isAttackedBySlideNJump(
118 sq, color, V.ELEPHANT, V.steps[V.ELEPHANT], 1
119 )
120 );
121 }
122
123 isAttackedByCannon([x, y], color) {
124 // Reversed process: is there an obstacle in line,
125 // and a cannon next in the same line?
126 for (const step of V.steps[V.ROOK]) {
127 let [i, j] = [x+step[0], y+step[1]];
128 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
129 i += step[0];
130 j += step[1];
131 }
132 if (V.OnBoard(i, j)) {
133 // Keep looking in this direction
134 i += step[0];
135 j += step[1];
136 while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
137 i += step[0];
138 j += step[1];
139 }
140 if (
141 V.OnBoard(i, j) &&
142 this.getPiece(i, j) == V.CANNON &&
143 this.getColor(i, j) == color
144 ) {
145 return true;
146 }
147 }
148 }
149 return false;
150 }
151
152 updateCastleFlags(move, piece) {
153 const c = V.GetOppCol(this.turn);
154 const firstRank = (c == "w" ? V.size.x - 2 : 1);
155 // Update castling flags if rooks are moved
156 const oppCol = this.turn;
157 const oppFirstRank = V.size.x - 1 - firstRank;
158 if (piece == V.KING)
159 this.castleFlags[c] = [V.size.y, V.size.y];
160 else if (
161 move.start.x == firstRank && //our rook moves?
162 this.castleFlags[c].includes(move.start.y)
163 ) {
164 const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
165 this.castleFlags[c][flagIdx] = V.size.y;
166 }
167 // NOTE: not "else if" because a rook could take an opposing rook
168 if (
169 move.end.x == oppFirstRank && //we took opponent rook?
170 this.castleFlags[oppCol].includes(move.end.y)
171 ) {
172 const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
173 this.castleFlags[oppCol][flagIdx] = V.size.y;
174 }
175 }
176
177 static get VALUES() {
178 return Object.assign(
179 { e: 3, c: 5 },
180 ChessRules.VALUES
181 );
182 }
183
184 static get SEARCH_DEPTH() {
185 return 2;
186 }
187
188 static GenRandInitFen(options) {
189 if (options.randomness == 0) {
190 return (
191 "c8c/ernbqkbnre/pppppppppp/91/91/91/91/PPPPPPPPPP/ERNBQKBNRE/C8C " +
192 "w 0 bibi -"
193 );
194 }
195
196 let pieces = { w: new Array(10), b: new Array(10) };
197 let flags = "";
198 // Shuffle pieces on second (and before-last rank if randomness == 2)
199 for (let c of ["w", "b"]) {
200 if (c == 'b' && options.randomness == 1) {
201 pieces['b'] = pieces['w'];
202 flags += flags;
203 break;
204 }
205
206 let positions = ArrayFun.range(10);
207
208 // Get random squares for bishops + elephants
209 const be1Pos = sample([0, 2, 4, 6, 8], 2);
210 const be2Pos = sample([1, 3, 5, 7, 9], 2);
211 const bishop1Pos = be1Pos[0];
212 const bishop2Pos = be2Pos[0];
213 const elephant1Pos = be1Pos[1];
214 const elephant2Pos = be2Pos[1];
215 // Remove chosen squares
216 (be1Pos.concat(be2Pos)).sort((x, y) => y - x).forEach(pos => {
217 positions.splice(pos, 1);
218 });
219
220 let randIndex = randInt(6);
221 const knight1Pos = positions[randIndex];
222 positions.splice(randIndex, 1);
223 randIndex = randInt(5);
224 const knight2Pos = positions[randIndex];
225 positions.splice(randIndex, 1);
226
227 randIndex = randInt(4);
228 const queenPos = positions[randIndex];
229 positions.splice(randIndex, 1);
230
231 const rook1Pos = positions[0];
232 const kingPos = positions[1];
233 const rook2Pos = positions[2];
234
235 pieces[c][elephant1Pos] = "e";
236 pieces[c][rook1Pos] = "r";
237 pieces[c][knight1Pos] = "n";
238 pieces[c][bishop1Pos] = "b";
239 pieces[c][queenPos] = "q";
240 pieces[c][kingPos] = "k";
241 pieces[c][bishop2Pos] = "b";
242 pieces[c][knight2Pos] = "n";
243 pieces[c][rook2Pos] = "r";
244 pieces[c][elephant2Pos] = "e";
245 flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
246 }
247 // Add turn + flags + enpassant
248 return (
249 "c8c/" + pieces["b"].join("") +
250 "/pppppppppp/91/91/91/91/PPPPPPPPPP/" +
251 pieces["w"].join("").toUpperCase() + "/C8C" +
252 " w 0 " + flags + " -"
253 );
254 }
255
256 };