The StubOpponent class acts as a mock opponent for a simulation, executing fixed bidding and building strategies during match phases.
using System;
using System.Linq;
using CitySim.Match;
namespace CitySim.Ai;
/// <summary>M2 test rival: fixed first-affordable-lot bids and first-legal-cell building. M6 replaces this with AI.</summary>
public sealed class StubOpponent
{
readonly Guid player;
readonly MatchState state;
readonly RoundMachine rounds;
int auctionId = -1;
double actAt;
public StubOpponent(Guid player,MatchState state,RoundMachine rounds) { this.player = player; this.state = state; this.rounds = rounds; }
public void Update(double now)
{
if (state.Phase == MatchPhase.Bid)
{
var view = rounds.GetAuctionView(player);
if (view == null || !view.Eligible || view.Resolving || view.Ready) return;
if (view.Id != auctionId) { auctionId = view.Id; actAt = now+Math.Min(state.Settings.StubThinkSeconds,state.Settings.MiniAuctionSeconds*0.5); }
if (now < actAt) return;
var lot = view.Lots.FirstOrDefault(l => l.Allowed && l.Lot.MinimumBid <= Math.Min(state.Settings.StubOpponentBid,view.AvailableCoins));
if (lot != null) rounds.BuyMarketCard(player,view.Id,lot.Lot.Index,now);
if (lot == null) rounds.Ready(player,now);
actAt = now+state.Settings.StubThinkSeconds;
}
else if (state.Phase == MatchPhase.Build && now >= actAt && !rounds.IsBuildReady(player))
{
var hand = state.Players[player].Hand;
if (hand.Count == 0) { rounds.Ready(player,now); return; }
var card = hand[0];
if (card == "grant") state.PlayGrant(player,0);
else
{
var placed = false;
for (var y = 0; y < state.Height && !placed; y++)
for (var x = 0; x < state.Width && !placed; x++)
if (state.CardPlayError(player,card,x,y) == null) placed = card == "land-claim" ? state.TryClaim(player,0,x,y).Success : state.TryPlace(player,card,x,y).Success;
if (!placed) state.Discard(player,0);
}
actAt = now+state.Settings.StubThinkSeconds;
}
}
}