From 898c24d5490b7173f49fcc424b09daff69ccd528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1rk=C3=B6zi=20Gergely=20J=C3=A1nos?= <cycss7@inf.elte.hu> Date: Mon, 2 May 2022 14:47:40 +0200 Subject: [PATCH] document classes in Logic.Data namespace --- Assets/Scripts/Logic/Data/Color.cs | 3 + Assets/Scripts/Logic/Data/GameOverview.cs | 35 ++++++-- Assets/Scripts/Logic/Data/GamePhase.cs | 20 +++++ Assets/Scripts/Logic/Data/GameTeam.cs | 85 ++++++++++++++++++- .../Scripts/Logic/Data/IGameEconomyConfig.cs | 23 ++++- Assets/Scripts/Logic/Data/IGameOverview.cs | 58 ++++++++++++- .../Scripts/Logic/Data/IGameOverviewConfig.cs | 8 ++ Assets/Scripts/Presentation/UI/BattleUI.cs | 3 +- 8 files changed, 223 insertions(+), 12 deletions(-) diff --git a/Assets/Scripts/Logic/Data/Color.cs b/Assets/Scripts/Logic/Data/Color.cs index ab01cb6..1925382 100644 --- a/Assets/Scripts/Logic/Data/Color.cs +++ b/Assets/Scripts/Logic/Data/Color.cs @@ -1,5 +1,8 @@ ďťżnamespace Logic.Data { +/// <summary> +/// Color that's primarily used to differentiate <see cref="GameTeam"/> instances. +/// </summary> public enum Color { Red, Blue diff --git a/Assets/Scripts/Logic/Data/GameOverview.cs b/Assets/Scripts/Logic/Data/GameOverview.cs index 4935513..4df8ab7 100644 --- a/Assets/Scripts/Logic/Data/GameOverview.cs +++ b/Assets/Scripts/Logic/Data/GameOverview.cs @@ -8,11 +8,17 @@ using Logic.Handler; using Logic.System; namespace Logic.Data { + +/// <summary> +/// Implementation of <see cref="IGameOverview"/>. +/// The interface was created to allow testing (mocking). +/// </summary> public class GameOverview : IGameOverview { #region Fields private readonly IList<BaseSystem> _systems = new List<BaseSystem>(); private readonly IList<BaseHandler> _handlers = new List<BaseHandler>(); + private readonly IGameOverviewConfig _config; private readonly GameTeam _redTeam; private readonly GameTeam _blueTeam; @@ -32,9 +38,7 @@ public class GameOverview : IGameOverview { public Random Random { get; } - public IEnumerable<GameTeam> Teams => new[] {_redTeam, _blueTeam}; - - public IGameOverviewConfig OverviewConfig { get; } + public IEnumerable<GameTeam> Teams => new[] { _redTeam, _blueTeam }; public IGameEconomyConfig EconomyConfig { get; } @@ -42,9 +46,19 @@ public class GameOverview : IGameOverview { #region Methods + /// <summary> + /// Creates a new game instance. + /// Also creates and initializes all aggregated game components (e.g. <see cref="GameWorld"/>). + /// </summary> + /// <param name="eventExceptionHandler">the callback that gets called when an unexpected + /// error occurs in the game logic (this callback should log the error or close the app)</param> + /// <param name="rngSeed">the seed to use for <see cref="Random"/></param> + /// <param name="overviewConfig">container of configuration entries related to this class</param> + /// <param name="economyConfig">the value for <see cref="EconomyConfig"/></param> + /// <param name="worldConfig">the value for <see cref="GameWorld.Config"/></param> public GameOverview(Action<Exception> eventExceptionHandler, int rngSeed, IGameOverviewConfig overviewConfig, IGameEconomyConfig economyConfig, IGameWorldConfig worldConfig) { - OverviewConfig = overviewConfig; + _config = overviewConfig; EconomyConfig = economyConfig; Events = new EventDispatcher((exceptions => { @@ -85,12 +99,17 @@ public class GameOverview : IGameOverview { throw new Exception($"Unexpected team: {team}"); } + /// <summary> + /// Tries to update the value of <see cref="GamePhase"/> based on its current value. + /// Also updates <see cref="TimeLeftFromPhase"/>. + /// </summary> + /// <exception cref="Exception">if advancing from the current phase is not possible</exception> internal void AdvancePhase() { GamePhase oldPhase = CurrentPhase; if (CurrentPhase == GamePhase.Prepare) { CurrentPhase = GamePhase.Fight; - TimeLeftFromPhase = Math.Max(OverviewConfig.FightingPhaseDuration, + TimeLeftFromPhase = Math.Max(_config.FightingPhaseDuration, World.GetTileObjectsOfType<Barrack>() .Max(barrack => barrack.QueuedUnits.Count * World.Config.BarrackSpawnCooldownTime @@ -110,6 +129,11 @@ public class GameOverview : IGameOverview { Events.Raise(new PhaseAdvancedEvent(this, oldPhase)); } + /// <summary> + /// Decreases the value of <see cref="TimeLeftFromPhase"/>, + /// calling <see cref="AdvancePhase"/> if it reaches 0. + /// </summary> + /// <param name="deltaTime">the amount of time that has passed</param> internal void DecreaseTimeLeftFromPhase(float deltaTime) { TimeLeftFromPhase -= deltaTime; if (TimeLeftFromPhase <= 0) { @@ -138,4 +162,5 @@ public class GameOverview : IGameOverview { #endregion } + } diff --git a/Assets/Scripts/Logic/Data/GamePhase.cs b/Assets/Scripts/Logic/Data/GamePhase.cs index e5ad9e1..8d4e0f8 100644 --- a/Assets/Scripts/Logic/Data/GamePhase.cs +++ b/Assets/Scripts/Logic/Data/GamePhase.cs @@ -1,8 +1,28 @@ ďťżnamespace Logic.Data { +/// <summary> +/// Represents the current state the of the game. +/// </summary> public enum GamePhase { + /// <summary> + /// The simulation is paused, teams can build towers and purchase units. + /// The next phase is always <see cref="Fight"/>. + /// </summary> Prepare, + + /// <summary> + /// A simulation is running: units are advancing, towers are shooting. + /// The next phase can either be <see cref="Prepare"/> or <see cref="Finished"/> + /// depending on whether any castle got destroyed. + /// </summary> Fight, + + /// <summary> + /// The game is over: one of the teams won (or they tied). + /// No (modifying) actions are possible. + /// The only way to "move forward" is to create a new game. + /// No next phase exists: advancing the phase isn't possible. + /// </summary> Finished } diff --git a/Assets/Scripts/Logic/Data/GameTeam.cs b/Assets/Scripts/Logic/Data/GameTeam.cs index 9cc9214..ca8920f 100644 --- a/Assets/Scripts/Logic/Data/GameTeam.cs +++ b/Assets/Scripts/Logic/Data/GameTeam.cs @@ -5,6 +5,12 @@ using Logic.Data.World; using Logic.Event.Team; namespace Logic.Data { + +/// <summary> +/// Represents a player in the game. +/// <see cref="Building"/>, <see cref="Unit"/> instances belong to players and they have money to manage. +/// This class also contains some statistics related to them. +/// </summary> public class GameTeam { #region Fields @@ -15,20 +21,44 @@ public class GameTeam { #region Properties + /// <summary> + /// The game instance this team belongs to. + /// </summary> public IGameOverview Overview { get; } + /// <summary> + /// The color used to identify this team within the game. + /// </summary> public Color TeamColor { get; } + /// <summary> + /// The (unique) barracks of this team. + /// The list doesn't change and its size of always exactly 2. + /// </summary> public IReadOnlyList<Barrack> Barracks { get; } + /// <summary> + /// The castle of this team. + /// </summary> public Castle Castle { get; } + /// <summary> + /// The current (unique) towers of this team. + /// </summary> public IEnumerable<Tower> Towers => Overview.World.GetTileObjectsOfType<Tower>() .Where(t => t.OwnerColor == TeamColor); + /// <summary> + /// The current (unique) units of this team. + /// </summary> public IEnumerable<Unit> Units => Overview.World.Units .Where(t => t.Owner == this); + /// <summary> + /// The current (unique) positions where a tower could be placed. + /// This value is either cached, or querying it is fast. + /// Please keep in mind that building a tower (among other actions) changes the returned value. + /// </summary> public ISet<TilePosition> AvailableTowerPositions { get { if (_availableTowerPositionsCache == null) RecalculateAvailableTowerPositions(); @@ -36,21 +66,38 @@ public class GameTeam { } } + /// <summary> + /// The current money of this team. + /// </summary> public int Money { get; private set; } - public int PresentTowerCount => Towers.Count(); - + /// <summary> + /// The total amount of money this team has spent. + /// </summary> public int MoneySpent { get; private set; } + /// <summary> + /// The total amount of units this team has purchased. + /// </summary> public int PurchasedUnitCount { get; private set; } + /// <summary> + /// The total amount of towers this team has built. + /// Destroying towers doesn't decrement this value. + /// </summary> public int BuiltTowerCount { get; private set; } - public int AliveUnits => Units.Count(); #endregion #region Methods + /// <summary> + /// Creates and initializes a new instance. Should be called by <see cref="GameOverview"/>. + /// </summary> + /// <param name="overview">the game this team belongs to</param> + /// <param name="color">the value for <see cref="TeamColor"/></param> + /// <param name="castle">the value for <see cref="Castle"/></param> + /// <param name="barracks">the value for <see cref="Barracks"/></param> internal GameTeam(IGameOverview overview, Color color, Castle castle, IEnumerable<Barrack> barracks) { Overview = overview; TeamColor = color; @@ -59,6 +106,13 @@ public class GameTeam { Money = overview.EconomyConfig.StartingBalance; } + /// <summary> + /// Decreases this team's <see cref="Money"/> and increases <see cref="MoneySpent"/> + /// by the specified amount. Fails if not enough money is available. + /// </summary> + /// <param name="amount">the amount of money to spend</param> + /// <exception cref="ArgumentException">if the specified amount is invalid + /// or if this team doesn't have enough money</exception> internal void SpendMoney(int amount) { if (amount <= 0) throw new ArgumentException($"Cannot spend non-positive amount {amount}"); if (amount > Money) throw new ArgumentException($"Cannot spend {amount} when balance is {Money}"); @@ -69,6 +123,11 @@ public class GameTeam { Overview.Events.Raise(new TeamMoneyUpdatedEvent(this, oldMoney)); } + /// <summary> + /// Increases the value of <see cref="Money"/> by the specified amount. + /// </summary> + /// <param name="amount">the amount of money to give</param> + /// <exception cref="ArgumentException">if the specified amount is invalid</exception> internal void GiveMoney(int amount) { if (amount <= 0) throw new ArgumentException($"Cannot give non-positive amount {amount}"); @@ -77,10 +136,19 @@ public class GameTeam { Overview.Events.Raise(new TeamMoneyUpdatedEvent(this, oldMoney)); } + /// <summary> + /// Gets the count of units from the specified type this team has purchased in total. + /// </summary> + /// <param name="unitTypeData">the unit type to query</param> + /// <returns>the total amount of units of the specified type this team has purchased</returns> public int GetDeployedUnitTypeCount(IUnitTypeData unitTypeData) { return _deployedUnitTypeCounts.TryGetValue(unitTypeData, out int count) ? count : 0; } + /// <summary> + /// Increases the value returned by <see cref="GetDeployedUnitTypeCount"/> for the specified type. + /// </summary> + /// <param name="unitTypeData">the type whose counter to increment</param> internal void IncrementPurchasedUnitCount(IUnitTypeData unitTypeData) { int newCount = 1; if (_deployedUnitTypeCounts.TryGetValue(unitTypeData, out int count)) { @@ -92,15 +160,25 @@ public class GameTeam { Overview.Events.Raise(new TeamStatisticsUpdatedEvent(this)); } + /// <summary> + /// Increments the value of <see cref="BuiltTowerCount"/>. + /// </summary> internal void IncrementBuiltTowerCount() { BuiltTowerCount++; Overview.Events.Raise(new TeamStatisticsUpdatedEvent(this)); } + /// <summary> + /// Clears the <see cref="AvailableTowerPositions"/> cache. + /// </summary> internal void InvalidateCachedAvailableTowerPositions() { _availableTowerPositionsCache = null; } + /// <summary> + /// Calculates the value returned by <see cref="AvailableTowerPositions"/> + /// (bypassing the cache, meaning the cache isn't used, the value is recalculated). + /// </summary> private void RecalculateAvailableTowerPositions() { GameWorld world = Overview.World; _availableTowerPositionsCache = new HashSet<TilePosition>(); @@ -139,4 +217,5 @@ public class GameTeam { #endregion } + } diff --git a/Assets/Scripts/Logic/Data/IGameEconomyConfig.cs b/Assets/Scripts/Logic/Data/IGameEconomyConfig.cs index 2ed631f..61a2772 100644 --- a/Assets/Scripts/Logic/Data/IGameEconomyConfig.cs +++ b/Assets/Scripts/Logic/Data/IGameEconomyConfig.cs @@ -1,9 +1,30 @@ -ďťżnamespace Logic.Data { +ďťżusing Logic.Data.World; +namespace Logic.Data { + +/// <summary> +/// Container of configuration entries related to the <see cref="IGameOverview"/> class. +/// </summary> public interface IGameEconomyConfig { + /// <summary> + /// Initial value of <see cref="GameTeam.Money"/>. + /// </summary> public int StartingBalance { get; } + + /// <summary> + /// Amount of money each <see cref="GameTeam"/> receives at the end of each round. + /// </summary> public int RoundBasePay { get; } + + /// <summary> + /// Amount of money a <see cref="GameTeam"/> receives when an enemy <see cref="Unit"/> is destroyed. + /// </summary> public int NewUnitsDestroyedPay { get; } + + /// <summary> + /// Amount of money a <see cref="GameTeam"/> receives at the end of each round. + /// This value gets multiplied by <see cref="GameTeam.PurchasedUnitCount"/> and that amount is given. + /// </summary> public int TotalUnitsPurchasedPay { get; } } diff --git a/Assets/Scripts/Logic/Data/IGameOverview.cs b/Assets/Scripts/Logic/Data/IGameOverview.cs index 5c2638d..a4c267f 100644 --- a/Assets/Scripts/Logic/Data/IGameOverview.cs +++ b/Assets/Scripts/Logic/Data/IGameOverview.cs @@ -6,18 +6,72 @@ using Logic.Event; namespace Logic.Data { -//Interface only has 1 implementation, but is required for mocking (testing). +/// <summary> +/// Container of everything related to the game. +/// This class is responsible for integrating the different components of the game: the world, the teams. +/// It also implements a feature: the phase management. +/// </summary> public interface IGameOverview { + /// <summary> + /// The common event dispatcher used everywhere in this game instance. + /// </summary> EventDispatcher Events { get; } + + /// <summary> + /// The common command dispatcher used everywhere in this game instance. + /// </summary> CommandDispatcher Commands { get; } + + /// <summary> + /// The world of this game instance. + /// </summary> GameWorld World { get; } + + /// <summary> + /// The current phase of this game instance. + /// </summary> + /// <seealso cref="TimeLeftFromPhase"/> GamePhase CurrentPhase { get; } + + /// <summary> + /// The time until the team is updated "automatically". + /// The value is never negative and may be infinite. + /// </summary> float TimeLeftFromPhase { get; } + + /// <summary> + /// The random instance used as the randomness source in this game instance. + /// This random should be used directly to get random values + /// or it can be used to seed a new random instance. + /// </summary> Random Random { get; } + + /// <summary> + /// The unique teams in this game instance. + /// The count of elements is always exactly 2. + /// </summary> public IEnumerable<GameTeam> Teams { get; } - public IGameOverviewConfig OverviewConfig { get; } + + /// <summary> + /// Container of configuration entries related to economy (money management) features. + /// </summary> public IGameEconomyConfig EconomyConfig { get; } + + /// <summary> + /// Gets the team whose <see cref="GameTeam.TeamColor"/> equals the specified value. + /// </summary> + /// <param name="color">the color whose associated team to get</param> + /// <returns>the <see cref="GameTeam"/> associated with the specified color</returns> + /// <exception cref="Exception">if the specified color is invalid</exception> GameTeam GetTeam(Color color); + + /// <summary> + /// Gets the <see cref="GameTeam"/> which isn't the specified team. + /// This works because there are only 2 teams. + /// </summary> + /// <param name="team">the team whose enemy to get</param> + /// <returns>the enemy of the specified team</returns> + /// <exception cref="Exception">if the specified team is invalid</exception> GameTeam GetEnemyTeam(GameTeam team); } diff --git a/Assets/Scripts/Logic/Data/IGameOverviewConfig.cs b/Assets/Scripts/Logic/Data/IGameOverviewConfig.cs index b150924..9a0c799 100644 --- a/Assets/Scripts/Logic/Data/IGameOverviewConfig.cs +++ b/Assets/Scripts/Logic/Data/IGameOverviewConfig.cs @@ -1,6 +1,14 @@ ďťżnamespace Logic.Data { +/// <summary> +/// Container of configuration entries related to the <see cref="IGameOverview"/> class. +/// </summary> public interface IGameOverviewConfig { + /// <summary> + /// The preferred duration of the <see cref="GamePhase.Fight"/>. + /// This value can be overridden in special circumstances + /// (e.g. this isn't enough to spawn all the units from the barracks) + /// </summary> public float FightingPhaseDuration { get; } } diff --git a/Assets/Scripts/Presentation/UI/BattleUI.cs b/Assets/Scripts/Presentation/UI/BattleUI.cs index be5f112..ae3f291 100644 --- a/Assets/Scripts/Presentation/UI/BattleUI.cs +++ b/Assets/Scripts/Presentation/UI/BattleUI.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Logic.Data; using UnityEngine; using UnityEngine.UIElements; @@ -118,7 +119,7 @@ public class BattleUI : MonoBehaviour { VisualElement stats = GetRoundStats(team.TeamColor).Q(PlayerStatContainer); stats.Clear(); - string[] texts = { $"Active towers: {team.PresentTowerCount}", $"Alive Units: {team.AliveUnits}" }; + string[] texts = { $"Active towers: {team.Towers.Count()}", $"Alive Units: {team.Units.Count()}" }; foreach (string text in texts) stats.Add(new Label { text = text }); } -- GitLab