Initial commit
583
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,583 @@
|
|||
# Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio
|
||||
|
||||
### Csharp ###
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
### VisualStudio ###
|
||||
|
||||
# User-specific files
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
|
||||
# Mono auto generated files
|
||||
|
||||
# Build results
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
|
||||
# MSTest test Results
|
||||
|
||||
# NUnit
|
||||
|
||||
# Build Results of an ATL Project
|
||||
|
||||
# Benchmark Results
|
||||
|
||||
# .NET Core
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
|
||||
# StyleCop
|
||||
|
||||
# Files built by Visual Studio
|
||||
|
||||
# Chutzpah Test files
|
||||
|
||||
# Visual C++ cache files
|
||||
|
||||
# Visual Studio profiler
|
||||
|
||||
# Visual Studio Trace Files
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
|
||||
# TeamCity is a build add-in
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
|
||||
# Visual Studio code coverage results
|
||||
|
||||
# NCrunch
|
||||
|
||||
# MightyMoose
|
||||
|
||||
# Web workbench (sass)
|
||||
|
||||
# Installshield output folder
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
|
||||
# Click-Once directory
|
||||
|
||||
# Publish Web Output
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
|
||||
# NuGet Packages
|
||||
# NuGet Symbol Packages
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
# except build/, which is used as an MSBuild target.
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
|
||||
# Windows Store app package directories and files
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
# but keep track of directories ending in .cache
|
||||
|
||||
# Others
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
|
||||
# RIA/Silverlight projects
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
|
||||
# SQL Server files
|
||||
|
||||
# Business Intelligence projects
|
||||
|
||||
# Microsoft Fakes
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
|
||||
# Visual Studio 6 build log
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
|
||||
# Paket dependency manager
|
||||
|
||||
# FAKE - F# Make
|
||||
|
||||
# CodeRush personal settings
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
|
||||
# BizTalk build output
|
||||
|
||||
# OpenCover UI analysis results
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
|
||||
# Local History for Visual Studio
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
|
||||
# JetBrains Rider
|
||||
|
||||
### VisualStudio Patch ###
|
||||
# Additional files built by Visual Studio
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio
|
||||
29
BucketChain.Test/BucketChain.Test.csproj
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BucketChain\BucketChain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
96
BucketChain.Test/BucketTests.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace BucketChain.Test;
|
||||
|
||||
public sealed class BucketTests
|
||||
{
|
||||
private const double BucketSize = 1.2D;
|
||||
|
||||
[Fact]
|
||||
public void Construction()
|
||||
{
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.IsEmpty.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fill_Simple()
|
||||
{
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.Fill(BucketSize)
|
||||
.Should().Be(BucketSize, "full amount used");
|
||||
bucket.IsEmpty.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fill_NotEnough()
|
||||
{
|
||||
const double Available = BucketSize / 2;
|
||||
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.Fill(Available)
|
||||
.Should().Be(Available, "everything available used");
|
||||
bucket.IsEmpty.Should().BeFalse("not completely full, but not empty either");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fill_MoreThanNeeded()
|
||||
{
|
||||
const double Available = BucketSize * 3;
|
||||
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.Fill(Available)
|
||||
.Should().Be(BucketSize, "can't take more than fits into bucket");
|
||||
bucket.IsEmpty.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Fill_InvalidAmount()
|
||||
{
|
||||
const double Available = -3;
|
||||
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.Fill(Available)
|
||||
.Should().Be(0, "cannot take anything");
|
||||
bucket.IsEmpty.Should().BeTrue("bucket is still empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_Simple()
|
||||
{
|
||||
var bucket = new Bucket(BucketSize);
|
||||
bucket.Fill(BucketSize * 2);
|
||||
|
||||
bucket.Empty()
|
||||
.Should().Be(BucketSize, "bucket was full and gets emptied completely");
|
||||
bucket.IsEmpty.Should().BeTrue("has been emptied");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_PartiallyFull()
|
||||
{
|
||||
var bucket = new Bucket(BucketSize);
|
||||
bucket.Fill(BucketSize / 2);
|
||||
|
||||
bucket.Empty()
|
||||
.Should().BeApproximately(BucketSize / 2,
|
||||
double.Epsilon, "bucket was only partially full and gets emptied completely");
|
||||
bucket.IsEmpty.Should().BeTrue("has been emptied");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_Empty()
|
||||
{
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.Empty()
|
||||
.Should().Be(0, "bucket was empty");
|
||||
bucket.IsEmpty.Should().BeTrue("bucket is still empty");
|
||||
}
|
||||
}
|
||||
153
BucketChain.Test/ChainTests.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
BucketChain.Test/FireTests.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace BucketChain.Test;
|
||||
|
||||
public sealed class FireTests
|
||||
{
|
||||
private const double InitialSize = 12.34D;
|
||||
private const double GrowRate = 0.85D;
|
||||
private const int Times = 17;
|
||||
|
||||
[Fact]
|
||||
public void Construction_Simple()
|
||||
{
|
||||
var fire = new Fire(InitialSize, default(double));
|
||||
|
||||
fire.FireSize.Should().Be(InitialSize, "has not grown yet");
|
||||
fire.Extinguished.Should().BeFalse("size > 0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BurnHigher()
|
||||
{
|
||||
var fire = new Fire(InitialSize, GrowRate);
|
||||
fire.BurnHigher();
|
||||
|
||||
fire.FireSize.Should().Be(InitialSize + GrowRate, "grow happened once");
|
||||
fire.Extinguished.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BurnHigher_Multiple()
|
||||
{
|
||||
var fire = new Fire(InitialSize, GrowRate);
|
||||
for (var i = 0; i < Times; i++)
|
||||
{
|
||||
fire.BurnHigher();
|
||||
}
|
||||
|
||||
fire.FireSize.Should().BeApproximately(InitialSize + GrowRate * Times,
|
||||
2E-14, $"grow happened {Times} times");
|
||||
fire.Extinguished.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHitByWater_Simple()
|
||||
{
|
||||
var fire = new Fire(InitialSize, default);
|
||||
|
||||
fire.GetHitByWater(InitialSize - 1);
|
||||
fire.FireSize.Should().Be(1);
|
||||
fire.Extinguished.Should().BeFalse("not yet extinguished");
|
||||
|
||||
fire.GetHitByWater(1D);
|
||||
fire.Extinguished.Should().BeTrue("now extinguished");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHitByWater_Complex()
|
||||
{
|
||||
var fire = new Fire(InitialSize, GrowRate);
|
||||
|
||||
fire.GetHitByWater(InitialSize - 1);
|
||||
fire.Extinguished.Should().BeFalse("not yet extinguished");
|
||||
|
||||
fire.BurnHigher();
|
||||
|
||||
fire.GetHitByWater(1D);
|
||||
fire.Extinguished.Should().BeFalse("not yet extinguished, because it grew");
|
||||
|
||||
fire.GetHitByWater(1D);
|
||||
fire.Extinguished.Should().BeTrue("now extinguished");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(10, "🔥 10")]
|
||||
[InlineData(1.23, "🔥 1")]
|
||||
[InlineData(1.83, "🔥 2")]
|
||||
[InlineData(0, "🔥 0")]
|
||||
public void StringRepresentation(double size, string expected)
|
||||
{
|
||||
var fire = new Fire(size, default(double));
|
||||
|
||||
fire.ToString()
|
||||
.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
466
BucketChain.Test/PersonTests.cs
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace BucketChain.Test;
|
||||
|
||||
public sealed class PersonTests
|
||||
{
|
||||
[Fact]
|
||||
public void Construction()
|
||||
{
|
||||
var person = new Person();
|
||||
|
||||
person.LeftNeighbor.Should().BeNull("not set yet");
|
||||
person.RightNeighbor.Should().BeNull("not set yet");
|
||||
person.HasBucket.Should().BeFalse("no bucket received yet");
|
||||
person.LastStepPerformed.Should().Be(0, "performed in no step yet");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JoinChain_OneOther_Right()
|
||||
{
|
||||
var firstPerson = new Person();
|
||||
var newPerson = new Person();
|
||||
|
||||
newPerson.JoinChain(firstPerson, true);
|
||||
|
||||
firstPerson.LeftNeighbor.Should().BeNull();
|
||||
firstPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side");
|
||||
newPerson.LeftNeighbor.Should().BeSameAs(firstPerson);
|
||||
newPerson.RightNeighbor.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JoinChain_OneOther_Left()
|
||||
{
|
||||
var firstPerson = new Person();
|
||||
var newPerson = new Person();
|
||||
|
||||
newPerson.JoinChain(firstPerson, false);
|
||||
|
||||
firstPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side");
|
||||
firstPerson.RightNeighbor.Should().BeNull();
|
||||
newPerson.LeftNeighbor.Should().BeNull();
|
||||
newPerson.RightNeighbor.Should().BeSameAs(firstPerson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JoinChain_TwoOthers_Left()
|
||||
{
|
||||
var (firstPerson, secondPerson) = CreateMinimalChain();
|
||||
var newPerson = new Person();
|
||||
|
||||
newPerson.JoinChain(firstPerson, false);
|
||||
|
||||
firstPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side of first person");
|
||||
firstPerson.RightNeighbor.Should().BeSameAs(secondPerson, "unchanged");
|
||||
secondPerson.LeftNeighbor.Should().BeSameAs(firstPerson, "unchanged");
|
||||
secondPerson.RightNeighbor.Should().BeNull();
|
||||
newPerson.LeftNeighbor.Should().BeNull();
|
||||
newPerson.RightNeighbor.Should().BeSameAs(firstPerson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JoinChain_TwoOthers_Right()
|
||||
{
|
||||
var (firstPerson, secondPerson) = CreateMinimalChain();
|
||||
var newPerson = new Person();
|
||||
|
||||
newPerson.JoinChain(secondPerson, true);
|
||||
|
||||
firstPerson.LeftNeighbor.Should().BeNull();
|
||||
firstPerson.RightNeighbor.Should().BeSameAs(secondPerson, "unchanged");
|
||||
secondPerson.LeftNeighbor.Should().BeSameAs(firstPerson, "unchanged");
|
||||
secondPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side of second person");
|
||||
newPerson.LeftNeighbor.Should().BeSameAs(secondPerson);
|
||||
newPerson.RightNeighbor.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JoinChain_TwoOthers_Center_Option1()
|
||||
{
|
||||
var (firstPerson, secondPerson) = CreateMinimalChain();
|
||||
var newPerson = new Person();
|
||||
|
||||
newPerson.JoinChain(firstPerson, true);
|
||||
|
||||
firstPerson.LeftNeighbor.Should().BeNull();
|
||||
firstPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side of first person");
|
||||
secondPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side of second person");
|
||||
secondPerson.RightNeighbor.Should().BeNull();
|
||||
newPerson.LeftNeighbor.Should().BeSameAs(firstPerson);
|
||||
newPerson.RightNeighbor.Should().BeSameAs(secondPerson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JoinChain_TwoOthers_Center_Option2()
|
||||
{
|
||||
var (firstPerson, secondPerson) = CreateMinimalChain();
|
||||
var newPerson = new Person();
|
||||
|
||||
newPerson.JoinChain(secondPerson, false);
|
||||
|
||||
firstPerson.LeftNeighbor.Should().BeNull();
|
||||
firstPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side of first person");
|
||||
secondPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side of second person");
|
||||
secondPerson.RightNeighbor.Should().BeNull();
|
||||
newPerson.LeftNeighbor.Should().BeSameAs(firstPerson);
|
||||
newPerson.RightNeighbor.Should().BeSameAs(secondPerson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FightFire_Simple()
|
||||
{
|
||||
const double FireInitialSize = 45.67D;
|
||||
const double BucketSize = 2.4D;
|
||||
|
||||
var fire = new Fire(FireInitialSize, default(double));
|
||||
var bucket = CreateFilledBucket(BucketSize);
|
||||
var person = new Person();
|
||||
SetBucket(person, bucket, true);
|
||||
|
||||
person.FightFire(fire);
|
||||
|
||||
fire.FireSize.Should().BeApproximately(FireInitialSize - BucketSize,
|
||||
double.Epsilon, "fire shrank by amount of water in the bucket");
|
||||
bucket.IsEmpty.Should().BeTrue("bucket content has been used");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FightFire_NoBucket()
|
||||
{
|
||||
const double FireInitialSize = 45.67D;
|
||||
|
||||
var fire = new Fire(FireInitialSize, default(double));
|
||||
var person = new Person();
|
||||
|
||||
person.FightFire(fire);
|
||||
|
||||
fire.FireSize.Should().BeApproximately(FireInitialSize,
|
||||
double.Epsilon, "fire size did not change");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseWell_ExistingBucket_Forward()
|
||||
{
|
||||
const double WellInitialFillLevel = 20D;
|
||||
const double BucketSize = 1.86D;
|
||||
|
||||
var well = new Well(WellInitialFillLevel, default(double));
|
||||
var bucket = new Bucket(BucketSize);
|
||||
var person = new Person();
|
||||
SetBucket(person, bucket, true);
|
||||
|
||||
person.UseWell(well, null);
|
||||
|
||||
bucket.IsEmpty.Should().BeFalse("has been filled");
|
||||
well.LitersRemaining.Should().BeApproximately(WellInitialFillLevel - BucketSize,
|
||||
double.Epsilon, "well level has been reduced by bucket amount");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseWell_ExistingBucket_Backward()
|
||||
{
|
||||
const double WellInitialFillLevel = 20D;
|
||||
const double BucketSize = 1.86D;
|
||||
|
||||
var well = new Well(WellInitialFillLevel, default(double));
|
||||
var bucket = new Bucket(BucketSize);
|
||||
var person = new Person();
|
||||
SetBucket(person, bucket, false);
|
||||
|
||||
person.UseWell(well, null);
|
||||
|
||||
bucket.IsEmpty.Should().BeFalse("has been filled");
|
||||
well.LitersRemaining.Should().BeApproximately(WellInitialFillLevel - BucketSize,
|
||||
double.Epsilon, "well level has been reduced by bucket amount");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseWell_NoBucket()
|
||||
{
|
||||
const double WellInitialFillLevel = 20D;
|
||||
|
||||
var well = new Well(WellInitialFillLevel, default(double));
|
||||
var person = new Person();
|
||||
|
||||
person.UseWell(well, null);
|
||||
|
||||
well.LitersRemaining.Should().Be(WellInitialFillLevel, "unchanged");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseWell_AdditionalBucket()
|
||||
{
|
||||
const double WellInitialFillLevel = 20D;
|
||||
const double BucketSize = 1.86D;
|
||||
|
||||
var well = new Well(WellInitialFillLevel, default(double));
|
||||
var bucket = new Bucket(BucketSize);
|
||||
var person = new Person();
|
||||
|
||||
person.UseWell(well, bucket);
|
||||
|
||||
bucket.IsEmpty.Should().BeFalse("has been filled");
|
||||
well.LitersRemaining.Should().BeApproximately(WellInitialFillLevel - BucketSize,
|
||||
double.Epsilon, "well level has been reduced by bucket amount");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_Forward_Simple()
|
||||
{
|
||||
var (first, second) = CreateMinimalChain();
|
||||
var bucket = CreateFilledBucket(1.2D);
|
||||
SetBucket(first, bucket, true);
|
||||
|
||||
HasBucket(first, true).Should().BeTrue();
|
||||
HasBucket(second, true).Should().BeFalse();
|
||||
first.MoveBucket(1)
|
||||
.Should().BeTrue("successfully moved forward to next person");
|
||||
HasBucket(first, true).Should().BeFalse("sent forward");
|
||||
HasBucket(second, true).Should().BeTrue("received");
|
||||
HasBucket(first, false).Should().BeFalse();
|
||||
HasBucket(second, false).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_Backward_Simple()
|
||||
{
|
||||
var (first, second) = CreateMinimalChain();
|
||||
var bucket = new Bucket(1.2D);
|
||||
SetBucket(second, bucket, false);
|
||||
|
||||
HasBucket(first, true).Should().BeFalse();
|
||||
HasBucket(second, true).Should().BeFalse();
|
||||
HasBucket(second, false).Should().BeTrue();
|
||||
first.MoveBucket(1)
|
||||
.Should().BeTrue("successfully moved backward from next person");
|
||||
HasBucket(first, true).Should().BeFalse();
|
||||
HasBucket(second, true).Should().BeFalse();
|
||||
HasBucket(first, false).Should().BeTrue("received");
|
||||
HasBucket(second, false).Should().BeFalse("sent backward");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_Forward_And_Backward()
|
||||
{
|
||||
var (first, second) = CreateMinimalChain();
|
||||
var bucket = CreateFilledBucket(1.2D);
|
||||
var backwardBucket = new Bucket(1.2D);
|
||||
SetBucket(first, bucket, true);
|
||||
SetBucket(second, backwardBucket, false);
|
||||
|
||||
HasBucket(first, true).Should().BeTrue();
|
||||
HasBucket(second, true).Should().BeFalse();
|
||||
HasBucket(first, false).Should().BeFalse();
|
||||
HasBucket(second, false).Should().BeTrue();
|
||||
first.MoveBucket(1)
|
||||
.Should().BeTrue("successfully moved forward to and backward from next person");
|
||||
HasBucket(first, true).Should().BeFalse("sent forward");
|
||||
HasBucket(second, true).Should().BeTrue("received");
|
||||
HasBucket(first, false).Should().BeTrue("received");
|
||||
HasBucket(second, false).Should().BeFalse("sent backward");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_NoBuckets()
|
||||
{
|
||||
var (first, second) = CreateMinimalChain();
|
||||
|
||||
HasBucket(first, true).Should().BeFalse();
|
||||
HasBucket(second, true).Should().BeFalse();
|
||||
HasBucket(first, false).Should().BeFalse();
|
||||
HasBucket(second, false).Should().BeFalse();
|
||||
first.MoveBucket(1)
|
||||
.Should().BeTrue("nothing was moved, but there was no error either");
|
||||
HasBucket(first, true).Should().BeFalse();
|
||||
HasBucket(second, true).Should().BeFalse();
|
||||
HasBucket(first, false).Should().BeFalse();
|
||||
HasBucket(second, false).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_ForwardBlocked()
|
||||
{
|
||||
var (first, second) = CreateMinimalChain();
|
||||
var bucket1 = CreateFilledBucket(1.2D);
|
||||
var bucket2 = CreateFilledBucket(1.2D);
|
||||
SetBucket(first, bucket1, true);
|
||||
SetBucket(second, bucket2, true);
|
||||
|
||||
HasBucket(first, true).Should().BeTrue();
|
||||
HasBucket(second, true).Should().BeTrue();
|
||||
first.MoveBucket(1)
|
||||
.Should().BeTrue("nothing was moved, but there was no error either");
|
||||
HasBucket(first, true).Should().BeTrue();
|
||||
HasBucket(second, true).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_BackwardBlocked()
|
||||
{
|
||||
var (first, second) = CreateMinimalChain();
|
||||
var bucket1 = CreateFilledBucket(1.2D);
|
||||
var bucket2 = CreateFilledBucket(1.2D);
|
||||
SetBucket(first, bucket1, false);
|
||||
SetBucket(second, bucket2, false);
|
||||
|
||||
HasBucket(first, false).Should().BeTrue();
|
||||
HasBucket(second, false).Should().BeTrue();
|
||||
first.MoveBucket(1)
|
||||
.Should().BeTrue("nothing was moved, but there was no error either");
|
||||
HasBucket(first, false).Should().BeTrue();
|
||||
HasBucket(second, false).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_AlreadyPerformedInStep_Simple()
|
||||
{
|
||||
const int Step = 12;
|
||||
|
||||
var person = new Person();
|
||||
SetLastStepPerformed(person, Step);
|
||||
|
||||
person.MoveBucket(Step)
|
||||
.Should().BeFalse("already performed in this step");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_AlreadyPerformedInStep_Complex()
|
||||
{
|
||||
const int Step = 12;
|
||||
|
||||
var (first, second) = CreateMinimalChain();
|
||||
var third = new Person();
|
||||
third.JoinChain(second, true);
|
||||
SetBucket(first, CreateFilledBucket(1.2D), true);
|
||||
|
||||
first.MoveBucket(Step)
|
||||
.Should().BeTrue("can move");
|
||||
second.MoveBucket(Step)
|
||||
.Should().BeFalse("already performed in this step");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveBucket_NoNeighbor()
|
||||
{
|
||||
var person = new Person();
|
||||
SetBucket(person, CreateFilledBucket(1.2D), true);
|
||||
|
||||
person.MoveBucket(1)
|
||||
.Should().BeFalse("no right neighbor to move to/from");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(StringRepresentationData))]
|
||||
public void StringRepresentation(Person person, string expected, string reason)
|
||||
{
|
||||
person.ToString()
|
||||
.Should().Be(expected, reason);
|
||||
}
|
||||
|
||||
public static TheoryData<Person, string, string> StringRepresentationData()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
{
|
||||
CreatePerson(null),
|
||||
"🫲",
|
||||
"has no bucket"
|
||||
},
|
||||
{
|
||||
CreatePerson(false),
|
||||
"🔘",
|
||||
"has empty forward bucket only"
|
||||
},
|
||||
{
|
||||
CreatePerson(false, false),
|
||||
"🔘",
|
||||
"has empty backward bucket only"
|
||||
},
|
||||
{
|
||||
CreatePerson(true),
|
||||
"🔵",
|
||||
"has full forward bucket only"
|
||||
},
|
||||
{
|
||||
CreatePerson(true, false),
|
||||
"🔵",
|
||||
"has full backward bucket only"
|
||||
},
|
||||
{
|
||||
CreatePerson(false,
|
||||
then: p => SetBucket(p, CreateFilledBucket(1), false)),
|
||||
"🔘",
|
||||
"has empty forward bucket and full backward bucket"
|
||||
},
|
||||
{
|
||||
CreatePerson(true,
|
||||
then: p => SetBucket(p, new Bucket(1D), false)),
|
||||
"🔵",
|
||||
"has full forward bucket and empty backward bucket"
|
||||
}
|
||||
};
|
||||
|
||||
static Person CreatePerson(bool? bucketFull, bool forwardBucket = true, Action<Person>? then = null)
|
||||
{
|
||||
var person = new Person();
|
||||
if (bucketFull == null)
|
||||
{
|
||||
return person;
|
||||
}
|
||||
|
||||
var bucket = bucketFull.Value ? CreateFilledBucket(1D) : new Bucket(1D);
|
||||
SetBucket(person, bucket, forwardBucket);
|
||||
then?.Invoke(person);
|
||||
|
||||
return person;
|
||||
}
|
||||
}
|
||||
|
||||
private static (Person FirstPerson, Person SecondPerson) CreateMinimalChain()
|
||||
{
|
||||
var firstPerson = new Person();
|
||||
var secondPerson = new Person();
|
||||
|
||||
secondPerson.JoinChain(firstPerson, true);
|
||||
|
||||
return (firstPerson, secondPerson);
|
||||
}
|
||||
|
||||
private static Bucket CreateFilledBucket(double size)
|
||||
{
|
||||
var bucket = new Bucket(size);
|
||||
bucket.Fill(size);
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
#region Test Helper - ignore
|
||||
|
||||
private static void SetBucket(Person person, Bucket? bucket, bool forwardBucket)
|
||||
{
|
||||
var field = GetBucketField(forwardBucket);
|
||||
field.SetValue(person, bucket);
|
||||
}
|
||||
|
||||
private static bool HasBucket(Person person, bool forwardBucket)
|
||||
{
|
||||
var field = GetBucketField(forwardBucket);
|
||||
return field.GetValue(person) != null;
|
||||
}
|
||||
|
||||
private static FieldInfo GetBucketField(bool forwardBucket) =>
|
||||
typeof(Person).GetField(forwardBucket ? "_forwardBucket" : "_backwardBucket",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
|
||||
private static void SetLastStepPerformed(Person person, int step)
|
||||
{
|
||||
var prop = typeof(Person).GetProperty("LastStepPerformed",
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
prop!.SetValue(person, step);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
125
BucketChain.Test/WellTests.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace BucketChain.Test;
|
||||
|
||||
public sealed class WellTests
|
||||
{
|
||||
private const double MaxCapacity = 20D;
|
||||
private const double RefillRate = 0.75D;
|
||||
private const double BucketSize = 2.3D;
|
||||
|
||||
[Fact]
|
||||
public void Construction()
|
||||
{
|
||||
var well = new Well(MaxCapacity, default);
|
||||
|
||||
well.LitersRemaining.Should().Be(MaxCapacity, "initially a well is filled completely");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillBucket_Simple()
|
||||
{
|
||||
var well = new Well(MaxCapacity, default);
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
bucket.IsEmpty.Should().BeTrue();
|
||||
well.FillBucket(bucket);
|
||||
bucket.IsEmpty.Should().BeFalse();
|
||||
well.LitersRemaining.Should().BeApproximately(MaxCapacity - BucketSize,
|
||||
double.Epsilon, "bucket capacity drawn from the well");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillBucket_Multiple()
|
||||
{
|
||||
const int Times = 5;
|
||||
|
||||
var well = new Well(MaxCapacity, default);
|
||||
|
||||
for (var i = 0; i < Times; i++)
|
||||
{
|
||||
well.FillBucket(new(BucketSize));
|
||||
}
|
||||
|
||||
well.LitersRemaining.Should().BeApproximately(MaxCapacity - BucketSize * Times,
|
||||
2E-14, "bucket capacity drawn from the well several times");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillBucket_Empty()
|
||||
{
|
||||
var well = new Well(MaxCapacity, default);
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
well.FillBucket(new(MaxCapacity));
|
||||
well.FillBucket(bucket);
|
||||
bucket.IsEmpty.Should().BeTrue("nothing left in the well");
|
||||
well.LitersRemaining.Should().BeApproximately(0,
|
||||
double.Epsilon, "well is empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillBucket_AlmostEmpty()
|
||||
{
|
||||
var well = new Well(MaxCapacity, default);
|
||||
var bucket = new Bucket(BucketSize);
|
||||
|
||||
well.FillBucket(new(MaxCapacity - BucketSize / 2));
|
||||
well.FillBucket(bucket);
|
||||
bucket.IsEmpty.Should().BeFalse("half full");
|
||||
well.LitersRemaining.Should().BeApproximately(0,
|
||||
double.Epsilon, "well is empty");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Refill_Simple()
|
||||
{
|
||||
var well = new Well(MaxCapacity, RefillRate);
|
||||
|
||||
well.FillBucket(new(MaxCapacity / 2));
|
||||
well.Refill();
|
||||
|
||||
well.LitersRemaining.Should().BeApproximately(MaxCapacity / 2 + RefillRate,
|
||||
2E-14, "well has been refilled by rate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Refill_Multiple()
|
||||
{
|
||||
const int Times = 8;
|
||||
|
||||
var well = new Well(MaxCapacity, RefillRate);
|
||||
|
||||
well.FillBucket(new(MaxCapacity / 2));
|
||||
for (var i = 0; i < Times; i++)
|
||||
{
|
||||
well.Refill();
|
||||
}
|
||||
|
||||
well.LitersRemaining.Should().BeApproximately(MaxCapacity / 2 + RefillRate * Times,
|
||||
2E-14, "well has been refilled several times, but is not yet full");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Refill_AlreadyFull()
|
||||
{
|
||||
var well = new Well(MaxCapacity, RefillRate);
|
||||
|
||||
well.Refill();
|
||||
|
||||
well.LitersRemaining.Should().Be(MaxCapacity, "max capacity cannot be exceeded");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringRepresentation()
|
||||
{
|
||||
var well = new Well(MaxCapacity, RefillRate);
|
||||
|
||||
well.FillBucket(new(BucketSize * 2));
|
||||
well.Refill();
|
||||
|
||||
well.ToString()
|
||||
.Should().Be("💧 16/20");
|
||||
}
|
||||
}
|
||||
31
BucketChain.sln
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.4.33103.184
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BucketChain", "BucketChain\BucketChain.csproj", "{7F30E637-BAFE-42FA-A173-F42B3902ED3B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BucketChain.Test", "BucketChain.Test\BucketChain.Test.csproj", "{161D9B2A-4E8B-43B6-A77E-40BED559521F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D322E690-379C-4802-8F7E-1AC6AD090D59}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
46
BucketChain/Bucket.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
namespace BucketChain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a bucket used to transport water.
|
||||
/// </summary>
|
||||
public sealed class Bucket
|
||||
{
|
||||
// TODO
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new bucket with the given capacity.
|
||||
/// </summary>
|
||||
/// <param name="capacityLiters">Bucket capacity in liters</param>
|
||||
public Bucket(double capacityLiters)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this bucket is currently completely empty or not.
|
||||
/// </summary>
|
||||
public bool IsEmpty => false; // TODO
|
||||
|
||||
/// <summary>
|
||||
/// Fills the bucket to the brim if enough water is available.
|
||||
/// If there is not enough water to fill the bucket completely it takes as much as possible.
|
||||
/// If the bucket is already full no water is taken.
|
||||
/// </summary>
|
||||
/// <param name="maxAvailable">Max. available water, in liters</param>
|
||||
/// <returns>The amount taken for filling the bucket</returns>
|
||||
public double Fill(double maxAvailable)
|
||||
{
|
||||
// TODO
|
||||
return -1D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empties the bucket. The amount stored within before emptying is returned.
|
||||
/// </summary>
|
||||
/// <returns>Amount emptied from the bucket, in liters</returns>
|
||||
public double Empty()
|
||||
{
|
||||
// TODO
|
||||
return -1D;
|
||||
}
|
||||
}
|
||||
15
BucketChain/BucketChain.csproj
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HTLLeonding.Utility.LeoAnalyzers" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
55
BucketChain/Chain.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
namespace BucketChain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a bucket chain which connects a well with a fire by cooperating people with buckets.
|
||||
/// </summary>
|
||||
public sealed class Chain
|
||||
{
|
||||
// TODO
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new bucket chain based on the supplied parameters.
|
||||
/// </summary>
|
||||
/// <param name="requiredPeople">Min. amount of people required to reach the fire</param>
|
||||
/// <param name="availableBuckets">Max. amount of buckets that can be active in the chain</param>
|
||||
/// <param name="bucketSize">Size of the buckets used in the chain - all buckets have the same size</param>
|
||||
/// <param name="well">A reference to the well, used as water source</param>
|
||||
/// <param name="fire">A reference to the fire this chain attempts to extinguish</param>
|
||||
/// <param name="firstPerson">Reference to the first person in the chain</param>
|
||||
public Chain(int requiredPeople, int availableBuckets, double bucketSize,
|
||||
Well well, Fire fire, Person firstPerson)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operates the bucket chain. This includes multiple things:
|
||||
/// 1) Refill the well
|
||||
/// 2) Allow the fire to grow
|
||||
/// 3) If first person has a bucket, fill it at the well
|
||||
/// 4) Move the buckets along the chain of people
|
||||
/// 5) If the last person reaches the fire and has a full bucket, use it to fight the fire
|
||||
/// People can join the chain at various positions, so the <see cref="Chain"/> always has to
|
||||
/// determine the current first person before operating.
|
||||
/// </summary>
|
||||
/// <param name="currentStep">The current step</param>
|
||||
/// <param name="error">Set to true if an error occurred while operating, e.g. invalid state of the chain</param>
|
||||
/// <returns>True if the fire could be extinguished in this step; false otherwise</returns>
|
||||
public bool Operate(int currentStep, out bool error)
|
||||
{
|
||||
// TODO
|
||||
error = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents this <see cref="Chain"/> with the referenced <see cref="Well"/>, <see cref="Fire"/>
|
||||
/// and <see cref="Person"/> instances as string, ready to print to the terminal.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the bucket chain</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
// TODO
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
56
BucketChain/Fire.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
namespace BucketChain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a fire which the bucket chain will attempt to extinguish.
|
||||
/// </summary>
|
||||
public sealed class Fire
|
||||
{
|
||||
// TODO
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new fire with the passed properties.
|
||||
/// </summary>
|
||||
/// <param name="initialFireSize">Initial size of the fire</param>
|
||||
/// <param name="growRate">Rate by which the fire grows with each (chain operation) step</param>
|
||||
public Fire(double initialFireSize, double growRate)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current size of the fire; cannot be smaller than 0.
|
||||
/// </summary>
|
||||
// TODO
|
||||
// public double FireSize ...
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the fire has been extinguished (= size is 0).
|
||||
/// </summary>
|
||||
public bool Extinguished => false; // TODO
|
||||
|
||||
/// <summary>
|
||||
/// The fire is hit by a load of water from a bucket.
|
||||
/// Reduces the fire size by the amount of liters it received.
|
||||
/// </summary>
|
||||
/// <param name="amount">Liters of water</param>
|
||||
public void GetHitByWater(double amount)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the size of the fire.
|
||||
/// In this simulation the growth rate is linear & constant and not influenced by the size of the fire.
|
||||
/// </summary>
|
||||
public void BurnHigher()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of the fire showing the current size.
|
||||
/// The size is rounded to whole numbers.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the fire</returns>
|
||||
public override string ToString() => string.Empty; // TODO
|
||||
}
|
||||
109
BucketChain/Person.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
namespace BucketChain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a person working together with others in a bucket chain to extinguish a fire.
|
||||
/// </summary>
|
||||
public sealed class Person
|
||||
{
|
||||
// TODO
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new person.
|
||||
/// We are not interested in any specific details of the person,
|
||||
/// only the position in the chain is relevant.
|
||||
/// </summary>
|
||||
public Person()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the person currently has at least one bucket (forward or backward).
|
||||
/// </summary>
|
||||
public bool HasBucket => false; // TODO
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left neighbor of the person. Can be null.
|
||||
/// </summary>
|
||||
// TODO
|
||||
// public Person? LeftNeighbor ...
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right neighbor of the person. Can be null.
|
||||
/// </summary>
|
||||
// TODO
|
||||
// public Person? RightNeighbor ...
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the last step in which this person performed a bucket move action.
|
||||
/// Each person can only move, at most, once per step.
|
||||
/// </summary>
|
||||
// TODO
|
||||
// public int LastStepPerformed ...
|
||||
|
||||
/// <summary>
|
||||
/// The person joins an existing bucket chain at a specific position.
|
||||
/// This position is defined by the passed person and the information if this person will join
|
||||
/// the chain on the left or right side of the passed person.
|
||||
/// If the passed person already had a neighbor at this side this person joins between them.
|
||||
/// </summary>
|
||||
/// <param name="neighbor">The neighbor in the bucket chain</param>
|
||||
/// <param name="isLeftNeighbor">If true the passed person will become the left hand side
|
||||
/// neighbor of this person; otherwise it will become the right hand side neighbor</param>
|
||||
public void JoinChain(Person neighbor, bool isLeftNeighbor)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to move the forward bucket along the chain towards the fire.
|
||||
/// The person negotiates with the right hand side neighbor.
|
||||
/// If that neighbor can accept a forward bucket it received it.
|
||||
/// They also check if the right hand side neighbor has a backward (towards the well)
|
||||
/// bucket. If this person is able to receive the backward bucket it receives it.
|
||||
/// If at least one bucket is exchanged both involved people are done for the current step.
|
||||
/// </summary>
|
||||
/// <param name="currentStep">Number of the current (chain) operation step</param>
|
||||
/// <returns>True if the move attempt produced no errors - this does not mean
|
||||
/// that a bucket was moved, only that no invalid state was detected; false otherwise</returns>
|
||||
public bool MoveBucket(int currentStep)
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fights the passed fire with water from the forward bucket. This empties the bucket.
|
||||
/// After using it the now empty bucket is moved to the backward position.
|
||||
/// </summary>
|
||||
/// <param name="fire">The fire to throw water at</param>
|
||||
public void FightFire(Fire fire)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the passed well to fill the bucket.
|
||||
/// If this person currently has no empty bucket to fill but there are still unused buckets
|
||||
/// available for the chain a new bucket will be introduced with this method.
|
||||
/// If the person has no forward bucket but a backward bucket that one is filled.
|
||||
/// </summary>
|
||||
/// <param name="well">The well to use for filling the bucket</param>
|
||||
/// <param name="additionalBucket">A new bucket to use; can be null</param>
|
||||
public void UseWell(Well well, Bucket? additionalBucket)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of this person instance.
|
||||
/// Shows if the person has none, an empty or full bucket.
|
||||
/// If the person has both forward and backward buckets the forward bucket is displayed.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the person</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
// TODO
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
92
BucketChain/Program.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System.Text;
|
||||
using BucketChain;
|
||||
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
Console.WriteLine("*** BucketChain ***");
|
||||
|
||||
const int MaxSteps = 100;
|
||||
var stepCount = 0;
|
||||
|
||||
var well = new Well(20D, 0.5D);
|
||||
var fire = new Fire(5D, 0.25D);
|
||||
|
||||
var people = new Person[9];
|
||||
for (var i = 0; i < people.Length; i++)
|
||||
{
|
||||
people[i] = new Person();
|
||||
}
|
||||
|
||||
var chain = new Chain(8, 6, 1.25D, well, fire, people[0]);
|
||||
|
||||
while (stepCount < MaxSteps)
|
||||
{
|
||||
stepCount++;
|
||||
|
||||
switch (stepCount)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
// 01
|
||||
people[1].JoinChain(people[0], true);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
// 021
|
||||
people[2].JoinChain(people[1], false);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
// 3021
|
||||
people[3].JoinChain(people[0], false);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
{
|
||||
// 30214
|
||||
people[4].JoinChain(people[1], true);
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
{
|
||||
// 302145678
|
||||
var left = 4;
|
||||
for (var i = 5; i < people.Length; i++)
|
||||
{
|
||||
people[i].JoinChain(people[left++], true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (chain.Operate(stepCount, out var error))
|
||||
{
|
||||
PrintChain();
|
||||
break;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
Console.WriteLine("Error moving bucket");
|
||||
break;
|
||||
}
|
||||
|
||||
PrintChain();
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
continue;
|
||||
|
||||
void PrintChain()
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine($"#{stepCount:000}: {chain}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(fire.Extinguished ? "Fire extinguished 🥳" : "Fire destroyed the city 🥵");
|
||||
|
||||
Console.WriteLine($"{Environment.NewLine}Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
54
BucketChain/Well.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
namespace BucketChain;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a well which provides water for fighting the fire.
|
||||
/// </summary>
|
||||
public sealed class Well
|
||||
{
|
||||
// TODO
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new well with the supplied properties.
|
||||
/// Initially the well is filled to the brim.
|
||||
/// </summary>
|
||||
/// <param name="maxCapacity">The max. fill level of the well in liters.</param>
|
||||
/// <param name="refillRate">The amount of liters by which this well is refilled each (chain)
|
||||
/// operation step</param>
|
||||
public Well(double maxCapacity, double refillRate)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets how many liters currently remain in the well.
|
||||
/// Cannot exceed the max. capacity of the well or drop below 0.
|
||||
/// </summary>
|
||||
// TODO
|
||||
// public double LitersRemaining ...
|
||||
|
||||
/// <summary>
|
||||
/// Refills the well by the constant rate.
|
||||
/// </summary>
|
||||
public void Refill()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the passed bucket.
|
||||
/// Can provide at most the <see cref="LitersRemaining"/> and only subtracts the
|
||||
/// amount actually required to fill the bucket.
|
||||
/// </summary>
|
||||
/// <param name="bucket">The bucket to fill</param>
|
||||
public void FillBucket(Bucket bucket)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of this well.
|
||||
/// Shows the current and max. amount of liters - rounded to whole numbers.
|
||||
/// </summary>
|
||||
/// <returns>String representation of the well</returns>
|
||||
public override string ToString() => string.Empty; // TODO
|
||||
}
|
||||
BIN
pics/bucket_chain_sample_run.mp4
Normal file
BIN
pics/chain_illustration.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
pics/historic_bucket_chain.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
pics/join_chain_01.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
pics/join_chain_02.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
pics/join_chain_03.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
pics/modern_bucket_chain.jpg
Normal file
|
After Width: | Height: | Size: 420 KiB |
BIN
pics/person_states.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
pics/sample_run.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
pics/swap_sample_01.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
pics/swap_sample_02.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
pics/swap_sample_03.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
pics/swap_sample_04.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
pics/throwing_water.jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
209
readme.adoc
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
:sectnums:
|
||||
:nofooter:
|
||||
:toc: left
|
||||
:icons: font
|
||||
:data-uri:
|
||||
:source-highlighter: highlightjs
|
||||
:stem: latexmath
|
||||
|
||||
= Col.02 -- Bucket Chain
|
||||
|
||||
In this assignment you will simulate a simple bucket chain used to fight fires.
|
||||
This method was very common in the past, but is sometimes still used today.
|
||||
|
||||
People create a chain and pass buckets with water from one to the other until the last person can use it to fight the fire.
|
||||
|
||||
image:pics/historic_bucket_chain.jpg[Historic BucketChain, width=300]
|
||||
image:pics/throwing_water.jpg[Throwing Water, width=300]
|
||||
image:pics/modern_bucket_chain.jpg[Modern BucketChain, width=300]
|
||||
|
||||
This chain will be implemented as a *double linked list*.
|
||||
|
||||
[plantuml]
|
||||
----
|
||||
@startuml
|
||||
|
||||
class Bucket {
|
||||
-double _capacityLiters [readonly]
|
||||
-double _contentLiters
|
||||
+bool IsEmpty [readonly]
|
||||
|
||||
+Bucket(double)
|
||||
+double Fill(double)
|
||||
+double Empty()
|
||||
}
|
||||
|
||||
class Chain {
|
||||
-Fire _fire [readonly]
|
||||
-int _requiredPeople [readonly]
|
||||
-Well _well [readonly]
|
||||
-double _bucketSize [readonly]
|
||||
-Person _firstPerson
|
||||
-int _availableBuckets
|
||||
|
||||
+Chain(int, int, double, Well, Fire, Person)
|
||||
+bool Operate(int, out bool)
|
||||
+string ToString() [override]
|
||||
}
|
||||
|
||||
class Fire {
|
||||
-double _growRate [readonly]
|
||||
+double FireSize [private set]
|
||||
+bool Extinguished [readonly]
|
||||
|
||||
+Fire(double, double)
|
||||
+void GetHitByWater(double)
|
||||
+void BurnHigher()
|
||||
+string ToString() [override]
|
||||
}
|
||||
|
||||
class Person {
|
||||
-Bucket? _backwardBucket
|
||||
-Bucket? _forwardBucket
|
||||
+bool HasBucket [readonly]
|
||||
+Person? LeftNeighbor [private set]
|
||||
+Person? RightNeighbor [private set]
|
||||
+int LastStepPerformed [private set]
|
||||
|
||||
+Person()
|
||||
+void JoinChain(Person, bool)
|
||||
+bool MoveBucket(int)
|
||||
+void FightFire(Fire)
|
||||
+void UseWell(Well, Bucket?)
|
||||
+string ToString() [override]
|
||||
}
|
||||
|
||||
class Well {
|
||||
-double _maxCapacity [readonly]
|
||||
-double _refillRate [readonly]
|
||||
+double LitersRemaining [private set]
|
||||
|
||||
+Well(double, double)
|
||||
+void Refill()
|
||||
+void FillBucket(Bucket)
|
||||
+string ToString() [override]
|
||||
}
|
||||
|
||||
Chain "1" -r- "1" Person
|
||||
Person "0..2" -- "0..2" Person
|
||||
Chain "1" -l- "1" Fire
|
||||
Chain "1" -d- "1" Well
|
||||
Person "1" -- "0..2" Bucket
|
||||
|
||||
@enduml
|
||||
----
|
||||
|
||||
NOTE: The class diagram still contains the private fields, but _not_ the private methods. You need to start structuring your applications yourself.
|
||||
|
||||
== Bucket Movement
|
||||
|
||||
Buckets filled with water move in the _forward_ direction to the right, towards the fire.
|
||||
Emptied buckets move in the _backward_ direction to the left, towards the well.
|
||||
|
||||
.Movement of buckets in the chain
|
||||
image::pics/chain_illustration.png[Chain Movement,width=600]
|
||||
|
||||
CAUTION: The fire can only be fought if the chain is long enough. For example: if 6 people are required to span the distance between well and fire, but only 5 work in the chain the buckets cannot be emptied on the fire.
|
||||
|
||||
The `Chain` class calls the `UseWell` method for the _first_ person if they have an empty bucket, and it calls the `FightFire` method for the _last_ person if they have a filled bucket.
|
||||
|
||||
=== Bucket Exchange
|
||||
|
||||
Buckets are always exchanged between two people in the chain who negotiate the exchange themselves.
|
||||
The `Chain` class as overall instance simply calls the `MoveBucket` method for each person, but the actual actions are determined by the people themselves.
|
||||
|
||||
A person can, at all times, have:
|
||||
|
||||
* No buckets or
|
||||
* One forward bucket or
|
||||
* One backward bucket or
|
||||
* One forward _and_ one backward bucket
|
||||
|
||||
.Possible States of a person
|
||||
image::pics/person_states.png[Possible states of a person,width=600]
|
||||
|
||||
It can only accept a forward bucket if that 'slot' is empty -- the same is true for the backward bucket.
|
||||
Only first and last person can switch a bucket from 'forward' to 'backward' or vice versa (by filling or emptying).
|
||||
|
||||
The following shows _some_ possible exchange scenarios:
|
||||
|
||||
* image:pics/swap_sample_01.png[Swap Sample 1, width=400]
|
||||
* image:pics/swap_sample_02.png[Swap Sample 2, width=400]
|
||||
* image:pics/swap_sample_03.png[Swap Sample 3, width=400]
|
||||
* image:pics/swap_sample_04.png[Swap Sample 4, width=400]
|
||||
|
||||
=== Exchange limit
|
||||
|
||||
Each person can only perform one exchange per _step_.
|
||||
For example, if person 1 exchanges buckets with person 2, person 2 _cannot_ exchange buckets with person 3 during this step, but person 3 could still exchange buckets with person 4.
|
||||
This leads to buckets being transported step by step instead of being passed from start to finish within one step.
|
||||
|
||||
=== Number of Buckets
|
||||
|
||||
In each chain there is a limited amount of buckets.
|
||||
It is possible, that there are fewer buckets than people in the chain.
|
||||
That means, that some people sometimes won't have a bucket in hand.
|
||||
They still stay at their position and forward buckets as they arrive at their location.
|
||||
|
||||
== People joining the Chain
|
||||
|
||||
In the excitement of a burning fire people are running from all directions to join the bucket chain and help extinguish the fire.
|
||||
As a result, new people can join at all positions of the chain.
|
||||
Doing so they will talk to their new neighbors (left, right or both), quickly introducing themselves, so that everyone knows who to hand full buckets to and from whom to receive the empty ones.
|
||||
|
||||
* image:pics/join_chain_01.png[Join Chain Option 1, width=500]
|
||||
* image:pics/join_chain_02.png[Join Chain Option 2, width=500]
|
||||
* image:pics/join_chain_03.png[Join Chain Option 3, width=500]
|
||||
|
||||
There can be _more_ people than required, but the fire can only be fought if there are _at least_ enough.
|
||||
|
||||
== Implementation Hints
|
||||
|
||||
Parts of the specified logic is quite complex and might be hard to understand.
|
||||
Make sure to _carefully_ read the provided XML Doc as well as reading through and understanding the provided unit tests.
|
||||
|
||||
It might be helpful to draw different scenarios on a piece of paper before starting the implementation to better understand what has to happen in which case.
|
||||
|
||||
TIP: You _will_ need some `private` helper methods
|
||||
|
||||
=== Symbols
|
||||
|
||||
The string representations ask for specific symbols, you may use these:
|
||||
|
||||
[cols="1,4",width=50%]
|
||||
|===
|
||||
|Symbol |Usage
|
||||
|
||||
|🔘
|
||||
|Empty Bucket
|
||||
|
||||
|🔵
|
||||
|Filled Bucket
|
||||
|
||||
|🫲
|
||||
|No Bucket
|
||||
|
||||
|❔
|
||||
|Open slot for missing person in the chain
|
||||
|
||||
|🔥
|
||||
|Fire
|
||||
|
||||
|💧
|
||||
|Well
|
||||
|===
|
||||
|
||||
== Program & Sample Run
|
||||
|
||||
A `Program` has already been provided which creates a fire, a well, some people and a chain.
|
||||
It executes several steps on the chain to finally extinguish the fire.
|
||||
Based on the current step people will join the chain at different positions.
|
||||
Only after a while the chain will be long enough to start fighting the fire.
|
||||
Once you are done with your implementation this program should run to completion.
|
||||
|
||||
.Sample Run
|
||||
image::pics/sample_run.png[Sample Run]
|
||||
|
||||
The application will wait for a second between steps so that the progression of the buckets up and down the chain is better visible.
|
||||
|
||||
video::pics/bucket_chain_sample_run.mp4[Sample Run Video,width=500]
|
||||