From cef2cba7a5430af1cb440e2c2ff4126185aa6452 Mon Sep 17 00:00:00 2001 From: xiaji Date: Sat, 30 May 2026 22:38:52 +0800 Subject: [PATCH] fix: resolve 43 GDScript warnings and fix card display - Rename shadowed variables (round->_round, seed->_seed, is_processing->_is_processing) - Prefix unused parameters with underscore throughout - Add @warning_ignore for false positive integer_division warnings - Fix unused_signal warnings in event_bus.gd - Fix CardNode display: move setup() after add_child() so @onready works - Redesign CardNode with Panel background, suit symbols, red/black colors - Delete unused _current_hint in TrainingController --- src/ai/base_ai.gd | 10 +- src/ai/l2_rule_ai.gd | 2 +- src/core/actions.gd | 54 ++--- src/core/deck.gd | 11 +- src/core/game_state.gd | 132 ++++++------ src/core/hand_evaluator.gd | 357 ++++++++++++++++--------------- src/core/round.gd | 42 ++-- src/core/rule_engine.gd | 166 +++++++------- src/game/game_controller.gd | 202 ++++++++--------- src/game/training_controller.gd | 8 +- src/ui/components/card_node.gd | 46 +++- src/ui/components/card_node.tscn | 44 +++- src/ui/components/hand_area.gd | 7 +- src/ui/scenes/training_room.gd | 8 +- 14 files changed, 577 insertions(+), 512 deletions(-) diff --git a/src/ai/base_ai.gd b/src/ai/base_ai.gd index 989df83..5f10094 100644 --- a/src/ai/base_ai.gd +++ b/src/ai/base_ai.gd @@ -4,8 +4,8 @@ extends RefCounted var ai_name: String = "AI" -func decide(hand: Array, table: Array, current_rank: int, config: RuleConfig) -> HandEvaluator.EvaluatedPlay: - var pass_play := HandEvaluator.EvaluatedPlay.new() - pass_play.type = -1 - pass_play.primary_rank = 0 - return pass_play +func decide(_hand: Array, _table: Array, _current_rank: int, _config: RuleConfig) -> HandEvaluator.EvaluatedPlay: + var pass_play := HandEvaluator.EvaluatedPlay.new() + pass_play.type = -1 + pass_play.primary_rank = 0 + return pass_play diff --git a/src/ai/l2_rule_ai.gd b/src/ai/l2_rule_ai.gd index 6e8fca9..48b2724 100644 --- a/src/ai/l2_rule_ai.gd +++ b/src/ai/l2_rule_ai.gd @@ -31,7 +31,7 @@ func _score_all(moves: Array, hand_size: int, current_rank: int) -> Array: results.append({"move": m, "score": score}) return results -func _score_move(play: HandEvaluator.EvaluatedPlay, hand_size: int, current_rank: int) -> float: +func _score_move(play: HandEvaluator.EvaluatedPlay, hand_size: int, _current_rank: int) -> float: var score := 0.0 var remaining := hand_size - play.cards.size() score += (27.0 - remaining) / 27.0 * 0.3 diff --git a/src/core/actions.gd b/src/core/actions.gd index 8cc220c..939b4fb 100644 --- a/src/core/actions.gd +++ b/src/core/actions.gd @@ -3,38 +3,38 @@ class_name Actions extends RefCounted class Action: - extends RefCounted - var player_idx: int - var action_type: String - var cards: Array = [] - var seq_id: int = 0 - var nonce: int = 0 - var timestamp: int = 0 + extends RefCounted + var player_idx: int + var action_type: String + var cards: Array = [] + var seq_id: int = 0 + var nonce: int = 0 + var timestamp: int = 0 - func _to_string() -> String: - return "Action(seq=%d, player=%d, type=%s, cards=%d)" % [seq_id, player_idx, action_type, cards.size()] + func _to_string() -> String: + return "Action(seq=%d, player=%d, type=%s, cards=%d)" % [seq_id, player_idx, action_type, cards.size()] class GameStateSnapshot: - extends RefCounted - var game_state: Dictionary + extends RefCounted + var game_state: Dictionary class Team: - extends RefCounted - var team_id: int - var player_indices: Array[int] = [] - var score: int = 0 - var current_level: int = 2 + extends RefCounted + var team_id: int + var player_indices: Array[int] = [] + var score: int = 0 + var current_level: int = 2 - static func create_team(team_id: int, p1: int, p2: int) -> Team: - var t := Team.new() - t.team_id = team_id - t.player_indices = [p1, p2] - return t + static func create_team(p_team_id: int, p1: int, p2: int) -> Team: + var t := Team.new() + t.team_id = p_team_id + t.player_indices = [p1, p2] + return t - func teammate_of(player_idx: int) -> int: - if player_indices[0] == player_idx: - return player_indices[1] - return player_indices[0] + func teammate_of(player_idx: int) -> int: + if player_indices[0] == player_idx: + return player_indices[1] + return player_indices[0] - func contains(player_idx: int) -> bool: - return player_idx in player_indices + func contains(player_idx: int) -> bool: + return player_idx in player_indices diff --git a/src/core/deck.gd b/src/core/deck.gd index 742c7c8..d1505ea 100644 --- a/src/core/deck.gd +++ b/src/core/deck.gd @@ -16,9 +16,10 @@ static func _rank_for(original_id: int) -> int: return 15 if original_id == 53: return 16 + @warning_ignore("integer_division") return 2 + (original_id / 4) -static func create(seed: int = -1) -> Deck: +static func create(p_seed: int = -1) -> Deck: var d := Deck.new() d._cards = [] for deck_idx in range(2): @@ -29,15 +30,15 @@ static func create(seed: int = -1) -> Deck: var c := Card.create(orig_id, suit, rank) c.card_id = global_id d._cards.append(c) - if seed >= 0: - d._shuffle_with_seed(seed) + if p_seed >= 0: + d._shuffle_with_seed(p_seed) else: d._shuffle_random() return d -func _shuffle_with_seed(seed: int) -> void: +func _shuffle_with_seed(p_seed: int) -> void: var rng := RandomNumberGenerator.new() - rng.seed = seed + rng.seed = p_seed for i in range(_cards.size() - 1, 0, -1): var j := rng.randi_range(0, i) var tmp := _cards[i] diff --git a/src/core/game_state.gd b/src/core/game_state.gd index ff2b29c..32deb1e 100644 --- a/src/core/game_state.gd +++ b/src/core/game_state.gd @@ -14,101 +14,101 @@ var teams: Array = [] var player_hands: Array = [[], [], [], []] var player_names: Array[String] = ["Player", "AI-1", "AI-2", "AI-3"] var player_human: Array[bool] = [true, false, false, false] -var round: Round +var _round: Round var action_log: Array = [] -var seed: int = 0 +var _seed: int = 0 var finished_players: Array[int] = [] var current_winner_team: int = -1 var game_end_reason: String = "" static func create(config: RuleConfig, seed_: int = -1) -> GameState: - var gs := GameState.new() - gs.rule_config = config - if seed_ >= 0: - gs.seed = seed_ - else: - gs.seed = Time.get_unix_time_from_system() as int - gs.teams = [_Actions.Team.create_team(0, 0, 2), _Actions.Team.create_team(1, 1, 3)] - gs.round = Round.new() - return gs + var gs := GameState.new() + gs.rule_config = config + if seed_ >= 0: + gs._seed = seed_ + else: + gs._seed = Time.get_unix_time_from_system() as int + gs.teams = [_Actions.Team.create_team(0, 0, 2), _Actions.Team.create_team(1, 1, 3)] + gs._round = Round.new() + return gs func get_team(player_idx: int) -> Actions.Team: - for t in teams: - if t.contains(player_idx): - return t - return null + for t in teams: + if t.contains(player_idx): + return t + return null func get_partner(player_idx: int) -> int: - var t := get_team(player_idx) - if t != null: - return t.teammate_of(player_idx) - return -1 + var t := get_team(player_idx) + if t != null: + return t.teammate_of(player_idx) + return -1 func get_hand(player_idx: int) -> Array: - return player_hands[player_idx] + return player_hands[player_idx] func remove_cards_from_hand(player_idx: int, cards: Array) -> void: - var hand: Array = player_hands[player_idx] - var to_remove := {} - for c in cards: - to_remove[c.card_id] = true - var new_hand: Array = [] - for c in hand: - if not to_remove.has(c.card_id): - new_hand.append(c) - player_hands[player_idx] = new_hand + var hand: Array = player_hands[player_idx] + var to_remove := {} + for c in cards: + to_remove[c.card_id] = true + var new_hand: Array = [] + for c in hand: + if not to_remove.has(c.card_id): + new_hand.append(c) + player_hands[player_idx] = new_hand func add_cards_to_hand(player_idx: int, cards: Array) -> void: - for c in cards: - player_hands[player_idx].append(c) + for c in cards: + player_hands[player_idx].append(c) func deal_cards(deck: Deck) -> void: - for i in range(4): - player_hands[i] = deck.deal(27) + for i in range(4): + player_hands[i] = deck.deal(27) func is_player_finished(player_idx: int) -> bool: - return player_idx in finished_players + return player_idx in finished_players func add_finished_player(player_idx: int) -> void: - if not is_player_finished(player_idx): - finished_players.append(player_idx) + if not is_player_finished(player_idx): + finished_players.append(player_idx) func get_rank_for_player(player_idx: int) -> int: - var idx := finished_players.find(player_idx) - if idx >= 0: - return idx + 1 - return 0 + var idx := finished_players.find(player_idx) + if idx >= 0: + return idx + 1 + return 0 func all_hands_empty() -> bool: - var empty_count := 0 - for i in range(4): - if player_hands[i].is_empty(): - empty_count += 1 - return empty_count >= 3 + var empty_count := 0 + for i in range(4): + if player_hands[i].is_empty(): + empty_count += 1 + return empty_count >= 3 func to_packed_snapshot(for_player: int) -> Dictionary: - return { - "phase": phase, - "current_rank": current_rank, - "own_hand": _pack_hand(player_hands[for_player]), - "table": _pack_table(round.table), - "finished": finished_players.duplicate(), - "team_scores": [teams[0].score, teams[1].score] - } + return { + "phase": phase, + "current_rank": current_rank, + "own_hand": _pack_hand(player_hands[for_player]), + "table": _pack_table(_round.table), + "finished": finished_players.duplicate(), + "team_scores": [teams[0].score, teams[1].score] + } func _pack_hand(hand: Array) -> Array: - var result := [] - for c in hand: - result.append(c.to_packed()) - return result + var result := [] + for c in hand: + result.append(c.to_packed()) + return result func _pack_table(table: Array) -> Array: - var result := [] - for ep in table: - var d := {"type": ep.type, "primary_rank": ep.primary_rank, "is_pure": ep.is_pure_bomb} - var cards_packed := [] - for c in ep.cards: - cards_packed.append(c.to_packed()) - d["cards"] = cards_packed - result.append(d) - return result + var result := [] + for ep in table: + var d := {"type": ep.type, "primary_rank": ep.primary_rank, "is_pure": ep.is_pure_bomb} + var cards_packed := [] + for c in ep.cards: + cards_packed.append(c.to_packed()) + d["cards"] = cards_packed + result.append(d) + return result diff --git a/src/core/hand_evaluator.gd b/src/core/hand_evaluator.gd index 63a1450..11fc984 100644 --- a/src/core/hand_evaluator.gd +++ b/src/core/hand_evaluator.gd @@ -9,217 +9,220 @@ const _C = preload("res://src/core/constants.gd") const INVALID := -1 class EvaluatedPlay: - extends RefCounted - var type: int = INVALID - var primary_rank: int = 0 - var cards: Array[Card] = [] - var is_pure_bomb: bool = false + extends RefCounted + var type: int = INVALID + var primary_rank: int = 0 + var cards: Array[Card] = [] + var is_pure_bomb: bool = false - func _to_string() -> String: - var names := ["SINGLE","PAIR","TRIPLE","TRIPLE+2","STRAIGHT","PAIRS","STEEL","FLUSH","BOMB","ROCKET"] - return "EvaluatedPlay(%s, rank=%d, pure=%s)" % [names[type], primary_rank, is_pure_bomb] + func _to_string() -> String: + var names := ["SINGLE","PAIR","TRIPLE","TRIPLE+2","STRAIGHT","PAIRS","STEEL","FLUSH","BOMB","ROCKET"] + return "EvaluatedPlay(%s, rank=%d, pure=%s)" % [names[type], primary_rank, is_pure_bomb] static func evaluate(cards: Array[Card], current_rank: int, config: RuleConfig) -> EvaluatedPlay: - if cards.is_empty(): - return null - var sorted := cards.duplicate(false) - _sort_by_rank_and_suit(sorted) - var n := sorted.size() - if n == 1: - return _make_result(_C.TYPE_SINGLE, sorted[0].rank(), false, sorted) + if cards.is_empty(): + return null + var sorted := cards.duplicate(false) + _sort_by_rank_and_suit(sorted) + var n := sorted.size() + if n == 1: + return _make_result(_C.TYPE_SINGLE, sorted[0].rank(), false, sorted) - var wilds: Array[Card] = [] - var reals: Array[Card] = [] - for c in sorted: - if c.is_heart() and c.rank() == current_rank: - wilds.append(c) - else: - reals.append(c) - var has_wild := wilds.size() > 0 - if n == 2: - return _eval_two(reals, wilds, has_wild) - if n == 3: - return _eval_three(reals, wilds, has_wild) - if n == 4: - var result := _eval_four(reals, wilds, has_wild, current_rank, config) - if result != null: - return result - return _eval_multi(reals, wilds, has_wild, current_rank, n, config) + var wilds: Array[Card] = [] + var reals: Array[Card] = [] + for c in sorted: + if c.is_heart() and c.rank() == current_rank: + wilds.append(c) + else: + reals.append(c) + var has_wild := wilds.size() > 0 + if n == 2: + return _eval_two(reals, wilds, has_wild) + if n == 3: + return _eval_three(reals, wilds, has_wild) + if n == 4: + var result := _eval_four(reals, wilds, has_wild, current_rank, config) + if result != null: + return result + return _eval_multi(reals, wilds, has_wild, current_rank, n, config) static func _make_result(type: int, primary_rank: int, is_pure: bool, cards1: Array[Card]) -> EvaluatedPlay: - var r := EvaluatedPlay.new() - r.type = type - r.primary_rank = primary_rank - r.cards = cards1.duplicate(false) - r.is_pure_bomb = is_pure - return r + var r := EvaluatedPlay.new() + r.type = type + r.primary_rank = primary_rank + r.cards = cards1.duplicate(false) + r.is_pure_bomb = is_pure + return r static func _eval_two(reals: Array[Card], wilds: Array[Card], has_wild: bool) -> EvaluatedPlay: - if not has_wild: - if reals.size() == 2 and reals[0].rank() == reals[1].rank(): - return _make_result(_C.TYPE_PAIR, reals[0].rank(), true, reals) - else: - if reals.size() == 1: - return _make_result(_C.TYPE_PAIR, reals[0].rank(), false, reals + wilds) - return null + if not has_wild: + if reals.size() == 2 and reals[0].rank() == reals[1].rank(): + return _make_result(_C.TYPE_PAIR, reals[0].rank(), true, reals) + else: + if reals.size() == 1: + return _make_result(_C.TYPE_PAIR, reals[0].rank(), false, reals + wilds) + return null static func _eval_three(reals: Array[Card], wilds: Array[Card], has_wild: bool) -> EvaluatedPlay: - if not has_wild: - if reals.size() == 3 and _all_same_rank(reals): - return _make_result(_C.TYPE_TRIPLE, reals[0].rank(), true, reals) - else: - if reals.size() == 2 and reals[0].rank() == reals[1].rank(): - return _make_result(_C.TYPE_TRIPLE, reals[0].rank(), false, reals + wilds) - if reals.size() == 1 and wilds.size() >= 2: - return _make_result(_C.TYPE_TRIPLE, reals[0].rank(), false, reals + wilds.slice(0, 2)) - return null + if not has_wild: + if reals.size() == 3 and _all_same_rank(reals): + return _make_result(_C.TYPE_TRIPLE, reals[0].rank(), true, reals) + else: + if reals.size() == 2 and reals[0].rank() == reals[1].rank(): + return _make_result(_C.TYPE_TRIPLE, reals[0].rank(), false, reals + wilds) + if reals.size() == 1 and wilds.size() >= 2: + return _make_result(_C.TYPE_TRIPLE, reals[0].rank(), false, reals + wilds.slice(0, 2)) + return null -static func _eval_four(reals: Array[Card], wilds: Array[Card], has_wild: bool, current_rank: int, config: RuleConfig) -> EvaluatedPlay: - var all_cards := reals + wilds - if not has_wild and reals.size() == 4: - if _all_same_rank(reals): - return _make_result(_C.TYPE_BOMB, reals[0].rank(), true, reals) - var sj_count := 0 - var bj_count := 0 - for c in reals: - if c.rank() == 15: sj_count += 1 - if c.rank() == 16: bj_count += 1 - if sj_count == 2 and bj_count == 2: - return _make_result(_C.TYPE_ROCKET, 999, true, reals) - return null - if has_wild: - if reals.size() == 3 and _all_same_rank(reals): - return _make_result(_C.TYPE_BOMB, reals[0].rank(), false, all_cards) - if reals.size() == 2 and reals[0].rank() == reals[1].rank(): - return _make_result(_C.TYPE_BOMB, reals[0].rank(), false, all_cards) - return null +static func _eval_four(reals: Array[Card], wilds: Array[Card], has_wild: bool, _current_rank: int, _config: RuleConfig) -> EvaluatedPlay: + var all_cards := reals + wilds + if not has_wild and reals.size() == 4: + if _all_same_rank(reals): + return _make_result(_C.TYPE_BOMB, reals[0].rank(), true, reals) + var sj_count := 0 + var bj_count := 0 + for c in reals: + if c.rank() == 15: sj_count += 1 + if c.rank() == 16: bj_count += 1 + if sj_count == 2 and bj_count == 2: + return _make_result(_C.TYPE_ROCKET, 999, true, reals) + return null + if has_wild: + if reals.size() == 3 and _all_same_rank(reals): + return _make_result(_C.TYPE_BOMB, reals[0].rank(), false, all_cards) + if reals.size() == 2 and reals[0].rank() == reals[1].rank(): + return _make_result(_C.TYPE_BOMB, reals[0].rank(), false, all_cards) + return null -static func _eval_multi(reals: Array[Card], wilds: Array[Card], has_wild: bool, current_rank: int, n: int, config: RuleConfig) -> EvaluatedPlay: - if not has_wild: - return _eval_pure_multiple(reals, n, config) - if n == 5: - var result := _eval_triple_plus_two(reals, wilds) - if result != null: return result - result = _eval_straight(reals, wilds, 5, config) - if result != null: return result - if n >= 6 and n % 2 == 0: - var result := _eval_consecutive_pairs(reals, wilds, n / 2, config) - if result != null: return result - if n >= 6 and n % 3 == 0: - var result := _eval_steel_plate(reals, wilds, n / 3, config) - if result != null: return result - if n >= 5: - var result := _eval_straight(reals, wilds, n, config) - if result != null: return result - return null +static func _eval_multi(reals: Array[Card], wilds: Array[Card], has_wild: bool, _current_rank: int, n: int, config: RuleConfig) -> EvaluatedPlay: + if not has_wild: + return _eval_pure_multiple(reals, n, config) + if n == 5: + var result := _eval_triple_plus_two(reals, wilds) + if result != null: return result + result = _eval_straight(reals, wilds, 5, config) + if result != null: return result + if n >= 6 and n % 2 == 0: + @warning_ignore("integer_division") + var result := _eval_consecutive_pairs(reals, wilds, n / 2, config) + if result != null: return result + if n >= 6 and n % 3 == 0: + @warning_ignore("integer_division") + var result := _eval_steel_plate(reals, wilds, n / 3, config) + if result != null: return result + if n >= 5: + var result := _eval_straight(reals, wilds, n, config) + if result != null: return result + return null static func _eval_pure_multiple(reals: Array[Card], n: int, config: RuleConfig) -> EvaluatedPlay: - if n >= 5 and _is_straight(reals, config): - return _make_result(_C.TYPE_STRAIGHT, reals[n-1].rank(), true, reals) - if n >= 6 and n % 2 == 0 and _is_consecutive_pairs(reals, n / 2): - return _make_result(_C.TYPE_CONSECUTIVE_PAIRS, reals[n-1].rank(), true, reals) - if n == 5 and _is_triple_plus_two(reals): - return _make_result(_C.TYPE_TRIPLE_PLUS_TWO, _get_triple_rank(reals), true, reals) - if n >= 6 and n % 3 == 0 and _is_steel_plate(reals): - return _make_result(_C.TYPE_STEEL_PLATE, reals[n-1].rank(), true, reals) - if n >= 5 and _is_straight_flush(reals, config): - return _make_result(_C.TYPE_STRAIGHT_FLUSH, reals[n-1].rank(), true, reals) - return null + if n >= 5 and _is_straight(reals, config): + return _make_result(_C.TYPE_STRAIGHT, reals[n-1].rank(), true, reals) + @warning_ignore("integer_division") + if n >= 6 and n % 2 == 0 and _is_consecutive_pairs(reals, n / 2): + return _make_result(_C.TYPE_CONSECUTIVE_PAIRS, reals[n-1].rank(), true, reals) + if n == 5 and _is_triple_plus_two(reals): + return _make_result(_C.TYPE_TRIPLE_PLUS_TWO, _get_triple_rank(reals), true, reals) + if n >= 6 and n % 3 == 0 and _is_steel_plate(reals): + return _make_result(_C.TYPE_STEEL_PLATE, reals[n-1].rank(), true, reals) + if n >= 5 and _is_straight_flush(reals, config): + return _make_result(_C.TYPE_STRAIGHT_FLUSH, reals[n-1].rank(), true, reals) + return null static func _eval_triple_plus_two(reals: Array[Card], wilds: Array[Card]) -> EvaluatedPlay: - if reals.size() + wilds.size() != 5: return null - if reals.is_empty(): return null - var rank_counts := _rank_counts(reals) - if rank_counts.values().has(3): - return _make_result(_C.TYPE_TRIPLE_PLUS_TWO, _get_triple_rank(reals), wilds.is_empty(), reals + wilds) - return null + if reals.size() + wilds.size() != 5: return null + if reals.is_empty(): return null + var rank_counts := _rank_counts(reals) + if rank_counts.values().has(3): + return _make_result(_C.TYPE_TRIPLE_PLUS_TWO, _get_triple_rank(reals), wilds.is_empty(), reals + wilds) + return null static func _eval_straight(reals: Array[Card], wilds: Array[Card], n: int, config: RuleConfig) -> EvaluatedPlay: - if reals.size() + wilds.size() != n: return null - var sorted: Array[Card] = [] - for rc in reals: sorted.append(rc) - _sort_by_rank_and_suit(sorted) - var w := wilds.size() - var gaps: Array[int] = [] - for i in range(sorted.size() - 1): - var card_a: Card = sorted[i] - var card_b: Card = sorted[i+1] - var delta: int = card_b.rank() - card_a.rank() - if delta > 1: - gaps.append(delta - 1) - var total_gaps := 0 - for g in gaps: total_gaps += g - if total_gaps == w or (total_gaps <= w and w >= total_gaps): - var last_card: Card = sorted[sorted.size()-1] - var max_rank: int = last_card.rank() - if not config.straight_extends_to_ace and max_rank > 14: return null - return _make_result(_C.TYPE_STRAIGHT, max_rank, w == 0, reals + wilds) - return null + if reals.size() + wilds.size() != n: return null + var sorted: Array[Card] = [] + for rc in reals: sorted.append(rc) + _sort_by_rank_and_suit(sorted) + var w := wilds.size() + var gaps: Array[int] = [] + for i in range(sorted.size() - 1): + var card_a: Card = sorted[i] + var card_b: Card = sorted[i+1] + var delta: int = card_b.rank() - card_a.rank() + if delta > 1: + gaps.append(delta - 1) + var total_gaps := 0 + for g in gaps: total_gaps += g + if total_gaps == w or (total_gaps <= w and w >= total_gaps): + var last_card: Card = sorted[sorted.size()-1] + var max_rank: int = last_card.rank() + if not config.straight_extends_to_ace and max_rank > 14: return null + return _make_result(_C.TYPE_STRAIGHT, max_rank, w == 0, reals + wilds) + return null -static func _eval_consecutive_pairs(reals: Array[Card], wilds: Array[Card], pair_count: int, config: RuleConfig) -> EvaluatedPlay: - if reals.size() + wilds.size() != pair_count * 2: return null - if wilds.is_empty() and _is_consecutive_pairs(reals, pair_count): - return _make_result(_C.TYPE_CONSECUTIVE_PAIRS, reals[reals.size()-1].rank(), true, reals) - return null +static func _eval_consecutive_pairs(reals: Array[Card], wilds: Array[Card], pair_count: int, _config: RuleConfig) -> EvaluatedPlay: + if reals.size() + wilds.size() != pair_count * 2: return null + if wilds.is_empty() and _is_consecutive_pairs(reals, pair_count): + return _make_result(_C.TYPE_CONSECUTIVE_PAIRS, reals[reals.size()-1].rank(), true, reals) + return null -static func _eval_steel_plate(reals: Array[Card], wilds: Array[Card], triple_count: int, config: RuleConfig) -> EvaluatedPlay: - if reals.size() + wilds.size() != triple_count * 3: return null - if wilds.is_empty() and _is_steel_plate(reals): - return _make_result(_C.TYPE_STEEL_PLATE, reals[reals.size()-1].rank(), true, reals) - return null +static func _eval_steel_plate(reals: Array[Card], wilds: Array[Card], triple_count: int, _config: RuleConfig) -> EvaluatedPlay: + if reals.size() + wilds.size() != triple_count * 3: return null + if wilds.is_empty() and _is_steel_plate(reals): + return _make_result(_C.TYPE_STEEL_PLATE, reals[reals.size()-1].rank(), true, reals) + return null static func _all_same_rank(cards: Array[Card]) -> bool: - var r := cards[0].rank() - for i in range(1, cards.size()): - if cards[i].rank() != r: return false - return true + var r := cards[0].rank() + for i in range(1, cards.size()): + if cards[i].rank() != r: return false + return true static func _is_straight(cards: Array[Card], config: RuleConfig) -> bool: - for i in range(cards.size() - 1): - if cards[i+1].rank() - cards[i].rank() != 1: return false - if not config.straight_extends_to_ace and cards[cards.size()-1].rank() > 14: return false - return true + for i in range(cards.size() - 1): + if cards[i+1].rank() - cards[i].rank() != 1: return false + if not config.straight_extends_to_ace and cards[cards.size()-1].rank() > 14: return false + return true -static func _is_consecutive_pairs(cards: Array[Card], pair_count: int) -> bool: - for i in range(0, cards.size(), 2): - if cards[i].rank() != cards[i+1].rank(): return false - for i in range(0, cards.size() - 2, 2): - if cards[i+2].rank() - cards[i].rank() != 1: return false - return true +static func _is_consecutive_pairs(cards: Array[Card], _pair_count: int) -> bool: + for i in range(0, cards.size(), 2): + if cards[i].rank() != cards[i+1].rank(): return false + for i in range(0, cards.size() - 2, 2): + if cards[i+2].rank() - cards[i].rank() != 1: return false + return true static func _is_triple_plus_two(cards: Array[Card]) -> bool: - var rank_counts := _rank_counts(cards) - return rank_counts.values().has(3) and rank_counts.values().has(2) + var rank_counts := _rank_counts(cards) + return rank_counts.values().has(3) and rank_counts.values().has(2) static func _is_steel_plate(cards: Array[Card]) -> bool: - var rank_counts := _rank_counts(cards) - for count in rank_counts.values(): - if count != 3: return false - var ranks := rank_counts.keys() - ranks.sort() - for i in range(ranks.size() - 1): - if ranks[i+1] - ranks[i] != 1: return false - return true + var rank_counts := _rank_counts(cards) + for count in rank_counts.values(): + if count != 3: return false + var ranks := rank_counts.keys() + ranks.sort() + for i in range(ranks.size() - 1): + if ranks[i+1] - ranks[i] != 1: return false + return true static func _is_straight_flush(cards: Array[Card], config: RuleConfig) -> bool: - if not _is_straight(cards, config): return false - var suit := cards[0].suit() - for i in range(1, cards.size()): - if cards[i].suit() != suit: return false - return true + if not _is_straight(cards, config): return false + var suit := cards[0].suit() + for i in range(1, cards.size()): + if cards[i].suit() != suit: return false + return true static func _rank_counts(cards: Array[Card]) -> Dictionary: - var d := {} - for c in cards: - var r := c.rank() - d[r] = d.get(r, 0) + 1 - return d + var d := {} + for c in cards: + var r := c.rank() + d[r] = d.get(r, 0) + 1 + return d static func _get_triple_rank(cards: Array[Card]) -> int: - var rc := _rank_counts(cards) - for rank in rc: - if rc[rank] == 3: return rank - return 0 + var rc := _rank_counts(cards) + for rank in rc: + if rc[rank] == 3: return rank + return 0 static func _sort_by_rank_and_suit(cards: Array[Card]) -> void: - cards.sort_custom(func(a: Card, b: Card): return a.compare_to(b) < 0) + cards.sort_custom(func(a: Card, b: Card): return a.compare_to(b) < 0) diff --git a/src/core/round.gd b/src/core/round.gd index 1518f4d..438b9e9 100644 --- a/src/core/round.gd +++ b/src/core/round.gd @@ -9,33 +9,33 @@ var action_seq: int = 0 var is_cleared: bool = false func can_pass() -> bool: - return not table.is_empty() + return not table.is_empty() -func add_play(play: HandEvaluator.EvaluatedPlay, player_idx: int) -> void: - table.append(play) - if play.type == -1: - pass_count += 1 - else: - pass_count = 0 - if pass_count >= 3: - is_cleared = true - else: - is_cleared = false +func add_play(play: HandEvaluator.EvaluatedPlay, _player_idx: int) -> void: + table.append(play) + if play.type == -1: + pass_count += 1 + else: + pass_count = 0 + if pass_count >= 3: + is_cleared = true + else: + is_cleared = false func next_player() -> int: - return (active_player_idx + 1) % 4 + return (active_player_idx + 1) % 4 func last_non_pass_player() -> int: - for i in range(table.size() - 1, -1, -1): - if table[i].type != -1: - return i % 4 - return -1 + for i in range(table.size() - 1, -1, -1): + if table[i].type != -1: + return i % 4 + return -1 func next_seq() -> int: - action_seq += 1 - return action_seq + action_seq += 1 + return action_seq func reset_for_new_round() -> void: - table.clear() - pass_count = 0 - is_cleared = false + table.clear() + pass_count = 0 + is_cleared = false diff --git a/src/core/rule_engine.gd b/src/core/rule_engine.gd index efe2453..02826de 100644 --- a/src/core/rule_engine.gd +++ b/src/core/rule_engine.gd @@ -8,101 +8,101 @@ extends RefCounted const _C = preload("res://src/core/constants.gd") static func can_play( - hand: Array, - play: HandEvaluator.EvaluatedPlay, - table_history: Array, - last_player_idx: int, - current_rank: int, - config: RuleConfig + hand: Array, + play: HandEvaluator.EvaluatedPlay, + table_history: Array, + last_player_idx: int, + _current_rank: int, + config: RuleConfig ) -> Dictionary: - if play.type == -1: - var last_non_pass := _last_non_pass(table_history) - if table_history.is_empty() or last_non_pass == null: - return _err(_C.ERR_CANNOT_PASS) - if last_player_idx == _last_player_idx(table_history): - return _err(_C.ERR_CANNOT_PASS) - return _ok() - for pc in play.cards: - if not _card_in_hand(hand, pc): - return _err(_C.ERR_CARD_NOT_FOUND) - var last_play := _last_non_pass(table_history) - if last_play == null: - return _ok() - if last_player_idx < 0: - return _ok() - if _last_player_idx(table_history) == last_player_idx: - return _ok() - var cmp := compare(play, last_play, config) - if cmp <= 0: - return _err(_C.ERR_INVALID_CARDS) - if play.type != last_play.type: - if play.type != _C.TYPE_BOMB and play.type != _C.TYPE_ROCKET: - return _err(_C.ERR_INVALID_CARDS) - return _ok() + if play.type == -1: + var last_non_pass := _last_non_pass(table_history) + if table_history.is_empty() or last_non_pass == null: + return _err(_C.ERR_CANNOT_PASS) + if last_player_idx == _last_player_idx(table_history): + return _err(_C.ERR_CANNOT_PASS) + return _ok() + for pc in play.cards: + if not _card_in_hand(hand, pc): + return _err(_C.ERR_CARD_NOT_FOUND) + var last_play := _last_non_pass(table_history) + if last_play == null: + return _ok() + if last_player_idx < 0: + return _ok() + if _last_player_idx(table_history) == last_player_idx: + return _ok() + var cmp := compare(play, last_play, config) + if cmp <= 0: + return _err(_C.ERR_INVALID_CARDS) + if play.type != last_play.type: + if play.type != _C.TYPE_BOMB and play.type != _C.TYPE_ROCKET: + return _err(_C.ERR_INVALID_CARDS) + return _ok() static func compare(a: HandEvaluator.EvaluatedPlay, b: HandEvaluator.EvaluatedPlay, config: RuleConfig) -> int: - if a.type == -1 or b.type == -1: - return 0 - if a.type == _C.TYPE_ROCKET and b.type == _C.TYPE_ROCKET: - return 0 - if a.type == _C.TYPE_ROCKET: - return 1 - if b.type == _C.TYPE_ROCKET: - return -1 - if a.type == _C.TYPE_BOMB and b.type != _C.TYPE_BOMB: - return 1 - if b.type == _C.TYPE_BOMB and a.type != _C.TYPE_BOMB: - return -1 - if a.type == _C.TYPE_BOMB and b.type == _C.TYPE_BOMB: - return compare_bombs(a, b, config) - if a.type == b.type: - if a.primary_rank > b.primary_rank: - return 1 - if a.primary_rank < b.primary_rank: - return -1 - return 0 - return 0 + if a.type == -1 or b.type == -1: + return 0 + if a.type == _C.TYPE_ROCKET and b.type == _C.TYPE_ROCKET: + return 0 + if a.type == _C.TYPE_ROCKET: + return 1 + if b.type == _C.TYPE_ROCKET: + return -1 + if a.type == _C.TYPE_BOMB and b.type != _C.TYPE_BOMB: + return 1 + if b.type == _C.TYPE_BOMB and a.type != _C.TYPE_BOMB: + return -1 + if a.type == _C.TYPE_BOMB and b.type == _C.TYPE_BOMB: + return compare_bombs(a, b, config) + if a.type == b.type: + if a.primary_rank > b.primary_rank: + return 1 + if a.primary_rank < b.primary_rank: + return -1 + return 0 + return 0 static func compare_bombs(a: HandEvaluator.EvaluatedPlay, b: HandEvaluator.EvaluatedPlay, config: RuleConfig) -> int: - if a.is_pure_bomb and not b.is_pure_bomb: - return 1 - if b.is_pure_bomb and not a.is_pure_bomb: - return -1 - if config.bomb_compare_priority == RuleConfig.BOMB_BY_COUNT: - if a.cards.size() > b.cards.size(): - return 1 - if b.cards.size() > a.cards.size(): - return -1 - if a.primary_rank > b.primary_rank: - return 1 - if b.primary_rank > a.primary_rank: - return -1 - if a.cards.size() > b.cards.size(): - return 1 - if b.cards.size() > a.cards.size(): - return -1 - return 0 + if a.is_pure_bomb and not b.is_pure_bomb: + return 1 + if b.is_pure_bomb and not a.is_pure_bomb: + return -1 + if config.bomb_compare_priority == RuleConfig.BOMB_BY_COUNT: + if a.cards.size() > b.cards.size(): + return 1 + if b.cards.size() > a.cards.size(): + return -1 + if a.primary_rank > b.primary_rank: + return 1 + if b.primary_rank > a.primary_rank: + return -1 + if a.cards.size() > b.cards.size(): + return 1 + if b.cards.size() > a.cards.size(): + return -1 + return 0 static func _card_in_hand(hand: Array, card: Card) -> bool: - for c in hand: - if c.card_id == card.card_id: - return true - return false + for c in hand: + if c.card_id == card.card_id: + return true + return false static func _last_non_pass(table: Array) -> HandEvaluator.EvaluatedPlay: - for i in range(table.size() - 1, -1, -1): - if table[i].type != -1: - return table[i] - return null + for i in range(table.size() - 1, -1, -1): + if table[i].type != -1: + return table[i] + return null static func _last_player_idx(table: Array) -> int: - for i in range(table.size() - 1, -1, -1): - if table[i].type != -1: - return i % 4 - return -1 + for i in range(table.size() - 1, -1, -1): + if table[i].type != -1: + return i % 4 + return -1 static func _ok() -> Dictionary: - return {"ok": true, "error_code": _C.RESULT_OK, "data": null} + return {"ok": true, "error_code": _C.RESULT_OK, "data": null} static func _err(code: int) -> Dictionary: - return {"ok": false, "error_code": code, "data": null} + return {"ok": false, "error_code": code, "data": null} diff --git a/src/game/game_controller.gd b/src/game/game_controller.gd index feb149b..d8569f1 100644 --- a/src/game/game_controller.gd +++ b/src/game/game_controller.gd @@ -12,120 +12,120 @@ signal game_ended(winner_team: int, reason: String) var game_state: GameState var ai_players: Dictionary = {} -var is_processing: bool = false +var _is_processing: bool = false func start_game(config: RuleConfig, human_idx: int = 0, seed_: int = -1) -> void: - game_state = GameState.create(config, seed_) - game_state.player_human[human_idx] = true - for i in range(4): - if not game_state.player_human[i]: - ai_players[i] = L2RuleAI.new() - var deck := Deck.create(game_state.seed) - game_state.deal_cards(deck) - game_state.phase = GameState.Phase.PLAY - game_state.round.active_player_idx = 0 - state_changed.emit() + game_state = GameState.create(config, seed_) + game_state.player_human[human_idx] = true + for i in range(4): + if not game_state.player_human[i]: + ai_players[i] = L2RuleAI.new() + var deck := Deck.create(game_state._seed) + game_state.deal_cards(deck) + game_state.phase = GameState.Phase.PLAY + game_state._round.active_player_idx = 0 + state_changed.emit() func handle_human_play(cards: Array) -> Dictionary: - if is_processing: - return {"ok": false, "error_code": 1, "data": null} - var hand := game_state.get_hand(game_state.round.active_player_idx) - var play := HandEvaluator.evaluate(cards, game_state.current_rank, game_state.rule_config) - if play == null or play.type == -1: - return {"ok": false, "error_code": 1, "data": null} - var last_idx := game_state.round.last_non_pass_player() - var result := RuleEngine.can_play(hand, play, game_state.round.table, last_idx, game_state.current_rank, game_state.rule_config) - if not result.ok: - return result - _apply_play(game_state.round.active_player_idx, play) - _advance_turn() - return {"ok": true, "error_code": 0, "data": null} + if _is_processing: + return {"ok": false, "error_code": 1, "data": null} + var hand := game_state.get_hand(game_state._round.active_player_idx) + var play := HandEvaluator.evaluate(cards, game_state.current_rank, game_state.rule_config) + if play == null or play.type == -1: + return {"ok": false, "error_code": 1, "data": null} + var last_idx := game_state._round.last_non_pass_player() + var result := RuleEngine.can_play(hand, play, game_state._round.table, last_idx, game_state.current_rank, game_state.rule_config) + if not result.ok: + return result + _apply_play(game_state._round.active_player_idx, play) + _advance_turn() + return {"ok": true, "error_code": 0, "data": null} func handle_human_pass() -> Dictionary: - if is_processing: - return {"ok": false, "error_code": 1, "data": null} - var old_last := game_state.round.last_non_pass_player() - if old_last >= 0 and old_last == game_state.round.active_player_idx: - return {"ok": false, "error_code": 3, "data": null} - var pass_play := HandEvaluator.EvaluatedPlay.new() - pass_play.type = -1 - pass_play.primary_rank = 0 - _apply_play(game_state.round.active_player_idx, pass_play) - _advance_turn() - return {"ok": true, "error_code": 0, "data": null} + if _is_processing: + return {"ok": false, "error_code": 1, "data": null} + var old_last := game_state._round.last_non_pass_player() + if old_last >= 0 and old_last == game_state._round.active_player_idx: + return {"ok": false, "error_code": 3, "data": null} + var pass_play := HandEvaluator.EvaluatedPlay.new() + pass_play.type = -1 + pass_play.primary_rank = 0 + _apply_play(game_state._round.active_player_idx, pass_play) + _advance_turn() + return {"ok": true, "error_code": 0, "data": null} func _apply_play(player_idx: int, play: HandEvaluator.EvaluatedPlay) -> void: - if play.type != -1: - game_state.remove_cards_from_hand(player_idx, play.cards) - var action := Actions.Action.new() - action.player_idx = player_idx - action.action_type = "PASS" if play.type == -1 else "PLAY" - action.cards = play.cards.duplicate(false) - action.seq_id = game_state.round.next_seq() - action.timestamp = Time.get_unix_time_from_system() - game_state.action_log.append(action) - game_state.round.add_play(play, player_idx) - var hand: Array = game_state.get_hand(player_idx) - if hand.is_empty() and not game_state.is_player_finished(player_idx): - game_state.add_finished_player(player_idx) - EventBus.player_finished.emit(player_idx, game_state.finished_players.size()) - if game_state.all_hands_empty(): - _end_game() - if play.type != -1: - EventBus.player_played_cards.emit(player_idx, play.type, play.cards) - if play.type == _C.TYPE_BOMB or play.type == _C.TYPE_ROCKET: - EventBus.bomb_detonated.emit(player_idx, play.primary_rank) + if play.type != -1: + game_state.remove_cards_from_hand(player_idx, play.cards) + var action := Actions.Action.new() + action.player_idx = player_idx + action.action_type = "PASS" if play.type == -1 else "PLAY" + action.cards = play.cards.duplicate(false) + action.seq_id = game_state._round.next_seq() + action.timestamp = Time.get_unix_time_from_system() as int + game_state.action_log.append(action) + game_state._round.add_play(play, player_idx) + var hand: Array = game_state.get_hand(player_idx) + if hand.is_empty() and not game_state.is_player_finished(player_idx): + game_state.add_finished_player(player_idx) + EventBus.player_finished.emit(player_idx, game_state.finished_players.size()) + if game_state.all_hands_empty(): + _end_game() + if play.type != -1: + EventBus.player_played_cards.emit(player_idx, play.type, play.cards) + if play.type == _C.TYPE_BOMB or play.type == _C.TYPE_ROCKET: + EventBus.bomb_detonated.emit(player_idx, play.primary_rank) func _advance_turn() -> void: - var hand: Array = game_state.get_hand(game_state.round.active_player_idx) - if hand.is_empty(): - var partner := game_state.get_partner(game_state.round.active_player_idx) - if not game_state.is_player_finished(partner): - game_state.round.active_player_idx = partner - game_state.round.reset_for_new_round() - else: - _next_alive_player() - elif game_state.round.is_cleared: - game_state.round.reset_for_new_round() - else: - _next_alive_player() - var current := game_state.round.active_player_idx - turn_ready.emit(current, game_state.player_human[current]) - EventBus.turn_changed.emit(current) - if not game_state.player_human[current]: - _trigger_ai(current) + var hand: Array = game_state.get_hand(game_state._round.active_player_idx) + if hand.is_empty(): + var partner := game_state.get_partner(game_state._round.active_player_idx) + if not game_state.is_player_finished(partner): + game_state._round.active_player_idx = partner + game_state._round.reset_for_new_round() + else: + _next_alive_player() + elif game_state._round.is_cleared: + game_state._round.reset_for_new_round() + else: + _next_alive_player() + var current := game_state._round.active_player_idx + turn_ready.emit(current, game_state.player_human[current]) + EventBus.turn_changed.emit(current) + if not game_state.player_human[current]: + _trigger_ai(current) func _next_alive_player() -> void: - for _i in range(4): - game_state.round.active_player_idx = (game_state.round.active_player_idx + 1) % 4 - if not game_state.is_player_finished(game_state.round.active_player_idx): - return + for _i in range(4): + game_state._round.active_player_idx = (game_state._round.active_player_idx + 1) % 4 + if not game_state.is_player_finished(game_state._round.active_player_idx): + return func _trigger_ai(player_idx: int) -> void: - var ai: BaseAI = ai_players.get(player_idx) as BaseAI - if ai == null: - 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) - if decision.type == -1: - _apply_play(player_idx, decision) - else: - _apply_play(player_idx, decision) - _advance_turn() + var ai: BaseAI = ai_players.get(player_idx) as BaseAI + if ai == null: + 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) + if decision.type == -1: + _apply_play(player_idx, decision) + else: + _apply_play(player_idx, decision) + _advance_turn() func _end_game() -> void: - game_state.phase = GameState.Phase.GAME_OVER - var team_counts := [0, 0] - for fp in game_state.finished_players: - var t := game_state.get_team(fp) - if t != null: - team_counts[t.team_id] += 1 - if team_counts[0] >= 2: - game_state.current_winner_team = 0 - elif team_counts[1] >= 2: - game_state.current_winner_team = 1 - else: - game_state.current_winner_team = 0 if team_counts[0] > team_counts[1] else 1 - game_state.game_end_reason = "NORMAL" - game_ended.emit(game_state.current_winner_team, "NORMAL") - EventBus.game_over.emit(game_state.current_winner_team, "NORMAL") + game_state.phase = GameState.Phase.GAME_OVER + var team_counts := [0, 0] + for fp in game_state.finished_players: + var t := game_state.get_team(fp) + if t != null: + team_counts[t.team_id] += 1 + if team_counts[0] >= 2: + game_state.current_winner_team = 0 + elif team_counts[1] >= 2: + game_state.current_winner_team = 1 + else: + game_state.current_winner_team = 0 if team_counts[0] > team_counts[1] else 1 + game_state.game_end_reason = "NORMAL" + game_ended.emit(game_state.current_winner_team, "NORMAL") + EventBus.game_over.emit(game_state.current_winner_team, "NORMAL") diff --git a/src/game/training_controller.gd b/src/game/training_controller.gd index 993ac65..cbb383b 100644 --- a/src/game/training_controller.gd +++ b/src/game/training_controller.gd @@ -2,15 +2,13 @@ class_name TrainingController extends GameController -var _current_hint: HandEvaluator.EvaluatedPlay = null - func get_hint() -> HandEvaluator.EvaluatedPlay: - var hand := game_state.get_hand(game_state.round.active_player_idx) + var hand := game_state.get_hand(game_state._round.active_player_idx) if hand.is_empty(): return null var ai := L2RuleAI.new() - return ai.decide(hand, game_state.round.table, game_state.current_rank, game_state.rule_config) + return ai.decide(hand, game_state._round.table, game_state.current_rank, game_state.rule_config) func get_all_legal_moves() -> Array: - var hand := game_state.get_hand(game_state.round.active_player_idx) + var hand := game_state.get_hand(game_state._round.active_player_idx) return MoveGenerator.generate(hand, game_state.current_rank, game_state.rule_config) diff --git a/src/ui/components/card_node.gd b/src/ui/components/card_node.gd index 52b80c4..66cc935 100644 --- a/src/ui/components/card_node.gd +++ b/src/ui/components/card_node.gd @@ -7,8 +7,12 @@ signal card_double_clicked(card_node: CardNode) var card_data: Card = null var is_selected: bool = false -@onready var texture_rect: TextureRect = $TextureRect -@onready var label: Label = $Label +@onready var panel: Panel = $Panel +@onready var rank_label: Label = $Panel/RankLabel +@onready var suit_label: Label = $Panel/SuitLabel + +const SUIT_SYMBOLS := ["\u2660", "\u2665", "\u2663", "\u2666", "SJ", "BJ"] +const RANK_NAMES := ["", "", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "SJ", "BJ"] func setup(card: Card) -> void: card_data = card @@ -17,17 +21,43 @@ func setup(card: Card) -> void: func update_display() -> void: if card_data == null: return - var suits := ["S", "H", "C", "D", "SJ", "BJ"] - var ranks := ["", "", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "SJ", "BJ"] var suit := card_data.suit() var rank := card_data.rank() - if rank < ranks.size() and suit < suits.size() and label: - label.text = "%s %s" % [suits[suit], ranks[rank]] - modulate = Color.WHITE if not is_selected else Color(1.2, 1.2, 0.8) + var is_red := suit == 1 or suit == 3 + var color := Color.RED if is_red else Color.BLACK + + if rank_label: + rank_label.text = RANK_NAMES[rank] + rank_label.add_theme_color_override("font_color", color) + rank_label.add_theme_font_size_override("font_size", 14) + + if suit_label: + suit_label.text = SUIT_SYMBOLS[suit] + suit_label.add_theme_color_override("font_color", color) + suit_label.add_theme_font_size_override("font_size", 24) + + _update_panel() + +func _update_panel() -> void: + if panel == null: + return + var sbox := StyleBoxFlat.new() + sbox.bg_color = Color.WHITE if not is_selected else Color(1.0, 1.0, 0.6) + sbox.set_corner_radius_all(6) + sbox.border_width_left = 2 + sbox.border_width_right = 2 + sbox.border_width_top = 2 + sbox.border_width_bottom = 2 + sbox.border_color = Color(0.3, 0.3, 0.3) + sbox.content_margin_left = 2 + sbox.content_margin_right = 2 + sbox.content_margin_top = 2 + sbox.content_margin_bottom = 2 + panel.add_theme_stylebox_override("panel", sbox) func set_selected(sel: bool) -> void: is_selected = sel - update_display() + _update_panel() func _on_gui_input(event: InputEvent) -> void: if event is InputEventMouseButton: diff --git a/src/ui/components/card_node.tscn b/src/ui/components/card_node.tscn index 701cf26..e629c77 100644 --- a/src/ui/components/card_node.tscn +++ b/src/ui/components/card_node.tscn @@ -1,16 +1,40 @@ [gd_scene load_steps=2 format=3 uid="uid://card_node"] [ext_resource type="Script" path="res://src/ui/components/card_node.gd" id="1_script"] + [node name="CardNode" type="Control"] -custom_minimum_size = Vector2(80, 120) -size = Vector2(80, 120) +custom_minimum_size = Vector2(70, 100) +mouse_filter = 1 +size = Vector2(70, 100) script = ExtResource("1_script") -[node name="TextureRect" type="TextureRect" parent="."] -layout_mode = 0 -offset_right = 80.0 -offset_bottom = 120.0 -[node name="Label" type="Label" parent="."] -layout_mode = 0 -offset_right = 80.0 -offset_bottom = 120.0 + +[node name="Panel" type="Panel" parent="."] +layout_mode = 1 +mouse_filter = 2 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="RankLabel" type="Label" parent="Panel"] +layout_mode = 1 +anchors_preset = 2 +anchor_top = 1.0 +anchor_bottom = 1.0 +offset_top = -18.0 +offset_right = 12.0 +offset_bottom = -2.0 +text = "A" +horizontal_alignment = 1 + +[node name="SuitLabel" type="Label" parent="Panel"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_right = 0.5 +anchor_top = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -20.0 +offset_right = 20.0 +offset_bottom = 20.0 horizontal_alignment = 1 vertical_alignment = 1 diff --git a/src/ui/components/hand_area.gd b/src/ui/components/hand_area.gd index 4f3fce3..4688b54 100644 --- a/src/ui/components/hand_area.gd +++ b/src/ui/components/hand_area.gd @@ -13,17 +13,22 @@ const CARD_SCENE := preload("res://src/ui/components/card_node.tscn") var training_controller: TrainingController = null func update_hand(hand: Array) -> void: + print("[DEBUG] HandArea.update_hand called, cards count: ", hand.size()) for cn in card_nodes: cn.queue_free() card_nodes.clear() selected_cards.clear() for c in hand: var node := CARD_SCENE.instantiate() as CardNode + if node == null: + print("[DEBUG] CardNode instantiate returned null!") + continue + add_child(node) node.setup(c) node.card_clicked.connect(_on_card_clicked) node.card_double_clicked.connect(_on_card_double_clicked) - add_child(node) card_nodes.append(node) + print("[DEBUG] CardNodes created: ", card_nodes.size()) func _on_card_clicked(card_node: CardNode) -> void: card_node.set_selected(not card_node.is_selected) diff --git a/src/ui/scenes/training_room.gd b/src/ui/scenes/training_room.gd index dda6325..cf1cb5f 100644 --- a/src/ui/scenes/training_room.gd +++ b/src/ui/scenes/training_room.gd @@ -65,10 +65,14 @@ func _on_hint_pressed() -> void: hand_area.selected_cards.append(card) if status_label: status_label.text = "建议牌型: %s (rank=%d)" % [hint.type, hint.primary_rank] -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 hand_area: hand_area.disable_input() func _refresh_ui() -> void: if controller and controller.game_state and hand_area: - hand_area.update_hand(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) + else: + print("[DEBUG] _refresh_ui: controller=", controller, " game_state=", controller.game_state if controller else null, " hand_area=", hand_area)