add ai
This commit is contained in:
parent
84ea697eb2
commit
4a5cb1739f
185
src/Gobang.ts
185
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 { 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";
|
import { colors } from "./utils";
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Unspecified,
|
||||||
|
BLACK,
|
||||||
|
WHITE,
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = 13
|
||||||
|
|
||||||
|
|
||||||
|
class AIComputer {
|
||||||
|
wins: Array<Array<Array<Boolean>>> = 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
|
const lineColor = Color.BLACK
|
||||||
function columLine() {
|
function columLine() {
|
||||||
return (new Stack).apply({
|
return (new Stack).apply({
|
||||||
@ -26,13 +171,6 @@ function pointer(size: number) {
|
|||||||
height: size,
|
height: size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = 13
|
|
||||||
enum State {
|
|
||||||
Unspecified,
|
|
||||||
BLACK,
|
|
||||||
WHITE,
|
|
||||||
}
|
|
||||||
enum GameMode {
|
enum GameMode {
|
||||||
P2P,
|
P2P,
|
||||||
P2C,
|
P2C,
|
||||||
@ -150,6 +288,7 @@ class GoBangVH extends ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GoBangVM extends ViewModel<GoBangState, GoBangVH>{
|
class GoBangVM extends ViewModel<GoBangState, GoBangVH>{
|
||||||
|
computer!: AIComputer
|
||||||
onAttached(state: GoBangState, vh: GoBangVH) {
|
onAttached(state: GoBangState, vh: GoBangVH) {
|
||||||
vh.actualBuild(state)
|
vh.actualBuild(state)
|
||||||
vh.targetZone.forEach((e, idx) => {
|
vh.targetZone.forEach((e, idx) => {
|
||||||
@ -168,9 +307,15 @@ class GoBangVM extends ViewModel<GoBangState, GoBangVH>{
|
|||||||
if (it.role === 'black') {
|
if (it.role === 'black') {
|
||||||
it.matrix.set(idx, State.BLACK)
|
it.matrix.set(idx, State.BLACK)
|
||||||
it.role = 'white'
|
it.role = 'white'
|
||||||
|
if (this.computer) {
|
||||||
|
this.computer.oneStep(idx, State.BLACK)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
it.matrix.set(idx, State.WHITE)
|
it.matrix.set(idx, State.WHITE)
|
||||||
it.role = 'black'
|
it.role = 'black'
|
||||||
|
if (this.computer) {
|
||||||
|
this.computer.oneStep(idx, State.WHITE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
it.anchor = undefined
|
it.anchor = undefined
|
||||||
if (this.checkResult(idx)) {
|
if (this.checkResult(idx)) {
|
||||||
@ -235,22 +380,36 @@ class GoBangVM extends ViewModel<GoBangState, GoBangVH>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
computeNextStep(it: GoBangState) {
|
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 {
|
do {
|
||||||
x = Math.floor(Math.random() * count)
|
idx = this.computer.compute(tempMatrix, it.role === 'black' ? State.BLACK : State.WHITE)
|
||||||
y = Math.floor(Math.random() * count)
|
} while (it.matrix.get(idx) === State.Unspecified)
|
||||||
} while (it.matrix.get(x * count + y) === State.Unspecified)
|
this.computer.oneStep(idx, it.role === 'black' ? State.BLACK : State.WHITE)
|
||||||
this.updateState(state => {
|
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'
|
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) {
|
reset(it: GoBangState) {
|
||||||
it.matrix.clear()
|
it.matrix.clear()
|
||||||
it.role = "black"
|
it.role = "black"
|
||||||
it.anchor = undefined
|
it.anchor = undefined
|
||||||
|
this.computer = new AIComputer
|
||||||
if (it.gameMode === GameMode.C2P) {
|
if (it.gameMode === GameMode.C2P) {
|
||||||
this.computeNextStep(it)
|
this.computeNextStep(it)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user