Fix Atarigo + Gomoku, prepare Emergo
[vchess.git] / client / src / variants / Atarigo.js
CommitLineData
7c05a5f2
BA
1import { ChessRules, Move, PiPo } from "@/base_rules";
2import { randInt } from "@/utils/alea";
3import { ArrayFun } from "@/utils/array";
4
5export 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};