Хотите освоить C# и создать свою собственную карточную игру? Сегодня мы реализуем классический Blackjack (блэкджек, или «очко») в консоли. Проект поможет разобраться с объектно-ориентированным подходом, коллекциями, случайными числами и логикой игры. Давайте начнём!
Правила игры (кратко)
Игрок соревнуется с дилером. Цель — набрать сумму очков, близкую к 21, но не больше. Карты от 2 до 10 дают соответствующее число очков, валет, дама, король — 10 очков, туз — 1 или 11 (выбирается автоматически в пользу игрока без перебора). Игрок может брать новые карты (Hit) или остановиться (Stand). Дилер обязан брать, пока у него меньше 17 очков. Побеждает тот, у кого сумма ближе к 21, либо кто не перебрал.
Структура проекта
Создадим консольное приложение. Нам понадобятся три класса:
- Card — одна карта (масть, значение, очки)
- Deck — колода (список карт, перемешивание, выдача)
- Game — логика игры (игрок, дилер, ввод, вывод)
Весь код пишем в одном файле Program.cs для простоты.
Класс Card
public class Card
{
public string Suit { get; }
public string Rank { get; }
public int Value { get; }
public Card(string suit, string rank, int value)
{
Suit = suit;
Rank = rank;
Value = value;
}
public override string ToString() => $"{Rank}{Suit}";
}
Масти можно обозначать символами: ♠ ♥ ♣ ♦. Значения: 2-10, J, Q, K, A. Очки для туза временно зададим 11, но при подсчёте будем корректировать.
Класс Deck
Колода из 52 карт. Создадим метод Initialize() с перебором мастей и рангов.
public class Deck
{
private List<Card> _cards;
private readonly Random _random = new();
public Deck()
{
_cards = new List<Card>();
Initialize();
Shuffle();
}
private void Initialize()
{
string[] suits = { "♠", "♥", "♣", "♦" };
string[] ranks = { "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" };
int[] values = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11 };
for (int i = 0; i < suits.Length; i++)
{
for (int j = 0; j < ranks.Length; j++)
{
_cards.Add(new Card(suits[i], ranks[j], values[j]));
}
}
}
public void Shuffle()
{
for (int i = _cards.Count - 1; i > 0; i--)
{
int j = _random.Next(i + 1);
(_cards[i], _cards[j]) = (_cards[j], _cards[i]);
}
}
public Card DrawCard()
{
if (_cards.Count == 0)
{
Initialize();
Shuffle();
}
Card card = _cards[0];
_cards.RemoveAt(0);
return card;
}
}
Класс Game и логика подсчёта очков
Подсчёт очков: суммируем значения, но если сумма > 21 и есть туз (который дал 11), то заменяем 11 на 1 (уменьшаем сумму на 10).
public class Game
{
private Deck _deck;
private List<Card> _playerHand;
private List<Card> _dealerHand;
public Game()
{
_deck = new Deck();
_playerHand = new List<Card>();
_dealerHand = new List<Card>();
}
private int CalculateHandValue(List<Card> hand)
{
int sum = hand.Sum(c => c.Value);
int aceCount = hand.Count(c => c.Rank == "A");
while (sum > 21 && aceCount > 0)
{
sum -= 10;
aceCount--;
}
return sum;
}
public void Start()
{
// Начальная раздача
_playerHand.Add(_deck.DrawCard());
_dealerHand.Add(_deck.DrawCard());
_playerHand.Add(_deck.DrawCard());
_dealerHand.Add(_deck.DrawCard());
// Ход игрока
bool playerTurn = true;
while (playerTurn)
{
Console.Clear();
DisplayGame(false);
int playerScore = CalculateHandValue(_playerHand);
if (playerScore > 21)
{
Console.WriteLine("Перебор! Вы проиграли.");
playerTurn = false;
break;
}
Console.Write("Взять карту (h) или остановиться (s)? ");
string choice = Console.ReadLine()?.ToLower();
if (choice == "h")
{
_playerHand.Add(_deck.DrawCard());
}
else if (choice == "s")
{
playerTurn = false;
}
}
// Если игрок не перебрал, ход дилера
if (CalculateHandValue(_playerHand) <= 21)
{
DealerTurn();
DetermineWinner();
}
Console.WriteLine("Игра окончена. Нажмите любую клавишу...");
Console.ReadKey();
}
private void DealerTurn()
{
while (CalculateHandValue(_dealerHand) < 17)
{
_dealerHand.Add(_deck.DrawCard());
}
}
private void DetermineWinner()
{
Console.Clear();
DisplayGame(true);
int playerScore = CalculateHandValue(_playerHand);
int dealerScore = CalculateHandValue(_dealerHand);
if (dealerScore > 21)
Console.WriteLine("Дилер перебрал! Вы выиграли!");
else if (playerScore > dealerScore)
Console.WriteLine("Вы выиграли!");
else if (dealerScore > playerScore)
Console.WriteLine("Дилер выиграл!");
else
Console.WriteLine("Ничья!");
}
private void DisplayGame(bool revealDealer)
{
Console.WriteLine("=== Blackjack ===\n");
Console.Write("Дилер: ");
if (revealDealer)
{
foreach (var card in _dealerHand)
Console.Write(card + " ");
Console.WriteLine($" (очки: {CalculateHandValue(_dealerHand)})");
}
else
{
Console.Write($"{_dealerHand[0]} ??");
Console.WriteLine($" (очки: {CalculateHandValue(new List<Card> { _dealerHand[0] })})");
}
Console.Write("\nВаши карты: ");
foreach (var card in _playerHand)
Console.Write(card + " ");
Console.WriteLine($" (очки: {CalculateHandValue(_playerHand)})");
Console.WriteLine();
}
}
Точка входа
В Main просто создаём игру и запускаем цикл для повторных партий.
csharp
class Program
{
static void Main()
{
Console.Title = "Blackjack";
Console.OutputEncoding = System.Text.Encoding.UTF8;
bool playAgain = true;
while (playAgain)
{
var game = new Game();
game.Start();
Console.Write("Сыграем ещё? (y/n): ");
string answer = Console.ReadLine()?.ToLower();
if (answer != "y")
playAgain = false;
}
Console.WriteLine("Спасибо за игру!");
}
}
Улучшения, которые можно добавить
- Ставки и фишки – добавить переменную баланса игрока, ставку перед раздачей, удвоение ставки (double).
- Разделение пар (split) – если две карты одинакового ранга.
- Страховка при открытом тузе у дилера.
- Подсчёт карт – для продвинутых игроков.
Заключение
У нас получилась полноценная консольная игра Blackjack на C#. Вы познакомились с классами, списками, случайными числами, управлением игровым циклом и подсчётом очков с тузами. Этот код легко расширить, добавив новые возможности или графический интерфейс на WPF/Unity.
Попробуйте запустить, сыграйте пару партий и поэкспериментируйте с правилами. Если появятся вопросы – пишите в комментариях. Удачи в программировании!
Понравилась статья? Ставьте лайк и подписывайтесь на канал, чтобы не пропустить новые уроки по C# и созданию игр.