Skip to content

Commit ef4d423

Browse files
committed
feat: Add new AI setting choices.
1 parent b886057 commit ef4d423

2 files changed

Lines changed: 124 additions & 32 deletions

File tree

src/AI.ts

Lines changed: 113 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import {
2-
Tile,
3-
Sym,
4-
Mode,
5-
ReversiOptions,
6-
directionXYs,
7-
Reversi,
8-
} from './Reversi.js'
1+
import { Tile, Sym, ReversiOptions, directionXYs, Reversi } from './Reversi.js'
2+
import { HistoryData } from './View.svelte.js'
93

104
export type AILV = 0 | 1 | 2 | 3 | 4 | 5
115

@@ -18,10 +12,16 @@ export interface Hand {
1812
x: number
1913
y: number
2014
count: number
15+
/**
16+
* 着手点
17+
* * 配列は自分(0)、相手(1)
18+
*/
19+
choices: number[]
2120
opens: number
2221
fixed: number
2322
scores: {
2423
count: number
24+
choices: number
2525
opens: number
2626
position_corner: number
2727
position_corner_clue: number
@@ -32,24 +32,40 @@ export interface Hand {
3232
}
3333
}
3434

35-
interface BoardLog {
36-
// hand: Hand
37-
tiles: Tile[]
38-
counter: number
39-
}
40-
4135
// 考慮の重要度
4236
export interface AISetting {
4337
/**
4438
* ひっくり返せる石の数
39+
* * 配列は序盤(0)、中盤(1)、終盤(2)を表す
4540
*/
4641
count?: number[]
42+
/**
43+
* 着手点
44+
* * 配列は自分(0)、相手(1)
45+
*/
46+
choices?: number[]
4747
/**
4848
* 開放度理論:隣接する空きマスの数
49+
* * 配列は序盤(0)、中盤(1)、終盤(2)を表す
4950
*/
5051
opens?: number[]
5152
position_corner?: number
5253
position_corner_clue?: number
54+
55+
/**
56+
* ```
57+
* 00000000
58+
* 01111110
59+
* 01222210
60+
* 01233210
61+
* 01233210
62+
* 01222210
63+
* 01111110
64+
* 00000000
65+
* ```
66+
*
67+
* [5,6,7] の場合上の図の0を5点、1を6点、2を7点とする
68+
*/
5369
position_edge?: number[]
5470
next_turn?: number
5571
fixed?: number
@@ -75,6 +91,7 @@ export const AIsettings: AISettings = [
7591
// 4
7692
{
7793
count: [-1, 0, 1],
94+
choices: [1, 1],
7895
opens: [0.5, 0.5, 0.5],
7996
position_corner: 1,
8097
position_corner_clue: -1,
@@ -84,6 +101,7 @@ export const AIsettings: AISettings = [
84101
// 5
85102
{
86103
count: [-1, 0, 1],
104+
choices: [0, 1],
87105
opens: [0.5, 0.5, 0.5],
88106
position_corner: 1,
89107
position_corner_clue: -1,
@@ -98,7 +116,7 @@ export const AILVMAX = (AIsettings.length - 1) as AILV
98116
export abstract class AIReversi extends Reversi {
99117
hiScore: number
100118

101-
boardLog: BoardLog[] = []
119+
boardLog: HistoryData[] = []
102120
/**
103121
* If this is true, it is the thinking stage of AI
104122
*/
@@ -132,6 +150,7 @@ export abstract class AIReversi extends Reversi {
132150
}
133151
const {
134152
count,
153+
choices,
135154
opens,
136155
position_corner,
137156
position_corner_clue,
@@ -145,6 +164,12 @@ export abstract class AIReversi extends Reversi {
145164
scores.count += hand.count * count[term]
146165
}
147166

167+
if (choices) {
168+
for (let i = 0; i < 2; i++) {
169+
scores.choices += hand.choices[i] * choices[i]
170+
}
171+
}
172+
148173
// 開放度が低いほど高スコア
149174
// 開放度理論
150175
// ひっくり返した石に隣接する空きマスが少ないほど良い手
@@ -188,7 +213,7 @@ export abstract class AIReversi extends Reversi {
188213
}
189214

190215
if (next_turn) {
191-
this.logging()
216+
this.logging(x, y)
192217
if (this.term === 2) {
193218
const slots = this.addTile(x, y)
194219
if (slots.empty) {
@@ -224,20 +249,36 @@ export abstract class AIReversi extends Reversi {
224249
return hand
225250
}
226251

227-
logging() {
252+
logging(x: number, y: number) {
253+
this.thinking = true
254+
this.boardLog.push({
255+
x,
256+
y,
257+
sym: this.sym,
258+
tiles: this.tiles.slice(),
259+
turn: this.turn,
260+
})
261+
}
262+
263+
virtualHit(x: number, y: number) {
228264
this.thinking = true
229265
this.boardLog.push({
266+
x,
267+
y,
268+
sym: this.sym,
230269
tiles: this.tiles.slice(),
231-
counter: this.turn,
270+
turn: this.turn,
232271
})
272+
273+
return this.addTile(x, y)
233274
}
234275

235276
reset(index = 0) {
236277
const log = this.boardLog[index]
237278
if (log) {
238-
const { tiles, counter } = log
279+
const { tiles, turn } = log
239280
this.tiles = tiles
240-
this.turn = counter
281+
this.turn = turn
241282
this.sym = this.turn % 2 === 0 ? Tile.W : Tile.B
242283
}
243284
this.boardLog = []
@@ -253,11 +294,11 @@ export abstract class AIReversi extends Reversi {
253294
}
254295

255296
/**
256-
*
297+
* 序0 中1 終2
257298
* +----+----+----+----+----+
258299
* 0 0.2 0.4 0.6 0.8 1
259300
*/
260-
get term() {
301+
get term(): 0 | 1 | 2 {
261302
const per = this.countPer
262303
if (per > 0.8) {
263304
return 2
@@ -305,15 +346,13 @@ export abstract class AIReversi extends Reversi {
305346

306347
for (let y = 0; y < boardSize; y++) {
307348
for (let x = 0; x < boardSize; x++) {
308-
if (this.isTileEmpty(x, y)) {
309-
if (this.checkOKtoPlace(x, y)) {
310-
const hand = this.getHand(x, y, lv)
311-
if (hand.scores.total > this.hiScore) {
312-
this.hiScore = hand.scores.total
313-
}
314-
if (hand) {
315-
hands.push(hand)
316-
}
349+
if (this.checkOKtoPlace(x, y)) {
350+
const hand = this.getHand(x, y, lv)
351+
if (hand.scores.total > this.hiScore) {
352+
this.hiScore = hand.scores.total
353+
}
354+
if (hand) {
355+
hands.push(hand)
317356
}
318357
}
319358
}
@@ -326,10 +365,12 @@ export abstract class AIReversi extends Reversi {
326365
x,
327366
y,
328367
count: this.accumulator(x, y),
368+
choices: this.getChoices(x, y),
329369
opens: this.opens(x, y),
330370
fixed: this.fixedCount(x, y),
331371
scores: {
332372
count: 0,
373+
choices: 0,
333374
opens: 0,
334375
position_corner: 0,
335376
position_corner_clue: 0,
@@ -343,13 +384,53 @@ export abstract class AIReversi extends Reversi {
343384
return hand
344385
}
345386

387+
/**
388+
* いくつ石を返せるか
389+
*/
346390
accumulator(x: number, y: number) {
347391
let totalChanged = 0
348392
this.directionEach(x, y, () => totalChanged++)
349393
return totalChanged
350394
}
351395

352-
// 開放度理論
396+
/**
397+
* 着手可能数(自分の打てる選択肢ー相手の打てる選択肢)
398+
*
399+
* virtualHit後に使用
400+
* * 自分の打てる選択肢を多くすると有利になる
401+
* * 相手の打てる選択肢を減らせば不利にできる
402+
*/
403+
getChoices(x: number, y: number) {
404+
// 自分の手を打つ
405+
this.virtualHit(x, y)
406+
407+
// 相手のターンなので相手の打てる選択肢を数える
408+
const enemy = -this._getChoices()
409+
// 相手の手は打たずターンだけ進めて
410+
this.nextTurn()
411+
// 自分の打てる選択肢を数える
412+
const self = this._getChoices()
413+
414+
this.reset()
415+
416+
return [self, enemy]
417+
}
418+
private _getChoices() {
419+
let score = 0
420+
const { boardSize } = this
421+
for (let y = 0; y < boardSize; y++) {
422+
for (let x = 0; x < boardSize; x++) {
423+
if (this.checkOKtoPlace(x, y)) {
424+
score++
425+
}
426+
}
427+
}
428+
return score
429+
}
430+
431+
/**
432+
* 開放度理論
433+
*/
353434
opens(x: number, y: number) {
354435
const { boardSize } = this
355436
const opens = new Set<number>()

test/AI.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ describe(`AI`, () => {
2222
{
2323
fixed: 0,
2424
opens: 5,
25+
choices: [5, -5],
2526
count: 1,
2627
x: 5,
2728
y: 1,
2829
scores: {
2930
count: 1,
31+
choices: 0,
3032
opens: 0,
3133
position_corner: 0,
3234
position_edge: [0, 0, 0],
@@ -39,11 +41,13 @@ describe(`AI`, () => {
3941
{
4042
fixed: 0,
4143
opens: 3,
44+
choices: [6, -3],
4245
count: 1,
4346
x: 5,
4447
y: 2,
4548
scores: {
4649
count: 1,
50+
choices: 0,
4751
opens: 0,
4852
position_corner: 0,
4953
position_edge: [0, 0, 0],
@@ -56,12 +60,15 @@ describe(`AI`, () => {
5660
{
5761
fixed: 0,
5862
opens: 3,
63+
choices: [7, -4],
5964
count: 1,
6065
x: 5,
6166
y: 3,
6267
scores: {
6368
count: 1,
69+
choices: 0,
6470
opens: 0,
71+
6572
position_corner: 0,
6673
position_edge: [0, 0, 0],
6774
total: 1,
@@ -73,11 +80,13 @@ describe(`AI`, () => {
7380
{
7481
fixed: 0,
7582
opens: 4,
83+
choices: [5, -5],
7684
count: 1,
7785
x: 5,
7886
y: 4,
7987
scores: {
8088
count: 1,
89+
choices: 0,
8190
opens: 0,
8291
position_corner: 0,
8392
position_edge: [0, 0, 0],
@@ -90,11 +99,13 @@ describe(`AI`, () => {
9099
{
91100
fixed: 0,
92101
opens: 4,
102+
choices: [4, -4],
93103
count: 1,
94104
x: 5,
95105
y: 5,
96106
scores: {
97107
count: 1,
108+
choices: 0,
98109
opens: 0,
99110
position_corner: 0,
100111
position_edge: [0, 0, 0],

0 commit comments

Comments
 (0)