Game/DesertPumpGame.Workers.cs

Partial class for the DesertPumpGame that manages worker hiring and levels. It stores per-worker levels, computes costs and outputs, totals crew production, tracks hired count, and provides BuyWorker to pay, promote, play sound, invoke a Hired event and save.

File Access
namespace DesertPump;

/// <summary>
/// The hired help. Workers add litres per second on top of the pump, and unlike the
/// pump they keep working while you're doing something else entirely.
/// </summary>
public sealed partial class DesertPumpGame
{
	readonly int[] workerLevels = new int[WorkerType.Count];

	public int WorkerLevel( WorkerType worker ) => workerLevels[worker.Index];

	public bool IsHired( WorkerType worker ) => WorkerLevel( worker ) > 0;

	public bool IsWorkerMaxed( WorkerType worker ) => WorkerLevel( worker ) >= WorkerType.MaxLevel;

	/// <summary>Cost of the next level, or infinity when they've nothing left to learn.</summary>
	public double WorkerCost( WorkerType worker ) => IsWorkerMaxed( worker )
		? double.PositiveInfinity
		: worker.CostAt( WorkerLevel( worker ) );

	public bool CanHire( WorkerType worker ) => !IsWorkerMaxed( worker ) && Coins >= WorkerCost( worker );

	/// <summary>What one worker is currently producing.</summary>
	public double WorkerOutput( WorkerType worker ) => worker.OutputAt( WorkerLevel( worker ) );

	/// <summary>Everything the crew makes between them, litres per second.</summary>
	public double WorkerWaterPerSecond
	{
		get
		{
			double total = 0;

			foreach ( var worker in WorkerType.All )
			{
				total += WorkerOutput( worker );
			}

			return total;
		}
	}

	/// <summary>How many of the fifteen are on the payroll.</summary>
	public int HiredCount
	{
		get
		{
			var count = 0;

			foreach ( var worker in WorkerType.All )
			{
				if ( IsHired( worker ) ) count++;
			}

			return count;
		}
	}

	/// <summary>The pump and the crew together - what the tank actually fills at.</summary>
	public double TotalWaterPerSecond => CurrentPump.PerSecond + WorkerWaterPerSecond;

	/// <summary>Fired after hiring or promoting someone.</summary>
	public Action<WorkerType> Hired { get; set; }

	/// <summary>Hire a worker, or promote one you already have.</summary>
	public bool BuyWorker( WorkerType worker )
	{
		if ( IsWorkerMaxed( worker ) )
		{
			Refuse( $"{worker.Name} can't be promoted any further" );
			return false;
		}

		var cost = WorkerCost( worker );
		if ( Coins < cost )
		{
			Refuse( $"Need {Numbers.Short( cost - Coins )} more coins" );
			return false;
		}

		Coins -= cost;
		workerLevels[worker.Index]++;

		Play( UpgradeSound );
		Hired?.Invoke( worker );
		Save();

		return true;
	}
}