1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class GomokuRules
extends ChessRules
{
10 static get Monochrome() {
14 static get Notoodark() {
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]]);
28 static get HasFlags() {
32 static get HasEnpassant() {
36 static get ReverseColors() {
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
) {
46 for (let i
= 0; i
< row
.length
; i
++) {
47 if (row
[i
].toLowerCase() == V
.PAWN
) sumElts
++;
49 const num
= parseInt(row
[i
], 10);
50 if (isNaN(num
) || num
<= 0) return false;
54 if (sumElts
!= V
.size
.y
) return false;
60 return { x: 19, y: 19 };
63 static GenRandInitFen() {
64 return [...Array(19)].map(e
=> "991").join('/') + " w 0";
67 setOtherVariables() {}
81 canIplay(side
, [x
, y
]) {
82 return (side
== this.turn
&& this.board
[x
][y
] == V
.EMPTY
);
85 hoverHighlight([x
, y
], side
) {
86 if (!!side
&& side
!= this.turn
) return false;
87 return (this.board
[x
][y
] == V
.EMPTY
);
94 new PiPo({ x: x
, y: y
, c: this.turn
, p: V
.PAWN
})
97 start: { x: -1, y: -1 },
102 getPotentialMovesFrom([x
, y
]) {
103 return [this.doClick([x
, y
])];
106 getAllPotentialMoves() {
108 for (let i
= 0; i
< 19; i
++) {
109 for (let j
=0; j
< 19; j
++) {
110 if (this.board
[i
][j
] == V
.EMPTY
) moves
.push(this.doClick([i
, j
]));
127 countAlignedStones([x
, y
], color
) {
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;
133 for (let dir
of [-1, 1]) {
134 let [i
, j
] = [x
+ dir
* s
[0], y
+ dir
* s
[1]];
137 this.board
[i
][j
] != V
.EMPTY
&&
138 this.getColor(i
, j
) == color
145 if (countInLine
> maxInLine
) maxInLine
= countInLine
;
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
) {
156 !fiveAlign
.wNextTurn
&&
157 this.countAlignedStones([i
, j
], 'b') >= 5
159 fiveAlign
.wNextTurn
= true;
163 const c
= this.getColor(i
, j
);
164 if (!fiveAlign
[c
] && this.countAlignedStones([i
, j
], c
) >= 5)
169 if (fiveAlign
['w']) {
170 if (fiveAlign
['b']) return "1/2";
171 if (this.turn
== 'b' && fiveAlign
.wNextTurn
) return "*";
174 if (fiveAlign
['b']) return "0-1";
179 const color
= this.turn
;
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({
189 new PiPo({ x: i
, y: j
, c: color
, p: V
.PAWN
})
192 start: { x: -1, y: -1 }
194 if (nbAligned
> curMax
) {
198 else candidates
.push(move);
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
;
212 const threshold
= Math
.min(32, bestCentrality
);
213 const finalCandidates
= candidates
.filter(c
=> c
.centrality
>= threshold
);
214 return finalCandidates
[randInt(finalCandidates
.length
)];
218 return V
.CoordsToSquare(move.end
);