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