76 lines
3.1 KiB
C#
76 lines
3.1 KiB
C#
using System;
|
||
|
||
namespace AfterHours.Customers
|
||
{
|
||
/// <summary>
|
||
/// Kern-Mechanik des Spiels: Wie wohl fühlt sich ein Kunde gerade?
|
||
/// Fällt der Komfort unter <see cref="AbandonThreshold"/>, legt der Kunde
|
||
/// die Ware zurück und geht.
|
||
///
|
||
/// Engine-frei und damit testbar – siehe Tests/EditMode/ComfortModelTests.cs.
|
||
/// </summary>
|
||
public static class ComfortModel
|
||
{
|
||
public const float AbandonThreshold = 0.25f;
|
||
|
||
/// <summary>Komfortverlust pro anderem Kunden im Nahbereich.</summary>
|
||
private const float CrowdingPenaltyPerPerson = 0.09f;
|
||
|
||
/// <summary>Malus, wenn Personal ungefragt in Reichweite steht.</summary>
|
||
private const float UnwantedStaffPenalty = 0.18f;
|
||
|
||
/// <summary>Malus, wenn das Regal von der Straße aus einsehbar ist.</summary>
|
||
private const float ExposedShelfPenalty = 0.22f;
|
||
|
||
/// <summary>Erholung pro Sekunde, wenn niemand stört.</summary>
|
||
private const float RecoveryPerSecond = 0.12f;
|
||
|
||
/// <summary>
|
||
/// Zielkomfort für die aktuelle Situation (0..1).
|
||
/// </summary>
|
||
/// <param name="baseTolerance">Charaktereigenschaft des Kunden, 0..1. Höher = unerschrockener.</param>
|
||
/// <param name="discretionDemand">Diskretionsbedarf des betrachteten Produkts, 0..1.</param>
|
||
/// <param name="nearbyCustomers">Andere Kunden im Nahbereich.</param>
|
||
/// <param name="staffNearbyUnrequested">Personal steht daneben, ohne dass Beratung gewünscht war.</param>
|
||
/// <param name="shelfExposedToStreet">Regal ist vom Schaufenster aus einsehbar.</param>
|
||
public static float EvaluateTarget(
|
||
float baseTolerance,
|
||
float discretionDemand,
|
||
int nearbyCustomers,
|
||
bool staffNearbyUnrequested,
|
||
bool shelfExposedToStreet)
|
||
{
|
||
if (nearbyCustomers < 0) throw new ArgumentOutOfRangeException(nameof(nearbyCustomers));
|
||
|
||
float tolerance = Clamp01(baseTolerance);
|
||
float sensitivity = Clamp01(discretionDemand);
|
||
|
||
// Ein toleranter Kunde bei unkritischer Ware ist praktisch unstörbar.
|
||
float exposure = sensitivity * (1f - tolerance * 0.6f);
|
||
|
||
float penalty = 0f;
|
||
penalty += nearbyCustomers * CrowdingPenaltyPerPerson;
|
||
if (staffNearbyUnrequested) penalty += UnwantedStaffPenalty;
|
||
if (shelfExposedToStreet) penalty += ExposedShelfPenalty;
|
||
|
||
return Clamp01(1f - penalty * exposure * 2f);
|
||
}
|
||
|
||
/// <summary>Bewegt den aktuellen Komfort auf den Zielwert zu (Frame-Schritt).</summary>
|
||
public static float Step(float current, float target, float deltaTime)
|
||
{
|
||
if (target >= current)
|
||
{
|
||
// Erholung ist träge …
|
||
return Clamp01(current + RecoveryPerSecond * deltaTime);
|
||
}
|
||
// … Unbehagen schlägt sofort durch.
|
||
return Clamp01(target);
|
||
}
|
||
|
||
public static bool ShouldAbandon(float comfort) => comfort < AbandonThreshold;
|
||
|
||
private static float Clamp01(float v) => v < 0f ? 0f : (v > 1f ? 1f : v);
|
||
}
|
||
}
|