/*
Вставляем код сюда
https://www.tutorialspoint.com/compile_c_online.php
Сверху справа жмем Project -> Compile options
В поле Compilation command вставляем:
gcc -std=c99 -o main *.c -lm
^ ^
standard: c99 link math library
Жмем Compile, Execute
Играем
НА GLOT.IO НОРМАЛЬНО ПОИГРАТЬ НЕ ПОЛУЧИТСЯ
т.к. программе нужен ввод в реальном времени, а не заранее подготовленный
*/
#include <stdio.h> // printf, fgets
#include <math.h> // floor, rand
#include <stdlib.h> // srand (for rand init)
#include <time.h> // time (for srand based on time)
#define DECKS 8 /* Сколько колод добавить в игру */
#define DECK_SIZE 52 /* Размер одной колоды */
#define CARDS_TOTAL (DECKS * DECK_SIZE) /* Сколько всего карт будет в игре */
#define SUIT_SIZE 13 /* Сколько в колоде карт одной масти */
#define BUFFER_SIZE 255 /* Размер буфера для ввода из консоли */
/* Игрок */
typedef struct {
int id; /* ID игрока, используется для отображения карт, принадлежащих ему */
char* name; /* Имя, будет отображаться в консоли */
int sum; /* Сумма карт */
int hits; /* Сколько раз игрок забрал карту */
int valued_aces; /* Сколько у игрока полноценных тузов */
} Player;
/* Карта */
typedef struct {
int rank; /* Ранг карты */
int suit; /* Масть карты */
int holder; /* Кто держит карту (-1, если никто) */
} Card;
/* Колода карт */
typedef struct {
int c; /* Текущая карта. Увеличивается после каждого hit() */
Card card[CARDS_TOTAL]; /* Все карты со всех колод.
* Изначально массив пустой.
* Заполняется с помощью deck_fill() */
} Deck;
/* Возвращает случайное вещественное число от 0.0 до 1.0 */
double rand_double() {
/* Статическая переменная сохраняет свое значение даже после выхода их функции */
static int initialized = 0;
/* Используем это для инициализации рандома только при первом вызове функции */
if (!initialized) {
srand(time(NULL));
initialized = 1;
}
return (double) rand() / RAND_MAX;
}
/* Возвращает случайное целое число в заданном интервале */
int rand_int(int min, int max) {
return floor(rand_double() * (max - min + 1)) + min;
}
/* Заполняет колоду картами */
void deck_fill(Deck* deck) {
for (int d = 0; d < DECKS; d++) { // 0, 1, ...
for (int c = 0; c < DECK_SIZE; c++) {
int rank = (c % SUIT_SIZE) + 1; // 1,2,3,4,5,6,7,8,9,10,11,12,13; 1,2,3...; ...; ...;
int suit = c / SUIT_SIZE; // 0, 1, 2, 3
int holder = -1;
Card card = {
rank,
suit,
holder
};
deck->card[(d * DECK_SIZE) + c] = card;
}
}
}
/* Перемешивает все карты в колоде */
void deck_shuffle(Deck* deck) {
for (int i = 0; i < CARDS_TOTAL; i++) {
/* Генерируем новую случайную позицию в колоде, от i до cards-1 */
int j = rand_int(i, CARDS_TOTAL-1);
/* Меняем местами эти карты по позициям */
Card temp_card = deck->card[i];
deck->card[i] = deck->card[j];
deck->card[j] = temp_card;
}
}
/* Возвращает фейс карты, если есть. Иначе возвращает пробел. */
const char face_char(int rank) {
switch (rank) {
case 1: return 'A';
case 11: return 'J';
case 12: return 'Q';
case 13: return 'K';
default: return ' ';
}
}
/* Возвращает ссылку на строку с необходимой мастью
* ♥ = 0, ♦ = 1, ♣ = 2, ♠ = 3
* В windows и *nix кодировка символов разная
* В windows стандартная консолиь не отображает эти символы,
* нужно юзать powershell
* В *nix каждый символ занимает 2 байта, юзаем строки для хранения */
const char* suit_char(int suit) {
static const char* chars[] = {
#ifdef _WIN32
"\3", "\4", "\5", "\6"
#else
"\u2665", "\u2666", "\u2663", "\u2660"
#endif
};
if (suit < 0 ||suit > 13) return " ";
return chars[suit];
}
/* Распечатывает карту */
void print_card(Card card) {
int rank = card.rank;
int suit = card.suit;
int face = face_char(rank);
/* Важно поставить пробел после символа масти, т.к. если он занимает 2 байта,
* то следующий за ним символ может не распечататься */
printf("%s ", suit_char(suit));
if (face != ' ') {
printf("%c", face);
} else {
printf("%d", rank);
}
}
/* Распечатывает все карты в колоде */
void print_deck(Player* player, int players, Deck* deck) {
for (int c = 0; c < CARDS_TOTAL; c++) {
print_card(deck->card[c]);
int holder = deck->card[c].holder;
if (holder) {
for (int p = 0; p < players; p++) {
if (holder != player[p].id) continue;
printf(" (%s)", player[p].name);
}
}
printf("\n");
}
}
/* Создает и возвращает структуру игрока, заполняя её значениями по умолчанию */
Player player_create(char* name) {
/* Статическая переменная сохранит свое значение после выхода из функции */
static int id = 0;
Player player = {
id,
name,
0,
0,
0
};
id++;
return player;
}
/* Распечатывает (с конца) список игроков и карты на их руках */
void print_players(Player* player, int players, int current_player, Deck* deck) {
/* Заранее находим максимальную сумму среди игроков, не большую 21*/
int winner_sum = -1;
for (int p = players-1; p >= 0; p--) {
int sum = player[p].sum;
if (sum > winner_sum && sum <= 21) {
winner_sum = sum;
}
}
for (int p = players-1; p >= 0; p--) {
/* Карты диллера скрыты пока очередь не дойдет до него */
int dealer_cards_hidden = player[p].id == 0 && current_player != 0;
int is_current_player = player[p].id == current_player;
if (is_current_player) {
printf("> ");
}
printf("%s got ", player[p].name);
if (dealer_cards_hidden) {
printf("? (");
} else {
printf("%d (", player[p].sum);
}
int hits = 0;
for (int c = 0; c < CARDS_TOTAL; c++) {
if (deck->card[c].holder != player[p].id) continue;
hits++;
if (dealer_cards_hidden && hits == 2) {
printf("??");
} else {
print_card(deck->card[c]);
}
if (hits < player[p].hits) {
printf(", ");
}
if (hits == player[p].hits) break;
}
if (current_player == 0 && player[p].sum == winner_sum) {
printf(") - Winner!\n");
} else {
printf(")\n");
}
}
}
/* Очищает экран путем отправки команды в командную строку.
* Сама команда зависит от операционной системы. */
void clear_screen() {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
/* Игрок выбрал hit */
void hit(Player* player, Deck* deck) {
/* Инкрементируем итератор текущей карты */
int c = ++(deck->c);
/* Увеличиваем кол-во карт на руках игрока */
player->hits++;
/* Записываем владельца текущей карты */
deck->card[c].holder = player->id;
int sum = player->sum;
int rank = deck->card[c].rank;
/* По началу пусть значение будет равно рангу карты. */
int value = rank;
/* Если это туз, то его значение зависит от общей суммы значений.
Если сумма еще не превысила 21, то тузы стоят 11. Иначе они стоят 1.
Мы храним кол-во полноценных тузов, чтобы их обеценить при переполнении */
if (rank == 1 && sum + value <= 21) {
value = 11;
player->valued_aces++;
}
/* У карт с рангом >= 10 значение 10 */
if (rank >= 10) value = 10;
sum += value;
/* Если сумма превысила 21, и у игрока есть тузы
с полным значением, то обесцениваем их, пока сумма больше 21
и есть необесцененные тузы */
while (sum > 21 && player->valued_aces > 0) {
player->valued_aces--;
sum -= 10;
}
player->sum = sum;
}
/* Имитация игры дилера */
int imitate_dealer(Player* player, int players, Deck* deck) {
/* Находим минимальную сумму среди игроков */
int min_sum = 20;
int second_min_sum = min_sum;
for (int i = players-1; i >= 1; i--) {
if (player[i].sum < min_sum) {
second_min_sum = min_sum;
min_sum = player[i].sum;
}
}
/* Диллер бутет жать hit до тех пор, пока:
* его сумма меньше второй наименьшей суммы
* его сумма близка к 21 и все еще меньше наименьшей суммы */
int near_21 = 21 - player[0].sum < 2;
int less_than_min = player[0].sum < min_sum;
int less_than_second_min = player[0].sum < second_min_sum;
if (less_than_second_min || (near_21 && less_than_min)) {
hit(&player[0], deck);
return 1;
}
return 0;
}
/* Ход игрока */
int player_control(Player* player, Deck* deck) {
if (player->sum == 21) return 0;
/* Здесь будет храниться строка которую ввел пользователь (hit|stay|quit) */
char buffer[BUFFER_SIZE];
printf("\nWhat would you do? (hit|stay|quit): ");
/* Получаем введенную пользователем строку */
fgets(buffer, BUFFER_SIZE, stdin);
/* Простое управление путем проверки первого введенного символа */
switch(buffer[0]) {
/* Hit - еще карту */
case 'h':
case 'H': {
hit(player, deck);
/* Возвращаем 0 если игрок достиг или превысил суммы в 21 очко */
return player->sum < 21;
}
/* Stay - след. игрок */
case 's':
case 'S': {
return 0;
}
/* Quit */
case 'q':
case 'Q': {
exit(0);
}
/* Пользователь ввел что-то другое (или ничего) */
default: {
return 1;
}
}
}
/* Один раунд */
void round_start(Deck* deck, Player* player, int players) {
/* В начале раунда каждому игроку дается по 2 карты */
for (int p = players - 1; p >= 0; p--) {
hit(&player[p], deck);
hit(&player[p], deck);
}
for (int p = players - 1; p >= 0; ) {
int is_dealer = player[p].id == 0;
do {
clear_screen();
print_players(player, players, p, deck);
/* Определяем какую функцию запускать (дать управление игроку, или имитировать дилера)
* с помощью тернарного оператора прямо в шапке цикла.
* Используем возвращаемое значение функции, чтобы определить продолжать ли цикл */
} while(is_dealer ? imitate_dealer(player, players, deck) : player_control(&player[p], deck));
p--;
}
}
int main() {
/* Создаем, заполняем и перемешиваем колоду */
Deck deck;
deck_fill(&deck);
deck_shuffle(&deck);
/* Создаем массив игроков. Дилер всегда идет первым, на этом завязана логика игры. */
Player player[] = {
player_create("Dealer"),
player_create("You")
};
/* Вычисляем кол-во игроков (размер всего массива в байтах / размер его элемента в байтах) */
int players = sizeof(player) / sizeof(player[0]);
round_start(&deck, player, players);
return 0;
}