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
This commit is contained in:
xiaji
2026-05-30 22:38:52 +08:00
parent bad46b0109
commit cef2cba7a5
14 changed files with 577 additions and 512 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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)