using FluentAssertions; using Xunit; namespace BucketChain.Test; public sealed class ChainTests { private const double InitialFireSize = 10D; private const double FireGrowthRate = 0.25D; private const double WellSize = 20D; private const double WellRefillRate = 0.5D; private const double BucketSize = 1.5D; [Fact] public void Construction() { new Chain(10, 5, BucketSize, new Well(WellSize, WellRefillRate), new Fire(InitialFireSize, FireGrowthRate), new Person()) .Should().NotBeNull("only sets private fields, not much to check here"); } [Fact] public void Operate_Minimal() { const double FireSize = 3D; var (chain, fire, well, first) = CreateMinimalChain(2, 2, 2, FireSize); // step 1 // new bucket handed to first person who filled it, then moved to second person chain.Operate(1, out var error) .Should().BeFalse("fire still burning"); error.Should().BeFalse("no problem so far"); first.HasBucket.Should().BeFalse("moved to second person already"); first.RightNeighbor?.HasBucket.Should().BeTrue("has the new bucket"); well.LitersRemaining.Should().BeApproximately(WellSize - BucketSize, double.Epsilon, "bucket has been filled"); fire.Extinguished.Should().BeFalse("still burning"); fire.FireSize.Should().BeApproximately(FireSize + FireGrowthRate - BucketSize, double.Epsilon, "fire grew, then got hit by the water in the bucket"); var wellSize = well.LitersRemaining; var fireSize = fire.FireSize; // step 2 // new bucket handed to first person who filled it, then moved to the second person // empty bucket from previous step handed from second person to first chain.Operate(2, out error) .Should().BeFalse("fire still burning"); error.Should().BeFalse("no problem so far"); first.HasBucket.Should().BeTrue("there are now two buckets, has the one from step 1"); first.RightNeighbor?.HasBucket.Should().BeTrue("has the new bucket"); well.LitersRemaining.Should().BeApproximately(wellSize + WellRefillRate - BucketSize, double.Epsilon, "well refilled, then bucket has been filled"); fire.Extinguished.Should().BeFalse("still burning"); fire.FireSize.Should().BeApproximately(fireSize + FireGrowthRate - BucketSize, double.Epsilon, "fire grew, then got hit by the water in the bucket"); wellSize = well.LitersRemaining; // step 3 // no new buckets added, existing two continue circulating chain.Operate(3, out error) .Should().BeTrue("fire extinguished"); error.Should().BeFalse("no problem so far"); well.LitersRemaining.Should().BeApproximately(wellSize + WellRefillRate - BucketSize, double.Epsilon, "well refilled, then bucket has been filled"); fire.Extinguished.Should().BeTrue(); fire.FireSize.Should().BeApproximately(0D, double.Epsilon, "fire extinguished, but size cannot become negative"); } [Fact] public void Operate_NotLongEnough() { const int Steps = 100; var (chain, fire, _, _) = CreateMinimalChain(4, 3, 2); for (var i = 0; i < Steps; i++) { chain.Operate(i + 1, out var error) .Should().BeFalse("fire keeps burning"); error.Should().BeFalse("no error, chain is simply not long enough"); } fire.Extinguished.Should().BeFalse(); fire.FireSize.Should().BeApproximately(InitialFireSize + FireGrowthRate * Steps, 2E-14, "fire keeps growing"); } [Fact] public void Operate_Initially_NotLongEnough() { const int FirstSteps = 5; var (chain, fire, _, firstPerson) = CreateMinimalChain(4, 2, 2); var initialSize = fire.FireSize; for (var i = 0; i < FirstSteps; i++) { chain.Operate(i + 1, out var error) .Should().BeFalse("fire keeps burning"); error.Should().BeFalse("no error, chain is simply not long enough"); } fire.FireSize.Should().BeGreaterThan(initialSize, "fire grew"); // two additional people, but no more buckets, stays at 2 new Person().JoinChain(firstPerson, true); new Person().JoinChain(firstPerson, true); var s = FirstSteps; while (!chain.Operate(++s, out var error)) { fire.Extinguished.Should().BeFalse(); error.Should().BeFalse(); } fire.FireSize.Should().BeApproximately(0D, double.Epsilon, "fire extinguished"); s.Should().Be(49); } [Fact] public void StringRepresentation() { var (chain, fire, well, firstPerson) = CreateMinimalChain(4, 2, 1); chain.Operate(1, out _); chain.ToString() .Should().Be($"{well} | {firstPerson} | {firstPerson.RightNeighbor} | ❔ | ❔ | {fire}"); } private static (Chain Chain, Fire Fire, Well Well, Person FirstPerson) CreateMinimalChain(int requiredPeople, int peopleCount, int buckets, double? initialFireSize = null) { var fire = new Fire(initialFireSize ?? InitialFireSize, FireGrowthRate); var well = new Well(WellSize, WellRefillRate); var firstPerson = CreatePeople(); var chain = new Chain(requiredPeople, buckets, BucketSize, well, fire, firstPerson); return (chain, fire, well, firstPerson); Person CreatePeople() { var first = new Person(); var prev = first; for (var i = 1; i < peopleCount; i++) { var newPerson = new Person(); newPerson.JoinChain(prev, true); prev = newPerson; } return first; } } }