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

index 3934f0c0d5838908935a2ccb64f2a9adc45dd8dd..51ac4294bf6f9d7c49d047705037d873e8f7142c 100644 (file)
@@ -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);
+  }
+}
index 2fd5722dc7ceda00cf690d6d06e4fb3220f52a31..f12a08c1530ddbbed447d7ab78809f5eaec4e840 100644 (file)
 #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
index 6fc787a004a85b46f5e00d5c7e1e359f44e17ba7..c17f22c8d3f21348431a5a78b6761e39ed10e45e 100644 (file)
@@ -8,6 +8,7 @@
 
 typedef enum
 {
+  AllPass,
   NoTrumps,
   ClubsTrump,
   DiamondsTrump,
index 1f8440fd54c7b8337f9a05fd1b8f0faa56ed086c..8f17a2a572b2e3869ec37c9f54f38f67cead54f4 100644 (file)
@@ -1,16 +1,19 @@
 #include "card.hpp"
 #include "belot_common.hpp"
 
+#include <string.h>
 #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};
+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);
+}
+
index 9b0f1eec2cf1cf59f593c37fd1d48c1a30ce5f53..00c6611e302cddbf5b254de500af09532fc7ceda 100644 (file)
@@ -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
index b476618adc7b6f66de52a669a938e78c8d623aff..47229c034213a6af625ecae32bc932b86a147aac 100644 (file)
@@ -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;
 }
index abc5d763b198b98ef80ae560db8dc88925ee9046..180ea783769f1a951600d72f75284f6b41641d6e 100644 (file)
@@ -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
index 7a26d4c3fa1b831f9f5d83360cc2c681db4f70fe..070fdad1dbe309b9e54c59a24a533b17480acde2 100644 (file)
@@ -1,29 +1,45 @@
 #include "belot.hpp"
 #include "belot_common.hpp"
 #include "card.hpp"
+#include "player.hpp"
 
-#define THREAD_STACK_SIZE 1024
+#include <stdlib.h>
+
+#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 (file)
index 0000000..b6719da
--- /dev/null
@@ -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 <stdint.h>
+
+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 (file)
index 0000000..a9a49da
--- /dev/null
@@ -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 <stdint.h>
+
+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