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

现在,我们来完成右键插旗与胜负判定的代码。

一、完善tile.gd

extends Control

signal clicked(tile: Node)
signal flag_changed(flagged: bool)  # 【阶段6新增】插旗状态变化信号

@onready var bg: ColorRect = $ColorRect
@onready var num_label: Label = $NumLabel

var is_revealed: bool = false
var is_mine: bool = false
var is_flagged: bool = false  # 【阶段6新增】标记是否插旗
var adjacent_mines: int = 0

# 【阶段6修改】处理输入:新增右键逻辑
func _on_gui_input(event: InputEvent):
	if event is InputEventMouseButton and event.pressed:
		# 左键点击
		if event.button_index == MOUSE_BUTTON_LEFT:
			clicked.emit(self)
		# 【阶段6新增】右键点击:插旗
		elif event.button_index == MOUSE_BUTTON_RIGHT:
			toggle_flag()

func reveal():
	if is_revealed or is_flagged:  # 【阶段6修改】已插旗也不能翻开
		return
	is_revealed = true
	
	if is_mine:
		bg.color = Color(0.926, 0.0, 0.67, 1.0)
		num_label.text = "💣"
	else:
		bg.color = Color(0.9, 0.9, 0.9)
		if adjacent_mines > 0:
			num_label.text = str(adjacent_mines)
			match adjacent_mines:
				1: num_label.modulate = Color(0, 0, 1)
				2: num_label.modulate = Color(0, 0.5, 0)
				3: num_label.modulate = Color(1, 0, 0)
				4: num_label.modulate = Color(0, 0, 0.5)
				5: num_label.modulate = Color(0.5, 0, 0)
				6: num_label.modulate = Color(0, 0.5, 0.5)
				7: num_label.modulate = Color(0, 0, 0)
				8: num_label.modulate = Color(0.5, 0.5, 0.5)

# 【阶段6新增】核心功能:切换插旗状态
func toggle_flag():
	# 已翻开不能插旗
	if is_revealed:
		return
	
	# 切换状态:true变false,false变true
	is_flagged = !is_flagged
	
	# 更新显示
	if is_flagged:
		num_label.text = "🚩"
	else:
		num_label.text = ""
	
	# 发送信号,通知Main更新状态
	flag_changed.emit(is_flagged)

1.1 新增插旗状态变化信号

signal flag_changed(flagged: bool)  # 【阶段6新增】插旗状态变化信号
  • 新增的这个信号,用于告诉 Main“我插旗了,剩余地雷数减 1”;或把旗拔了,需要告诉 Main“我拔旗了,剩余地雷数加 1”;这个信号就是 Tile 和 Main 之间的 “短信”,用来传递 “插旗 / 拔旗” 的状态。
  • 参数flagged: bool是信号的 “捎带信息”,告诉 Main “现在是插旗了还是拔旗了”。

1.2 新增插旗状态变量

var is_flagged: bool = false  # 【阶段6新增】标记是否插旗
  • 变量名含义:is_flagged,“是否被插旗了”;
  • 类型:bool(布尔值),只有两个值:true(是,插旗了)或flase(否,没插旗);
  • 初始值:flase——游戏刚开始时,所有格子都没插旗。

1.3 处理输入函数修改

# 【阶段6修改】处理输入:新增右键逻辑
func _on_gui_input(event: InputEvent):
	if event is InputEventMouseButton and event.pressed:
		# 左键点击
		if event.button_index == MOUSE_BUTTON_LEFT:
			clicked.emit(self)
		# 【阶段6新增】右键点击:插旗
		elif event.button_index == MOUSE_BUTTON_RIGHT:
			toggle_flag()

这里,我们新增了一个处理右键点击的判断,如果是左键,发送clicked信号,让Main翻开格子。如果是右键,调用toggle_flag()函数,切换插旗状态。

1.4 修改翻开函数

func reveal():
	if is_revealed or is_flagged:  # 【阶段6修改】已插旗也不能翻开
		return
	is_revealed = true
	
	if is_mine:
		bg.color = Color(0.926, 0.0, 0.67, 1.0)
		num_label.text = "💣"
	else:
		bg.color = Color(0.9, 0.9, 0.9)
		if adjacent_mines > 0:
			num_label.text = str(adjacent_mines)
			match adjacent_mines:
				1: num_label.modulate = Color(0, 0, 1)
				2: num_label.modulate = Color(0, 0.5, 0)
				3: num_label.modulate = Color(1, 0, 0)
				4: num_label.modulate = Color(0, 0, 0.5)
				5: num_label.modulate = Color(0.5, 0, 0)
				6: num_label.modulate = Color(0, 0.5, 0.5)
				7: num_label.modulate = Color(0, 0, 0)
				8: num_label.modulate = Color(0.5, 0.5, 0.5)

在保护逻辑处加了一个判断,已被翻开的格子和被插旗的格子都不能被翻开。

1.5 新增切换插旗函数

先讲一个核心概念:什么是 “切换”(Toggle)?

生活类比:切换就像 “按开关灯”—— 按一下开灯(true),再按一下关灯(flase),再按一下又开灯…… 循环往复。

# 【阶段6新增】核心功能:切换插旗状态
func toggle_flag():
	# 已翻开不能插旗
	if is_revealed:
		return
	
	# 切换状态:true变false,false变true
	is_flagged = !is_flagged
	
	# 更新显示
	if is_flagged:
		num_label.text = "🚩"
	else:
		num_label.text = ""
	
	# 发送信号,通知Main更新状态
	flag_changed.emit(is_flagged)

这个函数总共做了件事:

  1. 保护逻辑,已翻开和被插旗的格子都不能被翻开
  2. 切换形态,用!逻辑非来取反
  3. 更新显示,插旗显示🚩,拔旗显示空
  4. 发送信号,通知Main更新状态

在这里,我们还是使用了系统自带的emoji,而非使用额外的美术素材,所以在不同系统不同电脑上可能存在显示问题。

    二、完善main.gd

    extends Control
    
    const GRID_SIZE = 10
    const TILE_SCENE = preload("res://场景/tile.tscn")
    const MINE_COUNT = 15
    
    var tiles: Array = []
    var remaining_mines: int = MINE_COUNT  # 【阶段6新增】剩余地雷数
    var game_over: bool = false            # 【阶段6新增】游戏结束标志
    
    @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
    
    # 【阶段6修改】连接信号:新增插旗信号
    func _connect_signals():
    	for tile in tiles:
    		tile.clicked.connect(_on_tile_clicked)
    		tile.flag_changed.connect(_on_tile_flag_changed)  # 【阶段6新增】
    
    # 【阶段6修改】处理左键点击:新增游戏结束判断
    func _on_tile_clicked(tile: Control):
    	if game_over:
    		return
    	
    	_reveal_tile(tile)
    	
    	# 【阶段6新增】如果踩到地雷,游戏结束
    	if tile.is_mine:
    		_game_over(false)
    
    # 【阶段6新增】处理插旗信号
    func _on_tile_flag_changed(flagged: bool):
    	if game_over:
    		return
    	
    	# 更新剩余地雷数
    	if flagged:
    		remaining_mines -= 1
    	else:
    		remaining_mines += 1
    	
    	print("剩余地雷:", remaining_mines)  # 后续可以换成UI显示
    
    # 【阶段6修改】递归翻开:新增游戏结束保护
    func _reveal_tile(tile: Control):
    	if game_over or tile.is_revealed or tile.is_flagged:
    		return
    	
    	tile.reveal()
    	
    	if 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)
    	
    	# 【阶段6新增】每次翻开后检查是否胜利
    	_check_victory()
    
    # 【阶段6新增】检查是否胜利
    func _check_victory():
    	var unrevealed_safe_count = 0
    	# 统计未翻开的非地雷格子数
    	for tile in tiles:
    		if not tile.is_revealed and not tile.is_mine:
    			unrevealed_safe_count += 1
    	
    	# 如果所有非地雷格子都翻开了,胜利
    	if unrevealed_safe_count == 0:
    		_game_over(true)
    
    # 【阶段6新增】游戏结束逻辑
    func _game_over(victory: bool):
    	game_over = true
    	
    	# 翻开所有地雷
    	for tile in tiles:
    		if tile.is_mine:
    			tile.reveal()
    	
    	# 简单提示(后续可以换成UI弹窗)
    	if victory:
    		print("🎉 恭喜你赢了!")
    	else:
    		print("💥 游戏结束!你踩到地雷了!")
    

    2.1 新增两个关键状态变量

    var remaining_mines: int = MINE_COUNT  # 【阶段6新增】剩余地雷数
    var game_over: bool = false            # 【阶段6新增】游戏结束标志
    • remaining_mines:插一个旗减 1,拔一个旗加 1,让玩家知道还剩多少地雷没找出来。
    • game_over:一旦变成true,所有操作(左键翻开、右键插旗)都要停止。

    2.2 修改信号连接函数

    # 【阶段6修改】连接信号:新增插旗信号
    func _connect_signals():
    	for tile in tiles:
    		tile.clicked.connect(_on_tile_clicked)
    		tile.flag_changed.connect(_on_tile_flag_changed)  # 【阶段6新增】
    • 遍历所有 100 个格子;
    • 每个格子的clicked信号都连接到_on_tile_clicked函数;
    • 每个格子的flag_changed信号都连接到_on_tile_flag_changed函数(新增的)。

    2.3 处理左键点击函数

    # 【阶段6修改】处理左键点击:新增游戏结束判断
    func _on_tile_clicked(tile: Control):
    	if game_over:
    		return
    	
    	_reveal_tile(tile)
    	
    	# 【阶段6新增】如果踩到地雷,游戏结束
    	if tile.is_mine:
    		_game_over(false)
    • 保护:如果game_overtrue(游戏结束了),直接返回,不执行后面的翻开操作;
    • 翻开地块;
    • 踩到地雷,游戏结束。

    2.4 新增插旗信号函数

    # 【阶段6新增】处理插旗信号
    func _on_tile_flag_changed(flagged: bool):
    	if game_over:
    		return
    	
    	# 更新剩余地雷数
    	if flagged:
    		remaining_mines -= 1
    	else:
    		remaining_mines += 1
    	
    	print("剩余地雷:", remaining_mines)  # 后续可以换成UI显示
    • 保护:和左键点击一样,游戏结束了也不能插旗 / 拔旗。
    • 如果flaggedtrue(插旗了)→ 剩余地雷数减 1(remaining_mines -= 1);如果flaggedfalse(拔旗了)→ 剩余地雷数加 1(remaining_mines += 1)。
    • 打印剩余地雷数

    2.5 修改递归翻开函数

    # 【阶段6修改】递归翻开:新增游戏结束保护
    func _reveal_tile(tile: Control):
    	if game_over or tile.is_revealed or tile.is_flagged:
    		return
    	
    	tile.reveal()
    	
    	if tile.adjacent_mines == 0:
    		# ...(遍历邻居的代码不变)
    		_reveal_tile(neighbor_tile)
    	
    	# 【阶段6新增】每次翻开后检查是否胜利
    	_check_victory()
    • 游戏结束保护:在最前面加了game_over—— 游戏结束了,递归也要立刻停止。
    • 每次翻开后检查是否胜利;

    2.6新增胜利检查函数

    # 【阶段6新增】检查是否胜利
    func _check_victory():
    	var unrevealed_safe_count = 0
    	# 统计未翻开的非地雷格子数
    	for tile in tiles:
    		if not tile.is_revealed and not tile.is_mine:
    			unrevealed_safe_count += 1
    	
    	# 如果所有非地雷格子都翻开了,胜利
    	if unrevealed_safe_count == 0:
    		_game_over(true)

    扫雷胜利条件所有非地雷格子都被翻开了,就算胜利!和插旗数量无关!

    • 定义unrevealed_safe_count,统计未翻开的安全格子数量
    • 遍历所有格子,统计未翻开的安全格子。如果格子没有被翻开,并且不是地雷,则计数器加一
    • 判断是否胜利:如果unrevealed_safe_count等于 0,说明 “没有未翻开的安全格子了”—— 也就是所有安全格子都翻开了!

    2.7 新增游戏结束函数

    # 【阶段6新增】游戏结束逻辑
    func _game_over(victory: bool):
    	game_over = true
    	
    	# 翻开所有地雷
    	for tile in tiles:
    		if tile.is_mine:
    			tile.reveal()
    	
    	# 简单提示(后续可以换成UI弹窗)
    	if victory:
    		print("🎉 恭喜你赢了!")
    	else:
    		print("💥 游戏结束!你踩到地雷了!")
    • 锁定游戏,把game_over设为true—— 这样所有的左键点击、右键插旗、递归翻开都会被保护逻辑拦截,游戏彻底锁定。
    • 翻开所有地雷
    • 简单提示

    三、测试游戏

    点击右上角的运行按钮,进行简单游戏,可以看到胜利:

    失败后,也会有对应的提示:

    Logo

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

    更多推荐