本文内容已上传开源仓库:扫雷:一个简单的扫雷游戏,基于Godot4.6 - AtomGit | GitCode

阶段四,我们已经能够显示数字了,但是有一个很麻烦的问题,如果点击一个周围地雷数为0的空白格子,它只会翻开自己这一个,周围的空白格子和数字格子还得我们一个个去点,非常累!

现在,我们来修改main.gd,使其可以在点击到白色格子时,自动翻开相邻的所有白格子和数字格子。

一、完善main.gd

extends Control

const GRID_SIZE = 10
const TILE_SCENE = preload("res://场景/tile.tscn")
const MINE_COUNT = 15

var tiles: Array = []

@onready var grid: GridContainer = $grid

func _ready():
	_init_grid()
	_place_mines()
	_calculate_adjacent_mines()
	_connect_signals()

func _init_grid():
	for i in range(GRID_SIZE * GRID_SIZE):
		var tile = TILE_SCENE.instantiate()
		tile.custom_minimum_size = Vector2(50, 50)
		grid.add_child(tile)
		tiles.append(tile)

func _place_mines():
	var rng = RandomNumberGenerator.new()
	rng.randomize()
	
	var placed_mines = 0
	while placed_mines < MINE_COUNT:
		var random_index = rng.randi_range(0, tiles.size() - 1)
		var tile = tiles[random_index]
		
		if not tile.is_mine:
			tile.is_mine = true
			placed_mines += 1

func _calculate_adjacent_mines():
	for i in range(tiles.size()):
		var tile = tiles[i]
		if tile.is_mine:
			continue
		
		var x = i % GRID_SIZE
		var y = i / GRID_SIZE
		
		var mine_count = 0
		for dx in [-1, 0, 1]:
			for dy in [-1, 0, 1]:
				if dx == 0 and dy == 0:
					continue
				
				var nx = x + dx
				var ny = y + dy
				
				if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
					var neighbor_index = ny * GRID_SIZE + nx
					var neighbor_tile = tiles[neighbor_index]
					if neighbor_tile.is_mine:
						mine_count += 1
		
		tile.adjacent_mines = mine_count

func _connect_signals():
	for tile in tiles:
		tile.clicked.connect(_on_tile_clicked)

func _on_tile_clicked(tile: Control):
	_reveal_tile(tile)

# 【修复】核心递归函数:自动展开空白区域
func _reveal_tile(tile: Control):
	if tile.is_revealed:
		return
	
	tile.reveal()
	
	# 【修复】只有当“不是地雷”且“周围地雷数为0”时,才递归翻开周围
	if not tile.is_mine and tile.adjacent_mines == 0:
		var i = tiles.find(tile)
		var x = i % GRID_SIZE
		var y = i / GRID_SIZE
		
		for dx in [-1, 0, 1]:
			for dy in [-1, 0, 1]:
				if dx == 0 and dy == 0:
					continue
				
				var nx = x + dx
				var ny = y + dy
				
				if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
					var neighbor_index = ny * GRID_SIZE + nx
					var neighbor_tile = tiles[neighbor_index]
					_reveal_tile(neighbor_tile)

1.1 处理点击函数

回顾阶段四的点击处理函数与新的处理函数:

# 阶段四的写法(直接翻开)
# func _on_tile_clicked(tile: Control):
# 	tile.reveal()

# 【阶段五修改】改成调用递归函数
func _on_tile_clicked(tile: Control):
	_reveal_tile(tile)
  • 阶段四我们只是 “点击哪个翻开哪个”;
  • 阶段五我们需要 “点击一个,翻开一片”,所以要把逻辑拆到一个单独的递归函数里。

1.2 _reveal_tile递归函数

递归就像 “多米诺骨牌”—— 你推倒第 1 个骨牌,第 1 个骨牌会推倒第 2 个,第 2 个推倒第 3 个…… 直到所有能推倒的骨牌都倒下。

  • 你点击第 1 个空白格子(推倒第 1 个骨牌);
  • 第 1 个空白格子会 “调用”_reveal_tile去翻开它周围的 8 个邻居;
  • 邻居里如果有空白格子,又会继续 “调用”_reveal_tile去翻开它的邻居;
  • 直到所有相连的空白格子和数字格子都翻开。
# 【修复】核心递归函数:自动展开空白区域
func _reveal_tile(tile: Control):
	if tile.is_revealed:
		return
	
	tile.reveal()
	
	# 【修复】只有当“不是地雷”且“周围地雷数为0”时,才递归翻开周围
	if not tile.is_mine and tile.adjacent_mines == 0:
		var i = tiles.find(tile)
		var x = i % GRID_SIZE
		var y = i / GRID_SIZE
		
		for dx in [-1, 0, 1]:
			for dy in [-1, 0, 1]:
				if dx == 0 and dy == 0:
					continue
				
				var nx = x + dx
				var ny = y + dy
				
				if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
					var neighbor_index = ny * GRID_SIZE + nx
					var neighbor_tile = tiles[neighbor_index]
					_reveal_tile(neighbor_tile)

1.2.1 保护逻辑

if tile.is_revealed:
		return

如果当前地块已经被翻开,那么什么都不做。

1.2.2 翻开当前地块

tile.reveal()

1.2.3 【核心】判断是否需要递归

	# 【修复】只有当“不是地雷”且“周围地雷数为0”时,才递归翻开周围
	if not tile.is_mine and tile.adjacent_mines == 0:
  • not tile.is_mine:当前格子不是地雷 —— 如果是地雷,绝对不能递归,否则会把所有地雷都翻开!
  • tile.adjacent_mines == 0:当前格子周围地雷数为 0—— 也就是 “空白格子”,只有空白格子才需要自动展开周围。

1.2.4 找到周围的邻居(和阶段四几乎相同)

var i = tiles.find(tile)
		var x = i % GRID_SIZE
		var y = i / GRID_SIZE
		
		for dx in [-1, 0, 1]:
			for dy in [-1, 0, 1]:
				if dx == 0 and dy == 0:
					continue
				
				var nx = x + dx
				var ny = y + dy
				
				if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
					var neighbor_index = ny * GRID_SIZE + nx
					var neighbor_tile = tiles[neighbor_index]

这部分和阶段四的_calculate_adjacent_mines()函数几乎一摸一样,我们简单回顾一下:

  1. 用tiles.find(tile)找到当前格子在数组里的索引;
  2. 索引转坐标(x,y);
  3. 双层循环遍历dx和dy,找周围8个邻居;
  4. 边界检查,避免数组越界;
  5. 坐标转回索引,取出邻居格子neighbor_tile。

1.2.5 【关键】递归调用自己

_reveal_tile(neighbor_tile)

每取出一个邻居格子,让邻居格子也执行一遍_reveal_tile函数。

执行流程:

  1. 你点击格子 A(空白格子);
  2. 格子 A 执行_reveal_tile,翻开自己,然后调用_reveal_tile(格子B)(它的邻居);
  3. 格子B执行_reveal_tile:如果格子B是数字格子,翻开自己,不递归;如果格子B是空白格子:翻开自己,继续调用_reveal_tile(格子C);
  4. 以此类推,直到所有相连的空白格子和数字格子都翻开。

二、测试游戏

点击右上角的测试,可以看到当我们的鼠标点击到空白格子时,将会翻开相邻一大片的格子:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐