1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { randInt
} from "@/utils/alea";
4 export class GomokuRules
extends ChessRules
{
6 static get Monochrome() {
10 static get Notoodark() {
16 // Draw all inter-squares lines, shifted:
17 for (let i
= 0; i
< V
.size
.x
; i
++)
18 lines
.push([[i
+0.5, 0.5], [i
+0.5, V
.size
.y
-0.5]]);
19 for (let j
= 0; j
< V
.size
.y
; j
++)
20 lines
.push([[0.5, j
+0.5], [V
.size
.x
-0.5, j
+0.5]]);
24 static get HasFlags() {
28 static get HasEnpassant() {
32 static get ReverseColors() {
36 static IsGoodPosition(position
) {
37 if (position
.length
== 0) return false;
38 const rows
= position
.split("/");
39 if (rows
.length
!= V
.size
.x
) return false;
40 for (let row
of rows
) {
42 for (let i
= 0; i
< row
.length
; i
++) {
43 if (row
[i
].toLowerCase() == V
.PAWN
) sumElts
++;
45 const num
= parseInt(row
[i
], 10);
46 if (isNaN(num
) || num
<= 0) return false;
50 if (sumElts
!= V
.size
.y
) return false;
56 return { x: 19, y: 19 };
59 static GenRandInitFen() {
60 return [...Array(19)].map(e
=> "991").join('/') + " w 0";
63 setOtherVariables() {}
77 canIplay(side
, [x
, y
]) {
78 return (side
== this.turn
&& this.board
[x
][y
] == V
.EMPTY
);
81 hoverHighlight([x
, y
], side
) {
82 if (!!side
&& side
!= this.turn
) return false;
83 return (this.board
[x
][y
] == V
.EMPTY
);
90 new PiPo({ x: x
, y: y
, c: this.turn
, p: V
.PAWN
})
93 start: { x: -1, y: -1 },
98 getPotentialMovesFrom([x
, y
]) {
99 return [this.doClick([x
, y
])];
102 getAllPotentialMoves() {
104 for (let i
= 0; i
< 19; i
++) {
105 for (let j
=0; j
< 19; j
++) {
106 if (this.board
[i
][j
] == V
.EMPTY
) moves
.push(this.doClick([i
, j
]));
123 countAlignedStones([x
, y
], color
) {
125 for (let s
of V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])) {
126 // Skip half of steps, since we explore both directions
127 if (s
[0] == -1 || (s
[0] == 0 && s
[1] == -1)) continue;
129 for (let dir
of [-1, 1]) {
130 let [i
, j
] = [x
+ dir
* s
[0], y
+ dir
* s
[1]];
133 this.board
[i
][j
] != V
.EMPTY
&&
134 this.getColor(i
, j
) == color
141 if (countInLine
> maxInLine
) maxInLine
= countInLine
;
147 let fiveAlign
= { w: false, b: false, wNextTurn: false };
148 for (let i
=0; i
<19; i
++) {
149 for (let j
=0; j
<19; j
++) {
150 if (this.board
[i
][j
] == V
.EMPTY
) {
152 !fiveAlign
.wNextTurn
&&
153 this.countAlignedStones([i
, j
], 'b') >= 5
155 fiveAlign
.wNextTurn
= true;
159 const c
= this.getColor(i
, j
);
160 if (!fiveAlign
[c
] && this.countAlignedStones([i
, j
], c
) >= 5)
165 if (fiveAlign
['w']) {
166 if (fiveAlign
['b']) return "1/2";
167 if (this.turn
== 'b' && fiveAlign
.wNextTurn
) return "*";
170 if (fiveAlign
['b']) return "0-1";
175 const color
= this.turn
;
178 for (let i
=0; i
<19; i
++) {
179 for (let j
=0; j
<19; j
++) {
180 if (this.board
[i
][j
] == V
.EMPTY
) {
181 const nbAligned
= this.countAlignedStones([i
, j
], color
);
182 if (nbAligned
>= curMax
) {
183 const move = new Move({
185 new PiPo({ x: i
, y: j
, c: color
, p: V
.PAWN
})
188 start: { x: -1, y: -1 }
190 if (nbAligned
> curMax
) {
194 else candidates
.push(move);
199 // Among a priori equivalent moves, select the most central ones.
200 // Of course this is not good, but can help this ultra-basic bot.
201 let bestCentrality
= 0;
202 candidates
.forEach(c
=> {
203 const deltaX
= Math
.min(c
.end
.x
, 18 - c
.end
.x
);
204 const deltaY
= Math
.min(c
.end
.y
, 18 - c
.end
.y
);
205 c
.centrality
= deltaX
* deltaX
+ deltaY
* deltaY
;
206 if (c
.centrality
> bestCentrality
) bestCentrality
= c
.centrality
;
208 const threshold
= Math
.min(32, bestCentrality
);
209 const finalCandidates
= candidates
.filter(c
=> c
.centrality
>= threshold
);
210 return finalCandidates
[randInt(finalCandidates
.length
)];
214 return V
.CoordsToSquare(move.end
);