initial
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user