From 4a5cb1739f1493df4b301b6ac95f58c0a966bcdd Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Tue, 17 Dec 2019 15:45:00 +0800 Subject: [PATCH] add ai --- src/Gobang.ts | 185 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 172 insertions(+), 13 deletions(-) diff --git a/src/Gobang.ts b/src/Gobang.ts index 341b9f50..2a17f43c 100644 --- a/src/Gobang.ts +++ b/src/Gobang.ts @@ -1,7 +1,152 @@ import { Stack, hlayout, Group, Color, stack, layoutConfig, LayoutSpec, vlayout, IVLayout, Text, ViewHolder, ViewModel, VMPanel, scroller, modal, text, gravity, Gravity, IHLayout, takeNonNull, View, log, popover } from "doric"; import { colors } from "./utils"; +enum State { + Unspecified, + BLACK, + WHITE, +} +const count = 13 + + +class AIComputer { + wins: Array>> = new Array(count).fill(0).map(_ => new Array(count).fill(0).map(_ => [])); + winCount = 0 + blackWins: number[] + whiteWins: number[] + constructor() { + for (let y = 0; y < count; y++) { + for (let x = 0; x < count - 4; x++) { + for (let k = 0; k < 5; k++) { + this.wins[x + k][y][this.winCount] = true; + } + this.winCount++; + } + } + + for (let x = 0; x < count; x++) { + for (let y = 0; y < count - 4; y++) { + for (let k = 0; k < 5; k++) { + this.wins[x][y + k][this.winCount] = true; + } + this.winCount++; + } + } + + for (let x = 0; x < count - 4; x++) { + for (let y = 0; y < count - 4; y++) { + for (let k = 0; k < 5; k++) { + this.wins[x + k][y + k][this.winCount] = true; + } + this.winCount++; + } + } + + for (let x = 0; x < count - 4; x++) { + for (let y = count - 1; y > 3; y--) { + for (let k = 0; k < 5; k++) { + this.wins[x + k][y - k][this.winCount] = true; + } + this.winCount++; + } + } + this.blackWins = new Array(this.winCount).fill(0) + this.whiteWins = new Array(this.winCount).fill(0) + } + + + oneStep(idx: number, role: State.BLACK | State.WHITE) { + const { x, y } = this.index2Position(idx) + for (let loop = 0; loop < this.winCount; loop++) { + if (this.wins[x][y][loop]) { + if (role === State.BLACK) { + this.blackWins[loop] += 1 + } else { + this.whiteWins[loop] += 1 + } + } + } + } + + index2Position(idx: number) { + const x = idx % count + const y = Math.floor(idx / count) + return { x, y } + } + + compute(matrix: State[], role: State.BLACK | State.WHITE) { + const myScore = new Array(matrix.length).fill(0) + const rivalScore = new Array(matrix.length).fill(0) + const myWins = role === State.BLACK ? this.blackWins : this.whiteWins + const rivalWins = role === State.BLACK ? this.whiteWins : this.blackWins + let max = 0 + let retIdx = 0 + matrix.forEach((state, idx) => { + if (state != State.Unspecified) { + return + } + const { x, y } = this.index2Position(idx) + for (let loop = 0; loop < this.winCount; loop++) { + if (this.wins[x][y][loop]) { + switch (rivalWins[loop]) { + case 1: + rivalScore[idx] += 200 + break + case 2: + rivalScore[idx] += 400 + break + case 3: + rivalScore[idx] += 2000 + break + case 4: + rivalScore[idx] += 10000 + break + default: + break + } + + switch (myWins[loop]) { + case 1: + myScore[idx] += 220 + break + case 2: + myScore[idx] += 420 + break + case 3: + myScore[idx] += 2200 + break + case 4: + myScore[idx] += 20000 + break + default: + break + } + } + } + if (rivalScore[idx] > max) { + max = rivalScore[idx]; + retIdx = idx + } else if (rivalScore[idx] == max) { + if (myScore[idx] > myScore[retIdx]) { + retIdx = idx + } + } + + if (myScore[idx] > max) { + max = myScore[idx] + retIdx = idx + } else if (myScore[idx] == max) { + if (rivalScore[idx] > rivalScore[retIdx]) { + retIdx = idx + } + } + + + }) + return retIdx + } +} const lineColor = Color.BLACK function columLine() { return (new Stack).apply({ @@ -26,13 +171,6 @@ function pointer(size: number) { height: size, }) } - -const count = 13 -enum State { - Unspecified, - BLACK, - WHITE, -} enum GameMode { P2P, P2C, @@ -150,6 +288,7 @@ class GoBangVH extends ViewHolder { } class GoBangVM extends ViewModel{ + computer!: AIComputer onAttached(state: GoBangState, vh: GoBangVH) { vh.actualBuild(state) vh.targetZone.forEach((e, idx) => { @@ -168,9 +307,15 @@ class GoBangVM extends ViewModel{ if (it.role === 'black') { it.matrix.set(idx, State.BLACK) it.role = 'white' + if (this.computer) { + this.computer.oneStep(idx, State.BLACK) + } } else { it.matrix.set(idx, State.WHITE) it.role = 'black' + if (this.computer) { + this.computer.oneStep(idx, State.WHITE) + } } it.anchor = undefined if (this.checkResult(idx)) { @@ -235,22 +380,36 @@ class GoBangVM extends ViewModel{ ) } } - computeNextStep(it: GoBangState) { - let x = 0, y = 0 + const tempMatrix: State[] = new Array(count * count).fill(0).map((_, idx) => { + return it.matrix.get(idx) || State.Unspecified + }) + let idx = 0 do { - x = Math.floor(Math.random() * count) - y = Math.floor(Math.random() * count) - } while (it.matrix.get(x * count + y) === State.Unspecified) + idx = this.computer.compute(tempMatrix, it.role === 'black' ? State.BLACK : State.WHITE) + } while (it.matrix.get(idx) === State.Unspecified) + this.computer.oneStep(idx, it.role === 'black' ? State.BLACK : State.WHITE) this.updateState(state => { - state.matrix.set(x * count + y, state.role === 'black' ? State.BLACK : State.WHITE) + state.matrix.set(idx, state.role === 'black' ? State.BLACK : State.WHITE) state.role = state.role === 'black' ? 'white' : 'black' }) + if (this.checkResult(idx)) { + modal(context).alert({ + title: "游戏结束", + msg: `恭喜获胜方${it.role === 'white' ? "黑方" : "白方"}`, + }).then(() => { + this.updateState(s => { + this.reset(s) + }) + }) + } } + reset(it: GoBangState) { it.matrix.clear() it.role = "black" it.anchor = undefined + this.computer = new AIComputer if (it.gameMode === GameMode.C2P) { this.computeNextStep(it) }