Very nice! Implementing all of the cards takes quite a bit of time. I had the same thought process as you - but a couple years later, I still have 3 cards left to implement.
Have you considered building on top of an existing engine? It may not be easy on top of the more popular ones, but Dominulator should have the infrastructure you need. You'll find references to it in this forum. It's a c# backend which has separated the rules of the game from the choices players make. Just about all of the cards (excluding Adventures) have been implemented. (though unit tests are minimal). On my PC, it can simulate a turn from rules similar to Geronimos in about 2 microseconds. A whole game takes about 83 microseconds.
Theoretically, you would just have to implement this interface to get all of the cards working:
public interface IPlayerAction
{
int GetCountToReturnToSupply(Card card, GameState gameState);
Card BanCardToDrawnIntoHandFromRevealedCards(GameState gameState);
Card BanCardForCurrentPlayerPurchase(GameState gameState);
Card ChooseCardToPlayFirst(GameState gameState, Card card1, Card card2);
Card GetTreasureFromHandToPlay(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromSupplyToEmbargo(GameState gameState);
Card GetCardFromSupplyToPlay(GameState gameState, CardPredicate acceptableCard);
Card GetCardFromSupplyToBuy(GameState gameState, CardPredicate acceptableCard);
Card GetCardFromSupplyToGain(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GuessCardTopOfDeck(GameState gameState);
Card NameACard(GameState gameState);
Card GetCardFromTrashToGain(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromPlayToTopDeckDuringCleanup(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromDiscardToTopDeck(GameState gameState, bool isOptional);
Card GetCardFromRevealedCardsToTopDeck(GameState gameState, bool isOptional);
Card GetCardFromRevealedCardsToTrash(GameState gameState, CardPredicate acceptableCard);
Card GetCardFromRevealedCardsToPutOnDeck(GameState gameState);
Card GetCardFromRevealedCardsToDiscard(GameState gameState);
Card GetCardFromHandToDeferToNextTurn(GameState gameState);
Card GetCardFromHandToDiscard(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromHandToIsland(GameState gameState);
Card GetCardFromHandToPassLeft(GameState gameState);
Card GetCardFromHandToPlay(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromHandToReveal(GameState gameState, CardPredicate acceptableCard); // always optional
Card GetCardFromHandToTopDeck(GameState gameState, CardPredicate acceptableCard, bool isOptional);
Card GetCardFromHandToTrash(GameState gameState, CardPredicate acceptableCard, bool isOptional, CollectionCards cardsTrashedSoFar);
Card GetCardFromHandOrDiscardToTrash(GameState gameState, CardPredicate acceptableCard, bool isOptional, out DeckPlacement deckPlacement);
Card GetCardFromOtherPlayersHandToDiscard(GameState gameState, PlayerState otherPlayer);
Card GetCardFromOtherPlayersRevealedCardsToTrash(GameState gameState, PlayerState otherPlayer, CardPredicate acceptableCard);
int GetNumberOfCoppersToPutInHandForCountingHouse(GameState gameState, int maxNumber);
bool ShouldPlayerDiscardCardFromDeck(GameState gameState, PlayerState player, Card card);
bool ShouldPlayerDiscardCardFromHand(GameState gameState, Card card);
bool ShouldRevealCardFromHandForCard(GameState gameState, Card card, Card cardFor);
bool ShouldRevealCardFromHand(GameState gameState, Card card);
bool ShouldPutCardInHand(GameState gameState, Card card);
bool WantToResign(GameState gameState);
bool ShouldPutDeckInDiscard(GameState gameState);
bool ShouldPutCardOnTopOfDeck(Card card, GameState gameState);
bool ShouldTrashCard(GameState gameState, Card card);
bool ShouldGainCard(GameState gameState, Card card);
PlayerActionChoice ChooseBetween(GameState gameState, IsValidChoice acceptableChoice);
DeckPlacement ChooseBetweenTrashAndTopDeck(GameState gameState, Card card);
DeckPlacement ChooseBetweenTrashTopDeckDiscard(GameState gameState, Card card);
string PlayerName { get; }
int GetCoinAmountToOverpayForCard(GameState gameState, Card card);
int GetCoinAmountToSpendInBuyPhase(GameState gameState);
int GetCoinAmountToUseInButcher(GameState gameState);
void ChooseLocationForStashAfterShuffle(GameState gameState, int[] locations);
}
And you could watch what happens during a game using this interface
public interface IGameLog
: IDisposable
{
void PushScope();
void PopScope();
void BeginRound(PlayerState playerState);
void EndRound(GameState gameState);
void BeginPhase(PlayerState playerState);
void EndPhase(PlayerState playerState);
void BeginTurn(PlayerState playerState);
void EndTurn(PlayerState playerState);
void PlayerNamedCard(PlayerState playerState, Card card);
void PlayerRevealedCard(PlayerState playerState, Card card, DeckPlacement source);
void PlayerBoughtCard(PlayerState playerState, Card card);
void PlayedCard(PlayerState playerState, Card card);
void CardWentToLocation(DeckPlacement deckPlacement);
void ReceivedDurationEffectFrom(PlayerState playerState, Card card);
void PlayerGainedCard(PlayerState playerState, Card card);
void PlayerDiscardCard(PlayerState playerState, Card card, DeckPlacement source);
void PlayerTrashedCard(PlayerState playerState, Card card);
void PlayerPutCardInHand(PlayerState playerState, Card card);
void PlayerTopDeckedCard(PlayerState playerState, Card card);
void PlayerSetAsideCardFromHandForNextTurn(PlayerState playerState, Card card);
void PlayerReturnedCardToHand(PlayerState playerState, Card card);
void PlayerReturnedCardToPile(PlayerState playerState, Card card);
void DrewCardIntoHand(PlayerState playerState, Card card);
void DiscardedCard(PlayerState playerState, Card card);
void ReshuffledDiscardIntoDeck(PlayerState playerState);
void StartGame(GameState gameState);
void EndGame(GameState gameState);
void PlayerGainedPotion(PlayerState playerState, int potionCount);
void PlayerGainedCoin(PlayerState playerState, int coinAmount);
void PlayerGainedCoinToken(PlayerState playerState, int coinAmount);
void PlayerGainedActions(PlayerState playerState, int actionAmount);
void PlayerGainedBuys(PlayerState playerState, int actionAmount);
void PlayerOverpaidForCard(Card boughtCard, int overPayAmount);
void PlayerGainedVictoryTokens(PlayerState playerState, int amount);
void PlayerChoseLocationForStash(PlayerState playerState, int[] positions);
}