You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
3.8 KiB

2 years ago
package main
// FIXME
// Working:
// - Moving
// - Approach captures
// - Withdrawl captures
// - Choosing randomly from moves with equal points
// Broken:
// - Continuations moves
import (
"fmt"
"math/rand"
)
var weakDirs = []dir{{0,-1}, {0,1}, {1,0}, {-1,0}}
var strongDirs = []dir{{-1,-1}, {-1,1}, {1,1}, {1,-1}}
type point struct {
row int
col int
}
type score struct {
d dir
attacking bool
continuation bool
deadCount int
approach bool
}
func (s score) points() int {
count := 0
if s.continuation {
count += 3
}
return count + s.deadCount
}
type place struct {
row int
col int
strong bool
s []score
maxPts int
bestScore []int
}
func (p *place) FindBestDir() {
if len(p.s) == 0 {
return
}
for i, s := range p.s {
if s.points() > p.maxPts {
p.maxPts = s.points()
p.bestScore = []int{i}
} else if s.points() == p.maxPts {
p.bestScore = append(p.bestScore, i)
}
}
}
func ScoreContinuation(m move, g *Game, blacklist []point) (move, bool, error) {
p := place{m.toRow, m.toCol, g.board[m.toRow][m.toCol].strong, make([]score, 0, 8), 0, make([]int, 0, 7)}
p.Score(g)
outer: for _, s := range p.s {
if s.points() > 0 {
newPt := point{m.toRow+s.d.rowOff, m.toCol+s.d.colOff}
for _, bl := range blacklist {
if newPt.row == bl.row && newPt.col == bl.col {
continue outer
}
}
return move{m.toRow, m.toCol, newPt.row, newPt.col}, s.approach, nil
}
}
return move{}, false, fmt.Errorf("No valid continuation")
}
func (p *place) Score(g *Game) {
dirs := make([]dir, 0, 8)
dirs = append(dirs, weakDirs...)
if p.strong {
dirs = append(dirs, strongDirs...)
}
for _, d := range dirs {
m := move{p.row, p.col, p.row + d.rowOff, p.col + d.colOff}
err := g.ValidateMove(m)
if err != nil {
// This is not a valid move
continue
}
// Check continuation
hasContinuation := false
for _, d2 := range dirs {
if d2.rowOff == d.rowOff && d2.colOff == d.colOff {
// Cannot move in the same direction
continue
}
m2 := move{m.toRow, m.toCol, m.toRow + d2.rowOff, m.toCol + d2.colOff}
if g.withdrawAvailable(m2) || g.approachAvailable(m) {
hasContinuation = true
break
}
}
// Check Attack
canWithdraw := g.withdrawAvailable(m)
canApproach := g.approachAvailable(m)
if !canWithdraw && !canApproach {
p.s = append(p.s, score{d, false, false, 0, false})
}
if canWithdraw {
p.s = append(p.s, score{d, true, hasContinuation, g.CountPotentialDead(m, false), false})
}
if canApproach {
p.s = append(p.s, score{d, true, hasContinuation, g.CountPotentialDead(m, true), true})
}
}
p.FindBestDir()
}
type cpu struct {
legal []place
bestScore int
bestMove []int
}
func (c *cpu) PickMove(g *Game) (move, bool, bool, bool) {
c.Clear()
c.GetLegalMoves(g)
for i := range c.legal {
if c.legal[i].maxPts > c.bestScore {
c.bestScore = c.legal[i].maxPts
c.bestMove = []int{i}
} else if c.legal[i].maxPts == c.bestScore {
c.bestMove = append(c.bestMove, i)
}
}
bestM := rand.Intn(len(c.bestMove))
m := c.legal[c.bestMove[bestM]]
bestS := rand.Intn(len(m.bestScore))
return move{
m.row,
m.col,
m.row+m.s[m.bestScore[bestS]].d.rowOff,
m.col+m.s[m.bestScore[bestS]].d.colOff},
m.s[m.bestScore[bestS]].approach,
m.s[m.bestScore[bestS]].attacking,
m.s[m.bestScore[bestS]].continuation
}
func (c *cpu) Clear() {
c.legal = make([]place, 0, 15)
c.bestMove = make([]int, 0, 5)
c.bestScore = 0
}
func (c *cpu) GetLegalMoves(g *Game) {
for r, row := range g.board {
for p, pt := range row {
if pt.state == Empty || pt.state == Black {
continue
}
pl := place{r, p, g.board[r][p].strong, make([]score, 0, 8), -1, make([]int, 0, 5)}
pl.Score(g)
if len(pl.s) > 0 {
c.legal = append(c.legal, pl)
}
}
}
}