Commit | Line | Data |
---|---|---|
7c05a5f2 BA |
1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
2 | import { randInt } from "@/utils/alea"; | |
3 | import { ArrayFun } from "@/utils/array"; | |
4 | ||
5 | export class AtarigoRules extends ChessRules { | |
6 | ||
7 | static get Monochrome() { | |
8 | return true; | |
9 | } | |
10 | ||
11 | static get Notoodark() { | |
12 | return true; | |
13 | } | |
14 | ||
15 | static get Lines() { | |
16 | let lines = []; | |
17 | // Draw all inter-squares lines, shifted: | |
18 | for (let i = 0; i < V.size.x; i++) | |
19 | lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]); | |
20 | for (let j = 0; j < V.size.y; j++) | |
21 | lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]); | |
22 | return lines; | |
23 | } | |
24 | ||
25 | static get HasFlags() { | |
26 | return false; | |
27 | } | |
28 | ||
29 | static get HasEnpassant() { | |
30 | return false; | |
31 | } | |
32 | ||
33 | static get ReverseColors() { | |
34 | return true; | |
35 | } | |
36 | ||
37 | static IsGoodPosition(position) { | |
38 | if (position.length == 0) return false; | |
39 | const rows = position.split("/"); | |
40 | if (rows.length != V.size.x) return false; | |
41 | for (let row of rows) { | |
42 | let sumElts = 0; | |
43 | for (let i = 0; i < row.length; i++) { | |
44 | if (row[i].toLowerCase() == V.PAWN) sumElts++; | |
45 | else { | |
46 | const num = parseInt(row[i], 10); | |
47 | if (isNaN(num) || num <= 0) return false; | |
48 | sumElts += num; | |
49 | } | |
50 | } | |
51 | if (sumElts != V.size.y) return false; | |
52 | } | |
53 | return true; | |
54 | } | |
55 | ||
56 | static IsGoodFen(fen) { | |
57 | if (!ChessRules.IsGoodFen(fen)) return false; | |
58 | const fenParsed = V.ParseFen(fen); | |
59 | // 3) Check capture "flag" | |
60 | if (!fenParsed.capture || !fenParsed.capture.match(/^[01]$/)) | |
61 | return false; | |
62 | return true; | |
63 | } | |
64 | ||
65 | static ParseFen(fen) { | |
66 | const fenParts = fen.split(" "); | |
67 | return Object.assign( | |
68 | ChessRules.ParseFen(fen), | |
69 | // Capture field allows to compute the score cleanly. | |
70 | { capture: fenParts[3] } | |
71 | ); | |
72 | } | |
73 | ||
74 | static get size() { | |
75 | return { x: 12, y: 12 }; | |
76 | } | |
77 | ||
78 | static GenRandInitFen() { | |
79 | return "93/93/93/93/93/5Pp5/5pP5/93/93/93/93/93 w 0 0"; | |
80 | } | |
81 | ||
82 | getFen() { | |
83 | return super.getFen() + " " + (this.capture ? 1 : 0); | |
84 | } | |
85 | ||
86 | setOtherVariables(fen) { | |
87 | this.capture = parseInt(V.ParseFen(fen).capture, 10); | |
88 | } | |
89 | ||
90 | getPiece() { | |
91 | return V.PAWN; | |
92 | } | |
93 | ||
94 | getPpath(b) { | |
95 | return "Gomoku/" + b; | |
96 | } | |
97 | ||
98 | onlyClick() { | |
99 | return true; | |
100 | } | |
101 | ||
102 | canIplay(side, [x, y]) { | |
103 | return (side == this.turn && this.board[x][y] == V.EMPTY); | |
104 | } | |
105 | ||
106 | hoverHighlight([x, y], side) { | |
107 | if (!!side && side != this.turn) return false; | |
108 | return (this.board[x][y] == V.EMPTY); | |
109 | } | |
110 | ||
111 | searchForEmptySpace([x, y], color, explored) { | |
112 | if (explored[x][y]) return false; //didn't find empty space | |
113 | explored[x][y] = true; | |
114 | let res = false; | |
115 | for (let s of V.steps[V.ROOK]) { | |
116 | const [i, j] = [x + s[0], y + s[1]]; | |
117 | if (V.OnBoard(i, j)) { | |
118 | if (this.board[i][j] == V.EMPTY) res = true; | |
119 | else if (this.getColor(i, j) == color) | |
120 | res = this.searchForEmptySpace([i, j], color, explored) || res; | |
121 | } | |
122 | } | |
123 | return res; | |
124 | } | |
125 | ||
126 | doClick([x, y]) { | |
127 | const color = this.turn; | |
128 | const oppCol = V.GetOppCol(color); | |
129 | let move = new Move({ | |
130 | appear: [ | |
131 | new PiPo({ x: x, y: y, c: color, p: V.PAWN }) | |
132 | ], | |
133 | vanish: [], | |
134 | start: { x: -1, y: -1 } | |
135 | }); | |
136 | V.PlayOnBoard(this.board, move); //put the stone | |
137 | let noSuicide = false; | |
138 | let captures = []; | |
139 | for (let s of V.steps[V.ROOK]) { | |
140 | const [i, j] = [x + s[0], y + s[1]]; | |
141 | if (V.OnBoard(i, j)) { | |
142 | if (this.board[i][j] == V.EMPTY) noSuicide = true; //clearly | |
143 | else if (this.getColor(i, j) == color) { | |
144 | // Free space for us = not a suicide | |
145 | if (!noSuicide) { | |
146 | let explored = ArrayFun.init(V.size.x, V.size.y, false); | |
147 | noSuicide = this.searchForEmptySpace([i, j], color, explored); | |
148 | } | |
149 | } | |
150 | else { | |
151 | // Free space for opponent = not a capture | |
152 | let explored = ArrayFun.init(V.size.x, V.size.y, false); | |
153 | const captureSomething = | |
154 | !this.searchForEmptySpace([i, j], oppCol, explored); | |
155 | if (captureSomething) { | |
156 | for (let ii = 0; ii < V.size.x; ii++) { | |
157 | for (let jj = 0; jj < V.size.y; jj++) { | |
158 | if (explored[ii][jj]) { | |
159 | captures.push( | |
160 | new PiPo({ x: ii, y: jj, c: oppCol, p: V.PAWN }) | |
161 | ); | |
162 | } | |
163 | } | |
164 | } | |
165 | } | |
166 | } | |
167 | } | |
168 | } | |
169 | V.UndoOnBoard(this.board, move); //remove the stone | |
170 | if (!noSuicide && captures.length == 0) return null; | |
171 | Array.prototype.push.apply(move.vanish, captures); | |
172 | return move; | |
173 | } | |
174 | ||
175 | getPotentialMovesFrom([x, y]) { | |
176 | const move = this.doClick([x, y]); | |
177 | return (!move ? [] : [move]); | |
178 | } | |
179 | ||
180 | getAllPotentialMoves() { | |
181 | let moves = []; | |
182 | for (let i = 0; i < V.size.x; i++) { | |
183 | for (let j=0; j < V.size.y; j++) { | |
184 | if (this.board[i][j] == V.EMPTY) { | |
e0798172 | 185 | const mv = this.doClick([i, j]); |
7c05a5f2 BA |
186 | if (!!mv) moves.push(mv); |
187 | } | |
188 | } | |
189 | } | |
190 | return moves; | |
191 | } | |
192 | ||
193 | filterValid(moves) { | |
194 | return moves; | |
195 | } | |
196 | ||
197 | getCheckSquares() { | |
198 | return []; | |
199 | } | |
200 | ||
201 | postPlay(move) { | |
202 | if (move.vanish.length >= 1) this.capture = true; | |
203 | } | |
204 | ||
205 | postUndo() { | |
206 | this.capture = false; | |
207 | } | |
208 | ||
209 | getCurrentScore() { | |
210 | if (this.capture) return (this.turn == 'w' ? "0-1" : "1-0"); | |
211 | return "*"; | |
212 | } | |
213 | ||
e0798172 BA |
214 | // Modified version to count liberties + find locations |
215 | countEmptySpaces([x, y], color, explored) { | |
216 | if (explored[x][y]) return []; | |
217 | explored[x][y] = true; | |
218 | let res = []; | |
219 | for (let s of V.steps[V.ROOK]) { | |
220 | const [i, j] = [x + s[0], y + s[1]]; | |
221 | if (V.OnBoard(i, j)) { | |
222 | if (!explored[i][j] && this.board[i][j] == V.EMPTY) { | |
223 | res.push([i, j]); | |
224 | explored[i][j] = true; //not counting liberties twice! | |
225 | } | |
226 | else if (this.getColor(i, j) == color) | |
227 | res = res.concat(this.countEmptySpaces([i, j], color, explored)); | |
228 | } | |
229 | } | |
230 | return res; | |
231 | } | |
232 | ||
7c05a5f2 BA |
233 | getComputerMove() { |
234 | const moves = super.getAllValidMoves(); | |
235 | if (moves.length == 0) return null; | |
e0798172 BA |
236 | // Any capture? |
237 | const captures = moves.filter(m => m.vanish.length >= 1); | |
238 | if (captures.length > 0) return captures[randInt(captures.length)]; | |
239 | // Any group in immediate danger? | |
240 | const color = this.turn; | |
241 | let explored = ArrayFun.init(V.size.x, V.size.y, false); | |
242 | for (let i = 0; i < V.size.x; i++) { | |
243 | for (let j = 0; j < V.size.y; j++) { | |
244 | if ( | |
245 | this.board[i][j] != V.EMPTY && | |
246 | this.getColor(i, j) == color && | |
247 | !explored[i][j] | |
248 | ) { | |
249 | // Before this search, reset liberties, | |
250 | // because two groups might share them. | |
251 | for (let ii = 0; ii < V.size.x; ii++) { | |
252 | for (let jj = 0; jj < V.size.y; jj++) { | |
253 | if (explored[ii][jj] && this.board[ii][jj] == V.EMPTY) | |
254 | explored[ii][jj] = false; | |
255 | } | |
256 | } | |
257 | const liberties = this.countEmptySpaces([i, j], color, explored); | |
258 | if (liberties.length == 1) { | |
259 | const L = liberties[0]; | |
260 | const toPlay = moves.find(m => m.end.x == L[0] && m.end.y == L[1]); | |
261 | if (!!toPlay) return toPlay; | |
262 | } | |
263 | } | |
264 | } | |
265 | } | |
266 | // At this point, pick a random move not far from current stones (TODO) | |
267 | const candidates = moves.filter(m => { | |
268 | const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); | |
269 | return ( | |
270 | steps.some(s => { | |
271 | const [i, j] = [m.end.x + s[0], m.end.y + s[1]]; | |
272 | return ( | |
273 | V.OnBoard(i, j) && | |
274 | this.board[i][j] != V.EMPTY && | |
275 | this.getColor(i, j) == color | |
276 | ); | |
277 | }) | |
278 | ); | |
279 | }); | |
280 | if (candidates.length > 0) return candidates[randInt(candidates.length)]; | |
7c05a5f2 BA |
281 | return moves[randInt(moves.length)]; |
282 | } | |
283 | ||
284 | getNotation(move) { | |
285 | return V.CoordsToSquare(move.end); | |
286 | } | |
287 | ||
288 | }; |