Framework/Betting/Pot.cs

A server-authoritative pot component for a card game that tracks total chips and per-player contributions. Host-only methods add contributions, award or split the pot among winners, apply a rake, compute a seat's contribution, and clear the pot. Total is network-synced for clients to read.

Networking
namespace CardGames;

/// <summary>
/// A shared pool of chips that players contribute to during a round. The host tracks contributions and awards the pot to winners.
/// </summary>
public sealed class Pot : Component
{
	/// <summary>
	/// Total chips in the pot. Host writes; everyone reads (for a chips-on-the-table layer or HUD).
	/// </summary>
	[Sync] public long Total { get; set; }

	/// <summary>
	/// Chips a player put into the pot. Host-only bookkeeping (e.g. to refund an uncalled bet).
	/// </summary>
	private readonly record struct Contribution( Connection Player, long Amount );

	// Not synced - clients only need the Total.
	private readonly List<Contribution> _contributions = new();

	/// <summary>
	/// Chips this seat has contributed to the pot.
	/// </summary>
	public long ContributionOf( Seat seat )
	{
		if ( seat is null ) return 0;
		var player = Connection.Find( seat.Player );

		long total = 0;
		foreach ( var c in _contributions )
			if ( c.Player == player )
				total += c.Amount;
		return total;
	}

	/// <summary>
	/// Host: add chips to the pot, tracked against the seat that put them in.
	/// </summary>
	public void Contribute( Seat seat, long amount )
	{
		if ( !Networking.IsHost || seat is null || amount <= 0 ) return;

		_contributions.Add( new Contribution( Connection.Find( seat.Player ), amount ) );
		Total += amount;
	}

	/// <summary>
	/// Host: hand the whole pot to one seat, then empty it.
	/// </summary>
	public void Award( Seat seat )
	{
		if ( !Networking.IsHost || seat is null ) return;

		seat.Currency += Total;
		Clear();
	}

	/// <summary>
	/// Host: split the pot as evenly as possible between winners (any odd chip goes to the first), then empty it.
	/// </summary>
	public void Split( IReadOnlyList<Seat> winners )
	{
		if ( !Networking.IsHost || winners is null || winners.Count == 0 ) return;

		long each = Total / winners.Count;
		long remainder = Total - each * winners.Count;
		for ( int i = 0; i < winners.Count; i++ )
			if ( winners[i] is not null )
				winners[i].Currency += each + (i == 0 ? remainder : 0);

		Clear();
	}

	/// <summary>
	/// Host: skim a fraction of the pot for the house (rake). Returns the chips taken.
	/// </summary>
	public long Rake( float fraction )
	{
		if ( !Networking.IsHost || fraction <= 0f ) return 0;

		long rake = (long)(Total * Math.Clamp( fraction, 0f, 1f ));
		Total -= rake;
		return rake;
	}

	/// <summary>
	/// Host: empty the pot and forget all contributions.
	/// </summary>
	public void Clear()
	{
		if ( !Networking.IsHost ) return;

		_contributions.Clear();
		Total = 0;
	}
}