feat: 优化开始训练指引并修复AI递归问题
- 修复 GameController._trigger_ai 中AI决策递归调用问题,增加 _is_processing 锁 - 修复 TrainingController 未加入场景树导致信号无法触发的问题 - 主菜单添加"游戏说明"按钮和详细指引对话框 - 训练室添加引导提示标签和操作状态反馈 - 优化提示功能显示中文牌型名称(单张、对子、顺子等) - 优化错误信息显示,提供更具体的失败原因 - 清除调试 print 语句 🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
@@ -102,8 +102,12 @@ func _next_alive_player() -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
func _trigger_ai(player_idx: int) -> void:
|
func _trigger_ai(player_idx: int) -> void:
|
||||||
|
if _is_processing:
|
||||||
|
return
|
||||||
|
_is_processing = true
|
||||||
var ai: BaseAI = ai_players.get(player_idx) as BaseAI
|
var ai: BaseAI = ai_players.get(player_idx) as BaseAI
|
||||||
if ai == null:
|
if ai == null:
|
||||||
|
_is_processing = false
|
||||||
return
|
return
|
||||||
var hand8: Array = game_state.get_hand(player_idx)
|
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)
|
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)
|
_apply_play(player_idx, decision)
|
||||||
else:
|
else:
|
||||||
_apply_play(player_idx, decision)
|
_apply_play(player_idx, decision)
|
||||||
|
_is_processing = false
|
||||||
_advance_turn()
|
_advance_turn()
|
||||||
|
|
||||||
func _end_game() -> void:
|
func _end_game() -> void:
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ extends Control
|
|||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
var start_btn := $VBoxContainer/StartButton as Button
|
var start_btn := $VBoxContainer/StartButton as Button
|
||||||
|
var help_btn := $VBoxContainer/HelpButton as Button
|
||||||
var quit_btn := $VBoxContainer/QuitButton as Button
|
var quit_btn := $VBoxContainer/QuitButton as Button
|
||||||
start_btn.pressed.connect(_on_start_pressed)
|
start_btn.pressed.connect(_on_start_pressed)
|
||||||
|
help_btn.pressed.connect(_on_help_pressed)
|
||||||
quit_btn.pressed.connect(_on_quit_pressed)
|
quit_btn.pressed.connect(_on_quit_pressed)
|
||||||
|
|
||||||
func _on_start_pressed() -> void:
|
func _on_start_pressed() -> void:
|
||||||
get_tree().change_scene_to_file("res://src/ui/scenes/training_room.tscn")
|
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:
|
func _on_quit_pressed() -> void:
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|||||||
@@ -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"]
|
[ext_resource type="Script" path="res://src/ui/scenes/main_menu.gd" id="1_script"]
|
||||||
|
|
||||||
[node name="MainMenu" type="Control"]
|
[node name="MainMenu" type="Control"]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
script = ExtResource("1_script")
|
script = ExtResource("1_script")
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 8
|
anchors_preset = 8
|
||||||
offset_left = 300.0
|
offset_left = 300.0
|
||||||
offset_top = 200.0
|
offset_top = 180.0
|
||||||
offset_right = 500.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"]
|
[node name="StartButton" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
text = "开始训练"
|
text = "开始训练"
|
||||||
|
|
||||||
|
[node name="HelpButton" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "游戏说明"
|
||||||
|
|
||||||
[node name="QuitButton" type="Button" parent="VBoxContainer"]
|
[node name="QuitButton" type="Button" parent="VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
text = "退出"
|
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 = "游戏说明"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ var controller: TrainingController
|
|||||||
@onready var pass_button: Button = $Buttons/PassButton
|
@onready var pass_button: Button = $Buttons/PassButton
|
||||||
@onready var hint_button: Button = $Buttons/HintButton
|
@onready var hint_button: Button = $Buttons/HintButton
|
||||||
@onready var status_label: Label = $StatusLabel
|
@onready var status_label: Label = $StatusLabel
|
||||||
|
@onready var guide_label: Label = $GuideLabel
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
play_button.pressed.connect(_on_play_pressed)
|
play_button.pressed.connect(_on_play_pressed)
|
||||||
@@ -16,54 +17,98 @@ func _ready() -> void:
|
|||||||
|
|
||||||
func start_training() -> void:
|
func start_training() -> void:
|
||||||
controller = TrainingController.new()
|
controller = TrainingController.new()
|
||||||
|
add_child(controller)
|
||||||
hand_area.training_controller = controller
|
hand_area.training_controller = controller
|
||||||
controller.start_game(Config.rule_config, 0)
|
controller.start_game(Config.rule_config, 0)
|
||||||
controller.turn_ready.connect(_on_turn_ready)
|
controller.turn_ready.connect(_on_turn_ready)
|
||||||
controller.state_changed.connect(_refresh_ui)
|
controller.state_changed.connect(_refresh_ui)
|
||||||
controller.game_ended.connect(_on_game_ended)
|
controller.game_ended.connect(_on_game_ended)
|
||||||
|
_update_guide_text()
|
||||||
_refresh_ui()
|
_refresh_ui()
|
||||||
|
|
||||||
|
func _update_guide_text() -> void:
|
||||||
|
if guide_label:
|
||||||
|
guide_label.text = "💡 提示:点击手牌选中,然后点击「出牌」或「过牌」。首次出牌建议点击「提示」按钮。"
|
||||||
|
|
||||||
func _on_turn_ready(player_idx: int, is_human: bool) -> void:
|
func _on_turn_ready(player_idx: int, is_human: bool) -> void:
|
||||||
if is_human:
|
if is_human:
|
||||||
hand_area.enable_input()
|
hand_area.enable_input()
|
||||||
if status_label: status_label.text = "你的回合"
|
if status_label:
|
||||||
|
status_label.text = "你的回合 - 请出牌或点击过牌"
|
||||||
else:
|
else:
|
||||||
hand_area.disable_input()
|
hand_area.disable_input()
|
||||||
if status_label and controller and controller.game_state:
|
if status_label and controller and controller.game_state:
|
||||||
status_label.text = "%s 思考中..." % controller.game_state.player_names[player_idx]
|
status_label.text = "%s 思考中..." % controller.game_state.player_names[player_idx]
|
||||||
|
|
||||||
func _on_play_pressed() -> void:
|
func _on_play_pressed() -> void:
|
||||||
if not hand_area: return
|
if not hand_area:
|
||||||
|
return
|
||||||
var selected := hand_area.selected_cards
|
var selected := hand_area.selected_cards
|
||||||
if selected.is_empty():
|
if selected.is_empty():
|
||||||
|
if status_label:
|
||||||
|
status_label.text = "请先选择要出的牌"
|
||||||
return
|
return
|
||||||
var result := controller.handle_human_play(selected)
|
var result := controller.handle_human_play(selected)
|
||||||
if not result.ok:
|
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
|
return
|
||||||
hand_area.clear_selection()
|
hand_area.clear_selection()
|
||||||
|
if status_label:
|
||||||
|
status_label.text = "✓ 出牌成功!"
|
||||||
_refresh_ui()
|
_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:
|
func _on_pass_pressed() -> void:
|
||||||
var result := controller.handle_human_pass()
|
var result := controller.handle_human_pass()
|
||||||
if not result.ok:
|
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
|
return
|
||||||
|
if status_label:
|
||||||
|
status_label.text = "✓ 已过牌"
|
||||||
_refresh_ui()
|
_refresh_ui()
|
||||||
|
|
||||||
func _on_hint_pressed() -> void:
|
func _on_hint_pressed() -> void:
|
||||||
var hint := controller.get_hint()
|
var hint := controller.get_hint()
|
||||||
if hint == null or hint.type == -1:
|
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
|
return
|
||||||
if not hand_area: return
|
|
||||||
hand_area.clear_selection()
|
hand_area.clear_selection()
|
||||||
for card in hint.cards:
|
for card in hint.cards:
|
||||||
for cn in hand_area.card_nodes:
|
for cn in hand_area.card_nodes:
|
||||||
if cn.card_data != null and cn.card_data.card_id == card.card_id:
|
if cn.card_data != null and cn.card_data.card_id == card.card_id:
|
||||||
cn.set_selected(true)
|
cn.set_selected(true)
|
||||||
hand_area.selected_cards.append(card)
|
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:
|
func _on_game_ended(winner_team: int, _reason: String) -> void:
|
||||||
if status_label: status_label.text = "游戏结束! 队伍 %d 获胜" % winner_team
|
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:
|
func _refresh_ui() -> void:
|
||||||
if controller and controller.game_state and hand_area:
|
if controller and controller.game_state and hand_area:
|
||||||
var hand: Array = controller.game_state.get_hand(0)
|
var hand: Array = controller.game_state.get_hand(0)
|
||||||
print("[DEBUG] _refresh_ui: hand size = ", hand.size())
|
|
||||||
hand_area.update_hand(hand)
|
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)
|
|
||||||
|
|||||||
@@ -1,30 +1,48 @@
|
|||||||
[gd_scene load_steps=3 format=3 uid="uid://training_room"]
|
[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/scenes/training_room.gd" id="1_script"]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://src/ui/components/hand_area.gd" id="2_script"]
|
[ext_resource type="Script" path="res://src/ui/components/hand_area.gd" id="2_script"]
|
||||||
|
|
||||||
[node name="TrainingRoom" type="Control"]
|
[node name="TrainingRoom" type="Control"]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
script = ExtResource("1_script")
|
script = ExtResource("1_script")
|
||||||
|
|
||||||
[node name="StatusLabel" type="Label" parent="."]
|
[node name="StatusLabel" type="Label" parent="."]
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
offset_right = 400.0
|
offset_right = 400.0
|
||||||
offset_bottom = 50.0
|
offset_bottom = 40.0
|
||||||
text = "掼蛋训练模式"
|
text = "掼蛋训练模式"
|
||||||
horizontal_alignment = 1
|
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="."]
|
[node name="HandArea" type="HBoxContainer" parent="."]
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
offset_top = 500.0
|
offset_top = 500.0
|
||||||
offset_right = 800.0
|
offset_right = 800.0
|
||||||
offset_bottom = 650.0
|
offset_bottom = 650.0
|
||||||
script = ExtResource("2_script")
|
script = ExtResource("2_script")
|
||||||
|
|
||||||
[node name="Buttons" type="HBoxContainer" parent="."]
|
[node name="Buttons" type="HBoxContainer" parent="."]
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
offset_top = 660.0
|
offset_top = 660.0
|
||||||
offset_right = 800.0
|
offset_right = 800.0
|
||||||
offset_bottom = 720.0
|
offset_bottom = 720.0
|
||||||
|
|
||||||
[node name="PlayButton" type="Button" parent="Buttons"]
|
[node name="PlayButton" type="Button" parent="Buttons"]
|
||||||
text = "出牌"
|
text = "出牌"
|
||||||
|
|
||||||
[node name="PassButton" type="Button" parent="Buttons"]
|
[node name="PassButton" type="Button" parent="Buttons"]
|
||||||
text = "过牌"
|
text = "过牌"
|
||||||
|
|
||||||
[node name="HintButton" type="Button" parent="Buttons"]
|
[node name="HintButton" type="Button" parent="Buttons"]
|
||||||
text = "提示"
|
text = "提示"
|
||||||
|
|||||||
Reference in New Issue
Block a user