From: Vladimir Garistov Date: Thu, 11 Apr 2024 04:22:36 +0000 (+0300) Subject: WIP: FreeRTOS belot example, almost working X-Git-Url: https://kolegite.com/gitweb/?a=commitdiff_plain;h=438600f633a1a1d7c052a77f0446a36063a4682b;p=vmks.git WIP: FreeRTOS belot example, almost working --- diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.cpp b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp index 3934f0c..51ac429 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/belot.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp @@ -1,3 +1,4 @@ +#include "freertos/projdefs.h" #include "belot.hpp" #include "belot_common.hpp" #include "deck.hpp" @@ -12,7 +13,7 @@ extern SemaphoreHandle_t uart_mutex; -Belot::Belot() : deck(), game_mode{NoTrumps}, bidding_over{false}, joined_players{0}, player_in_turn{0}, bid_winner{0}, multiplier{1} +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++) { @@ -26,8 +27,12 @@ Belot::Belot() : deck(), game_mode{NoTrumps}, bidding_over{false}, joined_player // Not sure what to do with these here. ¯\_(ツ)_/¯ //player_card_inputs[i] = ; //player_turn_semaphors[i] = ; - pot[i] = Card(); + trick[i] = Card(); + previous_trick[i] = Card(); + bids[i] = BidNone; } + + deck.shuffle(); } int8_t Belot::join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t turn_semaphor) @@ -60,8 +65,249 @@ int8_t Belot::join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t { big_oof(); } - xSemaphoreGive(player_turn_semaphors[player_in_turn]); + if (xSemaphoreGive(player_turn_semaphors[player_in_turn]) != pdTRUE) + { + big_oof(); + } } return player_id; -} \ No newline at end of file +} + +void Belot::signal_next_player() +{ + player_in_turn = (player_in_turn + 1) % NUM_PLAYERS; + if (xSemaphoreGive(player_turn_semaphors[player_in_turn]) != pdTRUE) + { + big_oof(); + } +} + +int8_t Belot::get_dealer_id() +{ + return dealer; +} + +void Belot::split_deck() +{ + deck.split(); +} + +int Belot::draw_cards(Card *cards_buf, size_t num_cards) +{ + return deck.draw_multiple(cards_buf, num_cards); +} + +int Belot::give_cards_to_player(Card *cards_buf, size_t num_cards, int8_t player_id) +{ + if (cards_buf == NULL) + { + return -1; + } + if (player_id < 0 || player_id >= NUM_PLAYERS) + { + return -2; + } + + for (size_t i = 0; i < num_cards; i++) + { + if (xQueueSendToBack(player_card_inputs[player_id], &cards_buf[i], portMAX_DELAY) != pdTRUE) + { + return -3; + } + } + + return 0; +} + +const bid_t *Belot::view_bids() +{ + return (const bid_t *) bids; +} + +int Belot::bid(bid_t bid, int8_t player_id) +{ + bid_t highest_bid = current_highest_bid(); + + if (bid == BidNone) + { + return -1; + } + if (bid == BidPass) + { + bids[player_id] = bid; + pass_counter++; + if (bidding_over()) + { + // When the curent player ends his turn, the player_in_turn counter will be incremented and the dealer will deal the last cards + player_in_turn = (dealer + NUM_PLAYERS - 1) % NUM_PLAYERS; + } + return 0; + } + if (bid <= highest_bid) + { + return -2; + } + if (bid == BidContra) + { + multiplier = 2; + } + else if (bid == BidRecontra) + { + multiplier = 4; + } + else + { + switch (bid) + { + case BidNoTrumps: game_mode = NoTrumps; break; + case BidClubs: game_mode = ClubsTrump; break; + case BidDiamonds: game_mode = DiamondsTrump; break; + case BidHearts: game_mode = HeartsTrump; break; + case BidSpades: game_mode = SpadesTrump; break; + case BidAllTrumps: game_mode = AllTrumps; break; + default: break; + } + bid_winner = player_id; + } + bids[player_id] = bid; + pass_counter = 0; + + return 0; +} + +bid_t Belot::current_highest_bid() +{ + bid_t highest_bid = BidNone, next_bid; + + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + next_bid = bids[(dealer + 1 + i) % NUM_PLAYERS]; + if (next_bid > highest_bid) + { + highest_bid = next_bid; + } + } + + return highest_bid; +} + +bool Belot::bidding_over() +{ + bid_t highest_bid = current_highest_bid(); + + switch (highest_bid) + { + case BidNone: return false; + case BidPass: return pass_counter == NUM_PLAYERS; + default: return pass_counter == NUM_PLAYERS - 1; + } +} + +game_mode_t Belot::get_game_mode() +{ + return game_mode; +} + +int Belot::return_card(Card *card) +{ + if (card == NULL) + { + return -1; + } + + deck.add(card); + *card = NO_CARD; + + return 0; +} + +const Card *Belot::view_trick() +{ + return (const Card*) trick; +} + +const Card *Belot::view_previous_trick() +{ + return (const Card*) previous_trick; +} + +int Belot::play_card(Card *card, int8_t player_id) +{ + if (card == NULL) + { + return -1; + } + if (player_id < 0 || player_id >= NUM_PLAYERS) + { + return -2; + } + + trick[player_id] = *card; + return_card(card); + + if (trick[(player_id + 1) % NUM_PLAYERS] != NO_CARD) + { + int8_t winning_team = calculate_score_round((player_id + 1) % NUM_PLAYERS); + + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print("\nScore:\nEven team - "); + Serial.println(score_round[0]); + Serial.print("Odd team - "); + Serial.println(score_round[1]); + Serial.println(); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + + tricks_played++; + if (tricks_played == TRICKS_PER_GAME) + { + score_round[winning_team] += 10; + tricks_played = 0; + calculate_score_game(); + } + } + + return 0; +} + +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; + } + } + + uint8_t winning_team = winning_player_id % 2; + + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + score_round[winning_team] += trick[i].get_points(game_mode); + previous_trick[i] = trick[i]; + trick[i] = NO_CARD; + } + player_in_turn = (winning_player_id + NUM_PLAYERS - 1) % NUM_PLAYERS; + + return winning_team; +} + +void Belot::calculate_score_game() +{ + // TODO + while (true) + { + vTaskDelay(1000); + } +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.hpp b/Examples/FreeRTOS/freertos_belot_sim/belot.hpp index 2fd5722..f12a08c 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/belot.hpp +++ b/Examples/FreeRTOS/freertos_belot_sim/belot.hpp @@ -14,27 +14,64 @@ #define NUM_TEAMS 2 #define NUM_PLAYERS 4 #define CARDS_PER_PLAYER 8 +#define TRICKS_PER_GAME 8 + +typedef enum +{ + BidNone = 0, + BidPass = 1, + BidNoTrumps = 2, + BidClubs = 3, + BidDiamonds = 4, + BidHearts = 5, + BidSpades = 6, + BidAllTrumps = 7, + BidContra = 8, + BidRecontra = 9 +} +bid_t; class Belot { private: Deck deck; game_mode_t game_mode; - bool bidding_over; uint8_t joined_players; uint8_t player_in_turn; - uint8_t bid_winner; + int8_t bid_winner; + int8_t dealer; uint8_t multiplier; + uint8_t pass_counter; + uint8_t tricks_played; uint16_t score_game[NUM_TEAMS]; uint16_t score_round[NUM_TEAMS]; const char *player_names[NUM_PLAYERS]; QueueHandle_t player_card_inputs[NUM_PLAYERS]; SemaphoreHandle_t player_turn_semaphors[NUM_PLAYERS]; - Card pot[NUM_PLAYERS]; + Card trick[NUM_PLAYERS]; + Card previous_trick[NUM_PLAYERS]; + bid_t bids[NUM_PLAYERS]; + + bid_t current_highest_bid(); + int8_t calculate_score_round(uint8_t first_card); + void calculate_score_game(); public: Belot(); int8_t join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t turn_semaphor); + void signal_next_player(); + int8_t get_dealer_id(); + void split_deck(); + int draw_cards(Card *cards_buf, size_t num_cards); + int give_cards_to_player(Card *cards_buf, size_t num_cards, int8_t player_id); + const bid_t *view_bids(); + int bid(bid_t bid, int8_t player_id); + bool bidding_over(); + game_mode_t get_game_mode(); + int return_card(Card *card); + const Card *view_trick(); + const Card *view_previous_trick(); + int play_card(Card *card, int8_t player_id); }; #endif \ No newline at end of file diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot_common.hpp b/Examples/FreeRTOS/freertos_belot_sim/belot_common.hpp index 6fc787a..c17f22c 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/belot_common.hpp +++ b/Examples/FreeRTOS/freertos_belot_sim/belot_common.hpp @@ -8,6 +8,7 @@ typedef enum { + AllPass, NoTrumps, ClubsTrump, DiamondsTrump, diff --git a/Examples/FreeRTOS/freertos_belot_sim/card.cpp b/Examples/FreeRTOS/freertos_belot_sim/card.cpp index 1f8440f..8f17a2a 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/card.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/card.cpp @@ -1,16 +1,19 @@ #include "card.hpp" #include "belot_common.hpp" +#include #include #include -// 7 8 9 10 J Q K A -static const uint8_t no_trump_strenghts[NUM_OF_VALUES] = {0, 1, 2, 6, 3, 4, 5, 7}; -static const uint8_t trump_strenghts[NUM_OF_VALUES] = {8, 9, 14, 12, 15, 10, 11, 13}; -static const uint8_t no_trump_points[NUM_OF_VALUES] = {0, 0, 0, 10, 2, 3, 4, 11}; -static const uint8_t trump_points[NUM_OF_VALUES] = {0, 0, 14, 10, 20, 3, 4, 11}; +const Card NO_CARD = Card(InvalidSuite, InvalidValue); -Card::Card() : suite{Spades}, value{Ace} +// 7 8 9 10 J Q K A INVALID +static const int8_t no_trump_strenghts[NUM_OF_VALUES + 1] = {0, 1, 2, 6, 3, 4, 5, 7, -1}; +static const int8_t trump_strenghts[NUM_OF_VALUES + 1] = {8, 9, 14, 12, 15, 10, 11, 13, -1}; +static const int8_t no_trump_points[NUM_OF_VALUES + 1] = {0, 0, 0, 10, 2, 3, 4, 11, -1}; +static const int8_t trump_points[NUM_OF_VALUES + 1] = {0, 0, 14, 10, 20, 3, 4, 11, -1}; + +Card::Card() : suite{InvalidSuite}, value{InvalidValue} {} Card::Card(card_suite_t suite_init, card_value_t value_init) : suite{suite_init}, value{value_init} @@ -36,7 +39,7 @@ int Card::get_strength(game_mode_t gamemode) case HeartsTrump: return suite == Hearts ? trump_strenghts[(size_t) value] : no_trump_strenghts[(size_t) value]; case SpadesTrump: return suite == Spades ? trump_strenghts[(size_t) value] : no_trump_strenghts[(size_t) value]; case AllTrumps: return trump_strenghts[(size_t) value]; - default: return -1; + default: return -2; } } @@ -50,6 +53,54 @@ int Card::get_points(game_mode_t gamemode) case HeartsTrump: return suite == Hearts ? trump_points[(size_t) value] : no_trump_points[(size_t) value]; case SpadesTrump: return suite == Spades ? trump_points[(size_t) value] : no_trump_points[(size_t) value]; case AllTrumps: return trump_points[(size_t) value]; - default: return -1; + default: return -2; + } +} + +// Requires at least 18 bytes of buffer +int Card::print(char *buf) +{ + const char *value_text = NULL; + const char *suite_text = NULL; + + if (buf == NULL) + { + return -1; + } + + switch (value) + { + case Seven: value_text = "Seven"; break; + case Eight: value_text = "Eight"; break; + case Nine: value_text = "Nine"; break; + case Ten: value_text = "Ten"; break; + case Jack: value_text = "Jack"; break; + case Queen: value_text = "Queen"; break; + case King: value_text = "King"; break; + case Ace: value_text = "Ace"; break; + default: value_text = ""; break; } + switch (suite) + { + case Clubs: suite_text = " of Clubs"; break; + case Diamonds: suite_text = " of Diamonds"; break; + case Hearts: suite_text = " of Hearts"; break; + case Spades: suite_text = " of Spades"; break; + default: suite_text = ""; break; + } + strcpy(buf, value_text); + strcpy(buf + strlen(value_text), suite_text); + + return 0; +} + +bool operator==(const Card lhs, const Card rhs) +{ + return (lhs.suite == rhs.suite && lhs.value == rhs.value); } + +bool operator!=(const Card lhs, const Card rhs) +{ + return (lhs.suite != rhs.suite || lhs.value != rhs.value); +} + diff --git a/Examples/FreeRTOS/freertos_belot_sim/card.hpp b/Examples/FreeRTOS/freertos_belot_sim/card.hpp index 9b0f1ee..00c6611 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/card.hpp +++ b/Examples/FreeRTOS/freertos_belot_sim/card.hpp @@ -11,7 +11,8 @@ typedef enum __attribute__((packed)) Clubs = 0, Diamonds = 1, Hearts = 2, - Spades = 3 + Spades = 3, + InvalidSuite } card_suite_t; @@ -24,7 +25,8 @@ typedef enum __attribute__((packed)) Jack = 4, Queen = 5, King = 6, - Ace = 7 + Ace = 7, + InvalidValue } card_value_t; @@ -41,6 +43,11 @@ class Card card_value_t get_value(); int get_strength(game_mode_t gamemode); int get_points(game_mode_t gamemode); + int print(char *buf); + friend bool operator==(const Card lhs, const Card rhs); + friend bool operator!=(const Card lhs, const Card rhs); }; +extern const Card NO_CARD; + #endif diff --git a/Examples/FreeRTOS/freertos_belot_sim/deck.cpp b/Examples/FreeRTOS/freertos_belot_sim/deck.cpp index b476618..47229c0 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/deck.cpp +++ b/Examples/FreeRTOS/freertos_belot_sim/deck.cpp @@ -26,10 +26,14 @@ Deck::Deck() int Deck::draw(Card *card_ptr) { - if (remaining == 0) + if (card_ptr == NULL) { return -1; } + if (remaining == 0) + { + return -2; + } *card_ptr = cards[start]; start = (start + 1) % DECK_SIZE; @@ -40,10 +44,14 @@ int Deck::draw(Card *card_ptr) int Deck::draw_multiple(Card *cards_buf, size_t num_cards) { - if (remaining < num_cards) + if (cards_buf == NULL) { return -1; } + if (remaining < num_cards) + { + return -2; + } if (start + num_cards > DECK_SIZE) { @@ -64,10 +72,14 @@ int Deck::draw_multiple(Card *cards_buf, size_t num_cards) int Deck::add(Card *card_ptr) { - if (remaining >= DECK_SIZE) + if (card_ptr == NULL) { return -1; } + if (remaining >= DECK_SIZE) + { + return -2; + } end = (end + 1) % DECK_SIZE; cards[end] = *card_ptr; @@ -78,10 +90,14 @@ int Deck::add(Card *card_ptr) int Deck::add_multiple(Card *cards_buf, size_t num_cards) { - if (remaining > DECK_SIZE - num_cards) + if (cards_buf == NULL) { return -1; } + if (remaining > DECK_SIZE - num_cards) + { + return -2; + } if (end + num_cards > DECK_SIZE) { @@ -119,8 +135,32 @@ void Deck::shuffle() 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 +int Deck::print(char *buf) +{ + char card_text[18]; + size_t pos = 0; + + for (uint8_t i = 0; i < DECK_SIZE; i++) + { + cards[i].print(card_text); + strcpy(buf + pos, card_text); + pos += strlen(card_text); + buf[pos] = '\n'; + pos++; + } + + return 0; } diff --git a/Examples/FreeRTOS/freertos_belot_sim/deck.hpp b/Examples/FreeRTOS/freertos_belot_sim/deck.hpp index abc5d76..180ea78 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/deck.hpp +++ b/Examples/FreeRTOS/freertos_belot_sim/deck.hpp @@ -24,6 +24,7 @@ class Deck int add_multiple(Card *cards_buf, size_t num_cards); void shuffle(); void split(); + int print(char *buf); }; #endif diff --git a/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino b/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino index 7a26d4c..070fdad 100644 --- a/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino +++ b/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino @@ -1,29 +1,45 @@ #include "belot.hpp" #include "belot_common.hpp" #include "card.hpp" +#include "player.hpp" -#define THREAD_STACK_SIZE 1024 +#include + +#define THREAD_STACK_SIZE 2048 #define PLAYERS 6 +#define NOISE_PIN 34 -static void belot_player(__attribute__((unused)) void *args); +static void play_belot(__attribute__((unused)) void *args); SemaphoreHandle_t uart_mutex = xSemaphoreCreateMutex(); + static Belot *lobby = NULL; -static SemaphoreHandle_t lobby_mutex = xSemaphoreCreateMutex(); +static SemaphoreHandle_t lobby_mutex; static const char *player_names[PLAYERS] = {"Asen", "Boris", "Violeta", "Georgi", "Dimitar", "Elena"}; static TaskHandle_t player_handles[PLAYERS]; void setup() { + pinMode(NOISE_PIN, INPUT); + //srandom(analogRead(NOISE_PIN)); + srandom(0); + + lobby_mutex = xSemaphoreCreateMutex(); + if (lobby_mutex == NULL) + { + big_oof(); + } + Serial.begin(38400); // Without some delay here Arduino IDE misses the first output delay(1000); // We can skip taking and giving uart_mutex here because no other threads are running yet Serial.println("The lobby is open!"); + delay(1000); for (uint8_t i = 0; i < PLAYERS; i++) { - if (xTaskCreate(belot_player, player_names[i], THREAD_STACK_SIZE, NULL, 1, &player_handles[i]) != pdPASS) + if (xTaskCreate(play_belot, player_names[i], THREAD_STACK_SIZE, NULL, 1, &player_handles[i]) != pdPASS) { big_oof(); } @@ -33,82 +49,59 @@ void setup() void loop() {} -void belot_player(__attribute__((unused)) void *args) +void play_belot(__attribute__((unused)) void *args) { - const char *name = pcTaskGetName(NULL); - QueueHandle_t card_input = xQueueCreate(CARDS_PER_PLAYER, sizeof(Card)); - SemaphoreHandle_t turn_semaphor = xSemaphoreCreateBinary(); - - if (card_input == NULL || turn_semaphor == NULL) - { - big_oof(); - } + Player player = Player(pcTaskGetName(NULL)); + // Games loop while (true) { - Belot *game; - int8_t player_id = -1; - bool created_game = false; + player.join_or_start_game(&lobby, lobby_mutex); - while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); - Serial.print(name); - Serial.println(" entered the lobby."); - if (xSemaphoreGive(uart_mutex) != pdTRUE) - { - big_oof(); - } - while (player_id < 0) + 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(); + + // Rounds loop + while (true) { - if (xSemaphoreTake(lobby_mutex, portMAX_DELAY) == pdTRUE) + int8_t dealer_id = player.get_dealer_id(); + + if (id == dealer_id) { - if (lobby == NULL) - { - // No existing game, create a new one - try - { - game = new Belot; - lobby = game; - } - catch (__attribute__((unused)) int err) - { - big_oof(); - } - created_game = true; - player_id = lobby->join(name, card_input, turn_semaphor); - } - else - { - player_id = lobby->join(name, card_input, turn_semaphor); - } - if (xSemaphoreGive(lobby_mutex) != pdTRUE) - { - big_oof(); - } + player.deal_first(); } - } - - // Wait for enough players to join - while (xSemaphoreTake(turn_semaphor, portMAX_DELAY) != pdTRUE); - // If you created the game, free the lobby - if (created_game) - { - while (xSemaphoreTake(lobby_mutex, portMAX_DELAY) != pdTRUE); - lobby = NULL; - if (xSemaphoreGive(lobby_mutex) != pdTRUE) + else if ((id + 1) % NUM_PLAYERS == dealer_id) { - big_oof(); + player.split_deck(); } + 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); + } + if (player.get_game_mode() == AllPass) + { + player.end_round(); + continue; + } + if (id == dealer_id) + { + player.deal_second(); + } + player.collect_cards(); + // 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); + } + player.end_round(); } - // Starting a new game - while (true) - { - vTaskDelay(100); - } - - // The game has ended - if (created_game) - { - delete game; - } + player.end_game(); } } diff --git a/Examples/FreeRTOS/freertos_belot_sim/player.cpp b/Examples/FreeRTOS/freertos_belot_sim/player.cpp new file mode 100644 index 0000000..b6719da --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/player.cpp @@ -0,0 +1,334 @@ +#include "freertos/projdefs.h" +#include "player.hpp" +#include "belot.hpp" +#include "belot_common.hpp" +#include "card.hpp" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" + +#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. + // Constructors can't return an error and I just can't be bothered with C++ exceptions for this. + name = new_name; + card_input = xQueueCreate(CARDS_PER_PLAYER, sizeof(Card)); + turn_semaphor = xSemaphoreCreateBinary(); + + if (card_input == NULL || turn_semaphor == NULL) + { + big_oof(); + } + + for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++) + { + hand[i] = Card(); + } +} + +Player::~Player() +{ + vQueueDelete(card_input); + vSemaphoreDelete(turn_semaphor); +} + +void Player::wait_for_turn() +{ + while (xSemaphoreTake(turn_semaphor, portMAX_DELAY) != pdTRUE); +} + +int Player::join_or_start_game(Belot **lobby, SemaphoreHandle_t lobby_mutex) +{ + if (lobby == NULL || lobby_mutex == NULL) + { + return -1; + } + + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(name); + Serial.println(" entered the lobby."); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + + while (player_id < 0) + { + if (xSemaphoreTake(lobby_mutex, portMAX_DELAY) == pdTRUE) + { + if (*lobby == NULL) + { + // No existing game, create a new one + try + { + game = new Belot; + *lobby = game; + } + catch (__attribute__((unused)) int err) + { + big_oof(); + } + created_game = true; + player_id = game->join(name, card_input, turn_semaphor); + } + else + { + game = *lobby; + player_id = game->join(name, card_input, turn_semaphor); + } + if (xSemaphoreGive(lobby_mutex) != pdTRUE) + { + big_oof(); + } + } + } + + // Wait for enough players to join + wait_for_turn(); + // If you created the game, free the lobby + if (created_game) + { + while (xSemaphoreTake(lobby_mutex, portMAX_DELAY) != pdTRUE); + *lobby = NULL; + if (xSemaphoreGive(lobby_mutex) != pdTRUE) + { + big_oof(); + } + } + + return 0; +} + +int Player::end_game() +{ + if (game == NULL) + { + return -1; + } + + if (created_game) + { + delete game; + } + game = NULL; + created_game = false; + player_id = -1; + + return 0; +} + +int8_t Player::get_id() +{ + return player_id; +} + +int8_t Player::get_dealer_id() +{ + return game->get_dealer_id(); +} + +void Player::split_deck() +{ + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(name); + Serial.println(" splits the deck."); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + game->split_deck(); + end_turn(); +} + +int Player::deal_two() +{ + Card buf[2]; + + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(name); + Serial.println(" deals 2 cards to each player."); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + if (game->draw_cards(buf, 2)) + { + return -1; + } + if (game->give_cards_to_player(buf, 2, (player_id + i) % NUM_PLAYERS)) + { + return -2; + } + } + + return 0; +} + +int Player::deal_three() +{ + Card buf[3]; + + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(name); + Serial.println(" deals 3 cards to each player."); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + if (game->draw_cards(buf, 3)) + { + return -1; + } + if (game->give_cards_to_player(buf, 3, (player_id + i) % NUM_PLAYERS)) + { + return -2; + } + } + + return 0; +} + +int Player::deal_first() +{ + int error_code = deal_three(); + error_code += deal_two(); + end_turn(); + return error_code; +} + +int Player::deal_second() +{ + int error_code = deal_three(); + end_turn(); + return error_code; +} + +void Player::end_turn() +{ + game->signal_next_player(); + wait_for_turn(); +} + +void Player::collect_cards() +{ + while (cards_received < CARDS_PER_PLAYER && xQueueReceive(card_input, &hand[cards_received], 0) == pdTRUE) + { + cards_received++; + } +} + +const Card *Player::view_hand() +{ + return (const Card *) hand; +} + +const bid_t *Player::view_bids() +{ + return game->view_bids(); +} + +int Player::bid(bid_t bid) +{ + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(name); + switch (bid) + { + case BidPass: Serial.println(": pass"); break; + case BidNoTrumps: Serial.println(": no trumps"); break; + case BidClubs: Serial.println(": clubs"); break; + case BidDiamonds: Serial.println(": diamonds"); break; + case BidHearts: Serial.println(": hearts"); break; + case BidSpades: Serial.println(": spades"); break; + case BidAllTrumps: Serial.println(": all trumps"); break; + case BidContra: Serial.println(": contra"); break; + case BidRecontra: Serial.println(": re-contra"); break; + default: Serial.println(": "); break; + } + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + int error_code = game->bid(bid, player_id); + if (!error_code) + { + end_turn(); + } + + return error_code; +} + +bool Player::bidding_over() +{ + return game->bidding_over(); +} + +game_mode_t Player::get_game_mode() +{ + return game->get_game_mode(); +} + +void Player::end_round() +{ + for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++) + { + if (hand[i] != NO_CARD) + { + game->return_card(&hand[i]); + } + } + + end_turn(); +} + +const Card *Player::view_trick() +{ + return game->view_trick(); +} + +const Card *Player::view_previous_trick() +{ + return game->view_previous_trick(); +} + +int Player::play_card(uint8_t card_num) +{ + char card_text[20]; + + if (card_num >= CARDS_PER_PLAYER) + { + return -1; + } + if (hand[card_num] == NO_CARD) + { + return -2; + } + + hand[card_num].print(card_text); + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(name); + Serial.print(": "); + Serial.println(card_text); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + + int error_code = game->play_card(&hand[card_num], player_id); + if (!error_code) + { + end_turn(); + } + + return error_code; +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/player.hpp b/Examples/FreeRTOS/freertos_belot_sim/player.hpp new file mode 100644 index 0000000..a9a49da --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/player.hpp @@ -0,0 +1,52 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include "belot.hpp" +#include "card.hpp" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" + +#include + +class Player +{ + private: + const char *name; + int8_t player_id; + Belot *game; + bool created_game; + uint8_t cards_received; + QueueHandle_t card_input; + SemaphoreHandle_t turn_semaphor; + Card hand[CARDS_PER_PLAYER]; + + void wait_for_turn(); + int deal_two(); + int deal_three(); + + public: + Player(const char *name); + ~Player(); + int join_or_start_game(Belot **lobby, SemaphoreHandle_t lobby_mutex); + int end_game(); + int8_t get_id(); + int8_t get_dealer_id(); + void split_deck(); + int deal_first(); + int deal_second(); + void end_turn(); + void collect_cards(); + const Card *view_hand(); + const bid_t *view_bids(); + int bid(bid_t bid); + bool bidding_over(); + game_mode_t get_game_mode(); + void end_round(); + const Card *view_trick(); + const Card *view_previous_trick(); + int play_card(uint8_t card_num); +}; + +#endif \ No newline at end of file