class_name PlayerHand extends Node3D ## Distance cards will be apart when there is a small amount of them var card_distance_max: float = 0.18 ## Distance cards will be apart when there is a large amount of them var card_distance_min: float = 0.09 # Card distances for hands of other players var _card_distance_max_others: float = 0.03 var _card_distance_min_others: float = 0.01 var _is_owned: bool ## How many test cards to add @export var card_scene: PackedScene @export var card_container: Node3D @export_range(0, 20, 0.1, "radians_as_degrees") var card_angle: float @export var name_label: Label3D @export var rank_label: Label3D @export var hide_client_name: bool = false @export var card_disallowed_label: Label var _selected_card: PlayingCard = null var _cards_in_hand_ids: Array[int] = [] var _trump_id: int = -1 var _first_played_card_id: int = -1 signal card_selected(card_id: int) signal card_played(card_id: int) func _ready() -> void: # tests! # tries playing same color assert(_is_card_allowed(1, 3, [1, 15]) == true) # tries playing trump while having same color assert(_is_card_allowed(15, 3, [1, 15]) == false) # tries playing trump while not having the same color assert(_is_card_allowed(15, 3, [19, 15]) == true) # tries playing first card assert(_is_card_allowed(27, -1, [27, 1, 15]) == true) # tries playing wizard as first assert(_is_card_allowed(59, -1, [59, 1, 15]) == true) # tries playing wizard as non-first assert(_is_card_allowed(59, 3, [59, 1, 15]) == true) # tries playing wizard after first is wizard assert(_is_card_allowed(59, 58, [59, 1, 15]) == true) # tries playing numbered after first is jester assert(_is_card_allowed(3, 54, [3, 1, 15]) == true) assert(_is_card_allowed(13, 48, [13]) == true) if hide_client_name: name_label.hide() func set_player_name(player_name: String) -> void: name_label.text = player_name func add_card(card: PlayingCard) -> void: card_container.add_child(card) _arrange_cards() func add_cards(card_ids: Array[int], is_owned: bool) -> void: _is_owned = is_owned card_ids.sort() for card_id in card_ids: var card: PlayingCard = card_scene.instantiate() if is_owned: card.card_clicked.connect(_on_card_clicked) card.setup_card_details(card_id) add_card(card) _cards_in_hand_ids.append(card_id) if card_ids.size() == 1: card.rotate_y(PI) await get_tree().create_timer(0.16).timeout func remove_card_by_id(card_id: int) -> void: for card in card_container.get_children(): if card.card_details.card_id == card_id: remove_card(card) return func remove_card(card: PlayingCard) -> void: card_container.remove_child(card) card.queue_free() _cards_in_hand_ids.erase(card.card_details.card_id) _arrange_cards() func _arrange_cards() -> void: var cards = card_container.get_children() # Only arrange cards if there are any! if cards.size() > 0: var card_distance: float = _get_card_distance(cards.size() - 1) var desired_width: float = (card_distance * (cards.size() - 1)) var card_position = -desired_width / 2.0 for card in cards: card.move_to(card_position) card_position += card_distance card.rotation.y = card_angle func _get_card_distance(card_count: int) -> float: if _is_owned: return card_distance_min + ((card_distance_max - card_distance_min) * (1.0 - (float(card_count) / 19.0))) else: return _card_distance_min_others + ((_card_distance_max_others - _card_distance_min_others) * (1.0 - (float(card_count) / 19.0))) func _on_card_clicked(playing_card: PlayingCard) -> void: print("check card is " + str(_first_played_card_id)) if not _is_card_allowed( playing_card.card_details.card_id, _first_played_card_id, _cards_in_hand_ids, ): _notify_disallowed_card() return if _selected_card: _selected_card.unselect() if _selected_card == playing_card: _selected_card = null else: _selected_card = playing_card playing_card.select() if _selected_card: card_selected.emit(_selected_card.card_details.card_id) else: card_selected.emit(-1) func _on_play_card_button_pressed() -> void: if not _selected_card: return card_played.emit(_selected_card.card_details.card_id) remove_card(_selected_card) _selected_card = null card_selected.emit(-1) func display_rank(rank: int, score: int) -> void: rank_label.text = "#{rank} ({pts} pts)".format({ "rank": rank, "pts": score, }) func _is_card_allowed(requested_card_id: int, first_played_card_id: int, hand_card_ids: Array[int]) -> bool: var card_color: Card.CardColor = CardManager.get_card_color(requested_card_id) var first_played_color = CardManager.get_card_color(first_played_card_id) print("card details: req: {r}, play: {p}, hand: {han}".format({ "r": requested_card_id, "p": first_played_card_id, "han": hand_card_ids, })) # no card played yet, everything is allowed if first_played_card_id == -1: print("no first played") return true if first_played_color == Card.CardColor.WHITE: print("first is white") return true # card is white, may play if card_color == Card.CardColor.WHITE: print("yours is white, may play") return true # requested card is same color as laid card; may play if first_played_color == card_color: print("colors match") return true # requested card is of different color else: var colors_in_hand: Array[Card.CardColor] = [] for id in hand_card_ids: colors_in_hand.append(CardManager.get_card_color(id)) # player has played color, must play requested color if colors_in_hand.has(first_played_color): print("has played color, must not play") return false # player does not have played color, may play anything else: print("does not have played color, may play") return true func _notify_disallowed_card() -> void: if card_disallowed_label: card_disallowed_label.show() await get_tree().create_timer(1.0).timeout card_disallowed_label.hide() func _on_web_client_tcp_trump_received(trump_id: int) -> void: _trump_id = trump_id func _on_web_client_tcp_received_first_card(card_id: int) -> void: _first_played_card_id = card_id func _on_web_client_tcp_reset_first_card() -> void: _first_played_card_id = -1