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) } } } }