From aef7778a4bf4fea0f7dd57aba82ba5119f809add Mon Sep 17 00:00:00 2001 From: denizk0461 Date: Fri, 18 Jul 2025 14:10:39 +0200 Subject: [PATCH] client can now only play allowed cards --- cards/card_manager.gd | 12 +++++ components/web_client_tcp.gd | 16 ++++++ elements/player_hand.gd | 95 ++++++++++++++++++++++++++++++++++++ game.tscn | 23 ++++++++- server_tcp.py | 8 ++- 5 files changed, 152 insertions(+), 2 deletions(-) diff --git a/cards/card_manager.gd b/cards/card_manager.gd index 83db494..62e7eef 100644 --- a/cards/card_manager.gd +++ b/cards/card_manager.gd @@ -155,3 +155,15 @@ func _get_card_name(card_id: int) -> String: suffix = "magician" return color + "_" + suffix + +func get_card_color(card_id: int) -> Card.CardColor: + if card_id < 13: + return Card.CardColor.BLUE + elif card_id < 26: + return Card.CardColor.GREEN + elif card_id < 39: + return Card.CardColor.RED + elif card_id < 52: + return Card.CardColor.YELLOW + else: + return Card.CardColor.WHITE diff --git a/components/web_client_tcp.gd b/components/web_client_tcp.gd index f540fc0..6af7446 100644 --- a/components/web_client_tcp.gd +++ b/components/web_client_tcp.gd @@ -17,6 +17,8 @@ var cached_response: String var _thread: Thread var _stop_thread: bool = false +var _is_first_play: bool = false + var _connected_client_ids: Array[String] = [] var _connected_client_names: Array[String] = [] @@ -48,6 +50,8 @@ signal received_bet_request(disallowed_bet: int, current_round: int) signal received_win() signal received_winning_client(client_id: String) signal notify_play_card_request() +signal reset_first_card() +signal received_first_card(card_id: int) signal received_played_card(client_id: String, card_id: int) signal received_play_winner_request() signal received_end_game_request() @@ -155,9 +159,15 @@ func _handle_response(response: String) -> void: "begin_playing_cards": # all players know it's time to play print(data) + _is_first_play = true + reset_first_card.emit.call_deferred() "request_card": # individual player asked to play card notify_play_card_request.emit.call_deferred() + "begin_next_play": + print("beginning next play!") + _is_first_play = true + reset_first_card.emit.call_deferred() "play_card": print(data) #client_played_card.emit.call_deferred(data["client_id"], data["played_card"]) @@ -166,6 +176,12 @@ func _handle_response(response: String) -> void: "end_game_request": received_end_game_request.emit.call_deferred() "client_played_card": + print("RECEIVED FOLLOWING CARD ID: " + str(data["played_card"])) + if _is_first_play: + print("set first card") + received_first_card.emit.call_deferred(data["played_card"]) + if not CardManager.get_card_color(data["played_card"]) == Card.CardColor.WHITE: + _is_first_play = false received_played_card.emit.call_deferred(data["client_id"], data["played_card"]) "client_won_play": if data["client_id"] == _client_id: diff --git a/elements/player_hand.gd b/elements/player_hand.gd index 82ffd45..5b9a1b6 100644 --- a/elements/player_hand.gd +++ b/elements/player_hand.gd @@ -21,13 +21,39 @@ var _is_owned: bool @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() @@ -47,6 +73,7 @@ func add_cards(card_ids: Array[int], is_owned: bool) -> void: 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 @@ -60,6 +87,7 @@ func remove_card_by_id(card_id: int) -> void: 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: @@ -81,6 +109,15 @@ func _get_card_distance(card_count: int) -> float: 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: @@ -108,3 +145,61 @@ func display_rank(rank: int, score: int) -> void: "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 diff --git a/game.tscn b/game.tscn index 47285a6..e1370bb 100644 --- a/game.tscn +++ b/game.tscn @@ -260,6 +260,23 @@ offset_right = -60.0 offset_bottom = 83.0 grow_horizontal = 0 +[node name="CardDisallowedLabel" type="Label" parent="HBoxContainer/GameUI"] +visible = false +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -11.5 +offset_right = 20.0 +offset_bottom = 11.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_colors/font_color = Color(1, 0.28, 0.28, 1) +text = "NOT ALLOWED!" + [node name="EnvironmentManager" type="Node3D" parent="." node_paths=PackedStringArray("web_client", "card_stack", "own_player_hand", "hand_markers_3", "hand_markers_4", "hand_markers_5", "hand_markers_6")] script = ExtResource("4_7jktm") web_client = NodePath("../WebClientTCP") @@ -282,9 +299,10 @@ transform = Transform3D(0.970511, 0, 0.241058, -0.137972, 0.820002, 0.555482, -0 [node name="WorldEnvironment" type="WorldEnvironment" parent="EnvironmentManager"] environment = SubResource("Environment_fc0e3") -[node name="PlayerHand0" parent="EnvironmentManager" instance=ExtResource("4_fc0e3")] +[node name="PlayerHand0" parent="EnvironmentManager" node_paths=PackedStringArray("card_disallowed_label") instance=ExtResource("4_fc0e3")] transform = Transform3D(1, 0, 0, 0, 0.403769, 0.914861, 0, -0.914861, 0.403769, 0, 0.490593, 2.17793) hide_client_name = true +card_disallowed_label = NodePath("../../HBoxContainer/GameUI/CardDisallowedLabel") [node name="HandMarker1" type="Marker3D" parent="EnvironmentManager"] transform = Transform3D(-0.906308, -0.408218, -0.109382, 0, 0.258819, -0.965926, 0.422618, -0.875426, -0.23457, -1.25, 0.4, -0.387) @@ -330,13 +348,16 @@ playing_card_scene = ExtResource("6_eow3j") [connection signal="received_connection_message" from="WebClientTCP" to="HBoxContainer/ClientInterface" method="_on_web_client_tcp_received_connection_message"] [connection signal="received_end_game_request" from="WebClientTCP" to="EnvironmentManager" method="_on_web_client_tcp_received_end_game_request"] [connection signal="received_expected_bet" from="WebClientTCP" to="EnvironmentManager" method="_on_web_client_tcp_received_expected_bet"] +[connection signal="received_first_card" from="WebClientTCP" to="EnvironmentManager/PlayerHand0" method="_on_web_client_tcp_received_first_card"] [connection signal="received_play_winner_request" from="WebClientTCP" to="EnvironmentManager" method="_on_web_client_tcp_received_play_winner_request"] [connection signal="received_played_card" from="WebClientTCP" to="EnvironmentManager" method="_on_web_client_tcp_received_played_card"] [connection signal="received_win" from="WebClientTCP" to="HBoxContainer/GameUI" method="_on_web_client_tcp_received_win"] [connection signal="received_winning_client" from="WebClientTCP" to="EnvironmentManager" method="_on_web_client_tcp_received_winning_client"] [connection signal="request_next_play" from="WebClientTCP" to="HBoxContainer/GameUI" method="_on_web_client_tcp_request_next_play"] [connection signal="request_next_round" from="WebClientTCP" to="HBoxContainer/GameUI" method="_on_web_client_tcp_request_next_round"] +[connection signal="reset_first_card" from="WebClientTCP" to="EnvironmentManager/PlayerHand0" method="_on_web_client_tcp_reset_first_card"] [connection signal="trump_received" from="WebClientTCP" to="EnvironmentManager" method="_on_web_client_tcp_trump_received"] +[connection signal="trump_received" from="WebClientTCP" to="EnvironmentManager/PlayerHand0" method="_on_web_client_tcp_trump_received"] [connection signal="pressed" from="HBoxContainer/ClientInterface/MarginContainer/VBoxContainer/ServerInputs/ConnectButton" to="HBoxContainer/ClientInterface" method="_on_connect_button_pressed"] [connection signal="pressed" from="HBoxContainer/ClientInterface/MarginContainer/VBoxContainer/ServerInputs/CreateLobbyButton" to="HBoxContainer/ClientInterface" method="_on_create_lobby_button_pressed"] [connection signal="pressed" from="HBoxContainer/ClientInterface/MarginContainer/VBoxContainer/ServerInputs/CreateLobbyButton" to="HBoxContainer/GameUI" method="_on_create_lobby_button_pressed"] diff --git a/server_tcp.py b/server_tcp.py index 8a6f6c7..36651c4 100644 --- a/server_tcp.py +++ b/server_tcp.py @@ -424,6 +424,12 @@ async def process_input(message: str, websocket) -> None: elif data_object["response"] == "start_next_play": lobby_id = data_object["lobby_id"] + await lobby_manager.broadcast_message( + lobby_id, + json.dumps({ + "response": "begin_next_play", + }), + ) await lobby_manager.send_message( lobby_id, lobby_manager.get_lobby(lobby_id).client_playing_order[0], @@ -487,7 +493,7 @@ async def process_input(message: str, websocket) -> None: playing_client_id, json.dumps({ "response": "request_card", - "round": lobby_manager.get_lobby(lobby_id).current_round + "round": lobby_manager.get_lobby(lobby_id).current_round, }), )