☰
Godot 从零做围棋06——board主要函数
2026/11/5 2:04:16 网站建设 项目流程

上期我们做好了前期的准备。这期我们开始正式处理围棋游戏里面的相关逻辑函数。

寻找同色连同块

在围棋的游戏规则里面,我们判断一个子是否要被提是要看这个子的气是否为0。在同色棋子连成一整块时,这些棋子的气是共享的,这一个整体有气则不需要提子,没气则这一组内所有的棋子都要被提。

这个整体我们先叫它连通块,连通块的棋子直接是上下左右相连的(斜对角相邻不算,具体可以自己找网上的围棋游戏规则)。

这个函数就是用于找到这些连通块。

这个函数用的是BFS(广度优先搜索)的思路,具体里面代码的涵义在注释里有解释(解释的很详细了,如果对于代码格式等不熟悉可以看官方文档等资料)。

func get_group(start: Vector2i) -> Array[Vector2i]: #创建函数 get_group,函数里面的参数是start,其数据类型是Vector2i。这个start就是开始搜索的棋子的坐标,然后这个函数要返回数据类型为Array[Vector2i]的结果。 var group: Array[Vector2i] = []#声明最后要返回的对象group(初始为空)。 if is_empty(start): return group#如果函数开始时的棋子坐标上是空的,则直接返回空的group。 var color := get_stone(start).color#获取初始棋子的颜色。 var visited := {}#创建空字典,用于记录已经访问过的棋子坐标。 var queue: Array[Vector2i] = [start]#创建队列,且将初始的棋子先入栈。 visited[start] = true#将起点标记为已访问。 while not queue.is_empty():#while循环,当队列不为空就继续循环。 var p: Vector2i = queue.pop_front()#从队列头部取出并删除第一个元素。 group.append(p)#出队的点就是块里的一员,加入 group。 for n in _neighbors(p):#遍历棋子附近的棋子。 if visited.has(n): continue#如果遍历到的棋子已经被访问过,则跳过。 var s := get_stone(n)#获取访问的邻居的状态 if s != null and s.color == color:#如果邻居上的棋子不为空,且颜色与起点相同则执行。 visited[n] = true#将这个棋子标记为已访问。 queue.append(n)#将这个棋子入栈。 return group#返回块

计算同色连通块的气

用于判断这个块是否活,以及判断胜负所需的数据。

func get_liberties(group: Array[Vector2i]) -> int: var libs := {} for p in group: for n in _neighbors(p): if is_empty(n):#如果邻居为“空”,则多一个气。 libs[n] = true return libs.size() #简单来说就是,这个函数输入的是联通块,输出的是气的数量。只要理解了上个函数,这个函数自然不在话下

判断“死活”

判断与“start”一个“块”的同色棋子是否要被全部提子。

func is_group_alive(start: Vector2i) -> bool: if is_empty(start): return false return get_liberties(get_group(start)) > 0 #输入start(单个棋子的坐标),如果该棋子的连通块为空或死则返回false,如果该棋子的连通块有气则返回true。

落子

这一部分需要考虑的地方有很多,判断此处能不能落子包括打劫禁入点、没有气的地方等,以及落子的后续处理等。

func try_place(pos: Vector2i) -> bool: if not in_bounds(pos): return false #如果这个地方超出棋盘则返回false。 if not is_empty(pos): return false #如果这个地方有别的棋子则返回false。 if pos == _ko_point: return false # 打劫禁入点 #如果这个地方是打劫禁入点则返回false var color := current_player #获取当前棋手的棋子颜色。 # 1. 临时落子,先将棋子落下去判断后续的情况 var stone := Stone.new(color, pos) stones[pos] = stone # 2. 提对方,这里引用的函数在后续会写 var captured := _remove_dead_opponent_groups(pos, color) # 3. 检查自己是否自杀,如果自杀了则返回false var my_group := get_group(pos) if get_liberties(my_group) == 0: # 回滚 stones.erase(pos)#这里用的是引擎自带的哈希表删除函数 for p in captured: stones[p] = Stone.new(_opponent(color), p)#_opponent()函数在后续会写,作用是:输入黑返回白,输入白返回黑。 return false # 4. 记录打劫点 _ko_point = Vector2i(-1, -1) if captured.size() == 1 and my_group.size() == 1: _ko_point = captured[0] # 5. 记录历史 var move := Move.new(color, pos, captured) history.append(move) last_move = pos current_player = _opponent(color) return true #该函数输入当前选手要落子的地方,然后返回true(落子成功)或者false(无法在此处落子)

提子

# ---------- 提子 ---------- # 检查 pos 周围对方的棋块,没气的整块移除,返回被提的坐标列表 func _remove_dead_opponent_groups(pos: Vector2i, my_color: Stone.StoneColor) -> Array[Vector2i]: var captured: Array[Vector2i] = []#声明最终返回的所有被提的棋子坐标的集合。 var opp := _opponent(my_color)#获取被提的棋子的颜色。 for n in _neighbors(pos): var s := get_stone(n) if s == null or s.color != opp: continue#如果邻居不为相反的颜色或者为空则跳过。 var group := get_group(n)#获取异色邻居的联通块。 if get_liberties(group) == 0:#计算异色邻居联通块的气,如果为零则将块里面所有的棋子都加入captured。 for p in group: stones.erase(p) captured.append(p) return captured#返回返回被提的坐标列表。

悔棋

这一步主要是对落子后棋局所发生变化的逆向过程,主要干的事情有:撤销上一步所落的子、重新落回上一步所提的子、如果更新了打劫禁入点则撤销更新。当然还有一些容易疏忽的要点,比如说当棋局还没有开始下时无法悔棋。

# ---------- 悔棋 ---------- func undo() -> bool: if history.is_empty(): return false #如果还没有开始下棋则悔不了棋。 var move: Move = history.pop_back() #取出上一步记录且从history上删除这一步记录。 # 1. 移除这一步落下的棋子 stones.erase(move.position) # 2. 把被提的子放回去 for p in move.captured: stones[p] = Stone.new(_opponent(move.color), p) # 3. 恢复状态 current_player = move.color last_move = history[-1].position if not history.is_empty() else Vector2i(-1, -1) #三元条件表达式:值A if 条件 else 值B #含义:条件为真取值 A,否则取值 B。 #在此处的具体意思是如果当前history不为空则执行则将last_move赋值为当前history的history(-1),否则赋值为Vector(-1,-1) # 4. 重新计算打劫点 _ko_point = Vector2i(-1, -1) #先更新打劫点。 if not history.is_empty(): #如果history为空则直接跳过,此时_ko_point = Vector2i(-1, -1)。 var prev: Move = history[-1] if prev.captured.size() == 1 and prev.position == move.position: _ko_point = prev.captured[0] #如果悔棋后的上一步有单劫则更新打劫点。 return true

新局

这个函数很简单,只需要把存储的所有围棋状态以及历史都清空。

# ---------- 新局 ---------- func reset(): stones.clear() history.clear() current_player = Stone.StoneColor.BLACK last_move = Vector2i(-1, -1) _ko_point = Vector2i(-1, -1)

黑变白、白变黑辅助函数

这个函数主要是在围棋下棋时这种逻辑判断经常被用到,写一个函数来执行比较方便,这个函数用到了三元条件表达式,在前面悔棋的函数的注释里也介绍过。

# ---------- 辅助 ---------- func _opponent(color: Stone.StoneColor) -> Stone.StoneColor: return Stone.StoneColor.WHITE if color == Stone.StoneColor.BLACK else Stone.StoneColor.BLACK

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询