Files
PGiZ4_2/Assets/Scripts/GameManager.cs
T
2026-10-27 21:43:24 +03:00

361 lines
12 KiB
C#

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<CardModel>();
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<CardView>(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}";
}
}