From: Vladimir Garistov Date: Mon, 15 Apr 2024 04:29:58 +0000 (+0300) Subject: FEAT: first working version of the belot simulation X-Git-Url: https://kolegite.com/gitweb/?a=commitdiff_plain;h=7782da88093e0d7d85dbd62d79810077c9752494;p=vmks.git FEAT: first working version of the belot simulation --- diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.cpp b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp index 51ac429..42c2686 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/belot.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp @@ -3,6 +3,7 @@ #include "belot_common.hpp" #include "deck.hpp" #include "card.hpp" +#include "debug.hpp" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -11,8 +12,6 @@ #include #include -extern SemaphoreHandle_t uart_mutex; - Belot::Belot() : deck(), game_mode{AllPass}, joined_players{0}, player_in_turn{0}, bid_winner{-1}, dealer{1}, multiplier{1}, pass_counter{0}, tricks_played{0} { for (uint8_t i = 0; i < NUM_TEAMS; i++) @@ -52,6 +51,7 @@ int8_t Belot::join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t if (joined_players == NUM_PLAYERS) { + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(player_names[0]); Serial.print(" and "); @@ -65,6 +65,7 @@ int8_t Belot::join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t { big_oof(); } + #endif if (xSemaphoreGive(player_turn_semaphors[player_in_turn]) != pdTRUE) { big_oof(); @@ -250,6 +251,7 @@ int Belot::play_card(Card *card, int8_t player_id) { int8_t winning_team = calculate_score_round((player_id + 1) % NUM_PLAYERS); + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print("\nScore:\nEven team - "); Serial.println(score_round[0]); @@ -260,6 +262,12 @@ int Belot::play_card(Card *card, int8_t player_id) { big_oof(); } + #endif + + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + trick[i] = NO_CARD; + } tricks_played++; if (tricks_played == TRICKS_PER_GAME) @@ -275,21 +283,8 @@ int Belot::play_card(Card *card, int8_t player_id) int8_t Belot::calculate_score_round(uint8_t first_card) { - card_suite_t requested_suite = trick[first_card].get_suite(); - uint8_t highest_strenght = trick[first_card].get_strength(game_mode); - uint8_t winning_player_id = first_card; - - for (uint8_t i = 1; i < NUM_PLAYERS; i++) - { - Card next_card = trick[(first_card + i) % NUM_PLAYERS]; - - if (next_card.get_suite() == requested_suite && next_card.get_strength(game_mode) > highest_strenght) - { - highest_strenght = next_card.get_strength(game_mode); - winning_player_id = (first_card + i) % NUM_PLAYERS; - } - } - + + int8_t winning_player_id = find_winning_card(trick, first_card, game_mode); uint8_t winning_team = winning_player_id % 2; for (uint8_t i = 0; i < NUM_PLAYERS; i++) @@ -311,3 +306,40 @@ void Belot::calculate_score_game() vTaskDelay(1000); } } + +int8_t find_winning_card(const Card *cards, uint8_t first_card, game_mode_t game_mode) +{ + if (cards == NULL) + { + return -1; + } + if (game_mode == AllPass) + { + return -2; + } + + card_suite_t requested_suite = cards[first_card].get_suite(); + uint8_t highest_strenght = cards[first_card].get_strength(game_mode); + uint8_t winning_card_num = first_card; + + for (uint8_t i = 1; i < NUM_PLAYERS; i++) + { + Card next_card = cards[(first_card + i) % NUM_PLAYERS]; + + if (game_mode == NoTrumps || game_mode == AllTrumps) + { + if (next_card.get_suite() == requested_suite && next_card.get_strength(game_mode) > highest_strenght) + { + highest_strenght = next_card.get_strength(game_mode); + winning_card_num = (first_card + i) % NUM_PLAYERS; + } + } + else if (next_card.get_strength(game_mode) > highest_strenght) + { + highest_strenght = next_card.get_strength(game_mode); + winning_card_num = (first_card + i) % NUM_PLAYERS; + } + } + + return winning_card_num; +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.hpp b/Examples/FreeRTOS/freertos_belot_sim/belot.hpp index f12a08c..a882d0b 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/belot.hpp +++ b/Examples/FreeRTOS/freertos_belot_sim/belot.hpp @@ -74,4 +74,6 @@ class Belot int play_card(Card *card, int8_t player_id); }; +int8_t find_winning_card(const Card *cards, uint8_t first_card, game_mode_t game_mode); + #endif \ No newline at end of file diff --git a/Examples/FreeRTOS/freertos_belot_sim/card.cpp b/Examples/FreeRTOS/freertos_belot_sim/card.cpp index 8f17a2a..54462ff 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/card.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/card.cpp @@ -19,17 +19,17 @@ Card::Card() : suite{InvalidSuite}, value{InvalidValue} Card::Card(card_suite_t suite_init, card_value_t value_init) : suite{suite_init}, value{value_init} {} -card_suite_t Card::get_suite() +card_suite_t Card::get_suite() const { return suite; } -card_value_t Card::get_value() +card_value_t Card::get_value() const { return value; } -int Card::get_strength(game_mode_t gamemode) +int Card::get_strength(game_mode_t gamemode) const { switch (gamemode) { @@ -43,7 +43,7 @@ int Card::get_strength(game_mode_t gamemode) } } -int Card::get_points(game_mode_t gamemode) +int Card::get_points(game_mode_t gamemode) const { switch (gamemode) { @@ -58,7 +58,7 @@ int Card::get_points(game_mode_t gamemode) } // Requires at least 18 bytes of buffer -int Card::print(char *buf) +int Card::print(char *buf) const { const char *value_text = NULL; const char *suite_text = NULL; @@ -104,3 +104,14 @@ bool operator!=(const Card lhs, const Card rhs) return (lhs.suite != rhs.suite || lhs.value != rhs.value); } +card_suite_t game_mode_to_card_suite(game_mode_t game_mode) +{ + switch (game_mode) + { + case ClubsTrump: return Clubs; + case DiamondsTrump: return Diamonds; + case HeartsTrump: return Hearts; + case SpadesTrump: return Spades; + default: return InvalidSuite; + } +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/card.hpp b/Examples/FreeRTOS/freertos_belot_sim/card.hpp index 00c6611..37d35c6 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/card.hpp +++ b/Examples/FreeRTOS/freertos_belot_sim/card.hpp @@ -39,15 +39,17 @@ class Card public: Card(); Card(card_suite_t suite_init, card_value_t value_init); - card_suite_t get_suite(); - card_value_t get_value(); - int get_strength(game_mode_t gamemode); - int get_points(game_mode_t gamemode); - int print(char *buf); + card_suite_t get_suite() const; + card_value_t get_value() const; + int get_strength(game_mode_t gamemode) const; + int get_points(game_mode_t gamemode) const; + int print(char *buf) const; friend bool operator==(const Card lhs, const Card rhs); friend bool operator!=(const Card lhs, const Card rhs); }; extern const Card NO_CARD; +card_suite_t game_mode_to_card_suite(game_mode_t game_mode); + #endif diff --git a/Examples/FreeRTOS/freertos_belot_sim/debug.hpp b/Examples/FreeRTOS/freertos_belot_sim/debug.hpp new file mode 100644 index 0000000..e62adf4 --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/debug.hpp @@ -0,0 +1,8 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#define DEBUG + +extern SemaphoreHandle_t uart_mutex; + +#endif \ No newline at end of file diff --git a/Examples/FreeRTOS/freertos_belot_sim/deck.cpp b/Examples/FreeRTOS/freertos_belot_sim/deck.cpp index 47229c0..e8b7022 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/deck.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/deck.cpp @@ -99,9 +99,9 @@ int Deck::add_multiple(Card *cards_buf, size_t num_cards) return -2; } - if (end + num_cards > DECK_SIZE) + if (end + num_cards >= DECK_SIZE) { - size_t first_portion = num_cards - (end + num_cards) % DECK_SIZE; + size_t first_portion = num_cards - (end + 1 + num_cards) % DECK_SIZE; memcpy((void *) (cards + end), (const void *) cards_buf, first_portion * sizeof(Card)); memcpy((void *) cards, (const void *) (cards_buf + first_portion), (num_cards - first_portion) * sizeof(Card)); } @@ -132,19 +132,14 @@ void Deck::shuffle() } } +// Moving the start and end counters without copying any cards can have the same effect but I've implemented it this way for the sake of realism void Deck::split() { Card buf[DECK_SIZE]; - char buffer[600]; - print(buffer); - Serial.println(buffer); - Serial.println("==============================================="); // random is NOT thread-safe but using it here is fine because only one thread modifies the deck at a time (the dealer) size_t pos = (size_t) random() % remaining; draw_multiple(buf, pos); add_multiple(buf, pos); - print(buffer); - Serial.println(buffer); } // Requires at least 577 bytes of buffer diff --git a/Examples/FreeRTOS/freertos_belot_sim/dumb_af_bot.cpp b/Examples/FreeRTOS/freertos_belot_sim/dumb_af_bot.cpp new file mode 100644 index 0000000..d70cd0e --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/dumb_af_bot.cpp @@ -0,0 +1,140 @@ +#include "dumb_af_bot.hpp" +#include "card.hpp" +#include "belot.hpp" +#include "belot_common.hpp" + +#include +#include + +int dumb_af_bot_init(void *state, int8_t player_id, const Card *hand, const Card *trick, const Card *previous_trick, const bid_t *bids) +{ + if (state == NULL || hand == NULL || trick == NULL || previous_trick == NULL || bids == NULL) + { + return -1; + } + + dumb_af_bot_state_t *st = (dumb_af_bot_state_t *) state; + + st->player_id = player_id; + st->hand = hand; + st->trick = trick; + st->previous_trick = previous_trick; + st->bids = bids; + st->game_mode = AllPass; + st->all_trumps_announced = false; + + return 0; +} + +bid_t dumb_af_bot_bid(void *state) +{ + if (state == NULL) + { + return BidNone; + } + + dumb_af_bot_state_t *st = (dumb_af_bot_state_t *) state; + + // 30 godini TUES, 30 godini vsichko koz! + if (st->all_trumps_announced) + { + return BidPass; + } + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + if (st->bids[i] == BidAllTrumps) + { + st->all_trumps_announced = true; + break; + } + } + if (st->all_trumps_announced) + { + return BidPass; + } + else + { + st->all_trumps_announced = true; + return BidAllTrumps; + } +} + +int dumb_af_bot_choose_card(void *state) +{ + if (state == NULL) + { + return -1; + } + + card_suite_t requested_suite = InvalidSuite; + dumb_af_bot_state_t *st = (dumb_af_bot_state_t *) state; + card_suite_t trump_suite = game_mode_to_card_suite(st->game_mode); + int8_t winning_card_id = st->player_id; + + // Determine the requested suite + for (uint8_t i = 1; i < NUM_PLAYERS; i++) + { + if (st->trick[(st->player_id + i) % NUM_PLAYERS] != NO_CARD) + { + requested_suite = st->trick[(st->player_id + i) % NUM_PLAYERS].get_suite(); + winning_card_id = find_winning_card(st->trick, (st->player_id + i) % NUM_PLAYERS, st->game_mode); + break; + } + } + // Determine the strenght of the strongest card so far + int winning_card_strenght = st->trick[winning_card_id].get_strength(st->game_mode); + + if (requested_suite != InvalidSuite) + { + // Find the first card that matches the suite and is stronger + for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++) + { + if (st->hand[i].get_suite() == requested_suite && st->hand[i].get_strength(st->game_mode) > winning_card_strenght) + { + return i; + } + } + // Just throw the first card that matches the suite + for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++) + { + if (st->hand[i].get_suite() == requested_suite) + { + return i; + } + } + if (trump_suite != InvalidSuite) + { + // Find the first card that matches the trump suite and is strong enough + for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++) + { + if (st->hand[i].get_suite() == trump_suite && st->hand[i].get_strength(st->game_mode) > winning_card_strenght) + { + return i; + } + } + } + } + // Either we don't have a matching card or we are first in turn. In that case just throw the first card we have. + for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++) + { + if (st->hand[i] != NO_CARD) + { + return i; + } + } + + // We're out of cards! This shouldn't happen. + return -2; +} + +int dumb_af_bot_set_game_mode(void *state, game_mode_t game_mode) +{ + if (state == NULL) + { + return -1; + } + + ((dumb_af_bot_state_t *) state)->game_mode = game_mode; + + return 0; +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/dumb_af_bot.hpp b/Examples/FreeRTOS/freertos_belot_sim/dumb_af_bot.hpp new file mode 100644 index 0000000..a284ec4 --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/dumb_af_bot.hpp @@ -0,0 +1,30 @@ +#ifndef DUMB_AF_BOT_H +#define DUMB_AF_BOT_H + +#include "card.hpp" +#include "belot.hpp" +#include "belot_common.hpp" + +#include + +#define DUMB_AF_BOT_STATE_SIZE sizeof(dumb_af_bot_state_t) + +typedef struct +{ + game_mode_t game_mode; + int8_t player_id; + const Card *hand; + const Card *trick; + const Card *previous_trick; + const bid_t *bids; + bool all_trumps_announced; + //other stuff +} +dumb_af_bot_state_t; + +int dumb_af_bot_init(void *state, int8_t player_id, const Card *hand, const Card *trick, const Card *previous_trick, const bid_t *bids); +bid_t dumb_af_bot_bid(void *state); +int dumb_af_bot_choose_card(void *state); +int dumb_af_bot_set_game_mode(void *state, game_mode_t game_mode); + +#endif \ No newline at end of file diff --git a/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino b/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino index 070fdad..709e6eb 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino +++ b/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino @@ -2,6 +2,7 @@ #include "belot_common.hpp" #include "card.hpp" #include "player.hpp" +#include "dumb_af_bot.hpp" #include @@ -9,14 +10,36 @@ #define PLAYERS 6 #define NOISE_PIN 34 -static void play_belot(__attribute__((unused)) void *args); +typedef struct +{ + int (*bot_init)(void *state, int8_t player_id, const Card *hand, const Card *trick, const Card *previous_trick, const bid_t *bids); + bid_t (*bot_bid)(void *state); + int (*bot_choose_card)(void *state); + int (*bot_set_game_mode)(void *state, game_mode_t game_mode); + void *bot_state; +} +player_args_t; + +static void play_belot(void *pvParameters); +static void think_really_hard(void); SemaphoreHandle_t uart_mutex = xSemaphoreCreateMutex(); static Belot *lobby = NULL; static SemaphoreHandle_t lobby_mutex; + static const char *player_names[PLAYERS] = {"Asen", "Boris", "Violeta", "Georgi", "Dimitar", "Elena"}; static TaskHandle_t player_handles[PLAYERS]; +static const size_t bot_state_sizes[PLAYERS] = {DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE}; +static player_args_t player_args[PLAYERS] = +{ + {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL}, + {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL}, + {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL}, + {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL}, + {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL}, + {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL} +}; void setup() { @@ -39,7 +62,15 @@ void setup() for (uint8_t i = 0; i < PLAYERS; i++) { - if (xTaskCreate(play_belot, player_names[i], THREAD_STACK_SIZE, NULL, 1, &player_handles[i]) != pdPASS) + if (bot_state_sizes[i]) + { + player_args[i].bot_state = malloc(bot_state_sizes[i]); + if (player_args[i].bot_state == NULL) + { + big_oof(); + } + } + if (xTaskCreate(play_belot, player_names[i], THREAD_STACK_SIZE, (void *) &player_args[i], 1, &player_handles[i]) != pdPASS) { big_oof(); } @@ -49,9 +80,10 @@ void setup() void loop() {} -void play_belot(__attribute__((unused)) void *args) +static void play_belot(void *pvParameters) { Player player = Player(pcTaskGetName(NULL)); + player_args_t *args = (player_args_t *) pvParameters; // Games loop while (true) @@ -59,10 +91,8 @@ void play_belot(__attribute__((unused)) void *args) player.join_or_start_game(&lobby, lobby_mutex); int8_t id = player.get_id(); - __attribute__((unused)) const Card *hand = player.view_hand(); - __attribute__((unused)) const Card *trick = player.view_trick(); - __attribute__((unused)) const Card *previous_trick = player.view_previous_trick(); - __attribute__((unused)) const bid_t *bids = player.view_bids(); + // The following methods on the Player class return pointers, so the bot can save them in its state. This way there is no need to pass many arguments to the other bot functions. + args->bot_init(args->bot_state, id, player.view_hand(), player.view_trick(), player.view_previous_trick(), player.view_bids()); // Rounds loop while (true) @@ -80,8 +110,7 @@ void play_belot(__attribute__((unused)) void *args) player.collect_cards(); while (!player.bidding_over()) { - // 30 godini TUES, 30 godini vsichko koz! - player.get_game_mode() == AllTrumps ? player.bid(BidPass) : player.bid(BidAllTrumps); + player.bid(args->bot_bid(args->bot_state)); } if (player.get_game_mode() == AllPass) { @@ -93,15 +122,20 @@ void play_belot(__attribute__((unused)) void *args) player.deal_second(); } player.collect_cards(); + args->bot_set_game_mode(args->bot_state, player.get_game_mode()); // Tricks loop for (uint8_t i = 0; i < TRICKS_PER_GAME; i++) { - vTaskDelay(1000); - // Just throw the cards in order, who cares - player.play_card(i); + think_really_hard(); + player.play_card(args->bot_choose_card(args->bot_state)); } player.end_round(); } player.end_game(); } } + +static void think_really_hard(void) +{ + vTaskDelay(1000); +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/player.cpp b/Examples/FreeRTOS/freertos_belot_sim/player.cpp index b6719da..2d3d23c 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/player.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/player.cpp @@ -3,6 +3,7 @@ #include "belot.hpp" #include "belot_common.hpp" #include "card.hpp" +#include "debug.hpp" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -10,8 +11,6 @@ #include -extern SemaphoreHandle_t uart_mutex; - Player::Player(const char *new_name) : player_id{-1}, game{NULL}, created_game{false}, cards_received{0} { // Not a big deal if name is NULL, Serial.print does check for NULL pointers. @@ -49,6 +48,7 @@ int Player::join_or_start_game(Belot **lobby, SemaphoreHandle_t lobby_mutex) return -1; } + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(name); Serial.println(" entered the lobby."); @@ -56,6 +56,7 @@ int Player::join_or_start_game(Belot **lobby, SemaphoreHandle_t lobby_mutex) { big_oof(); } + #endif while (player_id < 0) { @@ -134,13 +135,18 @@ int8_t Player::get_dealer_id() void Player::split_deck() { + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(name); + Serial.print("("); + Serial.print(player_id); + Serial.print(")"); Serial.println(" splits the deck."); if (xSemaphoreGive(uart_mutex) != pdTRUE) { big_oof(); } + #endif game->split_deck(); end_turn(); } @@ -149,13 +155,18 @@ int Player::deal_two() { Card buf[2]; + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(name); + Serial.print("("); + Serial.print(player_id); + Serial.print(")"); Serial.println(" deals 2 cards to each player."); if (xSemaphoreGive(uart_mutex) != pdTRUE) { big_oof(); } + #endif for (uint8_t i = 0; i < NUM_PLAYERS; i++) { @@ -176,13 +187,18 @@ int Player::deal_three() { Card buf[3]; + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(name); + Serial.print("("); + Serial.print(player_id); + Serial.print(")"); Serial.println(" deals 3 cards to each player."); if (xSemaphoreGive(uart_mutex) != pdTRUE) { big_oof(); } + #endif for (uint8_t i = 0; i < NUM_PLAYERS; i++) { @@ -240,8 +256,12 @@ const bid_t *Player::view_bids() int Player::bid(bid_t bid) { + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(name); + Serial.print("("); + Serial.print(player_id); + Serial.print(")"); switch (bid) { case BidPass: Serial.println(": pass"); break; @@ -259,6 +279,7 @@ int Player::bid(bid_t bid) { big_oof(); } + #endif int error_code = game->bid(bid, player_id); if (!error_code) { @@ -315,14 +336,19 @@ int Player::play_card(uint8_t card_num) } hand[card_num].print(card_text); + #ifdef DEBUG while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); Serial.print(name); + Serial.print("("); + Serial.print(player_id); + Serial.print(")"); Serial.print(": "); Serial.println(card_text); if (xSemaphoreGive(uart_mutex) != pdTRUE) { big_oof(); } + #endif int error_code = game->play_card(&hand[card_num], player_id); if (!error_code)