using System; using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using TMPro; using UnityEngine; public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } [Header("Containers")] public CardContainer deckContainer; public CardContainer handContainer; public CardContainer boardContainer; public CardContainer discardContainer; [Header("Prefabs & UI")] public CardView cardPrefab; public TMP_Text statusText; public TMP_Text scoreText; [Header("Rules")] public int maxHandSize = 4; private int _playerScore; private int _enemyScore; private bool _isBusy; private CancellationToken _destroyToken; private void Awake() { Instance = this; _destroyToken = this.GetCancellationTokenOnDestroy(); } private void Start() { StartGameAsync().Forget(); } private async UniTaskVoid StartGameAsync() { _isBusy = true; UpdateScoreUI(); SetStatus("Подготовка колоды..."); CreateAndShuffleDeck(); await UniTask.Delay(400, cancellationToken: _destroyToken); SetStatus("Раздаём карты..."); for (var i = 0; i < 3; i++) { await DrawTopCardToHandAsync(); } SetStatus("Ваш ход: сыграйте карту на поле или сбросьте ненужную"); _isBusy = false; } private void CreateAndShuffleDeck() { var models = new List(); for (var copy = 0; copy < 2; copy++) { foreach (CardSuit suit in Enum.GetValues(typeof(CardSuit))) { foreach (CardRank rank in Enum.GetValues(typeof(CardRank))) { models.Add(new CardModel(suit, rank)); } } } for (var i = models.Count - 1; i > 0; i--) { var rnd = UnityEngine.Random.Range(0, i + 1); (models[i], models[rnd]) = (models[rnd], models[i]); } foreach (var model in models) { var card = Instantiate(cardPrefab, deckContainer.cardHolder); card.Initialize(model, faceUp: false); deckContainer.AddCardInstant(card); } } public bool CanStartDrag(CardView card) { if (_isBusy || card.CurrentContainer == null) return false; return card.CurrentContainer.type switch { ContainerType.Deck => deckContainer.Cards.Count > 0 && deckContainer.Cards.Contains(card), ContainerType.Hand => true, ContainerType.Board => true, ContainerType.Discard => discardContainer.Cards.Count > 0 && discardContainer.Cards.Contains(card), _ => false }; } public async UniTask HandleCardDropAsync(CardView card, CardContainer origin, CardContainer target, int returnIndex) { _isBusy = true; if (target == null || target == origin) { await origin.AddCardAnimatedAsync(card, returnIndex, _destroyToken); _isBusy = false; return; } switch (origin.type) { case ContainerType.Deck when target.type == ContainerType.Hand: { if (handContainer.Cards.Count < maxHandSize) { await handContainer.AddCardAnimatedAsync(card, -1, _destroyToken); SetStatus($"Взята карта: {FormatCardName(card.Model)}"); } else { SetStatus($"В руке нет места (максимум {maxHandSize} карты)"); await origin.AddCardAnimatedAsync(card, returnIndex, _destroyToken); } break; } case ContainerType.Hand when target.type == ContainerType.Board: await boardContainer.AddCardAnimatedAsync(card, -1, _destroyToken); await ResolveBattleOnBoardAsync(card); break; case ContainerType.Hand when target.type == ContainerType.Discard: await discardContainer.AddCardAnimatedAsync(card, -1, _destroyToken); SetStatus($"Сброшен {FormatCardName(card.Model)}. Берём замену..."); await UniTask.Delay(250, cancellationToken: _destroyToken); await DrawTopCardToHandAsync(); SetStatus("Ваш ход: выберите карту для атаки"); break; case ContainerType.Discard when target.type == ContainerType.Deck: await deckContainer.AddCardAnimatedAsync(card, -1, _destroyToken); await RecycleDiscardToDeckAsync(); SetStatus("Сброшенные карты вернулись в колоду!"); break; case ContainerType.Board when target.type == ContainerType.Discard: await discardContainer.AddCardAnimatedAsync(card, -1, _destroyToken); SetStatus("Карта отправлена в сброс"); break; default: SetStatus("Сюда карту положить нельзя"); await origin.AddCardAnimatedAsync(card, returnIndex, _destroyToken); break; } _isBusy = false; } private async UniTask ResolveBattleOnBoardAsync(CardView playerCard) { SetStatus($"Вы сыграли: {FormatCardName(playerCard.Model)}. Ход противника..."); await UniTask.Delay(400, cancellationToken: _destroyToken); if (deckContainer.Cards.Count == 0) { await RecycleDiscardToDeckAsync(); } if (deckContainer.Cards.Count == 0) { SetStatus("В колоде закончились карты. Ничья в раунде!"); boardContainer.RemoveCardListReference(playerCard); await discardContainer.AddCardAnimatedAsync(playerCard, -1, _destroyToken); return; } var enemyCard = deckContainer.Cards[^1]; deckContainer.RemoveCardListReference(enemyCard); await boardContainer.AddCardAnimatedAsync(enemyCard, -1, _destroyToken); await UniTask.Delay(650, cancellationToken: _destroyToken); var result = CompareCards(playerCard.Model, enemyCard.Model); switch (result) { case > 0: _playerScore++; SoundManager.Instance?.PlayWin(); SetStatus($"Победа! Ваш {FormatCardName(playerCard.Model)} побеждает {FormatCardNameGenitive(enemyCard.Model)}!"); break; case < 0: _enemyScore++; SoundManager.Instance?.PlayLose(); SetStatus($"Поражение: {FormatCardName(enemyCard.Model)} соперника оказался сильнее."); break; default: SetStatus("Боевая ничья! Силы стихий равны."); break; } UpdateScoreUI(); await UniTask.Delay(1100, cancellationToken: _destroyToken); boardContainer.RemoveCardListReference(playerCard); boardContainer.RemoveCardListReference(enemyCard); await UniTask.WhenAll( discardContainer.AddCardAnimatedAsync(playerCard, -1, _destroyToken), discardContainer.AddCardAnimatedAsync(enemyCard, -1, _destroyToken) ); if (handContainer.Cards.Count == 0) { if (deckContainer.Cards.Count == 0) await RecycleDiscardToDeckAsync(); await DrawTopCardToHandAsync(); } SetStatus("Ваш ход: выберите карту для атаки"); } private int CompareCards(CardModel p, CardModel e) { if (p.rank != e.rank) return p.rank > e.rank ? 1 : -1; if (p.suit == e.suit) return 0; var playerSuitAdvantage = (p.suit == CardSuit.Fire && e.suit == CardSuit.Earth) || (p.suit == CardSuit.Earth && e.suit == CardSuit.Air) || (p.suit == CardSuit.Air && e.suit == CardSuit.Water) || (p.suit == CardSuit.Water && e.suit == CardSuit.Fire); return playerSuitAdvantage ? 1 : -1; } private async UniTask DrawTopCardToHandAsync() { if (deckContainer.Cards.Count == 0 || handContainer.Cards.Count >= maxHandSize) return; var topCard = deckContainer.Cards[^1]; deckContainer.RemoveCardListReference(topCard); await handContainer.AddCardAnimatedAsync(topCard, -1, _destroyToken); } private async UniTask RecycleDiscardToDeckAsync() { if (discardContainer.Cards.Count == 0) return; SetStatus("Перетасовываем сброс обратно в колоду..."); var cardsToRecycle = new List(discardContainer.Cards); discardContainer.Cards.Clear(); foreach (var c in cardsToRecycle) { await deckContainer.AddCardAnimatedAsync(c, -1, _destroyToken); } } public async UniTaskVoid DrawCardByClickAsync() { if (_isBusy) return; if (handContainer.Cards.Count >= maxHandSize) { SetStatus($"В руке уже максимум карт ({maxHandSize})"); return; } _isBusy = true; await DrawTopCardToHandAsync(); _isBusy = false; } public async UniTaskVoid PlayCardFromHandByClickAsync(CardView card) { if (_isBusy || card == null || card.CurrentContainer != handContainer) return; _isBusy = true; handContainer.RemoveCardListReference(card); await boardContainer.AddCardAnimatedAsync(card, -1, _destroyToken); await ResolveBattleOnBoardAsync(card); _isBusy = false; } public async UniTaskVoid RecycleDiscardByClickAsync() { if (_isBusy) return; if (discardContainer.Cards.Count == 0) { SetStatus("Стопка сброса пуста"); return; } _isBusy = true; await RecycleDiscardToDeckAsync(); SetStatus("Колода пополнена из сброса!"); _isBusy = false; } private void UpdateScoreUI() { if (scoreText != null) scoreText.text = $"Игрок: {_playerScore} | Противник: {_enemyScore}"; } private void SetStatus(string message) { if (statusText != null) statusText.text = message; } private static string FormatCardName(CardModel card) { var rank = card.rank switch { CardRank.Servant => "Слуга", CardRank.Knight => "Рыцарь", CardRank.Lord => "Владыка", _ => "Воин" }; var suit = card.suit switch { CardSuit.Fire => "Огня", CardSuit.Earth => "Земли", CardSuit.Air => "Воздуха", CardSuit.Water => "Воды", _ => "" }; return $"{rank} {suit}"; } private static string FormatCardNameGenitive(CardModel card) { var rank = card.rank switch { CardRank.Servant => "Слугу", CardRank.Knight => "Рыцаря", CardRank.Lord => "Владыку", _ => "Воина" }; var suit = card.suit switch { CardSuit.Fire => "Огня", CardSuit.Earth => "Земли", CardSuit.Air => "Воздуха", CardSuit.Water => "Воды", _ => "" }; return $"{rank} {suit}"; } }