Сегодня нас ждёт интересный проект — сделаем блек-джек с графическим интерфейсом, используя все механики фронтенд-разработки. Мы уже писали эту игру на Python, настало время перенести её в веб. Для реализации нам понадобится только браузер и редактор кода, поэтому почувствовать себя фронтендером сможет каждый.
В процессе будем активно использовать манипуляции с DOM для отображения карт, поработаем с массивами и объектами и используем популярный алгоритм Фишера — Йетса для перемешивания колоды. Летс плей.
Правила игры
- Есть колода из 54 карт, масти не важны. В казино используют сразу до 8 колод, перемешивая их друг с другом, у нас будет одна — так алгоритм будет проще.
- Каждому игроку сдаётся в открытую по две карты, раздающему (дилеру) — одна карта.
- Карты с цифрами от 2 до 10 дают номинальное число очков (от 2 до 10), картинка — 10 очков, туз — 11 очков (или одно, если на руке больше 11 очков).
- Цель игры — набрать как можно больше очков, но не больше 21. Если получается больше 21 — игрок автоматически проиграл.
- Игрок смотрит свою карту и принимает решение, взять ещё или ему хватит. После каждой взятой карты игрок смотрит на сумму очков и решает, будет брать ещё или нет.
- Когда игрок останавливается, крупье сдаёт себе карты по той же схеме. По классическим правилам дилер должен изначально тянуть карты до тех пор, пока не наберёт минимум 17 очков.
- Если у игрока больше очков, чем у крупье, то он выиграл. Если очков больше у дилера — выиграл он.
Логика проекта
Вся механика игры будет реализована в JavaScript, а в HTML мы разместим контейнеры для отображения карт и блок с кнопками.
Внутренняя логика игры будет такой:
- Создаём массив со значениями каждой карты.
- С помощью циклов создаём полный набор карт, добавляя каждую в массив.
- Перемешиваем массив карт, карты раздаются игроку и дилеру.
- Проходим по массиву карт, добавляя значения каждой карты к общей сумме очков игрока или дилера.
- Дополнительные функции сразу проверяют, нужно ли пересчитать сумму очков, если в руке есть туз, и управляют логикой завершения игры, определяя, выиграл игрок или дилер.
А вот как мы это реализуем на практике.
- Добавим в HTML-разметку контейнеры для отображения карт игрока и дилера, а также элементы для показа суммы очков и кнопок действий.
- Добавим стили: сделаем красивый фон и раскрасим кнопки.
- Подготовим картинки для колоды.
- В JavaScript напишем функцию
buildDeck
для создания колоды карт, а затем функциюshuffleDeck
для их перемешивания. - С помощью функции
startGame
раздадим начальные карты игроку и дилеру, скрыв одну из карт дилера. - Реализуем логику для действий «Взять карту» и «Остановиться» в функциях
hit
иstay
. Эти функции будут обновлять сумму очков игрока и крупье и проверять, нужно ли завершить игру. - В функции
stay
будем проверять итоговые суммы очков игрока и дилера, определяя победителя или ничью. - Функция
restartGame
сбросит все значения и состояния игры, чтобы начать новую партию. - Функция
getValue
будет определять ценность карты на основе её значения (число, валет, дама, король или туз). - Вспомогательные функции
checkAce
иreduceAce
будут определять, считать туз как 11 или как 1, чтобы избежать перебора очков.
Звучит как план, можно стартовать.
Создаём HTML-страницу
Создадим страницу с базовой разметкой и сразу подключим файлы со стилями и скриптом. Их пока нет, но дальше мы их наполним. Картинки будут лежать в папке img проекта — с ними тоже поработаем чуть позже. В общем, сделаем каркас из страницы и пустых файлов, чтобы потом к этому не возвращаться.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Black Jack</title>
<!-- Подключение файла стилей -->
<link rel="stylesheet" href="style.css">
<!-- Подключение скрипта с логикой игры -->
<script src="main.js"></script>
</head>
<body>
</body>
</html>
Теперь добавим на страницу элементы, которые отвечают за сумму очков и карты: заголовки с текущей суммой очков дилера и игрока, контейнеры для их карт, где будут изображения карт и картинка рубашки карты, что изначально у дилера скрыта.
<!-- Сумма очков дилера -->
<h2>Дилер: <span id="dealer-sum">0</span></h2>
<div id="dealer-cards">
<!-- Картинка скрытой карты дилера -->
<img id="hidden" src="./img/back.png" class="card-img">
</div>
<!-- Сумма очков игрока -->
<h2>Игрок: <span id="your-sum"></span></h2>
<div id="your-cards" class="card-container"></div>
Наконец, создадим блок с кнопками и заголовок, который появляется по завершении игры. При запуске игры будут видны две кнопки. По нажатию на «Взять» (hit
) игроку сдаётся ещё одна карта, по клику на «Остановиться» (stay
) — подсчитывается сумма очков и выводится сообщение о завершении игры. После этого в блоке появляется третья кнопка — «Заново» (restart
), по клику на неё игра перезапускается. Для каждой кнопки сделаем подсказку (data-tooltip
) при наведении.
<!-- Кнопки управления игрой -->
<div class="buttons">
<!-- Кнопка "Взять карту" -->
<button id="hit" data-tooltip="Взять">
<img src="./img/hit.png" alt="Icon">
</button>
<!-- Кнопка "Остановиться" -->
<button id="stay" data-tooltip="Остановиться">
<img src="./img/stay.png" alt="Icon">
</button>
<!-- Кнопка "Заново" для перезапуска игры, скрыта по умолчанию -->
<button id="restart" style="display: none;" data-tooltip="Заново">
<img src="./img/restart.png" alt="Icon">
</button>
</div>
<!-- Отображение результата игры -->
<h2 id="results"></h2>
Вот что получилось. Пока негусто, но это потому что нет стилей и картинок:
Добавляем изображения карт и иконки в проект
Для проекта нам нужны изображения самих карт и иконки для кнопок. Карты можно использовать как локально, сохранив их в папку проекта, так и генерировать каждый раз, используя Deck of Cards API.
Мы возьмём готовые карты с OpenGameArt.org — это платформа, предоставляющая бесплатные графические ресурсы для разработки игр. Возьмём колоду карт художницы Ланеи Циммерман. Уже подготовленные (переименованные и сжатые) изображения можно скачать в папке проекта, там же лежат и иконки управления. Заходим и сохраняем всё, что там есть, в папку img.
Каждую карту нужно именовать по её значению и масти, чтобы мы могли обращаться к ним в коде и правильно считать их значения. Например, карта десятки пик должна называться 10-S (Spades — пики). Аналогично именуем оставшиеся карты: червы — Hearts (H), трефы — Clubs (C), бубны — Diamonds (D).
Обновляем страницу — теперь видна одна гигантская карта, без стилей дальше точно не обойтись:
Стилизуем и красим кнопки
Создаём файл style.css, в котором будут все наши стили, и сохраняем его в той же папке, что и страницу. Сначала стилизуем фон страницы, чтобы он имитировал игровой стол:
body {
/* Основной шрифт страницы */
font-family: Arial, Helvetica, sans-serif;
/* Выравнивание текста по центру */
text-align: center;
/* Фон с радиальным градиентом, имитирующим цвет сукна */
background: radial-gradient(circle at center, #35654d, #1b3120, #050c05);
/* Сбрасываем внешние отступы */
margin: 0;
/* Сбрасываем внутренние отступы */
padding: 0;
/* Высота страницы на всю высоту экрана */
height: 100vh;
}
Появилось сукно и выравнивание, но карты по-прежнему большие:
Зададим стиль заголовка и установим адаптивный размер для карт, чтобы они правильно выглядели на разных экранах:
h2 {
/* Цвет текста заголовков */
color: aliceblue;
/* Убираем нижний отступ для заголовков */
margin-bottom: 0;
}
.card-img {
/* Ширина изображения карты относительно ширины экрана */
width: 20vw;
/* Сохраняем соотношение сторон при изменении ширины */
height: auto;
/* Максимальная ширина изображения карты */
max-width: 125px;
/* Отступы вокруг изображения карты */
margin: 1em;
}
Карты стали выглядеть адекватно, но с иконками управления по-прежнему беда, надо продолжать настраивать стили:
Установим стили для контейнера с кнопками и для самих кнопок:
.buttons {
/* Выравнивание кнопок по центру и расположение их в строку */
display: flex;
/* Выравнивание кнопок по вертикали */
align-items: center;
/* Выравнивание кнопок по горизонтали */
justify-content: center;
/* Задаём расстояние между кнопками */
gap: 2em;
}
button {
/* Относительное позиционирование кнопки, чтобы тултип отображался корректно */
position: relative;
/* Выравниваем содержимое по центру */
display: flex;
/* Центрируем содержимое по вертикали */
align-items: center;
/* Центрируем содержимое по горизонтали */
justify-content: center;
/* Размеры кнопки */
width: 60px;
height: 60px;
/* Скругляем углы */
border-radius: 50%;
/* Задаём цвет фона */
background-color: #c5c5c5;
/* Граница кнопки */
border: 1px solid #333333;
/* Курсор меняется на указатель при наведении */
cursor: pointer;
/* Добавляем тень*/
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
button img {
/* Размер иконок внутри кнопки */
width: 30px;
height: 30px;
}
Вот, так намного лучше:
И напоследок добавим через псевдоэлемент after
стилизацию для подсказки на кнопке при наведении:
button::after {
/* Отображаем подсказку, используя атрибут data-tooltip */
content: attr(data-tooltip);
/* Абсолютное позиционирование подсказки */
position: absolute;
/* Позиционируем подсказку под кнопкой */
bottom: -30px;
/* Центрируем подсказку относительно кнопки */
left: 50%;
transform: translateX(-50%);
/* Цвет фона */
background-color: #343434;
/* Цвет текста */
color: white;
/* Отступы внутри подсказки */
padding: 0.5em;
/* Закруглённые углы */
border-radius: 5px;
/* Отключаем перенос текста в подсказке */
white-space: nowrap;
/* Изначально скрываем подсказку */
opacity: 0;
/* Отключаем возможность взаимодействия с подсказкой */
pointer-events: none;
/* Плавное появление подсказки при наведении */
transition: opacity 0.3s;
}
button:hover::after {
/* Показываем подсказку при наведении */
opacity: 1;
}
С дизайном закончили, теперь переходим к логике игры. Вот что получилось после применения всех стилей:
Создаём колоду
Теперь нам понадобится знание JavaScript. Создадим файл script.js и дальше будем работать в нём.
Для начала настроим глобальные переменные, которые будут отслеживать состояние игры:
// Текущая суммы очков дилера
let dealerSum = 0;
// Текущая сумма очков игрока
let yourSum = 0;
// Отслеживание числа тузов у дилера
let dealerAceCount = 0;
// Отслеживание числа тузов у игрока
let yourAceCount = 0;
// Скрытая карта дилера
let hidden;
// Колода карт
let deck;
// Позволяет игроку брать карты, пока сумма очков не превысит 21
let canHit = true;
Затем напишем функции, которые будут управлять колодой карт. При загрузке страницы вызывается функция buildDeck()
для создания полной колоды, затем shuffleDeck()
для перемешивания карт и, наконец, startGame()
для начала игры.
// Код, который выполняется при загрузке страницы
window.onload = function () {
// Создаём колоду карт
buildDeck();
// Перемешиваем колоду
shuffleDeck();
// Начинаем игру, раздавая карты дилеру и игроку
startGame();
};
// Создание колоды карт
function buildDeck() {
// Значения карт: от туза (Ace) до короля (King)
let values = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
// Масти карт
let types = ["C", "D", "H", "S"];
// Пустой массив для колоды карт
deck = [];
// Создаём комбинации значений и мастей, добавляя их в колоду
for (let i = 0; i < types.length; i++) {
for (let j = 0; j < values.length; j++) {
deck.push(values[j] + "-" + types[i]);
}
}
}
// Перемешивание колоды с использованием алгоритма Фишера — Йетса
function shuffleDeck() {
for (let i = 0; i < deck.length; i++) {
// Выбираем случайный индекс для обмена с текущей картой
let j = Math.floor(Math.random() * deck.length);
// Меняем местами текущую карту со случайной картой
let temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
Функция shuffleDeck()
перемешивает колоду, используя алгоритм Фишера — Йетса. Это классический и широко используемый алгоритм для случайного перемешивания элементов массива. Алгоритм проходит по массиву с конца к началу и для каждого элемента случайно выбирает один из элементов, который находится перед ним, включая его же, и меняет их местами. Это гарантирует, что каждый элемент будет равновероятно находиться в любом месте в массиве.
Отображение карт
Чтобы элементы карт отображались на странице, добавим функцию createCard()
:
function createCard(cardSrc) {
// Создаём новый элемент <img>, который будет представлять карту
let cardImg = document.createElement("img");
// Устанавливаем атрибут src, чтобы указать путь к изображению карты
cardImg.src = cardSrc;
// Добавляем класс card-img для стилизации изображения карты
cardImg.classList.add("card-img");
// Возвращаем созданный элемент изображения
return cardImg;
}
Начало игры
Функция startGame()
начинает игру, раздаёт карты игроку и дилеру, а кроме этого добавляет обработчики событий для кнопок «Взять карту» и «Остановиться».
Сначала извлекается из колоды скрытая карта дилера, её значение добавляется к сумме очков дилера, но сама карта остаётся скрытой до конца хода игрока. Затем дилеру раздаются дополнительные карты, пока сумма очков не достигнет 17. После этого игроку раздаются две карты, и их значения также добавляются к его сумме очков. После раздачи на кнопки вешаются обработчики событий, чтобы игрок мог управлять ходом игры:
function startGame() {
// Извлекаем скрытую карту дилера из колоды
hidden = deck.pop();
// Добавляем значение скрытой карты к сумме очков дилера
dealerSum += getValue(hidden);
// Проверяем скрытую карту на туз и увеличиваем счётчик тузов
dealerAceCount += checkAce(hidden);
// Дилер берёт карты, пока его сумма очков не достигнет 17
while (dealerSum < 17) {
// Создаём изображение карты и добавляем его на экран
let cardImg = createCard("./img/" + deck.pop() + ".png");
// Добавляем значение карты к сумме очков дилера
dealerSum += getValue(cardImg.src);
// Проверяем карту на туз и увеличиваем счётчик тузов
dealerAceCount += checkAce(cardImg.src);
// Добавляем изображение карты дилера в контейнер
document.getElementById("dealer-cards").append(cardImg);
}
// Игроку раздаются две карты
for (let i = 0; i < 2; i++) {
// Создаём изображение карты и добавляем его
let cardImg = createCard("./img/" + deck.pop() + ".png");
// Добавляем значение карты к сумме очков игрока
yourSum += getValue(cardImg.src);
// Проверяем карту на туз и увеличиваем счётчик тузов
yourAceCount += checkAce(cardImg.src);
// Добавляем изображение карты игрока в контейнер
document.getElementById("your-cards").append(cardImg);
}
// Обновляем отображение суммы очков игрока на экране
document.getElementById("your-sum").innerText = yourSum;
// Добавляем обработчик событий для кнопки "Взять карту"
document.getElementById("hit").addEventListener("click", hit);
// Добавляем обработчик событий для кнопки "Остановиться"
document.getElementById("stay").addEventListener("click", stay);
}
Мы добавили обработчики событий на кнопки — дальше реализуем логику этих кнопок в функциях hit()
и stay()
.
Обработка действий игрока
Функция hit()
позволяет игроку взять дополнительную карту, добавляет её к сумме очков и проверяет, не превысила ли сумма 21. Если игрок превысил 21 очко, то больше не может брать карты (canHit = false
).
Функция stay()
завершает ход игрока, раскрывает скрытую карту дилера и проверяет, нужно ли дилеру брать дополнительные карты. После этого сравниваются суммы очков игрока и дилера, определяется результат игры (победа, проигрыш или ничья), отображается на экране. Также stay()
меняет видимость кнопки «Заново» после завершения игры.
function hit() {
// Проверяем, может ли игрок дальше брать карты
if (!canHit) {
return; // Если нет, прерываем выполнение
}
// Создаём элемент изображения для новой карты, извлекая карту из колоды
let cardImg = createCard("./img/" + deck.pop() + ".png");
// Добавляем значение новой карты к общей сумме очков игрока
yourSum += getValue(cardImg.src);
// Проверяем карту на туз и увеличиваем счётчик тузов
yourAceCount += checkAce(cardImg.src);
// Добавляем изображение новой карты на экран в контейнер
document.getElementById("your-cards").append(cardImg);
// Обновляем отображение суммы очков игрока на экране
document.getElementById("your-sum").innerText = yourSum;
// Если сумма очков игрока с учётом тузов превышает 21, игрок больше не может брать карты
if (reduceAce(yourSum, yourAceCount) > 21) {
canHit = false;
}
}
function stay() {
// Применяем функцию для корректировки суммы очков дилера с учётом тузов
dealerSum = reduceAce(dealerSum, dealerAceCount);
// Применяем функцию для корректировки суммы очков игрока с учётом тузов
yourSum = reduceAce(yourSum, yourAceCount);
// Игрок завершил ход, больше нельзя брать карты
canHit = false;
// Раскрываем скрытую карту дилера на экране
document.getElementById("hidden").src = "./img/" + hidden + ".png";
// Инициализируем переменную для сообщения о результате игры
let message = "";
// Проверяем условия для определения результата игры
if (yourSum > 21) {
// Игрок проиграл, если его сумма очков больше 21
message = "Вы проиграли!";
} else if (dealerSum > 21) {
// Игрок выиграл, если дилер превысил 21
message = "Дилер проиграл, вы выиграли!";
} else if (yourSum == dealerSum) {
// Если суммы очков равны, объявляется ничья
message = "Ничья!";
} else if (yourSum > dealerSum) {
// Игрок выиграл, если его сумма очков больше суммы дилера
message = "Вы выиграли!";
} else if (yourSum < dealerSum) {
// Игрок проиграл, если его сумма очков меньше суммы дилера
message = "Вы проиграли!";
}
// Обновляем отображение суммы очков дилера
document.getElementById("dealer-sum").innerText = dealerSum;
// Обновляем отображение суммы очков игрока
document.getElementById("your-sum").innerText = yourSum;
// Выводим сообщение о результате игры
document.getElementById("results").innerText = message;
// Показываем кнопку "Заново", чтобы игрок мог начать новую игру
document.getElementById("restart").style.display = "inline-block";
// Добавляем обработчик события для кнопки "Заново", чтобы она сбрасывала игру при нажатии
document.getElementById("restart").addEventListener("click", restartGame);
}
Осталось совсем чуть-чуть. Дальше реализуем функцию restartGame()
, которая будет сбрасывать все игровые значения и состояния.
Начало новой игры
Функция restartGame()
сбрасывает все значения до начальных: обнуляются суммы очков игрока и дилера, очищаются поля с картами и сбрасываются счётчики тузов. После этого заново создаётся и перемешивается колода, и с помощью функции startGame()
игра запускается.
function restartGame() {
// Сбрасываем сумму очков дилера
dealerSum = 0;
// Сбрасываем сумму очков игрока
yourSum = 0;
// Сбрасываем количество тузов у дилера
dealerAceCount = 0;
// Сбрасываем количество тузов у игрока
yourAceCount = 0;
// Разрешаем игроку снова брать карты
canHit = true;
// Очищаем поле с картами дилера и добавляем изображение скрытой карты
document.getElementById("dealer-cards").innerHTML =
'<img id="hidden" src="./img/back.png" class="card-img">';
// Очищаем поле с картами игрока
document.getElementById("your-cards").innerHTML = "";
// Сбрасываем отображаемую сумму очков дилера до нуля
document.getElementById("dealer-sum").innerText = "0";
// Сбрасываем отображаемую сумму очков игрока до нуля
document.getElementById("your-sum").innerText = "0";
// Очищаем сообщение о результате игры
document.getElementById("results").innerText = "";
// Заново создаём колоду
buildDeck();
// Перемешиваем колоду
shuffleDeck();
// Запускаем новую игру
startGame();
// Скрываем кнопку "Заново", пока игра не завершится снова
document.getElementById("restart").style.display = "none";
}
Сейчас, если мы запустим игру, ничего происходить не будет: у нас не хватает функции определения стоимости карт и логики расчёта тузов. Напишем их дальше.
Вспомогательные функции
Осталось реализовать три функции, без которых игра не будет корректно работать:
getValue()
определяет ценность карты по её значению (число, валет, дама, король или туз). Функция читает название файла каждой карты и извлекает из него значение.checkAce()
проверяет, является ли карта тузом, и возвращает соответствующее значение.reduceAce()
корректирует сумму очков игрока или дилера, если в руке есть туз.
Напишем их:
function getValue(cardSrc) {
// Разбиваем строку пути к карте, чтобы получить её значение и масть
let data = cardSrc.split("/").pop().split("-");
// Берём первую часть строки, которая содержит значение карты
let value = data[0];
// Проверяем, является ли значение карты числом
if (isNaN(value)) {
// Если это не число и карта — туз, возвращаем 11
if (value == "A") {
return 11;
}
// Для карт J, Q, K (валет, дама, король) возвращаем 10
return 10;
}
// Для числовых карт возвращаем их числовое значение
return parseInt(value);
}
function checkAce(cardSrc) {
// Проверяем, является ли первая буква в названии карты буквой A (туз)
if (cardSrc[0] == "A") {
// Если да, возвращаем 1
return 1;
}
// Если нет, возвращаем 0
return 0;
}
function reduceAce(playerSum, playerAceCount) {
// Пока сумма очков больше 21 и есть тузы, которые можно пересчитать как 1
while (playerSum > 21 && playerAceCount > 0) {
// Уменьшаем сумму очков на 10 (делаем туз 1 вместо 11)
playerSum -= 10;
// Уменьшаем количество тузов, посчитанных как 11
playerAceCount -= 1;
}
// Возвращаем скорректированную сумму очков
return playerSum;
}
Всё готово! Можно собирать всё вместе и играть. Или поиграть на странице проекта.
Что можно улучшить
Мы сделали рабочий вариант игры — можно пощёлкать кнопки и убить время на скучном созвоне. Но, как и любой проект, его можно бесконечно улучшать:
- добавить несколько колод;
- добавить анимацию;
- сделать режим для нескольких игроков;
- прикрутить звуковые эффекты;
- реализовать сохранение прогресса в LocalStorage.
Исправим это в следующих версиях, а вы пока подпишитесь, чтобы не пропустить продолжение. А ещё лучше — скидывайте в комментарии свои варианты реализации новой логики.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Black Jack</title>
<link rel="stylesheet" href="style.css">
<script src="main.js"></script>
</head>
<body>
<h2>Дилер: <span id="dealer-sum">0</span></h2>
<div id="dealer-cards">
<img id="hidden" src="./img/back.png" class="card-img">
</div>
<h2>Игрок: <span id="your-sum"></span></h2>
<div id="your-cards" class="card-container"></div>
<div class="buttons">
<button id="hit" data-tooltip="Взять">
<img src="./img/hit.png" alt="Icon">
</button>
<button id="stay" data-tooltip="Остановиться">
<img src="./img/stay.png" alt="Icon">
</button>
<button id="restart" style="display: none;" data-tooltip="Заново">
<img src="./img/restart.png" alt="Icon">
</button>
</div>
<h2 id="results"></h2>
</body>
</html>
body {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
background: radial-gradient(circle at center, #35654d, #1b3120, #050c05);
margin: 0;
padding: 0;
height: 100vh;
}
h2 {
color: aliceblue;
margin-bottom: 0;
}
.card-img {
width: 20vw;
height: auto;
max-width: 125px;
margin: 1em;
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 2em;
}
button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #c5c5c5;
border: 1px solid #333333;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
button img {
width: 30px;
height: 30px;
}
button::after {
content: attr(data-tooltip);
position: absolute;
bottom: -30px;
left: 50%;
transform: translateX(-50%);
background-color: #343434;
color: white;
padding: 0.5em;
border-radius: 5px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
button:hover::after {
opacity: 1;
}
let dealerSum = 0;
let yourSum = 0;
let dealerAceCount = 0;
let yourAceCount = 0;
let hidden;
let deck;
let canHit = true;
window.onload = function () {
buildDeck();
shuffleDeck();
startGame();
};
function buildDeck() {
let values = [
"A",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"J",
"Q",
"K",
];
let types = ["C", "D", "H", "S"];
deck = [];
for (let i = 0; i < types.length; i++) {
for (let j = 0; j < values.length; j++) {
deck.push(values[j] + "-" + types[i]);
}
}
}
function shuffleDeck() {
for (let i = 0; i < deck.length; i++) {
let j = Math.floor(Math.random() * deck.length);
let temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
console.log(deck);
}
function createCard(cardSrc) {
let cardImg = document.createElement("img");
cardImg.src = cardSrc;
cardImg.classList.add("card-img");
return cardImg;
}
function startGame() {
hidden = deck.pop();
dealerSum += getValue(hidden);
dealerAceCount += checkAce(hidden);
while (dealerSum < 17) {
let cardImg = createCard("./img/" + deck.pop() + ".png");
dealerSum += getValue(cardImg.src);
dealerAceCount += checkAce(cardImg.src);
document.getElementById("dealer-cards").append(cardImg);
}
for (let i = 0; i < 2; i++) {
let cardImg = createCard("./img/" + deck.pop() + ".png");
yourSum += getValue(cardImg.src);
yourAceCount += checkAce(cardImg.src);
document.getElementById("your-cards").append(cardImg);
}
document.getElementById("your-sum").innerText = yourSum;
document.getElementById("hit").addEventListener("click", hit);
document.getElementById("stay").addEventListener("click", stay);
}
function hit() {
if (!canHit) {
return;
}
let cardImg = createCard("./img/" + deck.pop() + ".png");
yourSum += getValue(cardImg.src);
yourAceCount += checkAce(cardImg.src);
document.getElementById("your-cards").append(cardImg);
document.getElementById("your-sum").innerText = yourSum;
if (reduceAce(yourSum, yourAceCount) > 21) {
canHit = false;
}
}
function stay() {
dealerSum = reduceAce(dealerSum, dealerAceCount);
yourSum = reduceAce(yourSum, yourAceCount);
canHit = false;
document.getElementById("hidden").src = "./img/" + hidden + ".png";
let message = "";
if (yourSum > 21) {
message = "Вы проиграли!";
} else if (dealerSum > 21) {
message = "Дилер проиграл, вы выиграли!";
} else if (yourSum == dealerSum) {
message = "Ничья!";
} else if (yourSum > dealerSum) {
message = "Вы выиграли!";
} else if (yourSum < dealerSum) {
message = "Вы проиграли!";
}
document.getElementById("dealer-sum").innerText = dealerSum;
document.getElementById("your-sum").innerText = yourSum;
document.getElementById("results").innerText = message;
document.getElementById("restart").style.display = "inline-block";
document.getElementById("restart").addEventListener("click", restartGame);
}
function restartGame() {
dealerSum = 0;
yourSum = 0;
dealerAceCount = 0;
yourAceCount = 0;
canHit = true;
document.getElementById("dealer-cards").innerHTML =
'<img id="hidden" src="./img/back.png" class="card-img">';
document.getElementById("your-cards").innerHTML = "";
document.getElementById("dealer-sum").innerText = "0";
document.getElementById("your-sum").innerText = "0";
document.getElementById("results").innerText = "";
buildDeck();
shuffleDeck();
startGame();
document.getElementById("restart").style.display = "none";
}
function getValue(cardSrc) {
let data = cardSrc.split("/").pop().split("-");
let value = data[0];
if (isNaN(value)) {
if (value == "A") {
return 11;
}
return 10;
}
return parseInt(value);
}
function checkAce(cardSrc) {
if (cardSrc[0] == "A") {
return 1;
}
return 0;
}
function reduceAce(playerSum, playerAceCount) {
while (playerSum > 21 && playerAceCount > 0) {
playerSum -= 10;
playerAceCount -= 1;
}
return playerSum;
}