--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+ }
+ }
+}