Stat checks: check command with stat roll outcomes and its schema
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
50be9ad273
18 files changed
+589
No files matched your search
@@ -0,0 +1,56 @@
|
||||
namespace Cthangover.Mods.StatChecks.Tests
|
||||
{
|
||||
using Cthangover.Core.Mods.Testing;
|
||||
using Cthangover.Mods.Dialog;
|
||||
using Cthangover.Mods.Dialog.Scenarios;
|
||||
|
||||
/// <summary>
|
||||
/// <c>check</c> lines in a real scenario text: the queue rolls when it reaches the line,
|
||||
/// jumps to the arrow label on success and to <c>else</c> (or the next line) on failure,
|
||||
/// and leaves the roll in dialog variables.
|
||||
/// </summary>
|
||||
public class CheckCommandStrategyTests
|
||||
{
|
||||
private static DialogRuntime Run(string scenario)
|
||||
{
|
||||
var dlg = ScenarioParser.Parse(scenario);
|
||||
var runtime = new DialogRuntime();
|
||||
runtime.SetDialogQueueAndRun(null, dlg.Queue, null);
|
||||
runtime.Run();
|
||||
return runtime;
|
||||
}
|
||||
|
||||
// A stat no mod defines reads 0 for anyone, so the outcomes below are fixed whatever packs are loaded.
|
||||
private const string Nobody = "who=test_nobody_has_stats";
|
||||
|
||||
[ModTest("command/success_jumps_to_the_arrow")]
|
||||
static void SuccessJumps(ModTestContext ctx)
|
||||
{
|
||||
var runtime = Run($"check test_undefined_stat 0 {Nobody} dice=none bonus=3 quiet=1 -> :ok\nset outcome failed\nend\n:ok\nset outcome passed\nend\n");
|
||||
|
||||
ModAssert.Equal("passed", runtime.GetVariable("outcome"), "jumped to :ok");
|
||||
ModAssert.Equal("3", runtime.GetVariable("check_total"), "0 + 0 + 3");
|
||||
ModAssert.Equal("0", runtime.GetVariable("check_dc"), "difficulty");
|
||||
}
|
||||
|
||||
[ModTest("command/failure_falls_through_or_takes_else")]
|
||||
static void FailureFallsThroughOrTakesElse(ModTestContext ctx)
|
||||
{
|
||||
var through = Run($"check test_undefined_stat 5 {Nobody} dice=none quiet=1 -> :ok\nset outcome failed\nend\n:ok\nset outcome passed\nend\n");
|
||||
ModAssert.Equal("failed", through.GetVariable("outcome"), "fell through to the next line");
|
||||
|
||||
var other = Run($"check test_undefined_stat 5 {Nobody} dice=none quiet=1 else=no -> :ok\nset outcome next\nend\n:ok\nset outcome passed\nend\n:no\nset outcome else\nend\n");
|
||||
ModAssert.Equal("else", other.GetVariable("outcome"), "jumped to else");
|
||||
}
|
||||
|
||||
[ModTest("command/spec_for_the_validator")]
|
||||
static void SpecForTheValidator(ModTestContext ctx)
|
||||
{
|
||||
var spec = new CheckCommandStrategy();
|
||||
ModAssert.Equal(2, spec.MinPositional, "stat and difficulty");
|
||||
ModAssert.Equal(2, spec.MaxPositional, "nothing more");
|
||||
ModAssert.Equal("else", spec.JumpTargetNamedKey, "else is a label");
|
||||
ModAssert.NotNull(ScenarioCommandStrategyFactory.Get("check"), "registered as a DSL command");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Cthangover.Mods.StatChecks.Tests
|
||||
{
|
||||
using System;
|
||||
using Cthangover.Core.Mods.Testing;
|
||||
|
||||
/// <summary>Tabletop dice notation: valid forms roll within their range, "no dice" rolls 0, typos roll 0 and are invalid.</summary>
|
||||
public class DiceRollTests
|
||||
{
|
||||
[ModTest("dice/notation")]
|
||||
static void Notation(ModTestContext ctx)
|
||||
{
|
||||
ModAssert.True(DiceRoll.TryParse("2d6", out var count, out var sides) && count == 2 && sides == 6, "2d6");
|
||||
ModAssert.True(DiceRoll.TryParse("D20", out count, out sides) && count == 1 && sides == 20, "d20 in any case");
|
||||
ModAssert.True(DiceRoll.TryParse("none", out count, out _) && count == 0, "none");
|
||||
ModAssert.True(DiceRoll.TryParse("", out count, out _) && count == 0, "empty");
|
||||
foreach (var bad in new[] { "d1", "2x6", "-1d6", "d", "3d", "1000d6", "1.5d6" })
|
||||
ModAssert.False(DiceRoll.IsValid(bad), $"'{bad}' is not dice");
|
||||
}
|
||||
|
||||
[ModTest("dice/rolls_stay_in_range")]
|
||||
static void RollsStayInRange(ModTestContext ctx)
|
||||
{
|
||||
var random = new Random(3);
|
||||
var seenLow = false;
|
||||
var seenHigh = false;
|
||||
for (var i = 0; i < 400; i++)
|
||||
{
|
||||
var roll = DiceRoll.Roll("2d6", random);
|
||||
ModAssert.True(roll >= 2 && roll <= 12, $"2d6 gave {roll}");
|
||||
seenLow |= roll == 2;
|
||||
seenHigh |= roll == 12;
|
||||
}
|
||||
ModAssert.True(seenLow && seenHigh, "both ends of 2d6 come up in 400 rolls");
|
||||
ModAssert.Equal(0, DiceRoll.Roll("none"), "no dice");
|
||||
ModAssert.Equal(0, DiceRoll.Roll("oops"), "a typo rolls nothing");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Cthangover.Mods.StatChecks.Tests
|
||||
{
|
||||
using System;
|
||||
using Cthangover.Core.Mods.Testing;
|
||||
|
||||
/// <summary>A check is value + dice + bonus against the difficulty; reaching it exactly succeeds.</summary>
|
||||
public class StatCheckTests
|
||||
{
|
||||
[ModTest("check/total_and_threshold")]
|
||||
static void TotalAndThreshold(ModTestContext ctx)
|
||||
{
|
||||
var check = new StatCheck { Stat = "cha", Difficulty = 12, Dice = "none", Bonus = 2 };
|
||||
|
||||
check.Perform(10, null);
|
||||
ModAssert.Equal(12, check.Total, "10 + 0 + 2");
|
||||
ModAssert.True(check.Success, "reaching the difficulty exactly succeeds");
|
||||
|
||||
check.Perform(9, null);
|
||||
ModAssert.False(check.Success, "one short fails");
|
||||
ModAssert.Equal(0, check.Roll, "no dice");
|
||||
}
|
||||
|
||||
[ModTest("check/dice_add_to_the_total")]
|
||||
static void DiceAddToTheTotal(ModTestContext ctx)
|
||||
{
|
||||
var check = new StatCheck { Stat = "str", Difficulty = 100, Dice = "1d6", Bonus = -1 };
|
||||
var random = new Random(11);
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
check.Perform(5, random);
|
||||
ModAssert.True(check.Roll is >= 1 and <= 6, $"1d6 gave {check.Roll}");
|
||||
ModAssert.Equal(5 + check.Roll - 1, check.Total, "value + roll + bonus");
|
||||
ModAssert.False(check.Success, "100 is out of reach");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user