]> kolegite.com Git - vmks.git/commitdiff
WIP: FreeRTOS belot example
authorVladimir Garistov <vl.garistov@gmail.com>
Mon, 8 Apr 2024 01:09:07 +0000 (04:09 +0300)
committerVladimir Garistov <vl.garistov@gmail.com>
Mon, 8 Apr 2024 01:09:07 +0000 (04:09 +0300)
Examples/FreeRTOS/freertos_belot_sim/belot.cpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/belot.hpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/belot_common.hpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/card.cpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/card.hpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/deck.cpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/deck.hpp [new file with mode: 0644]
Examples/FreeRTOS/freertos_belot_sim/freertos_belot_sim.ino [new file with mode: 0644]

diff --git a/Examples/FreeRTOS/freertos_belot_sim/belot.cpp b/Examples/FreeRTOS/freertos_belot_sim/belot.cpp
new file mode 100644 (file)
index 0000000..3934f0c
--- /dev/null
@@ -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 <stdint.h>
+#include <HardwareSerial.h>
+
+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 (file)
index 0000000..2fd5722
--- /dev/null
@@ -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 <stdint.h>
+
+#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 (file)
index 0000000..6fc787a
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef BELOT_COMMON_H
+#define BELOT_COMMON_H
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#include <HardwareSerial.h>
+
+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 (file)
index 0000000..1f8440f
--- /dev/null
@@ -0,0 +1,55 @@
+#include "card.hpp"
+#include "belot_common.hpp"
+
+#include <stdint.h>
+#include <cstddef>
+
+//                                                        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 (file)
index 0000000..9b0f1ee
--- /dev/null
@@ -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 (file)
index 0000000..b476618
--- /dev/null
@@ -0,0 +1,126 @@
+#include "deck.hpp"
+#include "card.hpp"
+
+#include <cstddef>
+#include <stdlib.h>
+#include <string.h>
+
+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 (file)
index 0000000..abc5d76
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef DECK_H
+#define DECK_H
+
+#include "card.hpp"
+
+#include <cstddef>
+
+#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 (file)
index 0000000..7a26d4c
--- /dev/null
@@ -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;
+    }
+  }
+}