241 lines
7.7 KiB
GDScript
241 lines
7.7 KiB
GDScript
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)
|