Match/MatchState.Renovation.cs

The MatchState class manages the logic for the building renovation mechanic within a game match. It validates placement rules, previews scoring effects, and executes card and board state transitions upon successful renovation.

using System;
using System.Linq;
using CitySim.Cards;

namespace CitySim.Match;

public sealed partial class MatchState
{
 /// <summary>Checks a replacement against the board with the old building excluded, without emitting events.</summary>
 public string RenovationError(Guid owner,int eventSlot,int replacementSlot,int x,int y)
 {
  if (Phase!=MatchPhase.Build) return "#rules.match_state.wait_for_the_build_phase";
  if (!players.TryGetValue(owner,out var player)) return "#rules.match_state.unknown_player";
  if (eventSlot<0 || eventSlot>=player.Hand.Count || player.Hand[eventSlot]!="renovation") return "#rules.match_state._renovation.select_renovation_first";
  if (replacementSlot<0 || replacementSlot>=player.Hand.Count || replacementSlot==eventSlot) return "#rules.match_state._renovation.select_a_different_building_card";
  var replacement=player.Hand[replacementSlot];
  if (!CardLibrary.TryGet(replacement,out var card) || card.Type is CardType.Road or CardType.Event) return "#rules.match_state._renovation.choose_a_building_card";
  if (!InBounds(x,y)) return "#rules.match_state.outside_the_board";
  var old=board[x,y];
  if (old==null || old.Owner!=owner || CardLibrary.Get(old.CardId).Type is CardType.Road or CardType.Event) return "#rules.match_state._renovation.choose_one_of_your_buildings";
  if (old.CardId==replacement) return "#rules.match_state._renovation.choose_a_different_building_type";
  board[x,y]=null;
  try { return PlacementError(owner,replacement,x,y); }
  finally { board[x,y]=old; }
 }

 /// <summary>Preview is pure: the target is restored even if scoring throws.</summary>
 public int? PreviewRenovation(Guid owner,int eventSlot,int replacementSlot,int x,int y)
 {
  if (RenovationError(owner,eventSlot,replacementSlot,x,y)!=null) return null;
  var old=board[x,y]; board[x,y]=null;
  try { return PlacementScoring.Preview(this,players[owner].Hand[replacementSlot],x,y,owner); }
  finally { board[x,y]=old; }
 }

 /// <summary>Replaces an owned building; prior VP is retained and both selected cards are consumed.</summary>
 public PlacementResult TryRenovate(Guid owner,int eventSlot,int replacementSlot,int x,int y)
 {
  var error=RenovationError(owner,eventSlot,replacementSlot,x,y);
  if (error!=null) return new(false,error);
  var player=players[owner]; var old=board[x,y]; var id=player.Hand[replacementSlot];
  var cell=new BoardCell(x,y,id,owner,NextPlacementOrder,Round); PlacementEffects effects;
  board[x,y]=null;
  try { effects=PlacementScoring.Calculate(this,cell); }
  finally { board[x,y]=old; }
  player.MutableHand.RemoveAt(Math.Max(eventSlot,replacementSlot)); player.MutableHand.RemoveAt(Math.Min(eventSlot,replacementSlot));
  order++; ChangeCoins(owner,-Settings.BuildCost(CardLibrary.Get(id))); CommitPlacement(cell,effects,old);
  Events.Played(new(owner,"renovation"));
  return new(true,"",cell);
 }
}