Fix Shogi.js
[vchess.git] / client / src / variants / Atarigo.js
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 Options() {
8 return null;
9 }
10
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) {
189 const mv = this.doClick([i, j]);
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
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
237 getComputerMove() {
238 const moves = super.getAllValidMoves();
239 if (moves.length == 0) return null;
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)];
285 return moves[randInt(moves.length)];
286 }
287
288 getNotation(move) {
289 return V.CoordsToSquare(move.end);
290 }
291
292 };