+}
+else
+{
+ @* Кнопка для старта игры, видна только до начала *@
+
+}
+
+
+
+@* Блок с C# кодом *@
+@code {
+ // --- Поля состояния игры ---
+ private Deck GameDeck;
+ private List PlayerHand = new List(); // Инициализируем, чтобы избежать null reference до старта
+ private List DealerHand = new List(); // Инициализируем
+
+ // --- Вычисляемые свойства для счета ---
+ private int PlayerScore => CalculateScore(PlayerHand);
+ private int DealerScore => CalculateScore(DealerHand);
+
+ // --- Флаги состояния игры ---
+ private bool IsPlayerTurn;
+ private bool IsGameOver;
+ private bool IsGameStarted; // Показывает, была ли нажата кнопка "Начать игру"
+ private string GameMessage = string.Empty; // Инициализируем пустой строкой
+
+ // --- Инициализация компонента ---
+ protected override void OnInitialized()
+ {
+ // Игра не начинается автоматически при загрузке страницы
+ IsGameStarted = false;
+ }
+
+ // --- Метод начала новой игры ---
+ private void StartGame()
+ {
+ GameDeck = new Deck();
+ GameDeck.Shuffle();
+
+ PlayerHand = new List();
+ DealerHand = new List();
+
+ IsGameOver = false;
+ IsPlayerTurn = true;
+ GameMessage = "Ваш ход. Взять карту или стоп?";
+ IsGameStarted = true; // Показываем игровое поле
+
+ // Раздаем по 2 карты
+ PlayerHand.Add(GameDeck.DealCard());
+ DealerHand.Add(GameDeck.DealCard()); // Первая карта дилера
+ PlayerHand.Add(GameDeck.DealCard());
+ DealerHand.Add(GameDeck.DealCard()); // Вторая карта дилера
+
+ // Проверка на блэкджек у игрока или дилера сразу после раздачи
+ if (PlayerScore == 21 && DealerHand.Count == 2) // У игрока блэкджек
+ {
+ if (DealerScore == 21) // У дилера тоже блэкджек
+ {
+ RevealDealerCardAndEndGame("Ничья! У обоих Блэкджек!");
+ }
+ else // Только у игрока блэкджек
+ {
+ RevealDealerCardAndEndGame("Блэкджек! Вы выиграли!");
+ }
+ }
+ else if (DealerScore == 21 && DealerHand.Count == 2) // Только у дилера блэкджек (проверяем после игрока)
+ {
+ RevealDealerCardAndEndGame("Блэкджек у дилера! Вы проиграли.");
+ }
+ // Если ни у кого нет блэкджека, игра продолжается с сообщением "Ваш ход..."
+
+ StateHasChanged(); // Обновляем UI, чтобы показать начальное состояние
+ }
+
+
+ // --- Действие игрока: Взять карту (Hit) ---
+ private void PlayerHit()
+ {
+ if (!IsPlayerTurn || IsGameOver) return; // Нельзя брать карту не в свой ход или если игра окончена
+
+ PlayerHand.Add(GameDeck.DealCard());
+
+ if (PlayerScore > 21)
+ {
+ EndGame("Перебор! Вы проиграли.");
+ }
+ else if (PlayerScore == 21)
+ {
+ // Если игрок набрал ровно 21, его ход автоматически завершается
+ PlayerStand();
+ }
+ // Если меньше 21, игрок может брать еще
+
+ StateHasChanged(); // Обновляем UI после взятия карты
+ }
+
+ // --- Действие игрока: Стоп (Stand) ---
+ private void PlayerStand()
+ {
+ if (!IsPlayerTurn || IsGameOver) return; // Нельзя остановиться не в свой ход или если игра окончена
+
+ IsPlayerTurn = false; // Передаем ход дилеру
+ GameMessage = "Дилер ходит...";
+ StateHasChanged(); // Обновим сообщение и откроем карту дилера
+
+ // Можно добавить небольшую задержку перед ходом дилера для наглядности
+ // await Task.Delay(500); // Если раскомментировать, метод должен быть async Task
+ DealerTurn(); // Начинаем ход дилера
+ }
+
+
+ // --- Логика хода Дилера ---
+ // Простая синхронная версия. Для пауз нужен async/await и Task.Delay
+ private void DealerTurn()
+ {
+ // Дилер берет карты, пока его счет меньше 17
+ while (DealerScore < 17)
+ {
+ // В реальном приложении можно добавить паузу здесь
+ // await Task.Delay(800);
+ DealerHand.Add(GameDeck.DealCard());
+ // StateHasChanged(); // Обновлять UI после каждой карты дилера, если есть пауза
+ }
+
+ // После того, как дилер закончил брать карты, определяем победителя
+ DetermineWinner();
+ StateHasChanged(); // Показать финальный результат
+ }
+
+ // --- Подсчет очков для руки ---
+ private int CalculateScore(List hand)
+ {
+ if (hand == null || hand.Count == 0) return 0;
+
+ int score = 0;
+ int aceCount = 0;
+ foreach (var card in hand)
+ {
+ score += card.Value;
+ if (card.Rank == "A")
+ {
+ aceCount++;
+ }
+ }
+
+ // Корректировка значения Тузов (с 11 на 1 при переборе)
+ while (score > 21 && aceCount > 0)
+ {
+ score -= 10; // Считаем Туз как 1 вместо 11
+ aceCount--;
+ }
+ return score;
+ }
+
+ // --- Определение победителя ---
+ private void DetermineWinner()
+ {
+ // Убедимся, что карта дилера видна (IsPlayerTurn уже false)
+ if (PlayerScore > 21)
+ {
+ // Это условие уже должно было быть обработано в PlayerHit, но на всякий случай
+ EndGame("Перебор! Вы проиграли.");
+ }
+ else if (DealerScore > 21)
+ {
+ EndGame("У дилера перебор! Вы выиграли!");
+ }
+ else if (PlayerScore > DealerScore)
+ {
+ EndGame("Вы выиграли!");
+ }
+ else if (DealerScore > PlayerScore)
+ {
+ EndGame("Дилер выиграл!");
+ }
+ else // Очки равны
+ {
+ EndGame("Ничья (Push)!");
+ }
+ }
+
+ // --- Метод для завершения игры и установки сообщения ---
+ private void RevealDealerCardAndEndGame(string message)
+ {
+ IsPlayerTurn = false; // Убедимся, что карта дилера видна
+ EndGame(message);
+ }
+
+ private void EndGame(string message)
+ {
+ IsGameOver = true;
+ IsPlayerTurn = false; // Больше никто не ходит
+ GameMessage = message;
+ // StateHasChanged(); // Обычно вызывается в методе, который вызвал EndGame
+ }
+
+ // --- Вспомогательный метод для определения цвета карты ---
+ private string GetCardColor(Card card)
+ {
+ return (card.Suit == "♥" || card.Suit == "♦") ? "card-red" : "card-black";
+ }
+
+ // --- Реализация IDisposable (пока пустая, но может пригодиться) ---
+ public void Dispose()
+ {
+ // Здесь можно остановить таймеры или отписаться от событий, если они будут добавлены
+ }
+}
+
+@* --- КОНЕЦ КОДА ДЛЯ BlackjackGame.razor --- *@
\ No newline at end of file
diff --git a/Components/Pages/BlackjackGame.razor.razor b/Components/Pages/BlackjackGame.razor.razor
new file mode 100644
index 0000000..8e276a2
--- /dev/null
+++ b/Components/Pages/BlackjackGame.razor.razor
@@ -0,0 +1,169 @@
+/* BlackjackGame.razor.css - Стили для компонента Blackjack */
+
+/* Общие стили для секций игрока и дилера */
+h4 {
+ margin-bottom: 0.8rem; /* Немного больше отступа снизу */
+ font-weight: 600; /* Немного жирнее */
+ color: #e0e0e0; /* Светлый цвет для темного фона (если будете добавлять) */
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5); /* Легкая тень для читаемости */
+}
+
+/* Контейнер для карт (рука) */
+.hand {
+ display: flex;
+ flex-wrap: wrap; /* Карты будут переноситься, если не помещаются */
+ gap: 10px; /* Пространство между картами */
+ min-height: 95px; /* Минимальная высота, чтобы контейнер не схлопывался без карт */
+ margin-bottom: 1.5rem; /* Отступ под рукой */
+ padding: 8px;
+ background-color: rgba(0, 0, 0, 0.1); /* Легкий фон для области карт */
+ border-radius: 8px;
+ border: 1px solid rgba(255, 255, 255, 0.1); /* Тонкая светлая рамка */
+}
+
+/* Стиль для отдельной карты */
+.card {
+ background-color: #fff; /* Белый фон карты */
+ border: 1px solid #ababab; /* Серая рамка */
+ border-radius: 6px; /* Скругленные углы */
+ padding: 15px 10px; /* Внутренние отступы (больше по вертикали) */
+ min-width: 65px; /* Минимальная ширина карты */
+ text-align: center;
+ font-size: 1.5em; /* Размер шрифта ранга/масти */
+ font-weight: bold;
+ box-shadow: 3px 3px 7px rgba(0, 0, 0, 0.25); /* Тень для объема */
+ display: inline-block;
+ position: relative;
+ user-select: none; /* Запретить выделение текста на карте */
+ transition: transform 0.1s ease-in-out; /* Плавность при наведении (если добавим) */
+}
+
+/* Если захотите эффект при наведении */
+/*
+.card:hover {
+ transform: translateY(-3px);
+}
+*/
+
+/* Стиль для скрытой карты дилера ("???") */
+/* Мы не можем легко стилизовать сам текст "???", но можем стилизовать */
+/* первую карту в руке дилера, пока ход игрока. */
+/* Однако, текущая логика просто выводит текст "???" в .card */
+/* Давайте просто сделаем текст "?" серым, если бы мы обернули его в span */
+/* Если вы изменили Razor, как обсуждалось ранее: */
+/*
+.hidden-card-content {
+ color: #9e9e9e;
+ font-style: italic;
+ font-size: 0.9em;
+}
+*/
+/* В текущем варианте специальный стиль для "???" не применяется, она будет в стандартной карте */
+
+/* Цвета для мастей */
+.card-red {
+ color: #D32F2F; /* Насыщенный красный */
+}
+
+.card-black {
+ color: #212121; /* Глубокий черный */
+}
+
+/* Контейнер для кнопок действий */
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px; /* Расстояние между кнопками */
+ margin-top: 1.5rem; /* Отступ сверху */
+}
+
+/* Стиль для кнопок */
+.actions button,
+.btn-lg { /* Стиль для кнопки "Начать игру" */
+ padding: 10px 20px; /* Увеличим кнопки */
+ font-size: 1em;
+ cursor: pointer;
+ border-radius: 5px;
+ border: none; /* Убираем стандартную рамку Bootstrap */
+ transition: background-color 0.2s ease, transform 0.1s ease; /* Плавные переходы */
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2); /* Легкая тень для кнопок */
+}
+
+/* Улучшенные стили для кнопок Bootstrap (можно настроить под себя) */
+.actions .btn-primary { /* Взять карту */
+ background-color: #1976D2; /* Синий */
+ color: white;
+}
+.actions .btn-primary:hover:not(:disabled) {
+ background-color: #1565C0;
+ transform: translateY(-1px);
+}
+
+.actions .btn-secondary { /* Стоп */
+ background-color: #6c757d; /* Серый */
+ color: white;
+}
+.actions .btn-secondary:hover:not(:disabled) {
+ background-color: #5a6268;
+ transform: translateY(-1px);
+}
+
+.actions .btn-warning { /* Новая игра */
+ background-color: #FFA000; /* Оранжевый */
+ color: #212121; /* Темный текст на оранжевом */
+}
+.actions .btn-warning:hover:not(:disabled) {
+ background-color: #FF8F00;
+ transform: translateY(-1px);
+}
+
+/* Кнопка "Начать игру" */
+.btn-success.btn-lg {
+ background-color: #388E3C; /* Зеленый */
+ color: white;
+ font-size: 1.2em; /* Крупнее */
+ padding: 12px 25px;
+}
+.btn-success.btn-lg:hover:not(:disabled) {
+ background-color: #2E7D32;
+ transform: translateY(-1px);
+}
+
+
+/* Стиль для отключенных кнопок */
+.actions button:disabled {
+ cursor: not-allowed;
+ opacity: 0.5; /* Сделать полупрозрачными */
+ box-shadow: none; /* Убрать тень у неактивных */
+}
+
+/* Сообщения игры */
+.alert {
+ margin-top: 1rem;
+ margin-bottom: 1.5rem;
+ padding: 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+/* Успешное сообщение (выигрыш) */
+.alert-success {
+ background-color: #E8F5E9; /* Светло-зеленый фон */
+ color: #2E7D32; /* Темно-зеленый текст */
+ border: 1px solid #A5D6A7; /* Зеленая рамка */
+}
+
+/* Информационное сообщение (ход игры, ничья, проигрыш) */
+.alert-info {
+ background-color: #E3F2FD; /* Светло-голубой фон */
+ color: #1565C0; /* Темно-синий текст */
+ border: 1px solid #90CAF9; /* Голубая рамка */
+}
+
+/* Разделитель */
+hr {
+ border: none; /* Убираем стандартную линию */
+ border-top: 1px solid rgba(255, 255, 255, 0.2); /* Светлая полупрозрачная линия */
+ margin: 2rem 0; /* Увеличим отступы */
+}
\ No newline at end of file
diff --git a/Components/Pages/Error.razor b/Components/Pages/Error.razor
new file mode 100644
index 0000000..576cc2d
--- /dev/null
+++ b/Components/Pages/Error.razor
@@ -0,0 +1,36 @@
+@page "/Error"
+@using System.Diagnostics
+
+Error
+
+
Error.
+
An error occurred while processing your request.
+
+@if (ShowRequestId)
+{
+
+ Request ID:@RequestId
+
+}
+
+
Development Mode
+
+ Swapping to Development environment will display more detailed information about the error that occurred.
+
+
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
+ and restarting the app.
+