Retain the conditions
Keep the seeds and player count with the result, rather than reporting an isolated win percentage.
Developer Enrichment
A shared technical challenge for developers to explore in their spare moments.
I built Skyjo Strategy Challenge as an inter-office developer enrichment challenge based on the existing Skyjo card game. The aim was to give developers a fun, self-contained technical problem to work on during quieter periods, lunch breaks or their own spare moments, with scope to create their own strategies and compare them under the same benchmark conditions.
The challenge is to write an AI player strategy. The application provides the complete Skyjo simulation, compiles and executes the strategy locally in the browser, and lets developers benchmark it against built-in opponents under repeatable conditions.
Making that possible brings together runtime C# compilation, restricted access to public game state, contained execution in disposable workers and reproducible benchmarking, with recovery paths for strategies that return invalid moves, throw exceptions or never terminate.
A player needs useful game state; the engine must keep its hidden information.
Writing an AI for a card game is easy to explain. The engineering challenge is letting user-written code make useful decisions without receiving hidden cards or mutable engine state.
That code may throw, return an invalid move or never terminate. The workbench needs feedback and recovery paths for all three cases while the browser interface remains usable.
Let developers write the strategy without giving their code privileged access to the game.
Hiding cards from someone using a game interface is straightforward. Here, I was inviting developers to write C# code that would run against the simulator. I needed to give that code enough information to make useful decisions while keeping hidden information and engine internals outside its reach.
If a strategy could read face-down cards, inspect future draws or alter the game state directly, it could effectively play in “god mode”. Its results would invalidate the comparison and defeat the purpose of the enrichment challenge. The information boundary was essential to making the benchmark meaningful.
I addressed this with a separate strategy SDK. Developers implement
IPlayerStrategy against a restricted contract that
supplies copied, read-only snapshots of public game state. Hidden
card values, deck order and mutable engine objects are excluded from
that contract; the engine validates the decisions a strategy
returns.
Compile the strategy in the browser, then contain the run and its failure modes.
Monaco supplies C# source to Roslyn running inside the worker's WebAssembly environment. Compiler diagnostics return with source positions so the editor can connect an error to the line that needs changing. A successful compilation validates the strategy type and loads the generated assembly dynamically.
During a game, the engine validates decisions and uses deterministic legal fallbacks for exceptions or invalid actions. A non-terminating strategy needs a different boundary: the host times out and terminates the worker instead of relying on the strategy to cooperate.
Disposable Web Worker · separate .NET WebAssembly runtime
A result is useful when the game conditions can be replayed.
Fixed modes use repeatable seed sets; random modes generate and retain their seeds. Bulk benchmarks run 1,000 games, create a fresh submitted strategy instance for every game, and rotate it through player seats to reduce positional bias.
Results report average, median, minimum, maximum, wins and win rate. The workbench can export a standalone HTML scorecard with the submitted source and benchmark settings so a result can be reviewed later.
Keep the seeds and player count with the result, rather than reporting an isolated win percentage.
Fresh strategy instances avoid carrying state between games; changing seats reduces a fixed-position advantage.
Export the source, settings and statistics together in a standalone scorecard.
The complete edit–compile–benchmark pipeline runs as a static client-side application.
The project combines the game simulation, strategy SDK, runtime compiler and execution host. Automated MSTest coverage exercises compilation, deterministic results, SDK contracts, invalid or hostile strategies, and scorecard export.
Blazor WebAssembly delivers the workbench as static files. User source is compiled and executed locally, without an application backend receiving it.