Name | Rating | Preferred pos | Skill |
---|---|---|---|
Cristiano Ronaldo | 94 | LW/ST | 5 |
David Silva | 87 | CM | 4 |
Fedor Smolov | 79 | ST | 2 |
Actions
Хранилище
Механика
схема основных Action-ов
Action - основа гибкости игры
public interface IAction
{
string Explanation { get; }
int Value { get; }
bool IsAvailable { get; }
bool Execute(IParameters parameters);
void SetUp(Game game);
void Accept(ISuccess success);
}
Удобно хранить действия в одной структуре данных
Разные действия принимают разное количество параметров разных типов
public abstract class Action<T> : IAction
where T : class, IParameters
{
public abstract bool IsAvailable { get; }
public abstract string Explanation { get; }
public abstract int Value { get; }
public abstract bool AreSuitable(T parameters);
public abstract bool Execute(T parameters);
public abstract void Accept(ISuccess success);
protected bool wasSuccessfullyExecuted;
protected Game game;
// More code ...
}
Вызывается Action с конкретными параметрами
public abstract bool Execute(T parameters);
public bool Execute(IParameters parameters)
{
wasSuccessfullyExecuted = false;
var converted = CheckParameters(parameters);
return Execute(converted);
}
public class GetFromDeckAction : Action<GetFromDeckParameters> { public override string Explanation => "Get card from deck and put it in hand"; public override int Value => 5; public override bool IsAvailable => true; public override bool AreSuitable(GetFromDeckParameters parameters) => true; public override bool Execute(GetFromDeckParameters parameters) { var deck = parameters.Deck; if (!deck.Any) return false; game.CurrentPlayer.Team.Hand.InsertCard(deck.GetCard()); wasSuccessfullyExecuted = true; return wasSuccessfullyExecuted; } public override void Accept(ISuccess success) => success.Apply(this, wasSuccessfullyExecuted); }
Меняет глобальное состояние игры в зависимости от результата действия
public interface ISuccess { string Message { get; } void Apply(PassAction action, bool successful); // More code... }
public void Apply(PassAction action, bool successful) { var successPass = SuccessAction(action, g => $"ball moves to {game.BallPlace}, Nice!"); var failurePass = FailureAction(action, g => $"ball was intercepted by {game.BallOwner}"); Apply(successPass, failurePass, successful); }
Разные способы загрузки данных для игры
public interface IDatabase
{
PlayerInfo GetPlayerInfo(string name);
PlayerInfo GetPlayerOfType(params string[] types);
IEnumerable<PlayerInfo> GetPlayers(int count);
}
Обертка для IDatabase для создания объектов игры
public interface IFootballDatabase
{
FootballCard GetCardOfType(ZoneType zone);
IEnumerable<FootballCard>GetCards(int count);
}
Пример рассчета характеристики успешности паса
public static double PassPower(this Zone zone)
{
var totalAvgDef = zone.CardsAttributes(f => f.Defend).Average();
var totalAvgMid = zone.CardsAttributes(f => f.Midfield).Average();
var totalAvgAtt = zone.CardsAttributes(f => f.Attack).Average();
return (totalAvgDef + totalAvgMid + totalAvgAtt) / 3;
}
private static IEnumerable<double> CardsAttributes(this Zone zone, Func<FootballCard, double> attributeSelector) { var attributes = zone.GetCards().Select(f => attributeSelector(f)); if (!attributes.Any()) return new List<double>() { 0 }; return attributes; }
public class Game
{
public readonly Deck Deck;
public int MovesLeft { get; private set; }
public Player CurrentPlayer { get; }
public string Message { get; }
public IEnumerable<Player> GetOpponents { get; }
private void Next();
public void Turn(Tuple<IAction, IParameters> executionPair);
public bool IsEnd { get; }
public void AddPlayer(Player player);
}
public class Player
{
public readonly string Name;
public Team Team { get; set; }
public int Score { get; private set; }
public void IncreaseScore(int points);
}
public class Team
{
public readonly Squad Squad;
public readonly Hand Hand;
public IBall Ball { get; private set; }
public bool HasBall { get; }
public void Update(IBall ball);
public void SubstitutionFromHandToSquad(ZoneType type,
int cardPosition,
int newCardPosition);
public void SwapInSquad(ZoneType first, int firstPos,
ZoneType second, int secondPos;
}
public class Hand
{
public int HandSize { get; }
public bool Any { get; }
public FootballCard Peek { get; }
public bool Remove(FootballCard card);
public void InsertCard(FootballCard card);
public IEnumerable<FootballCard> GetCardsByRank(int count);
public bool Contains(FootballCard card);
}
public class Squad
{
private Dictionary<ZoneType, Zone> squad;
public readonly string Formation;
public string Name { get; private set; }
public bool Any { get; }
public bool IsActive(ZoneType zone, int position);
public FootballCard Remove(ZoneType type, int cardIndex);
public void Insert(ZoneType type, FootballCard card,
int position);
public double GetZonePower(ZoneType zoneType,
Func<Zone, double> calculate);
}
public class Zone : BaseZone
{
public bool Any;
public int Count;
public IEnumerable<FootballCard> GetCards();
public FootballCard RemoveCard(int cardIndex);
public void RemoveDeadCards();
public void InsertCard(FootballCard card, int position);
}
static class ZoneExtensions
{
public static double PassPower(this Zone zone);
public static double DefendPower(this Zone zone);
public static double WithAdditionalPower(this Zone zone,
FootballCard card);
public static double InterceptPower(this Zone zone);
public static double ShootPower(this Zone zone);
public static double PressurePower(this Zone zone);
public static void DecreaseRandomCardRank(this Zone zone,
int percent);
}
public class Ball: IBall
{
private List<Team> observers;
private Team owner;
public ZoneType Place { get; private set; }
public void AddObserver(Team observer);
public void Move();
public void InterceptedBy(Team newOwner);
public void Restart(Team newOwner);
private void UpdateObservers(Team newOwner);
}
Консольный режим
Взаимодействие с пользователем с консоли
Планируется полностью перенести игру на Юнити
// ...
var container = new StandardKernel();
container.Bind<IDatabase>().To<MongoDatabase>();
container.Bind<IFootballDatabase>().To<FootballDatabase>();
container.Bind<IBall>().To<Ball>();
container.Bind<IAction>().To<GetFromDeckAction>();
container.Bind<IAction>().To<InterceptionAction>();
container.Bind<IAction>().To<PassAction>();
container.Bind<IAction>().To<PressureAction>();
container.Bind<IAction>().To<ShootAction>();
container.Bind<IAction>().To<SwapAction>();
container.Bind<ISuccess>().To<Success>();
var controller = container.Get<ConsoleController>();
controller.Loop();
Вся игра построена на Action-ах. Суть тестов - корректное изменение состояния игры после применения Action-а
Метод тестирования:
[Test]
public void CheckPassTransition()
{
var ball = Parameters.Container.Get<Ball>();
var players = Parameters.GetPlayers(ball);
var first = players.Item1;
var second = players.Item2;
IAction action = Parameters.Container.Get<PassAction>();
action.SetUp(Parameters.Container.Get<Game>()
.AddPlayer(first)
.AddPlayer(second));
Assert.True(first.Team.HasBall);
Assert.AreEqual(first.Team.Ball.Place, ZoneType.MID);
var parameters = new EnemyParameters(second.Team);
if (action.Execute(parameters))
Assert.AreEqual(first.Team.Ball.Place, ZoneType.ATT);
else
{
Assert.True(second.Team.HasBall);
Assert.AreEqual(second.Team.Ball.Place, ZoneType.MID);
}
Общий подход к написанию тестов в итоге такой:
Невозможно привести игру в некорректное состояние - у Action есть две проверки:
Доступно ли действие
public abstract bool IsAvailable { get; }
Верные ли параметры
public abstract bool AreSuitable(T parameters);
Некорректный ввод обрабатывается и производится новая попытка ввода