Commit | Line | Data |
---|---|---|
7c05a5f2 BA |
1 | import { ChessRules, Move, PiPo } from "@/base_rules"; |
2 | import { randInt } from "@/utils/alea"; | |
d2af3400 BA |
3 | |
4 | export class GomokuRules extends ChessRules { | |
5 | ||
4313762d BA |
6 | static get Options() { |
7 | return null; | |
8 | } | |
9 | ||
7c05a5f2 BA |
10 | static get Monochrome() { |
11 | return true; | |
12 | } | |
13 | ||
14 | static get Notoodark() { | |
15 | return true; | |
16 | } | |
17 | ||
18 | static get Lines() { | |
19 | let lines = []; | |
20 | // Draw all inter-squares lines, shifted: | |
21 | for (let i = 0; i < V.size.x; i++) | |
22 | lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]); | |
23 | for (let j = 0; j < V.size.y; j++) | |
24 | lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]); | |
25 | return lines; | |
26 | } | |
27 | ||
28 | static get HasFlags() { | |
29 | return false; | |
30 | } | |
31 | ||
32 | static get HasEnpassant() { | |
33 | return false; | |
34 | } | |
35 | ||
36 | static get ReverseColors() { | |
37 | return true; | |
38 | } | |
39 | ||
40 | static IsGoodPosition(position) { | |
41 | if (position.length == 0) return false; | |
42 | const rows = position.split("/"); | |
43 | if (rows.length != V.size.x) return false; | |
44 | for (let row of rows) { | |
45 | let sumElts = 0; | |
46 | for (let i = 0; i < row.length; i++) { | |
47 | if (row[i].toLowerCase() == V.PAWN) sumElts++; | |
48 | else { | |
49 | const num = parseInt(row[i], 10); | |
50 | if (isNaN(num) || num <= 0) return false; | |
51 | sumElts += num; | |
52 | } | |
53 | } | |
54 | if (sumElts != V.size.y) return false; | |
55 | } | |
56 | return true; | |
57 | } | |
58 | ||
59 | static get size() { | |
60 | return { x: 19, y: 19 }; | |
61 | } | |
62 | ||
63 | static GenRandInitFen() { | |
64 | return [...Array(19)].map(e => "991").join('/') + " w 0"; | |
65 | } | |
66 | ||
67 | setOtherVariables() {} | |
68 | ||
69 | getPiece() { | |
70 | return V.PAWN; | |
71 | } | |
72 | ||
73 | getPpath(b) { | |
74 | return "Gomoku/" + b; | |
75 | } | |
76 | ||
77 | onlyClick() { | |
78 | return true; | |
79 | } | |
80 | ||
81 | canIplay(side, [x, y]) { | |
82 | return (side == this.turn && this.board[x][y] == V.EMPTY); | |
83 | } | |
84 | ||
85 | hoverHighlight([x, y], side) { | |
86 | if (!!side && side != this.turn) return false; | |
87 | return (this.board[x][y] == V.EMPTY); | |
88 | } | |
89 | ||
90 | doClick([x, y]) { | |
91 | return ( | |
92 | new Move({ | |
93 | appear: [ | |
94 | new PiPo({ x: x, y: y, c: this.turn, p: V.PAWN }) | |
95 | ], | |
96 | vanish: [], | |
97 | start: { x: -1, y: -1 }, | |
98 | }) | |
99 | ); | |
100 | } | |
101 | ||
102 | getPotentialMovesFrom([x, y]) { | |
103 | return [this.doClick([x, y])]; | |
104 | } | |
105 | ||
106 | getAllPotentialMoves() { | |
107 | let moves = []; | |
108 | for (let i = 0; i < 19; i++) { | |
109 | for (let j=0; j < 19; j++) { | |
e0798172 | 110 | if (this.board[i][j] == V.EMPTY) moves.push(this.doClick([i, j])); |
7c05a5f2 BA |
111 | } |
112 | } | |
113 | return moves; | |
114 | } | |
115 | ||
116 | filterValid(moves) { | |
117 | return moves; | |
118 | } | |
119 | ||
120 | getCheckSquares() { | |
121 | return []; | |
122 | } | |
123 | ||
124 | postPlay() {} | |
125 | postUndo() {} | |
126 | ||
127 | countAlignedStones([x, y], color) { | |
128 | let maxInLine = 0; | |
129 | for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { | |
130 | // Skip half of steps, since we explore both directions | |
131 | if (s[0] == -1 || (s[0] == 0 && s[1] == -1)) continue; | |
132 | let countInLine = 1; | |
133 | for (let dir of [-1, 1]) { | |
134 | let [i, j] = [x + dir * s[0], y + dir * s[1]]; | |
135 | while ( | |
136 | V.OnBoard(i, j) && | |
137 | this.board[i][j] != V.EMPTY && | |
138 | this.getColor(i, j) == color | |
139 | ) { | |
140 | countInLine++; | |
141 | i += dir * s[0]; | |
142 | j += dir * s[1]; | |
143 | } | |
144 | } | |
145 | if (countInLine > maxInLine) maxInLine = countInLine; | |
146 | } | |
147 | return maxInLine; | |
148 | } | |
149 | ||
150 | getCurrentScore() { | |
151 | let fiveAlign = { w: false, b: false, wNextTurn: false }; | |
152 | for (let i=0; i<19; i++) { | |
153 | for (let j=0; j<19; j++) { | |
154 | if (this.board[i][j] == V.EMPTY) { | |
155 | if ( | |
156 | !fiveAlign.wNextTurn && | |
157 | this.countAlignedStones([i, j], 'b') >= 5 | |
158 | ) { | |
159 | fiveAlign.wNextTurn = true; | |
160 | } | |
161 | } | |
162 | else { | |
163 | const c = this.getColor(i, j); | |
164 | if (!fiveAlign[c] && this.countAlignedStones([i, j], c) >= 5) | |
165 | fiveAlign[c] = true; | |
166 | } | |
167 | } | |
168 | } | |
169 | if (fiveAlign['w']) { | |
170 | if (fiveAlign['b']) return "1/2"; | |
171 | if (this.turn == 'b' && fiveAlign.wNextTurn) return "*"; | |
172 | return "1-0"; | |
173 | } | |
174 | if (fiveAlign['b']) return "0-1"; | |
175 | return "*"; | |
176 | } | |
177 | ||
178 | getComputerMove() { | |
179 | const color = this.turn; | |
180 | let candidates = []; | |
181 | let curMax = 0; | |
182 | for (let i=0; i<19; i++) { | |
183 | for (let j=0; j<19; j++) { | |
184 | if (this.board[i][j] == V.EMPTY) { | |
185 | const nbAligned = this.countAlignedStones([i, j], color); | |
186 | if (nbAligned >= curMax) { | |
187 | const move = new Move({ | |
188 | appear: [ | |
189 | new PiPo({ x: i, y: j, c: color, p: V.PAWN }) | |
190 | ], | |
191 | vanish: [], | |
192 | start: { x: -1, y: -1 } | |
193 | }); | |
194 | if (nbAligned > curMax) { | |
195 | curMax = nbAligned; | |
196 | candidates = [move]; | |
197 | } | |
198 | else candidates.push(move); | |
199 | } | |
200 | } | |
201 | } | |
202 | } | |
203 | // Among a priori equivalent moves, select the most central ones. | |
204 | // Of course this is not good, but can help this ultra-basic bot. | |
205 | let bestCentrality = 0; | |
206 | candidates.forEach(c => { | |
207 | const deltaX = Math.min(c.end.x, 18 - c.end.x); | |
208 | const deltaY = Math.min(c.end.y, 18 - c.end.y); | |
209 | c.centrality = deltaX * deltaX + deltaY * deltaY; | |
210 | if (c.centrality > bestCentrality) bestCentrality = c.centrality; | |
211 | }); | |
212 | const threshold = Math.min(32, bestCentrality); | |
213 | const finalCandidates = candidates.filter(c => c.centrality >= threshold); | |
214 | return finalCandidates[randInt(finalCandidates.length)]; | |
215 | } | |
216 | ||
217 | getNotation(move) { | |
218 | return V.CoordsToSquare(move.end); | |
219 | } | |
d2af3400 BA |
220 | |
221 | }; |