Files
game-cards/src/ui/scenes/training_room.gd

241 lines
7.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Control
const CARD_NODE_SCENE := preload("res://src/ui/components/card_node.tscn")
const FLIGHT_DURATION := 0.30
const COMMENTARY_FADE_IN := 0.20
const COMMENTARY_HOLD := 0.60
const COMMENTARY_FADE_OUT := 0.30
var controller: TrainingController
var table_cards: Array[CardNode] = []
@onready var hand_area := $ScrollContainer/HandArea as HandArea
@onready var play_button: Button = $Buttons/PlayButton
@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
@onready var table_label: Label = $TableLabel
@onready var commentary_label: Label = $CommentaryLabel
@onready var table_card_root: Control = $TableCardRoot
func _ready() -> void:
if play_button:
play_button.pressed.connect(_on_play_pressed)
if pass_button:
pass_button.pressed.connect(_on_pass_pressed)
if hint_button:
hint_button.pressed.connect(_on_hint_pressed)
start_training()
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)
controller.cards_played.connect(_on_cards_played)
controller.player_passed.connect(_on_player_passed)
_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 = "你的回合 - 请出牌或点击过牌"
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
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:
var error_msg := _get_error_message(result.error_code)
if status_label:
status_label.text = "%s" % error_msg
return
hand_area.clear_selection()
# 不立即刷新,等 cards_played 信号触发刷新
func _on_pass_pressed() -> void:
var result := controller.handle_human_pass()
if not result.ok:
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 = "✓ 已过牌"
func _on_cards_played(player_idx: int, play: HandEvaluator.EvaluatedPlay) -> void:
hand_area.disable_input()
var sorted_cards: Array[Card] = []
for c in play.cards:
sorted_cards.append(c)
sorted_cards.sort_custom(func(a: Card, b: Card): return a.compare_to(b) < 0)
var card_w := 60.0
var total_w := card_w * sorted_cards.size()
var start_x := (1280.0 - total_w) / 2.0
var table_y := 80.0
var ghosts: Array[CardNode] = []
var targets: Array[Vector2] = []
for i in range(sorted_cards.size()):
var src_node := _find_hand_node(sorted_cards[i])
var src_pos := src_node.global_position if src_node else Vector2(start_x + i * card_w, 600.0)
var ghost := CARD_NODE_SCENE.instantiate() as CardNode
get_tree().root.add_child(ghost)
ghost.setup(sorted_cards[i])
ghost.global_position = src_pos
ghost.mouse_filter = Control.MOUSE_FILTER_IGNORE
ghosts.append(ghost)
targets.append(Vector2(start_x + i * card_w, table_y))
for i in range(ghosts.size()):
var g := ghosts[i]
var t := create_tween().set_parallel(true)
t.tween_property(g, "global_position", targets[i], FLIGHT_DURATION) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
t.tween_property(g, "scale", Vector2(0.7, 0.7), FLIGHT_DURATION) \
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
await get_tree().create_timer(FLIGHT_DURATION).timeout
for g in ghosts:
g.queue_free()
_show_table_cards(sorted_cards)
_show_commentary(player_idx, play)
await get_tree().create_timer(COMMENTARY_HOLD).timeout
var fade := create_tween()
fade.tween_property(commentary_label, "modulate:a", 0.0, COMMENTARY_FADE_OUT)
await get_tree().create_timer(COMMENTARY_FADE_OUT).timeout
_clear_table_cards()
_refresh_ui()
func _on_player_passed(player_idx: int) -> void:
var player_name := controller.game_state.player_names[player_idx]
if status_label:
status_label.text = "%s 过牌" % player_name
# 清除桌面牌
_clear_table_cards()
await get_tree().create_timer(0.5).timeout
_refresh_ui()
func _show_table_cards(cards: Array[Card]) -> void:
_clear_table_cards()
# 按 rank 排序
var sorted_cards: Array[Card] = cards.duplicate(false)
sorted_cards.sort_custom(func(a: Card, b: Card): return a.compare_to(b) < 0)
var total_width := sorted_cards.size() * 60
var start_x := (1280 - total_width) / 2
for i in range(sorted_cards.size()):
var card: Card = sorted_cards[i]
var node := CARD_NODE_SCENE.instantiate() as CardNode
if node == null:
continue
table_card_root.add_child(node)
node.setup(card)
node.position = Vector2(start_x + i * 60, 80)
node.custom_minimum_size = Vector2(55, 80)
node.size = Vector2(55, 80)
node.mouse_filter = Control.MOUSE_FILTER_IGNORE
table_cards.append(node)
func _clear_table_cards() -> void:
for cn in table_cards:
cn.queue_free()
table_cards.clear()
func _show_commentary(player_idx: int, play: HandEvaluator.EvaluatedPlay) -> void:
if not commentary_label:
return
var pname := controller.game_state.player_names[player_idx]
var type_name := _get_type_name(play.type)
commentary_label.text = "%s 出了 %s%d张,主阶=%d" \
% [pname, type_name, play.cards.size(), play.primary_rank]
commentary_label.modulate.a = 0.0
var t := create_tween()
t.tween_property(commentary_label, "modulate:a", 1.0, COMMENTARY_FADE_IN)
func _find_hand_node(card: Card) -> CardNode:
for cn in hand_area.card_nodes:
if cn.card_data != null and cn.card_data.card_id == card.card_id:
return cn
return null
func _get_error_message(error_code: int) -> String:
match error_code:
1: return "无效的牌型组合"
2: return "不是你的回合"
3: return "不能过牌(你是领出者)"
4: return "牌不在手中"
_: return "出牌失败"
func _on_hint_pressed() -> void:
var hint := controller.get_hint()
if hint == null or hint.type == -1:
if status_label:
status_label.text = "建议:过牌(当前轮到你领出,但无合适牌型)"
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)
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
if hand_area:
hand_area.disable_input()
func _refresh_ui() -> void:
if controller and controller.game_state and hand_area:
var hand: Array[Card] = controller.game_state.get_hand(0)
hand_area.update_hand(hand)