Project · Unity companions

Unity collectathon rules companion

A complete console project testing unique collection, win/timeout terminal states, input bounds and a deterministic run story.

All C# source files below are loaded together in the playground. Files written by a run live in its virtual directory.

Program.cs

using System;
using System.Globalization;
using SharpRune.UnityRun;
Console.WriteLine(RunChecks.Run() + " run checks passed");
var story = new RunState(3, 5); story.Collect(1); story.Collect(1); story.Tick(1); story.Collect(2); story.Collect(3);
Console.WriteLine("Story: " + story.Score + "/" + story.Phase + "/" + story.Remaining.ToString("0.0", CultureInfo.InvariantCulture));

Domain/RunState.cs

using System;
using System.Collections.Generic;
namespace SharpRune.UnityRun
{
    public enum RunPhase { Running, Won, Lost }
    public sealed class RunState
    {
        private readonly HashSet<int> collected = new HashSet<int>();
        private readonly int target;
        public int Score { get; private set; }
        public double Remaining { get; private set; }
        public RunPhase Phase { get; private set; }
        public RunState(int target, double seconds)
        {
            if (target < 1 || seconds <= 0 || double.IsNaN(seconds) || double.IsInfinity(seconds)) throw new ArgumentOutOfRangeException();
            this.target = target; Remaining = seconds;
        }
        public bool Collect(int id)
        {
            if (Phase != RunPhase.Running || !collected.Add(id)) return false;
            Score++; if (Score >= target) Phase = RunPhase.Won; return true;
        }
        public void Tick(double seconds)
        {
            if (seconds < 0 || double.IsNaN(seconds) || double.IsInfinity(seconds)) throw new ArgumentOutOfRangeException();
            if (Phase != RunPhase.Running) return;
            Remaining = Math.Max(0, Remaining - seconds); if (Remaining == 0) Phase = RunPhase.Lost;
        }
    }
}

Checks/RunChecks.cs

using System;
using SharpRune.UnityRun;
public static class RunChecks
{
    public static int Run()
    {
        int passed = 0;
        void Check(bool condition, string name) { if (!condition) throw new InvalidOperationException(name); Console.WriteLine("PASS " + name); passed++; }
        var duplicate = new RunState(3, 10); bool first = duplicate.Collect(7); bool repeated = duplicate.Collect(7);
        Check(first && !repeated && duplicate.Score == 1, "duplicate contact guarded");
        var win = new RunState(2, 10); win.Collect(1); win.Collect(2); bool extra = win.Collect(3); win.Tick(1);
        Check(win.Phase == RunPhase.Won && !extra && win.Score == 2 && win.Remaining == 10, "win is terminal");
        var loss = new RunState(3, 2); loss.Tick(2);
        Check(loss.Phase == RunPhase.Lost && !loss.Collect(1) && loss.Score == 0, "timeout is terminal");
        bool rejected = false; try { new RunState(3, 2).Tick(-1); } catch (ArgumentOutOfRangeException) { rejected = true; }
        Check(rejected, "negative time rejected");
        rejected = false; try { new RunState(3, 2).Tick(double.NaN); } catch (ArgumentOutOfRangeException) { rejected = true; }
        Check(rejected, "nonfinite time rejected");
        var a = new RunState(3, 2); var b = new RunState(3, 2); a.Collect(1);
        Check(a.Score == 1 && b.Score == 0, "runs are independent");
        rejected = false; try { new RunState(0, 2); } catch (ArgumentOutOfRangeException) { rejected = true; }
        Check(rejected, "invalid setup rejected");
        return passed;
    }
}

Companion.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net9.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup>
</Project>