From: Vladimir Garistov Date: Mon, 8 Apr 2024 01:09:07 +0000 (+0300) Subject: WIP: FreeRTOS belot example X-Git-Url: https://kolegite.com/gitweb/?a=commitdiff_plain;h=d734754d2ed41177076a7eca0e8c93e45dad1c59;p=vmks.git WIP: FreeRTOS belot example --- diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.cpp b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp new file mode 100644 index 0000000..3934f0c --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp @@ -0,0 +1,67 @@ +#include "belot.hpp" +#include "belot_common.hpp" +#include "deck.hpp" +#include "card.hpp" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" + +#include +#include + +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} +{ + for (uint8_t i = 0; i < NUM_TEAMS; i++) + { + score_game[i] = 0; + score_round[i] = 0; + } + + for (uint8_t i = 0; i < NUM_PLAYERS; i++) + { + player_names[i] = NULL; + // Not sure what to do with these here. ¯\_(ツ)_/¯ + //player_card_inputs[i] = ; + //player_turn_semaphors[i] = ; + pot[i] = Card(); + } +} + +int8_t Belot::join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t turn_semaphor) +{ + uint8_t player_id = joined_players; + + if (player_id >= NUM_PLAYERS) + { + return -1; + } + + player_names[player_id] = name; + player_card_inputs[player_id] = card_input; + player_turn_semaphors[player_id] = turn_semaphor; + + joined_players++; + + if (joined_players == NUM_PLAYERS) + { + while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE); + Serial.print(player_names[0]); + Serial.print(" and "); + Serial.print(player_names[2]); + Serial.print(" started a game against "); + Serial.print(player_names[1]); + Serial.print(" and "); + Serial.print(player_names[3]); + Serial.println("."); + if (xSemaphoreGive(uart_mutex) != pdTRUE) + { + big_oof(); + } + xSemaphoreGive(player_turn_semaphors[player_in_turn]); + } + + return player_id; +} \ No newline at end of file diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.hpp b/Examples/FreeRTOS/freertos_belot_sim/belot.hpp new file mode 100644 index 0000000..2fd5722 --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/belot.hpp @@ -0,0 +1,40 @@ +#ifndef BELOT_H +#define BELOT_H + +#include "belot_common.hpp" +#include "deck.hpp" +#include "card.hpp" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" + +#include + +#define NUM_TEAMS 2 +#define NUM_PLAYERS 4 +#define CARDS_PER_PLAYER 8 + +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; + uint8_t multiplier; + 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]; + + public: + Belot(); + int8_t join(const char *name, QueueHandle_t card_input, SemaphoreHandle_t turn_semaphor); +}; + +#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 new file mode 100644 index 0000000..6fc787a --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/belot_common.hpp @@ -0,0 +1,28 @@ +#ifndef BELOT_COMMON_H +#define BELOT_COMMON_H + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include + +typedef enum +{ + NoTrumps, + ClubsTrump, + DiamondsTrump, + HeartsTrump, + SpadesTrump, + AllTrumps +} +game_mode_t; + +// Executes if we encounter an unrecoverable error. Not necessarily an out-of-memory error. +static inline void big_oof(void) +{ + vTaskSuspendAll(); + Serial.println("\nNema RAM! Susipaha go toia mikrokontroler..."); + while (true); +} + +#endif diff --git a/Examples/FreeRTOS/freertos_belot_sim/card.cpp b/Examples/FreeRTOS/freertos_belot_sim/card.cpp new file mode 100644 index 0000000..1f8440f --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/card.cpp @@ -0,0 +1,55 @@ +#include "card.hpp" +#include "belot_common.hpp" + +#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}; + +Card::Card() : suite{Spades}, value{Ace} +{} + +Card::Card(card_suite_t suite_init, card_value_t value_init) : suite{suite_init}, value{value_init} +{} + +card_suite_t Card::get_suite() +{ + return suite; +} + +card_value_t Card::get_value() +{ + return value; +} + +int Card::get_strength(game_mode_t gamemode) +{ + switch (gamemode) + { + case NoTrumps: return no_trump_strenghts[(size_t) value]; + case ClubsTrump: return suite == Clubs ? trump_strenghts[(size_t) value] : no_trump_strenghts[(size_t) value]; + case DiamondsTrump: return suite == Diamonds ? trump_strenghts[(size_t) value] : no_trump_strenghts[(size_t) value]; + 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; + } +} + +int Card::get_points(game_mode_t gamemode) +{ + switch (gamemode) + { + case NoTrumps: return no_trump_points[(size_t) value]; + case ClubsTrump: return suite == Clubs ? trump_points[(size_t) value] : no_trump_points[(size_t) value]; + case DiamondsTrump: return suite == Diamonds ? trump_points[(size_t) value] : no_trump_points[(size_t) value]; + 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; + } +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/card.hpp b/Examples/FreeRTOS/freertos_belot_sim/card.hpp new file mode 100644 index 0000000..9b0f1ee --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/card.hpp @@ -0,0 +1,46 @@ +#ifndef CARD_H +#define CARD_H + +#include "belot_common.hpp" + +#define NUM_OF_SUITES 4 +#define NUM_OF_VALUES 8 + +typedef enum __attribute__((packed)) +{ + Clubs = 0, + Diamonds = 1, + Hearts = 2, + Spades = 3 +} +card_suite_t; + +typedef enum __attribute__((packed)) +{ + Seven = 0, + Eight = 1, + Nine = 2, + Ten = 3, + Jack = 4, + Queen = 5, + King = 6, + Ace = 7 +} +card_value_t; + +class Card +{ + private: + card_suite_t suite; + card_value_t value; + + 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); +}; + +#endif diff --git a/Examples/FreeRTOS/freertos_belot_sim/deck.cpp b/Examples/FreeRTOS/freertos_belot_sim/deck.cpp new file mode 100644 index 0000000..b476618 --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/deck.cpp @@ -0,0 +1,126 @@ +#include "deck.hpp" +#include "card.hpp" + +#include +#include +#include + +static const card_suite_t new_deck_suits[NUM_OF_SUITES] = {Hearts, Clubs, Diamonds, Spades}; +static const card_value_t new_deck_values[NUM_OF_VALUES] = {Ace, /* Missing 2 - 6 */ Seven, Eight, Nine, Ten, Jack, Queen, King}; + +Deck::Deck() +{ + // Why the hell did I bother implementing a proper new deck order? It's not even correct, as cards 2 - 6 are missing! + for (int i = 0; i < NUM_OF_VALUES; i++) + { + cards[i] = Card(new_deck_suits[0], new_deck_values[i]); + cards[NUM_OF_VALUES + i] = Card(new_deck_suits[1], new_deck_values[i]); + cards[2 * NUM_OF_VALUES + i] = Card(new_deck_suits[2], new_deck_values[NUM_OF_VALUES - i - 1]); + cards[3 * NUM_OF_VALUES + i] = Card(new_deck_suits[3], new_deck_values[NUM_OF_VALUES - i - 1]); + } + + start = 0; + end = DECK_SIZE - 1; + remaining = DECK_SIZE; +} + +int Deck::draw(Card *card_ptr) +{ + if (remaining == 0) + { + return -1; + } + + *card_ptr = cards[start]; + start = (start + 1) % DECK_SIZE; + remaining--; + + return 0; +} + +int Deck::draw_multiple(Card *cards_buf, size_t num_cards) +{ + if (remaining < num_cards) + { + return -1; + } + + if (start + num_cards > DECK_SIZE) + { + size_t first_portion = num_cards - (start + num_cards) % DECK_SIZE; + memcpy((void *) cards_buf, (const void *) (cards + start), first_portion * sizeof(Card)); + memcpy((void *) (cards_buf + first_portion), (const void *) cards, (num_cards - first_portion) * sizeof(Card)); + } + else + { + memcpy((void *) cards_buf, (const void *) (cards + start), num_cards * sizeof(Card)); + } + + start = (start + num_cards) % DECK_SIZE; + remaining -= num_cards; + + return 0; +} + +int Deck::add(Card *card_ptr) +{ + if (remaining >= DECK_SIZE) + { + return -1; + } + + end = (end + 1) % DECK_SIZE; + cards[end] = *card_ptr; + remaining++; + + return 0; +} + +int Deck::add_multiple(Card *cards_buf, size_t num_cards) +{ + if (remaining > DECK_SIZE - num_cards) + { + return -1; + } + + if (end + num_cards > DECK_SIZE) + { + size_t first_portion = num_cards - (end + 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)); + } + else + { + memcpy((void *) (cards + end), (const void *) cards_buf, num_cards * sizeof(Card)); + } + + end = (end + num_cards) % DECK_SIZE; + remaining += num_cards; + + return 0; +} + +// Uses the Fisher–Yates algorithm +void Deck::shuffle() +{ + Card buf; + size_t pos; + + for (int i = remaining; i > 0; i--) + { + // random is NOT thread-safe but using it here is fine because only one thread modifies the deck at a time (the dealer) + pos = (size_t) random() % i; + buf = cards[(start + i) % DECK_SIZE]; + cards[(start + i) % DECK_SIZE] = cards[(start + pos) % DECK_SIZE]; + cards[(start + pos) % DECK_SIZE] = buf; + } +} + +void Deck::split() +{ + Card buf[DECK_SIZE]; + // 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); +} diff --git a/Examples/FreeRTOS/freertos_belot_sim/deck.hpp b/Examples/FreeRTOS/freertos_belot_sim/deck.hpp new file mode 100644 index 0000000..abc5d76 --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/deck.hpp @@ -0,0 +1,29 @@ +#ifndef DECK_H +#define DECK_H + +#include "card.hpp" + +#include + +#define DECK_SIZE (NUM_OF_SUITES * NUM_OF_VALUES) + +class Deck +{ + private: + // Ring buffer + Card cards[DECK_SIZE]; + size_t start; + size_t end; + size_t remaining; + + public: + Deck(); + int draw(Card *card_ptr); + int draw_multiple(Card *cards_buf, size_t num_cards); + int add(Card *card_ptr); + int add_multiple(Card *cards_buf, size_t num_cards); + void shuffle(); + void split(); +}; + +#endif diff --git a/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino b/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino new file mode 100644 index 0000000..7a26d4c --- /dev/null +++ b/Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino @@ -0,0 +1,114 @@ +#include "belot.hpp" +#include "belot_common.hpp" +#include "card.hpp" + +#define THREAD_STACK_SIZE 1024 +#define PLAYERS 6 + +static void belot_player(__attribute__((unused)) void *args); + +SemaphoreHandle_t uart_mutex = xSemaphoreCreateMutex(); +static Belot *lobby = NULL; +static SemaphoreHandle_t lobby_mutex = xSemaphoreCreateMutex(); +static const char *player_names[PLAYERS] = {"Asen", "Boris", "Violeta", "Georgi", "Dimitar", "Elena"}; +static TaskHandle_t player_handles[PLAYERS]; + +void setup() +{ + 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!"); + + for (uint8_t i = 0; i < PLAYERS; i++) + { + if (xTaskCreate(belot_player, player_names[i], THREAD_STACK_SIZE, NULL, 1, &player_handles[i]) != pdPASS) + { + big_oof(); + } + } +} + +void loop() +{} + +void belot_player(__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(); + } + + while (true) + { + Belot *game; + int8_t player_id = -1; + bool created_game = false; + + 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 = lobby->join(name, card_input, turn_semaphor); + } + else + { + player_id = lobby->join(name, card_input, turn_semaphor); + } + if (xSemaphoreGive(lobby_mutex) != pdTRUE) + { + big_oof(); + } + } + } + + // 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) + { + big_oof(); + } + } + // Starting a new game + while (true) + { + vTaskDelay(100); + } + + // The game has ended + if (created_game) + { + delete game; + } + } +}