361 lines
12 KiB
C#
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}";
|
|
}
|
|
} |