Commit | Line | Data |
---|---|---|
cd49e617 BA |
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 { | |
7e8a7ea1 | 6 | |
cd49e617 BA |
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], "oneStep"); | |
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]) { | |
cd49e617 BA |
100 | const finalSquares = [ |
101 | [3, 4], | |
102 | [7, 6] | |
103 | ]; | |
7e8a7ea1 | 104 | return super.getCastleMoves([x, y], finalSquares); |
cd49e617 BA |
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], "oneStep" | |
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(randomness) { | |
189 | if (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' && 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 | } | |
7e8a7ea1 | 255 | |
cd49e617 | 256 | }; |