Framework/Seat.cs

Represents a seat at a card table. Tracks index, occupying player GUID, chip ledger (Currency, Wager), status flags (Ready, Folded, AllIn, IsBot), last-round Outcome, a public hand label, and host-owned list of CardObject instances. Provides computed properties for Occupied and IsLocal and a ToString that returns a bot label or player display name.

Networking
using System;
using System.Collections.Generic;

namespace CardGames;

/// <summary>How a seat finished the last scored round - drives its end-screen tone. None = didn't play / not scored.</summary>
public enum SeatOutcome
{
	None,
	Win,
	Loss,
	Push
}

/// <summary>
/// A place at the table. Networked so every client knows which seats are taken and by whom.
/// The host owns the gameplay bookkeeping (the cards in the seat's hand).
/// </summary>
public sealed class Seat : Component
{
	[Property, Sync] public int Index { get; set; }

	/// <summary>
	/// Connection id of the seated player, or <see cref="Guid.Empty"/> if open.
	/// </summary>
	[Sync] public Guid Player { get; set; }

	public bool Occupied => Player != Guid.Empty;

	/// <summary>
	/// True if the local player occupies this seat.
	/// </summary>
	public bool IsLocal => Occupied && Player == Connection.Local.Id;

	// Game-agnostic chip ledger. The host writes these; everyone reads. A game decides what wagering
	// means (Blackjack: bet against the house; Poker: chips into a Pot) by composing a Bank that moves
	// chips between these two fields - see code/Framework/Betting/.

	/// <summary>
	/// The seat's chip bankroll. Seeded from the game's StartingStack; reset when the seat is destroyed.
	/// </summary>
	[Sync] public long Currency { get; set; }

	/// <summary>
	/// Chips committed this round - sits "in front of" the seat (what the HUD and a future physical-chip
	/// layer render). Moved here from <see cref="Currency"/> by <see cref="Bank.Commit"/>.
	/// </summary>
	[Sync] public long Wager { get; set; }

	/// <summary>
	/// This seat has locked in for the current phase (e.g. confirmed its bet).
	/// </summary>
	[Sync] public bool Ready { get; set; }

	/// <summary>
	/// Out of the current hand - folded (poker). Skipped by turn order and showdown; chips already in the
	/// pot stay there. Reset each hand.
	/// </summary>
	[Sync] public bool Folded { get; set; }

	/// <summary>
	/// All chips are committed (poker): the seat stays in the hand to showdown but can't act further. Reset
	/// each hand.
	/// </summary>
	[Sync] public bool AllIn { get; set; }

	/// <summary>
	/// This seat is played by the computer, not a connected player. Bots fill empty seats so a game (poker)
	/// is playable solo.
	/// </summary>
	[Sync] public bool IsBot { get; set; }

	/// <summary>
	/// This seat's result in the just-finished round, synced so every client can read its own outcome for the
	/// end-screen. Games that score per seat set it (Blackjack); others leave it <see cref="SeatOutcome.None"/>.
	/// </summary>
	[Sync] public SeatOutcome Outcome { get; set; }

	/// <summary>
	/// Publicly-visible value of this seat's hand for the on-table value decal (e.g. a Blackjack total, or a
	/// Poker hand category at showdown). Host-written and synced; empty = no decal. Hidden hands a client may
	/// not see are left empty (the local player computes their own privately - see HandValueDecalRenderer).
	/// </summary>
	[Sync] public string HandLabel { get; set; } = "";

	/// <summary>
	/// Host-side: the cards currently in this seat's hand.
	/// </summary>
	public List<CardObject> Cards { get; } = new();

	/// <summary>The seat's display name: a bot label, the connected player's name, or a fallback.</summary>
	public override string ToString() => IsBot
		? $"CPU {Index + 1}"
		: Connection.Find( Player )?.DisplayName ?? $"Player {Index + 1}";
}