#include "belot_common.hpp"
#include "deck.hpp"
#include "card.hpp"
+#include "debug.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <stdint.h>
#include <HardwareSerial.h>
-extern SemaphoreHandle_t uart_mutex;
-
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++)
if (joined_players == NUM_PLAYERS)
{
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(player_names[0]);
Serial.print(" and ");
{
big_oof();
}
+ #endif
if (xSemaphoreGive(player_turn_semaphors[player_in_turn]) != pdTRUE)
{
big_oof();
{
int8_t winning_team = calculate_score_round((player_id + 1) % NUM_PLAYERS);
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print("\nScore:\nEven team - ");
Serial.println(score_round[0]);
{
big_oof();
}
+ #endif
+
+ for (uint8_t i = 0; i < NUM_PLAYERS; i++)
+ {
+ trick[i] = NO_CARD;
+ }
tricks_played++;
if (tricks_played == TRICKS_PER_GAME)
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;
- }
- }
-
+
+ int8_t winning_player_id = find_winning_card(trick, first_card, game_mode);
uint8_t winning_team = winning_player_id % 2;
for (uint8_t i = 0; i < NUM_PLAYERS; i++)
vTaskDelay(1000);
}
}
+
+int8_t find_winning_card(const Card *cards, uint8_t first_card, game_mode_t game_mode)
+{
+ if (cards == NULL)
+ {
+ return -1;
+ }
+ if (game_mode == AllPass)
+ {
+ return -2;
+ }
+
+ card_suite_t requested_suite = cards[first_card].get_suite();
+ uint8_t highest_strenght = cards[first_card].get_strength(game_mode);
+ uint8_t winning_card_num = first_card;
+
+ for (uint8_t i = 1; i < NUM_PLAYERS; i++)
+ {
+ Card next_card = cards[(first_card + i) % NUM_PLAYERS];
+
+ if (game_mode == NoTrumps || game_mode == AllTrumps)
+ {
+ if (next_card.get_suite() == requested_suite && next_card.get_strength(game_mode) > highest_strenght)
+ {
+ highest_strenght = next_card.get_strength(game_mode);
+ winning_card_num = (first_card + i) % NUM_PLAYERS;
+ }
+ }
+ else if (next_card.get_strength(game_mode) > highest_strenght)
+ {
+ highest_strenght = next_card.get_strength(game_mode);
+ winning_card_num = (first_card + i) % NUM_PLAYERS;
+ }
+ }
+
+ return winning_card_num;
+}
int play_card(Card *card, int8_t player_id);
};
+int8_t find_winning_card(const Card *cards, uint8_t first_card, game_mode_t game_mode);
+
#endif
\ No newline at end of file
Card::Card(card_suite_t suite_init, card_value_t value_init) : suite{suite_init}, value{value_init}
{}
-card_suite_t Card::get_suite()
+card_suite_t Card::get_suite() const
{
return suite;
}
-card_value_t Card::get_value()
+card_value_t Card::get_value() const
{
return value;
}
-int Card::get_strength(game_mode_t gamemode)
+int Card::get_strength(game_mode_t gamemode) const
{
switch (gamemode)
{
}
}
-int Card::get_points(game_mode_t gamemode)
+int Card::get_points(game_mode_t gamemode) const
{
switch (gamemode)
{
}
// Requires at least 18 bytes of buffer
-int Card::print(char *buf)
+int Card::print(char *buf) const
{
const char *value_text = NULL;
const char *suite_text = NULL;
return (lhs.suite != rhs.suite || lhs.value != rhs.value);
}
+card_suite_t game_mode_to_card_suite(game_mode_t game_mode)
+{
+ switch (game_mode)
+ {
+ case ClubsTrump: return Clubs;
+ case DiamondsTrump: return Diamonds;
+ case HeartsTrump: return Hearts;
+ case SpadesTrump: return Spades;
+ default: return InvalidSuite;
+ }
+}
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);
- int print(char *buf);
+ card_suite_t get_suite() const;
+ card_value_t get_value() const;
+ int get_strength(game_mode_t gamemode) const;
+ int get_points(game_mode_t gamemode) const;
+ int print(char *buf) const;
friend bool operator==(const Card lhs, const Card rhs);
friend bool operator!=(const Card lhs, const Card rhs);
};
extern const Card NO_CARD;
+card_suite_t game_mode_to_card_suite(game_mode_t game_mode);
+
#endif
--- /dev/null
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#define DEBUG
+
+extern SemaphoreHandle_t uart_mutex;
+
+#endif
\ No newline at end of file
return -2;
}
- if (end + num_cards > DECK_SIZE)
+ if (end + num_cards >= DECK_SIZE)
{
- size_t first_portion = num_cards - (end + num_cards) % DECK_SIZE;
+ size_t first_portion = num_cards - (end + 1 + 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));
}
}
}
+// Moving the start and end counters without copying any cards can have the same effect but I've implemented it this way for the sake of realism
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
--- /dev/null
+#include "dumb_af_bot.hpp"
+#include "card.hpp"
+#include "belot.hpp"
+#include "belot_common.hpp"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+int dumb_af_bot_init(void *state, int8_t player_id, const Card *hand, const Card *trick, const Card *previous_trick, const bid_t *bids)
+{
+ if (state == NULL || hand == NULL || trick == NULL || previous_trick == NULL || bids == NULL)
+ {
+ return -1;
+ }
+
+ dumb_af_bot_state_t *st = (dumb_af_bot_state_t *) state;
+
+ st->player_id = player_id;
+ st->hand = hand;
+ st->trick = trick;
+ st->previous_trick = previous_trick;
+ st->bids = bids;
+ st->game_mode = AllPass;
+ st->all_trumps_announced = false;
+
+ return 0;
+}
+
+bid_t dumb_af_bot_bid(void *state)
+{
+ if (state == NULL)
+ {
+ return BidNone;
+ }
+
+ dumb_af_bot_state_t *st = (dumb_af_bot_state_t *) state;
+
+ // 30 godini TUES, 30 godini vsichko koz!
+ if (st->all_trumps_announced)
+ {
+ return BidPass;
+ }
+ for (uint8_t i = 0; i < NUM_PLAYERS; i++)
+ {
+ if (st->bids[i] == BidAllTrumps)
+ {
+ st->all_trumps_announced = true;
+ break;
+ }
+ }
+ if (st->all_trumps_announced)
+ {
+ return BidPass;
+ }
+ else
+ {
+ st->all_trumps_announced = true;
+ return BidAllTrumps;
+ }
+}
+
+int dumb_af_bot_choose_card(void *state)
+{
+ if (state == NULL)
+ {
+ return -1;
+ }
+
+ card_suite_t requested_suite = InvalidSuite;
+ dumb_af_bot_state_t *st = (dumb_af_bot_state_t *) state;
+ card_suite_t trump_suite = game_mode_to_card_suite(st->game_mode);
+ int8_t winning_card_id = st->player_id;
+
+ // Determine the requested suite
+ for (uint8_t i = 1; i < NUM_PLAYERS; i++)
+ {
+ if (st->trick[(st->player_id + i) % NUM_PLAYERS] != NO_CARD)
+ {
+ requested_suite = st->trick[(st->player_id + i) % NUM_PLAYERS].get_suite();
+ winning_card_id = find_winning_card(st->trick, (st->player_id + i) % NUM_PLAYERS, st->game_mode);
+ break;
+ }
+ }
+ // Determine the strenght of the strongest card so far
+ int winning_card_strenght = st->trick[winning_card_id].get_strength(st->game_mode);
+
+ if (requested_suite != InvalidSuite)
+ {
+ // Find the first card that matches the suite and is stronger
+ for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++)
+ {
+ if (st->hand[i].get_suite() == requested_suite && st->hand[i].get_strength(st->game_mode) > winning_card_strenght)
+ {
+ return i;
+ }
+ }
+ // Just throw the first card that matches the suite
+ for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++)
+ {
+ if (st->hand[i].get_suite() == requested_suite)
+ {
+ return i;
+ }
+ }
+ if (trump_suite != InvalidSuite)
+ {
+ // Find the first card that matches the trump suite and is strong enough
+ for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++)
+ {
+ if (st->hand[i].get_suite() == trump_suite && st->hand[i].get_strength(st->game_mode) > winning_card_strenght)
+ {
+ return i;
+ }
+ }
+ }
+ }
+ // Either we don't have a matching card or we are first in turn. In that case just throw the first card we have.
+ for (uint8_t i = 0; i < CARDS_PER_PLAYER; i++)
+ {
+ if (st->hand[i] != NO_CARD)
+ {
+ return i;
+ }
+ }
+
+ // We're out of cards! This shouldn't happen.
+ return -2;
+}
+
+int dumb_af_bot_set_game_mode(void *state, game_mode_t game_mode)
+{
+ if (state == NULL)
+ {
+ return -1;
+ }
+
+ ((dumb_af_bot_state_t *) state)->game_mode = game_mode;
+
+ return 0;
+}
--- /dev/null
+#ifndef DUMB_AF_BOT_H
+#define DUMB_AF_BOT_H
+
+#include "card.hpp"
+#include "belot.hpp"
+#include "belot_common.hpp"
+
+#include <stdint.h>
+
+#define DUMB_AF_BOT_STATE_SIZE sizeof(dumb_af_bot_state_t)
+
+typedef struct
+{
+ game_mode_t game_mode;
+ int8_t player_id;
+ const Card *hand;
+ const Card *trick;
+ const Card *previous_trick;
+ const bid_t *bids;
+ bool all_trumps_announced;
+ //other stuff
+}
+dumb_af_bot_state_t;
+
+int dumb_af_bot_init(void *state, int8_t player_id, const Card *hand, const Card *trick, const Card *previous_trick, const bid_t *bids);
+bid_t dumb_af_bot_bid(void *state);
+int dumb_af_bot_choose_card(void *state);
+int dumb_af_bot_set_game_mode(void *state, game_mode_t game_mode);
+
+#endif
\ No newline at end of file
#include "belot_common.hpp"
#include "card.hpp"
#include "player.hpp"
+#include "dumb_af_bot.hpp"
#include <stdlib.h>
#define PLAYERS 6
#define NOISE_PIN 34
-static void play_belot(__attribute__((unused)) void *args);
+typedef struct
+{
+ int (*bot_init)(void *state, int8_t player_id, const Card *hand, const Card *trick, const Card *previous_trick, const bid_t *bids);
+ bid_t (*bot_bid)(void *state);
+ int (*bot_choose_card)(void *state);
+ int (*bot_set_game_mode)(void *state, game_mode_t game_mode);
+ void *bot_state;
+}
+player_args_t;
+
+static void play_belot(void *pvParameters);
+static void think_really_hard(void);
SemaphoreHandle_t uart_mutex = xSemaphoreCreateMutex();
static Belot *lobby = NULL;
static SemaphoreHandle_t lobby_mutex;
+
static const char *player_names[PLAYERS] = {"Asen", "Boris", "Violeta", "Georgi", "Dimitar", "Elena"};
static TaskHandle_t player_handles[PLAYERS];
+static const size_t bot_state_sizes[PLAYERS] = {DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE, DUMB_AF_BOT_STATE_SIZE};
+static player_args_t player_args[PLAYERS] =
+{
+ {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL},
+ {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL},
+ {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL},
+ {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL},
+ {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL},
+ {.bot_init = dumb_af_bot_init, .bot_bid = dumb_af_bot_bid, .bot_choose_card = dumb_af_bot_choose_card, .bot_set_game_mode = dumb_af_bot_set_game_mode, .bot_state = NULL}
+};
void setup()
{
for (uint8_t i = 0; i < PLAYERS; i++)
{
- if (xTaskCreate(play_belot, player_names[i], THREAD_STACK_SIZE, NULL, 1, &player_handles[i]) != pdPASS)
+ if (bot_state_sizes[i])
+ {
+ player_args[i].bot_state = malloc(bot_state_sizes[i]);
+ if (player_args[i].bot_state == NULL)
+ {
+ big_oof();
+ }
+ }
+ if (xTaskCreate(play_belot, player_names[i], THREAD_STACK_SIZE, (void *) &player_args[i], 1, &player_handles[i]) != pdPASS)
{
big_oof();
}
void loop()
{}
-void play_belot(__attribute__((unused)) void *args)
+static void play_belot(void *pvParameters)
{
Player player = Player(pcTaskGetName(NULL));
+ player_args_t *args = (player_args_t *) pvParameters;
// Games loop
while (true)
player.join_or_start_game(&lobby, lobby_mutex);
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();
+ // The following methods on the Player class return pointers, so the bot can save them in its state. This way there is no need to pass many arguments to the other bot functions.
+ args->bot_init(args->bot_state, id, player.view_hand(), player.view_trick(), player.view_previous_trick(), player.view_bids());
// Rounds loop
while (true)
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);
+ player.bid(args->bot_bid(args->bot_state));
}
if (player.get_game_mode() == AllPass)
{
player.deal_second();
}
player.collect_cards();
+ args->bot_set_game_mode(args->bot_state, player.get_game_mode());
// 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);
+ think_really_hard();
+ player.play_card(args->bot_choose_card(args->bot_state));
}
player.end_round();
}
player.end_game();
}
}
+
+static void think_really_hard(void)
+{
+ vTaskDelay(1000);
+}
#include "belot.hpp"
#include "belot_common.hpp"
#include "card.hpp"
+#include "debug.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.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.
return -1;
}
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(name);
Serial.println(" entered the lobby.");
{
big_oof();
}
+ #endif
while (player_id < 0)
{
void Player::split_deck()
{
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(name);
+ Serial.print("(");
+ Serial.print(player_id);
+ Serial.print(")");
Serial.println(" splits the deck.");
if (xSemaphoreGive(uart_mutex) != pdTRUE)
{
big_oof();
}
+ #endif
game->split_deck();
end_turn();
}
{
Card buf[2];
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(name);
+ Serial.print("(");
+ Serial.print(player_id);
+ Serial.print(")");
Serial.println(" deals 2 cards to each player.");
if (xSemaphoreGive(uart_mutex) != pdTRUE)
{
big_oof();
}
+ #endif
for (uint8_t i = 0; i < NUM_PLAYERS; i++)
{
{
Card buf[3];
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(name);
+ Serial.print("(");
+ Serial.print(player_id);
+ Serial.print(")");
Serial.println(" deals 3 cards to each player.");
if (xSemaphoreGive(uart_mutex) != pdTRUE)
{
big_oof();
}
+ #endif
for (uint8_t i = 0; i < NUM_PLAYERS; i++)
{
int Player::bid(bid_t bid)
{
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(name);
+ Serial.print("(");
+ Serial.print(player_id);
+ Serial.print(")");
switch (bid)
{
case BidPass: Serial.println(": pass"); break;
{
big_oof();
}
+ #endif
int error_code = game->bid(bid, player_id);
if (!error_code)
{
}
hand[card_num].print(card_text);
+ #ifdef DEBUG
while (xSemaphoreTake(uart_mutex, portMAX_DELAY) != pdTRUE);
Serial.print(name);
+ Serial.print("(");
+ Serial.print(player_id);
+ Serial.print(")");
Serial.print(": ");
Serial.println(card_text);
if (xSemaphoreGive(uart_mutex) != pdTRUE)
{
big_oof();
}
+ #endif
int error_code = game->play_card(&hand[card_num], player_id);
if (!error_code)