From b3fe03f2295c0510c648fcb557fdc3af07469cd9 Mon Sep 17 00:00:00 2001 From: xiaji Date: Mon, 1 Jun 2026 22:55:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=BC=80=E5=A7=8B?= =?UTF-8?q?=E8=AE=AD=E7=BB=83=E6=8C=87=E5=BC=95=E5=B9=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?AI=E9=80=92=E5=BD=92=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 GameController._trigger_ai 中AI决策递归调用问题,增加 _is_processing 锁 - 修复 TrainingController 未加入场景树导致信号无法触发的问题 - 主菜单添加"游戏说明"按钮和详细指引对话框 - 训练室添加引导提示标签和操作状态反馈 - 优化提示功能显示中文牌型名称(单张、对子、顺子等) - 优化错误信息显示,提供更具体的失败原因 - 清除调试 print 语句 🤖 Generated with [Qoder][https://qoder.com] --- src/game/game_controller.gd | 5 +++ src/ui/scenes/main_menu.gd | 5 +++ src/ui/scenes/main_menu.tscn | 34 ++++++++++++++++-- src/ui/scenes/training_room.gd | 62 ++++++++++++++++++++++++++------ src/ui/scenes/training_room.tscn | 20 ++++++++++- 5 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/game/game_controller.gd b/src/game/game_controller.gd index d8569f1..9f72df4 100644 --- a/src/game/game_controller.gd +++ b/src/game/game_controller.gd @@ -102,8 +102,12 @@ func _next_alive_player() -> void: return func _trigger_ai(player_idx: int) -> void: + if _is_processing: + return + _is_processing = true var ai: BaseAI = ai_players.get(player_idx) as BaseAI if ai == null: + _is_processing = false return var hand8: Array = game_state.get_hand(player_idx) var decision: HandEvaluator.EvaluatedPlay = ai.decide(hand8, game_state._round.table, game_state.current_rank, game_state.rule_config) @@ -111,6 +115,7 @@ func _trigger_ai(player_idx: int) -> void: _apply_play(player_idx, decision) else: _apply_play(player_idx, decision) + _is_processing = false _advance_turn() func _end_game() -> void: diff --git a/src/ui/scenes/main_menu.gd b/src/ui/scenes/main_menu.gd index d0418ca..6a9a7fe 100644 --- a/src/ui/scenes/main_menu.gd +++ b/src/ui/scenes/main_menu.gd @@ -2,12 +2,17 @@ extends Control func _ready() -> void: var start_btn := $VBoxContainer/StartButton as Button + var help_btn := $VBoxContainer/HelpButton as Button var quit_btn := $VBoxContainer/QuitButton as Button start_btn.pressed.connect(_on_start_pressed) + help_btn.pressed.connect(_on_help_pressed) quit_btn.pressed.connect(_on_quit_pressed) func _on_start_pressed() -> void: get_tree().change_scene_to_file("res://src/ui/scenes/training_room.tscn") +func _on_help_pressed() -> void: + $HelpDialog.popup_centered() + func _on_quit_pressed() -> void: get_tree().quit() diff --git a/src/ui/scenes/main_menu.tscn b/src/ui/scenes/main_menu.tscn index ea79f8f..a32aa89 100644 --- a/src/ui/scenes/main_menu.tscn +++ b/src/ui/scenes/main_menu.tscn @@ -1,17 +1,45 @@ -[gd_scene load_steps=2 format=3 uid="uid://main_menu"] +[gd_scene load_steps=3 format=3 uid="uid://main_menu"] + [ext_resource type="Script" path="res://src/ui/scenes/main_menu.gd" id="1_script"] + [node name="MainMenu" type="Control"] layout_mode = 3 anchors_preset = 15 script = ExtResource("1_script") + [node name="VBoxContainer" type="VBoxContainer" parent="."] layout_mode = 1 anchors_preset = 8 offset_left = 300.0 -offset_top = 200.0 +offset_top = 180.0 offset_right = 500.0 -offset_bottom = 400.0 +offset_bottom = 420.0 + +[node name="TitleLabel" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "掼蛋训练模式" +horizontal_alignment = 1 + +[node name="Spacer1" type="Control" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 1 + [node name="StartButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 text = "开始训练" + +[node name="HelpButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "游戏说明" + [node name="QuitButton" type="Button" parent="VBoxContainer"] +layout_mode = 2 text = "退出" + +[node name="Spacer2" type="Control" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 1 + +[node name="HelpDialog" type="AcceptDialog" parent="."] +dialog_text = "【掼蛋训练模式说明】\n\n1. 游戏目标:与队友配合,尽快出完手中所有牌。\n2. 队伍分配:您与 Player 2 为同一队,AI-1 与 AI-3 为另一队。\n3. 出牌规则:\n - 单张、对子、三张、顺子、连对、钢板、炸弹、火箭等\n - 炸弹和火箭可以压制其他牌型\n - 同类型牌型需比上一家大才能出\n4. 操作指南:\n - 点击手牌选中/取消,双击可快速选中并出牌\n - 「出牌」按钮:打出选中的牌\n - 「过牌」按钮:跳过本轮出牌\n - 「提示」按钮:AI 会推荐最佳出牌方案\n5. 训练技巧:\n - 优先出小牌,保留大牌用于压制对手\n - 注意队友信号,适时配合\n - 使用提示功能学习最佳出牌策略" +title = "游戏说明" diff --git a/src/ui/scenes/training_room.gd b/src/ui/scenes/training_room.gd index cf1cb5f..410aaba 100644 --- a/src/ui/scenes/training_room.gd +++ b/src/ui/scenes/training_room.gd @@ -7,6 +7,7 @@ var controller: TrainingController @onready var pass_button: Button = $Buttons/PassButton @onready var hint_button: Button = $Buttons/HintButton @onready var status_label: Label = $StatusLabel +@onready var guide_label: Label = $GuideLabel func _ready() -> void: play_button.pressed.connect(_on_play_pressed) @@ -16,54 +17,98 @@ func _ready() -> void: func start_training() -> void: controller = TrainingController.new() + add_child(controller) hand_area.training_controller = controller controller.start_game(Config.rule_config, 0) controller.turn_ready.connect(_on_turn_ready) controller.state_changed.connect(_refresh_ui) controller.game_ended.connect(_on_game_ended) + _update_guide_text() _refresh_ui() +func _update_guide_text() -> void: + if guide_label: + guide_label.text = "💡 提示:点击手牌选中,然后点击「出牌」或「过牌」。首次出牌建议点击「提示」按钮。" + func _on_turn_ready(player_idx: int, is_human: bool) -> void: if is_human: hand_area.enable_input() - if status_label: status_label.text = "你的回合" + if status_label: + status_label.text = "你的回合 - 请出牌或点击过牌" else: hand_area.disable_input() if status_label and controller and controller.game_state: status_label.text = "%s 思考中..." % controller.game_state.player_names[player_idx] func _on_play_pressed() -> void: - if not hand_area: return + if not hand_area: + return var selected := hand_area.selected_cards if selected.is_empty(): + if status_label: + status_label.text = "请先选择要出的牌" return var result := controller.handle_human_play(selected) if not result.ok: - if status_label: status_label.text = "无效出牌" + var error_msg := _get_error_message(result.error_code) + if status_label: + status_label.text = "❌ %s" % error_msg return hand_area.clear_selection() + if status_label: + status_label.text = "✓ 出牌成功!" _refresh_ui() +func _get_error_message(error_code: int) -> String: + match error_code: + 1: return "无效的牌型组合" + 2: return "不是你的回合" + 3: return "不能过牌(你是领出者)" + 4: return "牌不在手中" + _: return "出牌失败" + func _on_pass_pressed() -> void: var result := controller.handle_human_pass() if not result.ok: - if status_label: status_label.text = "不能过牌" + var error_msg := _get_error_message(result.error_code) + if status_label: + status_label.text = "❌ %s" % error_msg return + if status_label: + status_label.text = "✓ 已过牌" _refresh_ui() func _on_hint_pressed() -> void: var hint := controller.get_hint() if hint == null or hint.type == -1: - if status_label: status_label.text = "建议:过牌" + if status_label: + status_label.text = "建议:过牌(当前轮到你领出,但无合适牌型)" + return + if not hand_area: return - if not hand_area: return hand_area.clear_selection() for card in hint.cards: for cn in hand_area.card_nodes: if cn.card_data != null and cn.card_data.card_id == card.card_id: cn.set_selected(true) hand_area.selected_cards.append(card) - if status_label: status_label.text = "建议牌型: %s (rank=%d)" % [hint.type, hint.primary_rank] + var type_name := _get_type_name(hint.type) + if status_label: + status_label.text = "💡 建议:出 %s(%d张,主阶=%d)" % [type_name, hint.cards.size(), hint.primary_rank] + +func _get_type_name(type_idx: int) -> String: + match type_idx: + 0: return "单张" + 1: return "对子" + 2: return "三张" + 3: return "三带二" + 4: return "顺子" + 5: return "连对" + 6: return "钢板" + 7: return "同花顺" + 8: return "炸弹" + 9: return "火箭" + _: return "未知" func _on_game_ended(winner_team: int, _reason: String) -> void: if status_label: status_label.text = "游戏结束! 队伍 %d 获胜" % winner_team @@ -72,7 +117,4 @@ func _on_game_ended(winner_team: int, _reason: String) -> void: func _refresh_ui() -> void: if controller and controller.game_state and hand_area: var hand: Array = controller.game_state.get_hand(0) - print("[DEBUG] _refresh_ui: hand size = ", hand.size()) hand_area.update_hand(hand) - else: - print("[DEBUG] _refresh_ui: controller=", controller, " game_state=", controller.game_state if controller else null, " hand_area=", hand_area) diff --git a/src/ui/scenes/training_room.tscn b/src/ui/scenes/training_room.tscn index b6a502e..23e645b 100644 --- a/src/ui/scenes/training_room.tscn +++ b/src/ui/scenes/training_room.tscn @@ -1,30 +1,48 @@ [gd_scene load_steps=3 format=3 uid="uid://training_room"] + [ext_resource type="Script" path="res://src/ui/scenes/training_room.gd" id="1_script"] + [ext_resource type="Script" path="res://src/ui/components/hand_area.gd" id="2_script"] + [node name="TrainingRoom" type="Control"] layout_mode = 3 anchors_preset = 15 script = ExtResource("1_script") + [node name="StatusLabel" type="Label" parent="."] layout_mode = 0 offset_right = 400.0 -offset_bottom = 50.0 +offset_bottom = 40.0 text = "掼蛋训练模式" horizontal_alignment = 1 + +[node name="GuideLabel" type="Label" parent="."] +layout_mode = 0 +offset_top = 45.0 +offset_right = 800.0 +offset_bottom = 75.0 +text = "提示:点击手牌选中,然后点击「出牌」或「过牌」。首次出牌建议点击「提示」按钮。" +horizontal_alignment = 1 +autowrap_mode = 2 + [node name="HandArea" type="HBoxContainer" parent="."] layout_mode = 0 offset_top = 500.0 offset_right = 800.0 offset_bottom = 650.0 script = ExtResource("2_script") + [node name="Buttons" type="HBoxContainer" parent="."] layout_mode = 0 offset_top = 660.0 offset_right = 800.0 offset_bottom = 720.0 + [node name="PlayButton" type="Button" parent="Buttons"] text = "出牌" + [node name="PassButton" type="Button" parent="Buttons"] text = "过牌" + [node name="HintButton" type="Button" parent="Buttons"] text = "提示"