您的位置:首页 > 房产 > 家装 > Golang 蒙特卡洛算法 在 五子棋中的实现

Golang 蒙特卡洛算法 在 五子棋中的实现

2024/12/23 15:58:22 来源:https://blog.csdn.net/u010349629/article/details/141062684  浏览:    关键词:Golang 蒙特卡洛算法 在 五子棋中的实现

蒙特卡洛算法在五子棋中的实现

引言

蒙特卡洛算法是一种基于随机抽样的数值计算方法,在许多领域都有广泛的应用。在围棋(五子棋)这类游戏中,蒙特卡洛算法被用于寻找最佳的走法。本文将通过一段示例代码来介绍如何利用蒙特卡洛树搜索(MCTS)算法来实现一个简单的五子棋AI。

五子棋游戏概述

五子棋是一种两人对弈的策略型棋类游戏,双方轮流落子,目标是首先在棋盘上形成连续的五个同色棋子的一方获胜。本示例中的五子棋游戏大小为15×15,并且默认中间位置已放置了一个“裁判”棋子。

蒙特卡洛树搜索(MCTS)

蒙特卡洛树搜索是一种概率性的搜索算法,常用于解决具有不确定性和信息不完全的游戏问题。它通过模拟大量的随机游戏过程来评估不同走法的好坏,从而选择最有可能带来胜利的走法。

代码解析

下面我们将逐步解析上述代码中的关键部分。

游戏状态表示

GoBang 结构体定义了游戏的状态,包括棋盘的大小、棋盘上的布局以及当前玩家等信息。

type GoBang struct {size   intboard  [][]intplayer int
}

其中,board 是一个二维数组,用来存储棋盘上每个位置的状态(0表示空位,1表示先手玩家的棋子,2表示后手玩家的棋子)。player 表示当前应走的玩家。

蒙特卡洛树节点

McTreeNode 结构体定义了蒙特卡洛树中的一个节点。

type McTreeNode struct {parent         *McTreeNodechildren       []*McTreeNodescore          float64visitCount     float64untriedActions []anynodeState      TreeState
}

每个节点包含指向父节点的指针、一个子节点列表、得分、访问次数、未尝试的动作列表以及与该节点相关联的游戏状态。

搜索函数

Search 函数实现了蒙特卡洛树搜索的核心逻辑。

func Search(simulate any, state TreeState, discount ...float64) TreeState {// ...
}

此函数接受一个模拟参数 simulate 和当前的游戏状态 state,并返回一个新的游戏状态。模拟参数可以是一个整数、一个时间间隔或者一个布尔函数,用于控制模拟的终止条件。

树策略

treePolicy 方法实现了树策略的选择逻辑,即如何从当前节点扩展树结构,以及如何选择下一个要探索的节点。

func (cur *McTreeNode) treePolicy(discountParamC float64) *McTreeNode {// ...
}
最佳子节点选择

chooseBestChild 方法用于选择最佳子节点,这是基于UCB1公式来完成的。

func (cur *McTreeNode) chooseBestChild(c float64) *McTreeNode {// ...
}
反向传播

backPropagate 方法负责将模拟的结果反向传播至根节点,以便更新各个节点的得分和访问次数。

func (cur *McTreeNode) backPropagate(result float64) {// ...
}
完整代码示例
package mainimport ("fmt""math""math/rand""strings""time"
)type GoBang struct {size   intboard  [][]intplayer int
}func NewGoBang(size int) *GoBang {w := &GoBang{size:   size,board:  make([][]int, size),player: 1,}for i := 0; i < size; i++ {w.board[i] = make([]int, size)}size /= 2// 默认中间落子w.board[size][size] = 2return w
}func main() {var (x, y  intboard = NewGoBang(15))board.Print()for board.IsTerminal() == 0 {if board.player == 1 {board = Search(time.Second*10, board).(*GoBang)} else if board.player == 2 {for {fmt.Print("已执棋,请输入坐标: ")_, _ = fmt.Scanln(&x, &y)x--y--if x < 0 || y < 0 || x >= board.size || y >= board.size {fmt.Println("超出棋盘范围")} else if board.board[x][y] > 0 {fmt.Println("已有棋子")} else {board.board[x][y] = 2board.player = 1break}}}board.Print()if board.IsTerminal() != 0 {fmt.Printf("Game Over: %d\n", board.IsTerminal())return}}
}func (w *GoBang) Print() {var (str strings.Buildernum = func(n int) {a, b := n/10, n%10if a > 0 {str.WriteByte(byte(a + '0'))} else {str.WriteByte(' ') // 1位数前面加空格}str.WriteByte(byte(b + '0'))})str.WriteString("   ")for i := 1; i <= w.size; i++ {str.WriteByte(' ')num(i)}str.WriteByte('\n')for i := 0; i < w.size; i++ {str.WriteString("   ")for j := 0; j < w.size; j++ {str.WriteString(" __")}str.WriteByte('\n')num(i + 1)str.WriteByte(' ')for j := 0; j < w.size; j++ {str.WriteByte('|')switch w.board[i][j] {case 0:str.WriteByte(' ')case 1:str.WriteByte('O')case 2:str.WriteByte('X')}str.WriteByte(' ')}str.WriteString("|\n")}str.WriteString("   ")for i := 0; i < w.size; i++ {str.WriteString(" __")}fmt.Println(str.String())
}func (w *GoBang) IsTerminal() int {full := -1 // 没有空位且都没赢for i := 0; i < w.size; i++ {for j := 0; j < w.size; j++ {if wc := w.board[i][j]; wc == 0 {full = 0 // 还有空位,没结束} else {// 向右cnt, x, y := 1, 0, j+1for ; y < w.size && w.board[i][y] == wc; y++ {cnt++}if cnt >= 5 {return wc}// 向下cnt, x = 1, i+1for ; x < w.size && w.board[x][j] == wc; x++ {cnt++}if cnt >= 5 {return wc}// 向右下cnt, x, y = 1, i+1, j+1for ; x < w.size && y < w.size && w.board[x][y] == wc; x, y = x+1, y+1 {cnt++}if cnt >= 5 {return wc}// 向左下cnt, x, y = 1, i+1, j-1for ; x < w.size && y >= 0 && w.board[x][y] == wc; x, y = x+1, y-1 {cnt++}if cnt >= 5 {return wc}}}}return full
}func (w *GoBang) Result(state int) float64 {switch state {case -1:return 0 // 都没赢且没空位case 1:return -1 // 先手赢了case 2:return +1 // 后手赢了default:return 0 // 都没赢且有空位}
}func (w *GoBang) GetActions() (res []any) {// 将棋子周围2格范围的空位加到结果中m := map[[2]int]struct{}{}for i := 0; i < w.size; i++ {for j := 0; j < w.size; j++ {// 跳过空位和己方棋子if w.board[i][j] == 0 || w.board[i][j] == w.player {continue}x0, x1, y0, y1 := i-2, i+2, j-2, j+2for ii := x0; ii <= x1; ii++ {for jj := y0; jj <= y1; jj++ {if ii >= 0 && jj >= 0 && ii < w.size && jj < w.size &&w.board[ii][jj] == 0 {p := [2]int{ii, jj}_, ok := m[p]if !ok {res = append(res, p)m[p] = struct{}{}}}}}}}return
}func (w *GoBang) ActionPolicy(action []any) any {// 随机选一个动作(todo 替换为根据评分选取动作)return action[rand.Intn(len(action))]
}func (w *GoBang) Action(action any) TreeState {// 初始化wn := &GoBang{size:   w.size,board:  make([][]int, w.size),player: 3 - w.player,}for i := 0; i < w.size; i++ {wn.board[i] = make([]int, w.size)for j := 0; j < w.size; j++ {wn.board[i][j] = w.board[i][j]}}// 新增落子ac := action.([2]int)wn.board[ac[0]][ac[1]] = w.playerreturn wn
}// MonteCarloTree
type (TreeState interface {IsTerminal() intResult(int) float64GetActions() []anyActionPolicy([]any) anyAction(any) TreeState}McTreeNode struct {parent         *McTreeNodechildren       []*McTreeNodescore          float64visitCount     float64untriedActions []anynodeState      TreeState}
)func Search(simulate any, state TreeState, discount ...float64) TreeState {var (root = &McTreeNode{nodeState: state}leaf *McTreeNodedp   = 1.4)if len(discount) > 0 {dp = discount[0]}var loop func() boolswitch s := simulate.(type) {case int:loop = func() bool {s-- // 模拟指定次数后退出return s >= 0}case time.Duration:// 超过时间后退出ts := time.Now().Add(s)loop = func() bool { return time.Now().Before(ts) }case func() bool:loop = sdefault:panic(simulate)}for loop() {leaf = root.treePolicy(dp)result, curState := 0, leaf.nodeStatefor {// 重复该过程,直到结束(找位 选位 填写)if result = curState.IsTerminal(); result != 0 {break}all := curState.GetActions()one := curState.ActionPolicy(all)curState = curState.Action(one)}// 根据结束状态计算结果,将该结果反向传播leaf.backPropagate(curState.Result(result))}return root.chooseBestChild(dp).nodeState // 选择最优子节点
}func (cur *McTreeNode) chooseBestChild(c float64) *McTreeNode {var (idx        = 0maxValue   = -math.MaxFloat64childValue float64)for i, child := range cur.children {childValue = (child.score / child.visitCount) +c*math.Sqrt(math.Log(cur.visitCount)/child.visitCount)if childValue > maxValue {maxValue = childValueidx = i}}return cur.children[idx]
}func (cur *McTreeNode) backPropagate(result float64) {// 反向传播,增加访问次数,更新分数nodeCursor := curfor nodeCursor.parent != nil {nodeCursor.score += resultnodeCursor.visitCount++nodeCursor = nodeCursor.parent}nodeCursor.visitCount++
}func (cur *McTreeNode) expand() *McTreeNode {// 按顺序添加,并移除res := cur.untriedActions[0]cur.untriedActions = cur.untriedActions[1:]child := &McTreeNode{parent:    cur,nodeState: cur.nodeState.Action(res),}cur.children = append(cur.children, child)return child
}func (cur *McTreeNode) treePolicy(discountParamC float64) *McTreeNode {nodeCursor := curfor nodeCursor.nodeState.IsTerminal() == 0 {if nodeCursor.untriedActions == nil {nodeCursor.untriedActions = nodeCursor.nodeState.GetActions()}if len(nodeCursor.untriedActions) > 0 {return nodeCursor.expand()}nodeCursor = nodeCursor.chooseBestChild(discountParamC)}return nodeCursor
}
结论

通过上述可以看到蒙特卡洛树搜索算法是如何在五子棋游戏中的实现。这种方法虽然简单但在实践中却非常有效,尤其是在处理复杂的决策问题时。此外通过调整算法中的参数(如模拟次数、探索因子等),还可以进一步优化AI的表现。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com