Skyjo Strategy Challenge

Developer Enrichment

Open application

Overview

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.

C# / .NET Blazor WebAssembly Roslyn Monaco Editor Web Workers MSTest
Skyjo Strategy Challenge workbench showing challenge guidance and a C# strategy skeleton with comments explaining each decision.

The idea

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.

A four-player Skyjo table with revealed cards, face-down cards, a draw pile and a visible discard card.

The strategy boundary

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.

Runtime compilation & execution

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.

From editor source to a disposable execution environment

Monaco EditorUser-written C# source

Disposable Web Worker · separate .NET WebAssembly runtime

  1. Roslyn compilerCompile against the restricted strategy SDK and framework references.
  2. Dynamic strategyValidate the implementation and load the generated assembly.
  3. Skyjo engineSupply public snapshots and validate every returned decision.
Workbench resultsSource-position diagnostics or benchmark statistics and retained seeds
The SDK defines what a strategy can see; the worker contains compilation and execution away from the UI. Workers are disposed after a run, including error and timeout paths.

Reproducible benchmarking

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.

Retain the conditions

Keep the seeds and player count with the result, rather than reporting an isolated win percentage.

Reset and rotate

Fresh strategy instances avoid carrying state between games; changing seats reduces a fixed-position advantage.

Keep the evidence

Export the source, settings and statistics together in a standalone scorecard.

OUTCOME

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.

Skyjo strategy workbench