Enhance AI and simulation components with presence management and routing capabilities
- 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.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
namespace HSchool.Ai;
|
||||
|
||||
/// <summary>
|
||||
/// One person's place on the graph. <see cref="NodeId"/> is null when they are off campus.
|
||||
/// There is no "on an edge" state — remaining minutes are spent occupying the current node.
|
||||
/// </summary>
|
||||
public readonly record struct Presence(
|
||||
string? NodeId,
|
||||
float RemainingMinutes,
|
||||
string? DestinationId,
|
||||
bool HeadingHome,
|
||||
string[] Path)
|
||||
{
|
||||
public static Presence OffCampus { get; } = new(null, 0f, null, false, []);
|
||||
|
||||
public bool IsOnCampus => NodeId is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hot-path movement in the library so tests can prove travel time without a world. Simulation
|
||||
/// copies the same loop onto components and does not call this every tick.
|
||||
/// </summary>
|
||||
public static class PresenceStepper
|
||||
{
|
||||
public static Presence Advance(Presence presence, WalkGraph walks, float minutes)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(walks);
|
||||
if (presence.NodeId is null || minutes <= 0)
|
||||
{
|
||||
return presence;
|
||||
}
|
||||
|
||||
var remaining = presence.RemainingMinutes - minutes;
|
||||
var node = presence.NodeId;
|
||||
var path = presence.Path;
|
||||
var index = 0;
|
||||
while (remaining <= 0 && index < path.Length)
|
||||
{
|
||||
node = path[index];
|
||||
index++;
|
||||
remaining += walks.TravelMinutes(node);
|
||||
}
|
||||
|
||||
if (remaining < 0)
|
||||
{
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
var leftover = index >= path.Length ? [] : path[index..];
|
||||
return presence with { NodeId = node, RemainingMinutes = remaining, Path = leftover };
|
||||
}
|
||||
|
||||
public static Presence StartWalk(Presence presence, WalkGraph walks, string destination, bool headingHome)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(walks);
|
||||
if (presence.NodeId is null)
|
||||
{
|
||||
var path = walks.Path(walks.TerritoryId, destination);
|
||||
return new Presence(walks.TerritoryId, 0f, destination, headingHome, [.. path]);
|
||||
}
|
||||
|
||||
var hops = walks.Path(presence.NodeId, destination);
|
||||
return presence with
|
||||
{
|
||||
DestinationId = destination,
|
||||
HeadingHome = headingHome,
|
||||
Path = [.. hops],
|
||||
};
|
||||
}
|
||||
|
||||
public static Presence ArriveOffCampus(Presence presence) =>
|
||||
presence.NodeId is not null
|
||||
&& presence.HeadingHome
|
||||
&& presence.Path.Length == 0
|
||||
&& presence.RemainingMinutes <= 0
|
||||
&& presence.NodeId.Equals(presence.DestinationId, StringComparison.Ordinal)
|
||||
? Presence.OffCampus
|
||||
: presence;
|
||||
}
|
||||
Reference in New Issue
Block a user