- Introduced the `HSchool.Ai` project, responsible for routing, day plans, and presence management. - Updated the `HSchool.Simulation` project to integrate with the new AI functionalities, improving decision-making and presence tracking. - Added `travelMinutes` to room and territory definitions, ensuring accurate movement calculations within the simulation. - Enhanced the `School` class to manage presence and implement empty-time skipping functionality. - Updated documentation to reflect the new AI features and their impact on school simulation. - Added tests for presence management and routing to ensure robust functionality and reliability.
81 lines
2.9 KiB
C#
81 lines
2.9 KiB
C#
namespace HSchool.Simulation;
|
|
|
|
/// <summary>
|
|
/// In-game calendar of one school. Time only moves while <see cref="IsRunning"/> is set, and it
|
|
/// moves by whole fixed steps — never by wall-clock deltas — so the same tick count always
|
|
/// produces the same date.
|
|
/// </summary>
|
|
public sealed class GameClock
|
|
{
|
|
/// <summary>Earliest date a school may start at; anything below is a typo, not a design choice.</summary>
|
|
public static readonly DateTime MinStartDate = new(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
|
|
public static readonly DateTime MaxStartDate = new(2999, 12, 31, 23, 59, 59, DateTimeKind.Utc);
|
|
|
|
private int _speedIndex = ClockSpeed.DefaultIndex;
|
|
|
|
public GameClock(DateTime startDate)
|
|
{
|
|
if (!IsValidStartDate(startDate))
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(startDate), startDate, "Start date is outside the supported range.");
|
|
}
|
|
|
|
// The game calendar is not tied to a real time zone; UTC keeps serialization unambiguous.
|
|
Time = DateTime.SpecifyKind(startDate, DateTimeKind.Utc);
|
|
}
|
|
|
|
public DateTime Time { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Schools live on their own: a new calendar starts running and only the player's pause
|
|
/// button stops it. Leaving for the menu does not.
|
|
/// </summary>
|
|
public bool IsRunning { get; set; } = true;
|
|
|
|
/// <summary>Index into <see cref="ClockSpeed.Multipliers"/>; invalid values are ignored.</summary>
|
|
public int SpeedIndex
|
|
{
|
|
get => _speedIndex;
|
|
set
|
|
{
|
|
if (ClockSpeed.IsValid(value))
|
|
{
|
|
_speedIndex = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public double Multiplier => ClockSpeed.MultiplierAt(_speedIndex);
|
|
|
|
public static bool IsValidStartDate(DateTime date) => date >= MinStartDate && date <= MaxStartDate;
|
|
|
|
/// <summary>Empty-time skip. Not a tick — the calendar jumps to an instant already known to be legal.</summary>
|
|
public void JumpTo(DateTime time)
|
|
{
|
|
if (!IsValidStartDate(time))
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(time), time, "Jump target is outside the supported range.");
|
|
}
|
|
|
|
Time = DateTime.SpecifyKind(time, DateTimeKind.Utc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advances the calendar by one fixed step of <paramref name="realSeconds"/>, scaled by the
|
|
/// base rate and the current speed. Does nothing while paused.
|
|
/// </summary>
|
|
/// <returns>Game minutes actually added; zero while paused.</returns>
|
|
public double Advance(double realSeconds, double gameMinutesPerRealSecond)
|
|
{
|
|
if (!IsRunning)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var gameMinutes = realSeconds * gameMinutesPerRealSecond * Multiplier;
|
|
Time = Time.AddMinutes(gameMinutes);
|
|
return gameMinutes;
|
|
}
|
|
}
|