Latest
This commit is contained in:
1228
.editorconfig
1228
.editorconfig
File diff suppressed because it is too large
Load Diff
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
.vs
|
.vs
|
||||||
bin
|
bin
|
||||||
obj
|
obj
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Linux-specific instructions
|
# Linux-specific instructions
|
||||||
|
|
||||||
- After every iteration, run `dotnet jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
- After every iteration, run `dotnet jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
||||||
60
AGENTS.md
60
AGENTS.md
@@ -1,30 +1,30 @@
|
|||||||
# Platform and documentation
|
# Platform and documentation
|
||||||
|
|
||||||
If this is a linux environment, read `AGENTS.linux.md`.
|
If this is a linux environment, read `AGENTS.linux.md`.
|
||||||
If this is a windows environment, read `AGENTS.windows.md`.
|
If this is a windows environment, read `AGENTS.windows.md`.
|
||||||
Follow the guidelines laid out in `CODESTYLE.md`.
|
Follow the guidelines laid out in `CODESTYLE.md`.
|
||||||
Also see the other related technical documentation in the docs folder.
|
Also see the other related technical documentation in the docs folder.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- Prefer extracting code to a shared helper to be reused instead of duplicating code. Always keep high maintainability standards.
|
- Prefer extracting code to a shared helper to be reused instead of duplicating code. Always keep high maintainability standards.
|
||||||
- If a class is to be used only once, consider nesting it inside of another class. Otherwise place each newly created class into its own file. The file name must match the class name.
|
- If a class is to be used only once, consider nesting it inside of another class. Otherwise place each newly created class into its own file. The file name must match the class name.
|
||||||
- When asked to begin working on a task, create a detailed implementation plan first, present the plan to the user, and ask for approval before beginning with the actual implementation.
|
- When asked to begin working on a task, create a detailed implementation plan first, present the plan to the user, and ask for approval before beginning with the actual implementation.
|
||||||
- Don't make assumptions in the plan. If necessary, ask all clarifying questions before presenting the final plan.
|
- Don't make assumptions in the plan. If necessary, ask all clarifying questions before presenting the final plan.
|
||||||
- When an task is finished, perform a code review to evaluate if the change is clean and maintainable with high software engineering standards. Iterate on the code and repeat the review process until satisfied.
|
- When an task is finished, perform a code review to evaluate if the change is clean and maintainable with high software engineering standards. Iterate on the code and repeat the review process until satisfied.
|
||||||
- If there's documnentation present, always keep it updated.
|
- If there's documnentation present, always keep it updated.
|
||||||
- After every iteration, evaluate if the test coverage would fall below 100%, and write tests if necessary.
|
- After every iteration, evaluate if the test coverage would fall below 100%, and write tests if necessary.
|
||||||
|
|
||||||
### Git
|
### Git
|
||||||
|
|
||||||
- Never change the .gitignore file without consent.
|
- Never change the .gitignore file without consent.
|
||||||
- Keep changes small with minimal churn and commit often. If one iteration encompasses many smaller tasks with more than one commit, create a git branch and do the commits there. Let me review the branch before merging it back to master.
|
- Keep changes small with minimal churn and commit often. If one iteration encompasses many smaller tasks with more than one commit, create a git branch and do the commits there. Let me review the branch before merging it back to master.
|
||||||
- When multiple commits are necessary, pause after every commit and ask the user to give a command to proceed.
|
- When multiple commits are necessary, pause after every commit and ask the user to give a command to proceed.
|
||||||
- After every iteration, do a git commit with a brief summary of the changes as a commit message.
|
- After every iteration, do a git commit with a brief summary of the changes as a commit message.
|
||||||
- If you find unexpected changes in the code (deletions, changes, diff results that were not communicated), never revert them and never restore the old state. Assume that those changes happened with intent.
|
- If you find unexpected changes in the code (deletions, changes, diff results that were not communicated), never revert them and never restore the old state. Assume that those changes happened with intent.
|
||||||
- Never use `git restore`, `git checkout --`, reset commands, or equivalent rollback actions to discard local changes unless the user explicitly asks for that exact rollback.
|
- Never use `git restore`, `git checkout --`, reset commands, or equivalent rollback actions to discard local changes unless the user explicitly asks for that exact rollback.
|
||||||
|
|
||||||
### Dotnet CLI
|
### Dotnet CLI
|
||||||
|
|
||||||
- If you need a separate output directory, use a subfolder under `artifacts`, and clean it up afterwards.
|
- If you need a separate output directory, use a subfolder under `artifacts`, and clean it up afterwards.
|
||||||
- Avoid running `dotnet build` and `dotnet test` in parallel in this repo; that can cause file-lock failures in `obj\Debug\net10.0`.
|
- Avoid running `dotnet build` and `dotnet test` in parallel in this repo; that can cause file-lock failures in `obj\Debug\net10.0`.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Windows-specific instructions
|
# Windows-specific instructions
|
||||||
|
|
||||||
- After the implementation is finished, run `python D:\Code\crlf.py $file1 $file2 ...` for changed files you recognize, in order to normalize all line endings of all touched files to CRLF.
|
- After the implementation is finished, run `python D:\Code\crlf.py $file1 $file2 ...` for changed files you recognize, in order to normalize all line endings of all touched files to CRLF.
|
||||||
- After every iteration, run `jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
- After every iteration, run `jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
||||||
102
CODESTYLE.md
102
CODESTYLE.md
@@ -1,51 +1,51 @@
|
|||||||
# Code Style
|
# Code Style
|
||||||
|
|
||||||
This repository follows the local `.editorconfig` and the style visible in the current local changes. Use these notes when creating new code.
|
This repository follows the local `.editorconfig` and the style visible in the current local changes. Use these notes when creating new code.
|
||||||
|
|
||||||
## Naming
|
## Naming
|
||||||
|
|
||||||
- Use PascalCase for namespaces, types, methods, properties, enum members, and non-field members.
|
- Use PascalCase for namespaces, types, methods, properties, enum members, and non-field members.
|
||||||
- Prefix enum type names with `E`, for example `ECellKind`, `EPipeMedium`, `EFailureKind`, and `EEditorTool`.
|
- Prefix enum type names with `E`, for example `ECellKind`, `EPipeMedium`, `EFailureKind`, and `EEditorTool`.
|
||||||
- Prefix struct type names with `S` when creating new structs.
|
- Prefix struct type names with `S` when creating new structs.
|
||||||
- Prefix interfaces with `I`.
|
- Prefix interfaces with `I`.
|
||||||
- Use camelCase for parameters and local variables.
|
- Use camelCase for parameters and local variables.
|
||||||
- Prefix private instance fields with `m_` and keep the remainder PascalCase, for example `m_Level` and `m_SelectedTool`.
|
- Prefix private instance fields with `m_` and keep the remainder PascalCase, for example `m_Level` and `m_SelectedTool`.
|
||||||
- Prefix private static fields and static readonly fields with `s_`.
|
- Prefix private static fields and static readonly fields with `s_`.
|
||||||
- Prefix constants with `c_`.
|
- Prefix constants with `c_`.
|
||||||
- Avoid `this.` unless it is needed for clarity or disambiguation.
|
- Avoid `this.` unless it is needed for clarity or disambiguation.
|
||||||
- Always use folder-based namespaces when creating types and refactoring.
|
- Always use folder-based namespaces when creating types and refactoring.
|
||||||
|
|
||||||
## Files And Types
|
## Files And Types
|
||||||
|
|
||||||
- Use file-scoped namespaces.
|
- Use file-scoped namespaces.
|
||||||
- Keep one reusable top-level class per file, with the file name matching the class name.
|
- Keep one reusable top-level class per file, with the file name matching the class name.
|
||||||
- If a helper type is used only by one class, prefer nesting it inside that class.
|
- If a helper type is used only by one class, prefer nesting it inside that class.
|
||||||
- Keep small, cohesive files and extract shared helpers instead of duplicating logic.
|
- Keep small, cohesive files and extract shared helpers instead of duplicating logic.
|
||||||
|
|
||||||
## Braces And Blocks
|
## Braces And Blocks
|
||||||
|
|
||||||
- Use braces for multi-line bodies.
|
- Use braces for multi-line bodies.
|
||||||
- If nesting a for-loop under another for-loop, always include curly braces in the parent for-loop.
|
- If nesting a for-loop under another for-loop, always include curly braces in the parent for-loop.
|
||||||
- Omit braces for simple single-line embedded statements when readability stays clear.
|
- Omit braces for simple single-line embedded statements when readability stays clear.
|
||||||
- Nested control flow with multi-line bodies should use braces at every multi-line level.
|
- Nested control flow with multi-line bodies should use braces at every multi-line level.
|
||||||
- Keep opening braces on the next line for types, methods, properties, accessors, and control blocks.
|
- Keep opening braces on the next line for types, methods, properties, accessors, and control blocks.
|
||||||
- Compact object initializers, switch expressions, and `with` expressions may keep the opening brace on the same line when cleanup formats them that way.
|
- Compact object initializers, switch expressions, and `with` expressions may keep the opening brace on the same line when cleanup formats them that way.
|
||||||
|
|
||||||
## Blank Lines
|
## Blank Lines
|
||||||
|
|
||||||
- Use a blank line to separate members.
|
- Use a blank line to separate members.
|
||||||
- Use a blank line after control-flow transfer clauses such as `return`, `continue`, `break`, and `throw` when more code follows in the same scope.
|
- Use a blank line after control-flow transfer clauses such as `return`, `continue`, `break`, and `throw` when more code follows in the same scope.
|
||||||
- Avoid extra blank lines inside short methods and between tightly related statements.
|
- Avoid extra blank lines inside short methods and between tightly related statements.
|
||||||
- Keep at most one blank line in code and declarations.
|
- Keep at most one blank line in code and declarations.
|
||||||
|
|
||||||
## Expressions And Formatting
|
## Expressions And Formatting
|
||||||
|
|
||||||
- Prefer `var` when the type is apparent or not useful to repeat; use explicit built-in types such as `int`, `bool`, and `string`.
|
- Prefer `var` when the type is apparent or not useful to repeat; use explicit built-in types such as `int`, `bool`, and `string`.
|
||||||
- Prefer target-typed `new()` when the type is evident.
|
- Prefer target-typed `new()` when the type is evident.
|
||||||
- Prefer object and collection initializers, including collection expressions such as `[".json"]`.
|
- Prefer object and collection initializers, including collection expressions such as `[".json"]`.
|
||||||
- Prefer pattern matching for combined checks, for example `cell is { HasPipe: true, Pressure: > 7 }`.
|
- Prefer pattern matching for combined checks, for example `cell is { HasPipe: true, Pressure: > 7 }`.
|
||||||
- Prefer switch expressions for simple value selection.
|
- Prefer switch expressions for simple value selection.
|
||||||
- Prefer expression-bodied properties and accessors when they remain simple.
|
- Prefer expression-bodied properties and accessors when they remain simple.
|
||||||
- Keep simple object initializers and property patterns on one line when they are short and readable.
|
- Keep simple object initializers and property patterns on one line when they are short and readable.
|
||||||
- Keep long boolean expressions and interpolated status strings readable without introducing unnecessary blank lines.
|
- Keep long boolean expressions and interpolated status strings readable without introducing unnecessary blank lines.
|
||||||
- Keep using directives outside namespaces.
|
- Keep using directives outside namespaces.
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -1,18 +1,18 @@
|
|||||||
# Reactor Maintenance
|
# Reactor Maintenance
|
||||||
|
|
||||||
C# WinUI 3 + Win2D level editor for the deterministic grid simulation described in `design.md`.
|
C# WinUI 3 + Win2D level editor for the deterministic grid simulation described in `design.md`.
|
||||||
|
|
||||||
## Projects
|
## Projects
|
||||||
|
|
||||||
- `src/ReactorMaintenance.Simulation`: UI-independent level model, editor operations, forecasts, simulation turns, versioned JSON serialization, and swappable difficulty balancing profiles.
|
- `src/ReactorMaintenance.Simulation`: UI-independent level model, editor operations, forecasts, simulation turns, versioned JSON serialization, and swappable difficulty balancing profiles.
|
||||||
- `src/ReactorMaintenance.Win2D`: Win2D editor app for painting floor/wall terrain plus cell props, loading/saving levels, advancing simulation turns, and activating the reactor.
|
- `src/ReactorMaintenance.Win2D`: Win2D editor app for painting floor/wall terrain plus cell props, loading/saving levels, advancing simulation turns, and activating the reactor.
|
||||||
- `tests/ReactorMaintenance.Simulation.Tests`: unit tests for deterministic simulation behavior.
|
- `tests/ReactorMaintenance.Simulation.Tests`: unit tests for deterministic simulation behavior.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
dotnet test tests\ReactorMaintenance.Simulation.Tests\ReactorMaintenance.Simulation.Tests.csproj
|
dotnet test tests\ReactorMaintenance.Simulation.Tests\ReactorMaintenance.Simulation.Tests.csproj
|
||||||
dotnet build src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
dotnet build src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
||||||
dotnet run --project src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
dotnet run --project src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Folder Name="/src/">
|
<Folder Name="/src/">
|
||||||
<Project Path="src/ReactorMaintenance.Simulation/ReactorMaintenance.Simulation.csproj" />
|
<Project Path="src/ReactorMaintenance.Simulation/ReactorMaintenance.Simulation.csproj" />
|
||||||
<Project Path="src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj">
|
<Project Path="src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj">
|
||||||
<Platform Project="x86" />
|
<Platform Project="x86" />
|
||||||
</Project>
|
</Project>
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tests/">
|
<Folder Name="/tests/">
|
||||||
<Project Path="tests/ReactorMaintenance.Simulation.Tests/ReactorMaintenance.Simulation.Tests.csproj" />
|
<Project Path="tests/ReactorMaintenance.Simulation.Tests/ReactorMaintenance.Simulation.Tests.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
2560
docs/design.md
2560
docs/design.md
File diff suppressed because it is too large
Load Diff
@@ -1,78 +1,78 @@
|
|||||||
using ReactorMaintenance.Simulation.Difficulties;
|
using ReactorMaintenance.Simulation.Difficulties;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public abstract class Balancing
|
public abstract class Balancing
|
||||||
{
|
{
|
||||||
public static Balancing Current { get; set; } = new NormalBalancing();
|
public static Balancing Current { get; set; } = new NormalBalancing();
|
||||||
|
|
||||||
public abstract int MinHazardValue { get; }
|
public abstract int MinHazardValue { get; }
|
||||||
public abstract int MaxHazardValue { get; }
|
public abstract int MaxHazardValue { get; }
|
||||||
public abstract int DefaultHazardStability { get; }
|
public abstract int DefaultHazardStability { get; }
|
||||||
public abstract int DefaultCellIntegrity { get; }
|
public abstract int DefaultCellIntegrity { get; }
|
||||||
public abstract int DefaultActionsPerTurn { get; }
|
public abstract int DefaultActionsPerTurn { get; }
|
||||||
public abstract int DefaultCoreHeat { get; }
|
public abstract int DefaultCoreHeat { get; }
|
||||||
public abstract int DefaultFacilityStability { get; }
|
public abstract int DefaultFacilityStability { get; }
|
||||||
public abstract int DefaultPower { get; }
|
public abstract int DefaultPower { get; }
|
||||||
public abstract int DefaultCooling { get; }
|
public abstract int DefaultCooling { get; }
|
||||||
public abstract int FirstGridCoordinate { get; }
|
public abstract int FirstGridCoordinate { get; }
|
||||||
public abstract int NeighborDistance { get; }
|
public abstract int NeighborDistance { get; }
|
||||||
public abstract int CurrentForecastTurn { get; }
|
public abstract int CurrentForecastTurn { get; }
|
||||||
public abstract int MinimumLevelSize { get; }
|
public abstract int MinimumLevelSize { get; }
|
||||||
public abstract int DefaultLevelWidth { get; }
|
public abstract int DefaultLevelWidth { get; }
|
||||||
public abstract int DefaultLevelHeight { get; }
|
public abstract int DefaultLevelHeight { get; }
|
||||||
public abstract int DefaultRobotCoordinate { get; }
|
public abstract int DefaultRobotCoordinate { get; }
|
||||||
public abstract int DefaultPipeFlow { get; }
|
public abstract int DefaultPipeFlow { get; }
|
||||||
public abstract int DefaultPipePressure { get; }
|
public abstract int DefaultPipePressure { get; }
|
||||||
public abstract int DefaultPressurePipeFlow { get; }
|
public abstract int DefaultPressurePipeFlow { get; }
|
||||||
public abstract int DefaultPressurePipePressure { get; }
|
public abstract int DefaultPressurePipePressure { get; }
|
||||||
public abstract int DefaultEditedPipeIntegrity { get; }
|
public abstract int DefaultEditedPipeIntegrity { get; }
|
||||||
public abstract int MinimumLeakRate { get; }
|
public abstract int MinimumLeakRate { get; }
|
||||||
public abstract int DamagedPipeIntegrity { get; }
|
public abstract int DamagedPipeIntegrity { get; }
|
||||||
public abstract int RepairedLeakRate { get; }
|
public abstract int RepairedLeakRate { get; }
|
||||||
public abstract int RepairedElectricalCharge { get; }
|
public abstract int RepairedElectricalCharge { get; }
|
||||||
public abstract int HeatToolIncrease { get; }
|
public abstract int HeatToolIncrease { get; }
|
||||||
public abstract int FireToolMinimumHeat { get; }
|
public abstract int FireToolMinimumHeat { get; }
|
||||||
public abstract int FireToolMinimumSmoke { get; }
|
public abstract int FireToolMinimumSmoke { get; }
|
||||||
public abstract int MaxForecastStepCount { get; }
|
public abstract int MaxForecastStepCount { get; }
|
||||||
public abstract int TurnIncrement { get; }
|
public abstract int TurnIncrement { get; }
|
||||||
public abstract int OverpressureThreshold { get; }
|
public abstract int OverpressureThreshold { get; }
|
||||||
public abstract int HeatIntegrityDamageThreshold { get; }
|
public abstract int HeatIntegrityDamageThreshold { get; }
|
||||||
public abstract int PipeFireIntegrityDamage { get; }
|
public abstract int PipeFireIntegrityDamage { get; }
|
||||||
public abstract int FireStabilityDamage { get; }
|
public abstract int FireStabilityDamage { get; }
|
||||||
public abstract int BurstLeakRate { get; }
|
public abstract int BurstLeakRate { get; }
|
||||||
public abstract int BrokenPipeFlow { get; }
|
public abstract int BrokenPipeFlow { get; }
|
||||||
public abstract int ElectrifiedCoolantPoolingThreshold { get; }
|
public abstract int ElectrifiedCoolantPoolingThreshold { get; }
|
||||||
public abstract int ElectricalChargeIncrease { get; }
|
public abstract int ElectricalChargeIncrease { get; }
|
||||||
public abstract int FuelVaporFireThreshold { get; }
|
public abstract int FuelVaporFireThreshold { get; }
|
||||||
public abstract int LiquidFuelFireThreshold { get; }
|
public abstract int LiquidFuelFireThreshold { get; }
|
||||||
public abstract int HeatIgnitionThreshold { get; }
|
public abstract int HeatIgnitionThreshold { get; }
|
||||||
public abstract int ElectricalIgnitionThreshold { get; }
|
public abstract int ElectricalIgnitionThreshold { get; }
|
||||||
public abstract int FireHeatIncrease { get; }
|
public abstract int FireHeatIncrease { get; }
|
||||||
public abstract int FireSmokeIncrease { get; }
|
public abstract int FireSmokeIncrease { get; }
|
||||||
public abstract int FireLiquidFuelConsumption { get; }
|
public abstract int FireLiquidFuelConsumption { get; }
|
||||||
public abstract int FireFuelVaporConsumption { get; }
|
public abstract int FireFuelVaporConsumption { get; }
|
||||||
public abstract int SmokeDecay { get; }
|
public abstract int SmokeDecay { get; }
|
||||||
public abstract int PressurizedFuelLeakPressureThreshold { get; }
|
public abstract int PressurizedFuelLeakPressureThreshold { get; }
|
||||||
public abstract int PassiveFuelVaporHeatOffset { get; }
|
public abstract int PassiveFuelVaporHeatOffset { get; }
|
||||||
public abstract int PassiveFuelVaporDivisor { get; }
|
public abstract int PassiveFuelVaporDivisor { get; }
|
||||||
public abstract int MinimumCoolantHeatReduction { get; }
|
public abstract int MinimumCoolantHeatReduction { get; }
|
||||||
public abstract int CoolantHeatReductionDivisor { get; }
|
public abstract int CoolantHeatReductionDivisor { get; }
|
||||||
public abstract int CoolantSteamHeatThreshold { get; }
|
public abstract int CoolantSteamHeatThreshold { get; }
|
||||||
public abstract int CoolantSteamSmokeIncrease { get; }
|
public abstract int CoolantSteamSmokeIncrease { get; }
|
||||||
public abstract int PressureLeakSmokeThreshold { get; }
|
public abstract int PressureLeakSmokeThreshold { get; }
|
||||||
public abstract int PressureLeakSmokeIncrease { get; }
|
public abstract int PressureLeakSmokeIncrease { get; }
|
||||||
public abstract int GeneratorHeatIncrease { get; }
|
public abstract int GeneratorHeatIncrease { get; }
|
||||||
public abstract int CoolingPumpHeatReduction { get; }
|
public abstract int CoolingPumpHeatReduction { get; }
|
||||||
public abstract int ReactorHeatIncrease { get; }
|
public abstract int ReactorHeatIncrease { get; }
|
||||||
public abstract int SmokeSpreadThreshold { get; }
|
public abstract int SmokeSpreadThreshold { get; }
|
||||||
public abstract int SmokeSpreadIncrease { get; }
|
public abstract int SmokeSpreadIncrease { get; }
|
||||||
public abstract int CriticalCellStabilityThreshold { get; }
|
public abstract int CriticalCellStabilityThreshold { get; }
|
||||||
public abstract int MeltdownCoreHeatThreshold { get; }
|
public abstract int MeltdownCoreHeatThreshold { get; }
|
||||||
public abstract int StabilityCollapseThreshold { get; }
|
public abstract int StabilityCollapseThreshold { get; }
|
||||||
public abstract int GeneratorPowerOutput { get; }
|
public abstract int GeneratorPowerOutput { get; }
|
||||||
public abstract int CoolingPumpOutput { get; }
|
public abstract int CoolingPumpOutput { get; }
|
||||||
public abstract int ReactorReadyPowerThreshold { get; }
|
public abstract int ReactorReadyPowerThreshold { get; }
|
||||||
public abstract int ReactorReadyCoolingThreshold { get; }
|
public abstract int ReactorReadyCoolingThreshold { get; }
|
||||||
public abstract int ReactorReadyCoreHeatThreshold { get; }
|
public abstract int ReactorReadyCoreHeatThreshold { get; }
|
||||||
}
|
}
|
||||||
@@ -1,76 +1,76 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Difficulties;
|
namespace ReactorMaintenance.Simulation.Difficulties;
|
||||||
|
|
||||||
public class NormalBalancing : Balancing
|
public class NormalBalancing : Balancing
|
||||||
{
|
{
|
||||||
public override int MinHazardValue => 0;
|
public override int MinHazardValue => 0;
|
||||||
public override int MaxHazardValue => 10;
|
public override int MaxHazardValue => 10;
|
||||||
public override int DefaultHazardStability => 10;
|
public override int DefaultHazardStability => 10;
|
||||||
public override int DefaultCellIntegrity => 10;
|
public override int DefaultCellIntegrity => 10;
|
||||||
public override int DefaultActionsPerTurn => 3;
|
public override int DefaultActionsPerTurn => 3;
|
||||||
public override int DefaultCoreHeat => 5;
|
public override int DefaultCoreHeat => 5;
|
||||||
public override int DefaultFacilityStability => 10;
|
public override int DefaultFacilityStability => 10;
|
||||||
public override int DefaultPower => 5;
|
public override int DefaultPower => 5;
|
||||||
public override int DefaultCooling => 0;
|
public override int DefaultCooling => 0;
|
||||||
public override int FirstGridCoordinate => 0;
|
public override int FirstGridCoordinate => 0;
|
||||||
public override int NeighborDistance => 1;
|
public override int NeighborDistance => 1;
|
||||||
public override int CurrentForecastTurn => 0;
|
public override int CurrentForecastTurn => 0;
|
||||||
public override int MinimumLevelSize => 4;
|
public override int MinimumLevelSize => 4;
|
||||||
public override int DefaultLevelWidth => 16;
|
public override int DefaultLevelWidth => 16;
|
||||||
public override int DefaultLevelHeight => 12;
|
public override int DefaultLevelHeight => 12;
|
||||||
public override int DefaultRobotCoordinate => 1;
|
public override int DefaultRobotCoordinate => 1;
|
||||||
public override int DefaultPipeFlow => 4;
|
public override int DefaultPipeFlow => 4;
|
||||||
public override int DefaultPipePressure => 4;
|
public override int DefaultPipePressure => 4;
|
||||||
public override int DefaultPressurePipeFlow => 5;
|
public override int DefaultPressurePipeFlow => 5;
|
||||||
public override int DefaultPressurePipePressure => 6;
|
public override int DefaultPressurePipePressure => 6;
|
||||||
public override int DefaultEditedPipeIntegrity => 8;
|
public override int DefaultEditedPipeIntegrity => 8;
|
||||||
public override int MinimumLeakRate => 1;
|
public override int MinimumLeakRate => 1;
|
||||||
public override int DamagedPipeIntegrity => 4;
|
public override int DamagedPipeIntegrity => 4;
|
||||||
public override int RepairedLeakRate => 0;
|
public override int RepairedLeakRate => 0;
|
||||||
public override int RepairedElectricalCharge => 0;
|
public override int RepairedElectricalCharge => 0;
|
||||||
public override int HeatToolIncrease => 2;
|
public override int HeatToolIncrease => 2;
|
||||||
public override int FireToolMinimumHeat => 7;
|
public override int FireToolMinimumHeat => 7;
|
||||||
public override int FireToolMinimumSmoke => 3;
|
public override int FireToolMinimumSmoke => 3;
|
||||||
public override int MaxForecastStepCount => 12;
|
public override int MaxForecastStepCount => 12;
|
||||||
public override int TurnIncrement => 1;
|
public override int TurnIncrement => 1;
|
||||||
public override int OverpressureThreshold => 7;
|
public override int OverpressureThreshold => 7;
|
||||||
public override int HeatIntegrityDamageThreshold => 10;
|
public override int HeatIntegrityDamageThreshold => 10;
|
||||||
public override int PipeFireIntegrityDamage => 1;
|
public override int PipeFireIntegrityDamage => 1;
|
||||||
public override int FireStabilityDamage => 1;
|
public override int FireStabilityDamage => 1;
|
||||||
public override int BurstLeakRate => 3;
|
public override int BurstLeakRate => 3;
|
||||||
public override int BrokenPipeFlow => 0;
|
public override int BrokenPipeFlow => 0;
|
||||||
public override int ElectrifiedCoolantPoolingThreshold => 3;
|
public override int ElectrifiedCoolantPoolingThreshold => 3;
|
||||||
public override int ElectricalChargeIncrease => 2;
|
public override int ElectricalChargeIncrease => 2;
|
||||||
public override int FuelVaporFireThreshold => 4;
|
public override int FuelVaporFireThreshold => 4;
|
||||||
public override int LiquidFuelFireThreshold => 6;
|
public override int LiquidFuelFireThreshold => 6;
|
||||||
public override int HeatIgnitionThreshold => 8;
|
public override int HeatIgnitionThreshold => 8;
|
||||||
public override int ElectricalIgnitionThreshold => 4;
|
public override int ElectricalIgnitionThreshold => 4;
|
||||||
public override int FireHeatIncrease => 2;
|
public override int FireHeatIncrease => 2;
|
||||||
public override int FireSmokeIncrease => 2;
|
public override int FireSmokeIncrease => 2;
|
||||||
public override int FireLiquidFuelConsumption => 1;
|
public override int FireLiquidFuelConsumption => 1;
|
||||||
public override int FireFuelVaporConsumption => 1;
|
public override int FireFuelVaporConsumption => 1;
|
||||||
public override int SmokeDecay => 1;
|
public override int SmokeDecay => 1;
|
||||||
public override int PressurizedFuelLeakPressureThreshold => 7;
|
public override int PressurizedFuelLeakPressureThreshold => 7;
|
||||||
public override int PassiveFuelVaporHeatOffset => 3;
|
public override int PassiveFuelVaporHeatOffset => 3;
|
||||||
public override int PassiveFuelVaporDivisor => 3;
|
public override int PassiveFuelVaporDivisor => 3;
|
||||||
public override int MinimumCoolantHeatReduction => 1;
|
public override int MinimumCoolantHeatReduction => 1;
|
||||||
public override int CoolantHeatReductionDivisor => 2;
|
public override int CoolantHeatReductionDivisor => 2;
|
||||||
public override int CoolantSteamHeatThreshold => 7;
|
public override int CoolantSteamHeatThreshold => 7;
|
||||||
public override int CoolantSteamSmokeIncrease => 2;
|
public override int CoolantSteamSmokeIncrease => 2;
|
||||||
public override int PressureLeakSmokeThreshold => 8;
|
public override int PressureLeakSmokeThreshold => 8;
|
||||||
public override int PressureLeakSmokeIncrease => 1;
|
public override int PressureLeakSmokeIncrease => 1;
|
||||||
public override int GeneratorHeatIncrease => 1;
|
public override int GeneratorHeatIncrease => 1;
|
||||||
public override int CoolingPumpHeatReduction => 2;
|
public override int CoolingPumpHeatReduction => 2;
|
||||||
public override int ReactorHeatIncrease => 1;
|
public override int ReactorHeatIncrease => 1;
|
||||||
public override int SmokeSpreadThreshold => 6;
|
public override int SmokeSpreadThreshold => 6;
|
||||||
public override int SmokeSpreadIncrease => 1;
|
public override int SmokeSpreadIncrease => 1;
|
||||||
public override int CriticalCellStabilityThreshold => 3;
|
public override int CriticalCellStabilityThreshold => 3;
|
||||||
public override int MeltdownCoreHeatThreshold => 10;
|
public override int MeltdownCoreHeatThreshold => 10;
|
||||||
public override int StabilityCollapseThreshold => 0;
|
public override int StabilityCollapseThreshold => 0;
|
||||||
public override int GeneratorPowerOutput => 3;
|
public override int GeneratorPowerOutput => 3;
|
||||||
public override int CoolingPumpOutput => 3;
|
public override int CoolingPumpOutput => 3;
|
||||||
public override int ReactorReadyPowerThreshold => 3;
|
public override int ReactorReadyPowerThreshold => 3;
|
||||||
public override int ReactorReadyCoolingThreshold => 3;
|
public override int ReactorReadyCoolingThreshold => 3;
|
||||||
public override int ReactorReadyCoreHeatThreshold => 8;
|
public override int ReactorReadyCoreHeatThreshold => 8;
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,35 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public sealed class CellIntegrityEffect : ISimulationEffect
|
public sealed class CellIntegrityEffect : ISimulationEffect
|
||||||
{
|
{
|
||||||
public CellState Apply(CellState cell)
|
public CellState Apply(CellState cell)
|
||||||
{
|
{
|
||||||
var integrity = cell.Integrity;
|
var integrity = cell.Integrity;
|
||||||
var hazards = cell.Hazards;
|
var hazards = cell.Hazards;
|
||||||
|
|
||||||
if (cell is { HasPipe: true } && cell.Pressure > Balancing.Current.OverpressureThreshold)
|
if (cell is { HasPipe: true } && cell.Pressure > Balancing.Current.OverpressureThreshold)
|
||||||
integrity -= cell.Pressure - Balancing.Current.OverpressureThreshold;
|
integrity -= cell.Pressure - Balancing.Current.OverpressureThreshold;
|
||||||
|
|
||||||
if (hazards.Heat >= Balancing.Current.HeatIntegrityDamageThreshold || hazards.Fire)
|
if (hazards.Heat >= Balancing.Current.HeatIntegrityDamageThreshold || hazards.Fire)
|
||||||
{
|
{
|
||||||
integrity -= cell.HasPipe ? Balancing.Current.PipeFireIntegrityDamage : Balancing.Current.MinHazardValue;
|
integrity -= cell.HasPipe ? Balancing.Current.PipeFireIntegrityDamage : Balancing.Current.MinHazardValue;
|
||||||
hazards = hazards with { Stability = hazards.Stability - Balancing.Current.FireStabilityDamage };
|
hazards = hazards with { Stability = hazards.Stability - Balancing.Current.FireStabilityDamage };
|
||||||
}
|
}
|
||||||
|
|
||||||
cell = cell with {
|
cell = cell with {
|
||||||
Integrity = Rules.Clamp(integrity),
|
Integrity = Rules.Clamp(integrity),
|
||||||
Hazards = hazards.Clamp()
|
Hazards = hazards.Clamp()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (integrity > Balancing.Current.MinHazardValue || !cell.HasPipe)
|
if (integrity > Balancing.Current.MinHazardValue || !cell.HasPipe)
|
||||||
return cell;
|
return cell;
|
||||||
|
|
||||||
return cell with {
|
return cell with {
|
||||||
LeakRate = Math.Max(cell.LeakRate, Balancing.Current.BurstLeakRate),
|
LeakRate = Math.Max(cell.LeakRate, Balancing.Current.BurstLeakRate),
|
||||||
Flow = Balancing.Current.BrokenPipeFlow,
|
Flow = Balancing.Current.BrokenPipeFlow,
|
||||||
PipeOpen = false
|
PipeOpen = false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public sealed class FireAndElectricalHazardEffect : ISimulationEffect
|
public sealed class FireAndElectricalHazardEffect : ISimulationEffect
|
||||||
{
|
{
|
||||||
public CellState Apply(CellState cell)
|
public CellState Apply(CellState cell)
|
||||||
{
|
{
|
||||||
var hazards = cell.Hazards;
|
var hazards = cell.Hazards;
|
||||||
if (hazards.CoolantPooling >= Balancing.Current.ElectrifiedCoolantPoolingThreshold && cell.Powered)
|
if (hazards.CoolantPooling >= Balancing.Current.ElectrifiedCoolantPoolingThreshold && cell.Powered)
|
||||||
hazards = hazards with { ElectricalCharge = hazards.ElectricalCharge + Balancing.Current.ElectricalChargeIncrease };
|
hazards = hazards with { ElectricalCharge = hazards.ElectricalCharge + Balancing.Current.ElectricalChargeIncrease };
|
||||||
|
|
||||||
var hasFuel = hazards.FuelVapor >= Balancing.Current.FuelVaporFireThreshold || hazards.LiquidFuel >= Balancing.Current.LiquidFuelFireThreshold;
|
var hasFuel = hazards.FuelVapor >= Balancing.Current.FuelVaporFireThreshold || hazards.LiquidFuel >= Balancing.Current.LiquidFuelFireThreshold;
|
||||||
var hasIgnition = hazards.Heat >= Balancing.Current.HeatIgnitionThreshold || hazards.ElectricalCharge >= Balancing.Current.ElectricalIgnitionThreshold || cell is { Prop: ECellProp.Generator, Powered: true };
|
var hasIgnition = hazards.Heat >= Balancing.Current.HeatIgnitionThreshold || hazards.ElectricalCharge >= Balancing.Current.ElectricalIgnitionThreshold || cell is { Prop: ECellProp.Generator, Powered: true };
|
||||||
if ((hasFuel && hasIgnition) || hazards.Fire)
|
if ((hasFuel && hasIgnition) || hazards.Fire)
|
||||||
{
|
{
|
||||||
hazards = hazards with {
|
hazards = hazards with {
|
||||||
Fire = hasFuel || hazards.Fire,
|
Fire = hasFuel || hazards.Fire,
|
||||||
Heat = hazards.Heat + Balancing.Current.FireHeatIncrease,
|
Heat = hazards.Heat + Balancing.Current.FireHeatIncrease,
|
||||||
Smoke = hazards.Smoke + Balancing.Current.FireSmokeIncrease,
|
Smoke = hazards.Smoke + Balancing.Current.FireSmokeIncrease,
|
||||||
LiquidFuel = Math.Max(Balancing.Current.MinHazardValue, hazards.LiquidFuel - Balancing.Current.FireLiquidFuelConsumption),
|
LiquidFuel = Math.Max(Balancing.Current.MinHazardValue, hazards.LiquidFuel - Balancing.Current.FireLiquidFuelConsumption),
|
||||||
FuelVapor = Math.Max(Balancing.Current.MinHazardValue, hazards.FuelVapor - Balancing.Current.FireFuelVaporConsumption)
|
FuelVapor = Math.Max(Balancing.Current.MinHazardValue, hazards.FuelVapor - Balancing.Current.FireFuelVaporConsumption)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (hazards.Smoke > Balancing.Current.MinHazardValue)
|
else if (hazards.Smoke > Balancing.Current.MinHazardValue)
|
||||||
hazards = hazards with { Smoke = hazards.Smoke - Balancing.Current.SmokeDecay };
|
hazards = hazards with { Smoke = hazards.Smoke - Balancing.Current.SmokeDecay };
|
||||||
|
|
||||||
return cell with { Hazards = hazards.Clamp() };
|
return cell with { Hazards = hazards.Clamp() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public interface IAreaSimulationEffect
|
public interface IAreaSimulationEffect
|
||||||
{
|
{
|
||||||
CellState[] Apply(LevelState level, CellState[] cells);
|
CellState[] Apply(LevelState level, CellState[] cells);
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public interface ISimulationEffect
|
public interface ISimulationEffect
|
||||||
{
|
{
|
||||||
CellState Apply(CellState cell);
|
CellState Apply(CellState cell);
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public sealed class MachineEffect : ISimulationEffect
|
public sealed class MachineEffect : ISimulationEffect
|
||||||
{
|
{
|
||||||
public CellState Apply(CellState cell)
|
public CellState Apply(CellState cell)
|
||||||
{
|
{
|
||||||
var hazards = cell.Prop switch {
|
var hazards = cell.Prop switch {
|
||||||
ECellProp.Generator when cell.Powered => cell.Hazards with { Heat = cell.Hazards.Heat + Balancing.Current.GeneratorHeatIncrease },
|
ECellProp.Generator when cell.Powered => cell.Hazards with { Heat = cell.Hazards.Heat + Balancing.Current.GeneratorHeatIncrease },
|
||||||
ECellProp.CoolingPump when cell.Powered => cell.Hazards with { Heat = cell.Hazards.Heat - Balancing.Current.CoolingPumpHeatReduction },
|
ECellProp.CoolingPump when cell.Powered => cell.Hazards with { Heat = cell.Hazards.Heat - Balancing.Current.CoolingPumpHeatReduction },
|
||||||
ECellProp.Reactor => cell.Hazards with { Heat = cell.Hazards.Heat + Balancing.Current.ReactorHeatIncrease },
|
ECellProp.Reactor => cell.Hazards with { Heat = cell.Hazards.Heat + Balancing.Current.ReactorHeatIncrease },
|
||||||
_ => cell.Hazards
|
_ => cell.Hazards
|
||||||
};
|
};
|
||||||
|
|
||||||
return cell with { Hazards = hazards.Clamp() };
|
return cell with { Hazards = hazards.Clamp() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public sealed class PipeLeakEffect : ISimulationEffect
|
public sealed class PipeLeakEffect : ISimulationEffect
|
||||||
{
|
{
|
||||||
public CellState Apply(CellState cell)
|
public CellState Apply(CellState cell)
|
||||||
{
|
{
|
||||||
if (!cell.HasPipe || cell.LeakRate <= Balancing.Current.MinHazardValue)
|
if (!cell.HasPipe || cell.LeakRate <= Balancing.Current.MinHazardValue)
|
||||||
return cell;
|
return cell;
|
||||||
|
|
||||||
var hazards = cell.Pipe switch {
|
var hazards = cell.Pipe switch {
|
||||||
EPipeMedium.Fuel => cell.Hazards with {
|
EPipeMedium.Fuel => cell.Hazards with {
|
||||||
LiquidFuel = cell.Hazards.LiquidFuel + cell.LeakRate,
|
LiquidFuel = cell.Hazards.LiquidFuel + cell.LeakRate,
|
||||||
FuelVapor = cell.Hazards.FuelVapor + (cell.Pressure >= Balancing.Current.PressurizedFuelLeakPressureThreshold ? cell.LeakRate : Math.Max(Balancing.Current.MinHazardValue, cell.Hazards.Heat - Balancing.Current.PassiveFuelVaporHeatOffset) / Balancing.Current.PassiveFuelVaporDivisor)
|
FuelVapor = cell.Hazards.FuelVapor + (cell.Pressure >= Balancing.Current.PressurizedFuelLeakPressureThreshold ? cell.LeakRate : Math.Max(Balancing.Current.MinHazardValue, cell.Hazards.Heat - Balancing.Current.PassiveFuelVaporHeatOffset) / Balancing.Current.PassiveFuelVaporDivisor)
|
||||||
},
|
},
|
||||||
EPipeMedium.Coolant => cell.Hazards with {
|
EPipeMedium.Coolant => cell.Hazards with {
|
||||||
CoolantPooling = cell.Hazards.CoolantPooling + cell.LeakRate,
|
CoolantPooling = cell.Hazards.CoolantPooling + cell.LeakRate,
|
||||||
Heat = cell.Hazards.Heat - Math.Max(Balancing.Current.MinimumCoolantHeatReduction, cell.LeakRate / Balancing.Current.CoolantHeatReductionDivisor),
|
Heat = cell.Hazards.Heat - Math.Max(Balancing.Current.MinimumCoolantHeatReduction, cell.LeakRate / Balancing.Current.CoolantHeatReductionDivisor),
|
||||||
Smoke = cell.Hazards.Smoke + (cell.Hazards.Heat >= Balancing.Current.CoolantSteamHeatThreshold ? Balancing.Current.CoolantSteamSmokeIncrease : Balancing.Current.MinHazardValue)
|
Smoke = cell.Hazards.Smoke + (cell.Hazards.Heat >= Balancing.Current.CoolantSteamHeatThreshold ? Balancing.Current.CoolantSteamSmokeIncrease : Balancing.Current.MinHazardValue)
|
||||||
},
|
},
|
||||||
EPipeMedium.Pressure => cell.Hazards with { Smoke = cell.Hazards.Smoke + (cell.Pressure >= Balancing.Current.PressureLeakSmokeThreshold ? Balancing.Current.PressureLeakSmokeIncrease : Balancing.Current.MinHazardValue) },
|
EPipeMedium.Pressure => cell.Hazards with { Smoke = cell.Hazards.Smoke + (cell.Pressure >= Balancing.Current.PressureLeakSmokeThreshold ? Balancing.Current.PressureLeakSmokeIncrease : Balancing.Current.MinHazardValue) },
|
||||||
_ => cell.Hazards
|
_ => cell.Hazards
|
||||||
};
|
};
|
||||||
|
|
||||||
return cell with { Hazards = hazards.Clamp() };
|
return cell with { Hazards = hazards.Clamp() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,37 +1,37 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Effects;
|
namespace ReactorMaintenance.Simulation.Effects;
|
||||||
|
|
||||||
public sealed class SmokeSpreadEffect : IAreaSimulationEffect
|
public sealed class SmokeSpreadEffect : IAreaSimulationEffect
|
||||||
{
|
{
|
||||||
public CellState[] Apply(LevelState level, CellState[] cells)
|
public CellState[] Apply(LevelState level, CellState[] cells)
|
||||||
{
|
{
|
||||||
var next = cells.ToArray();
|
var next = cells.ToArray();
|
||||||
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
{
|
{
|
||||||
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
{
|
{
|
||||||
var position = new GridPosition(x, y);
|
var position = new GridPosition(x, y);
|
||||||
var cell = cells[level.Index(position)];
|
var cell = cells[level.Index(position)];
|
||||||
if (cell.Hazards.Smoke < Balancing.Current.SmokeSpreadThreshold)
|
if (cell.Hazards.Smoke < Balancing.Current.SmokeSpreadThreshold)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpreadToNeighbors(level, next, position);
|
SpreadToNeighbors(level, next, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SpreadToNeighbors(LevelState level, CellState[] next, GridPosition position)
|
private static void SpreadToNeighbors(LevelState level, CellState[] next, GridPosition position)
|
||||||
{
|
{
|
||||||
foreach (var neighbor in position.Neighbors().Where(level.InBounds))
|
foreach (var neighbor in position.Neighbors().Where(level.InBounds))
|
||||||
{
|
{
|
||||||
var neighborCell = next[level.Index(neighbor)];
|
var neighborCell = next[level.Index(neighbor)];
|
||||||
if (!neighborCell.IsWalkable || neighborCell.DoorLocked)
|
if (!neighborCell.IsWalkable || neighborCell.DoorLocked)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
next[level.Index(neighbor)] = neighborCell with { Hazards = neighborCell.Hazards with { Smoke = Rules.Clamp(neighborCell.Hazards.Smoke + Balancing.Current.SmokeSpreadIncrease) } };
|
next[level.Index(neighbor)] = neighborCell with { Hazards = neighborCell.Hazards with { Smoke = Rules.Clamp(neighborCell.Hazards.Smoke + Balancing.Current.SmokeSpreadIncrease) } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Hazards;
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
public abstract class Hazard
|
public abstract class Hazard
|
||||||
{
|
{
|
||||||
public abstract IEnumerable<Forecast> Predict(LevelState level, int turns);
|
public abstract IEnumerable<Forecast> Predict(LevelState level, int turns);
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Hazards;
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
public sealed class IgnitionHazard : Hazard
|
public sealed class IgnitionHazard : Hazard
|
||||||
{
|
{
|
||||||
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
{
|
{
|
||||||
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
{
|
{
|
||||||
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
{
|
{
|
||||||
var position = new GridPosition(x, y);
|
var position = new GridPosition(x, y);
|
||||||
var cell = level.GetCell(position);
|
var cell = level.GetCell(position);
|
||||||
if (cell.Hazards.Fire)
|
if (cell.Hazards.Fire)
|
||||||
yield return new(EFailureKind.Ignition, position, turns, turns == Balancing.Current.TurnIncrement ? $"FUEL IGNITION PREDICTED AT {x},{y} NEXT TURN" : $"FUEL IGNITION PREDICTED AT {x},{y} IN {turns} TURNS");
|
yield return new(EFailureKind.Ignition, position, turns, turns == Balancing.Current.TurnIncrement ? $"FUEL IGNITION PREDICTED AT {x},{y} NEXT TURN" : $"FUEL IGNITION PREDICTED AT {x},{y} IN {turns} TURNS");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Hazards;
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
public sealed class MeltdownHazard : Hazard
|
public sealed class MeltdownHazard : Hazard
|
||||||
{
|
{
|
||||||
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
{
|
{
|
||||||
if (level.Global is { Lost: true, Status: "CORE MELTDOWN" })
|
if (level.Global is { Lost: true, Status: "CORE MELTDOWN" })
|
||||||
yield return new(EFailureKind.Meltdown, null, turns, "CORE MELTDOWN APPROACHING");
|
yield return new(EFailureKind.Meltdown, null, turns, "CORE MELTDOWN APPROACHING");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Hazards;
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
public sealed class PipeBurstHazard : Hazard
|
public sealed class PipeBurstHazard : Hazard
|
||||||
{
|
{
|
||||||
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
{
|
{
|
||||||
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
{
|
{
|
||||||
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
{
|
{
|
||||||
var position = new GridPosition(x, y);
|
var position = new GridPosition(x, y);
|
||||||
var cell = level.GetCell(position);
|
var cell = level.GetCell(position);
|
||||||
if (cell is { HasPipe: true, PipeOpen: false } && cell.Flow == Balancing.Current.BrokenPipeFlow && cell.LeakRate >= Balancing.Current.BurstLeakRate)
|
if (cell is { HasPipe: true, PipeOpen: false } && cell.Flow == Balancing.Current.BrokenPipeFlow && cell.LeakRate >= Balancing.Current.BurstLeakRate)
|
||||||
yield return new(EFailureKind.PipeBurst, position, turns, $"PIPE BURST PREDICTED AT {x},{y} IN {turns} TURNS");
|
yield return new(EFailureKind.PipeBurst, position, turns, $"PIPE BURST PREDICTED AT {x},{y} IN {turns} TURNS");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using ReactorMaintenance.Simulation;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Hazards;
|
namespace ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
public sealed class StabilityCollapseHazard : Hazard
|
public sealed class StabilityCollapseHazard : Hazard
|
||||||
{
|
{
|
||||||
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
{
|
{
|
||||||
if (level.Global is { Lost: true, Status: "FACILITY STABILITY COLLAPSE" })
|
if (level.Global is { Lost: true, Status: "FACILITY STABILITY COLLAPSE" })
|
||||||
yield return new(EFailureKind.StabilityCollapse, null, turns, "FACILITY STABILITY COLLAPSE APPROACHING");
|
yield return new(EFailureKind.StabilityCollapse, null, turns, "FACILITY STABILITY COLLAPSE APPROACHING");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,119 +1,119 @@
|
|||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public enum EEditorTool
|
public enum EEditorTool
|
||||||
{
|
{
|
||||||
Cursor,
|
Cursor,
|
||||||
Floor,
|
Floor,
|
||||||
Wall,
|
Wall,
|
||||||
Reactor,
|
Reactor,
|
||||||
CoolingPump,
|
CoolingPump,
|
||||||
Generator,
|
Generator,
|
||||||
PressureRegulator,
|
PressureRegulator,
|
||||||
DiagnosticTerminal,
|
DiagnosticTerminal,
|
||||||
ControlTerminal,
|
ControlTerminal,
|
||||||
CoolantPipe,
|
CoolantPipe,
|
||||||
FuelPipe,
|
FuelPipe,
|
||||||
PressurePipe,
|
PressurePipe,
|
||||||
Leak,
|
Leak,
|
||||||
Repair,
|
Repair,
|
||||||
Heat,
|
Heat,
|
||||||
Fire,
|
Fire,
|
||||||
Robot
|
Robot
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LevelEditor
|
public static class LevelEditor
|
||||||
{
|
{
|
||||||
public static LevelState Apply(LevelState level, GridPosition position, EEditorTool tool)
|
public static LevelState Apply(LevelState level, GridPosition position, EEditorTool tool)
|
||||||
{
|
{
|
||||||
if (!level.InBounds(position))
|
if (!level.InBounds(position))
|
||||||
return level;
|
return level;
|
||||||
|
|
||||||
if (tool == EEditorTool.Robot)
|
if (tool == EEditorTool.Robot)
|
||||||
return level.GetCell(position).IsWalkable ? level with { Robot = position } : level;
|
return level.GetCell(position).IsWalkable ? level with { Robot = position } : level;
|
||||||
|
|
||||||
var cell = level.GetCell(position);
|
var cell = level.GetCell(position);
|
||||||
cell = tool switch {
|
cell = tool switch {
|
||||||
EEditorTool.Cursor => cell,
|
EEditorTool.Cursor => cell,
|
||||||
EEditorTool.Floor => cell with { Terrain = ECellTerrain.Floor },
|
EEditorTool.Floor => cell with { Terrain = ECellTerrain.Floor },
|
||||||
EEditorTool.Wall => cell with {
|
EEditorTool.Wall => cell with {
|
||||||
Terrain = ECellTerrain.Wall,
|
Terrain = ECellTerrain.Wall,
|
||||||
Prop = ECellProp.None,
|
Prop = ECellProp.None,
|
||||||
Pipe = EPipeMedium.None,
|
Pipe = EPipeMedium.None,
|
||||||
Flow = Balancing.Current.MinHazardValue,
|
Flow = Balancing.Current.MinHazardValue,
|
||||||
Pressure = Balancing.Current.MinHazardValue,
|
Pressure = Balancing.Current.MinHazardValue,
|
||||||
LeakRate = Balancing.Current.MinHazardValue,
|
LeakRate = Balancing.Current.MinHazardValue,
|
||||||
PipeOpen = false,
|
PipeOpen = false,
|
||||||
Powered = false
|
Powered = false
|
||||||
},
|
},
|
||||||
EEditorTool.Reactor => cell with { Terrain = ECellTerrain.Floor, Prop = ECellProp.Reactor },
|
EEditorTool.Reactor => cell with { Terrain = ECellTerrain.Floor, Prop = ECellProp.Reactor },
|
||||||
EEditorTool.CoolingPump => cell with {
|
EEditorTool.CoolingPump => cell with {
|
||||||
Terrain = ECellTerrain.Floor,
|
Terrain = ECellTerrain.Floor,
|
||||||
Prop = ECellProp.CoolingPump,
|
Prop = ECellProp.CoolingPump,
|
||||||
Powered = true
|
Powered = true
|
||||||
},
|
},
|
||||||
EEditorTool.Generator => cell with {
|
EEditorTool.Generator => cell with {
|
||||||
Terrain = ECellTerrain.Floor,
|
Terrain = ECellTerrain.Floor,
|
||||||
Prop = ECellProp.Generator,
|
Prop = ECellProp.Generator,
|
||||||
Powered = true
|
Powered = true
|
||||||
},
|
},
|
||||||
EEditorTool.PressureRegulator => cell with { Terrain = ECellTerrain.Floor, Prop = ECellProp.PressureRegulator },
|
EEditorTool.PressureRegulator => cell with { Terrain = ECellTerrain.Floor, Prop = ECellProp.PressureRegulator },
|
||||||
EEditorTool.DiagnosticTerminal => cell with {
|
EEditorTool.DiagnosticTerminal => cell with {
|
||||||
Terrain = ECellTerrain.Floor,
|
Terrain = ECellTerrain.Floor,
|
||||||
Prop = ECellProp.DiagnosticTerminal,
|
Prop = ECellProp.DiagnosticTerminal,
|
||||||
Powered = true
|
Powered = true
|
||||||
},
|
},
|
||||||
EEditorTool.ControlTerminal => cell with {
|
EEditorTool.ControlTerminal => cell with {
|
||||||
Terrain = ECellTerrain.Floor,
|
Terrain = ECellTerrain.Floor,
|
||||||
Prop = ECellProp.ControlTerminal,
|
Prop = ECellProp.ControlTerminal,
|
||||||
Powered = true
|
Powered = true
|
||||||
},
|
},
|
||||||
EEditorTool.CoolantPipe => cell with {
|
EEditorTool.CoolantPipe => cell with {
|
||||||
Pipe = EPipeMedium.Coolant,
|
Pipe = EPipeMedium.Coolant,
|
||||||
Flow = Balancing.Current.DefaultPipeFlow,
|
Flow = Balancing.Current.DefaultPipeFlow,
|
||||||
Pressure = Balancing.Current.DefaultPipePressure,
|
Pressure = Balancing.Current.DefaultPipePressure,
|
||||||
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
||||||
PipeOpen = true
|
PipeOpen = true
|
||||||
},
|
},
|
||||||
EEditorTool.FuelPipe => cell with {
|
EEditorTool.FuelPipe => cell with {
|
||||||
Pipe = EPipeMedium.Fuel,
|
Pipe = EPipeMedium.Fuel,
|
||||||
Flow = Balancing.Current.DefaultPipeFlow,
|
Flow = Balancing.Current.DefaultPipeFlow,
|
||||||
Pressure = Balancing.Current.DefaultPipePressure,
|
Pressure = Balancing.Current.DefaultPipePressure,
|
||||||
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
||||||
PipeOpen = true
|
PipeOpen = true
|
||||||
},
|
},
|
||||||
EEditorTool.PressurePipe => cell with {
|
EEditorTool.PressurePipe => cell with {
|
||||||
Pipe = EPipeMedium.Pressure,
|
Pipe = EPipeMedium.Pressure,
|
||||||
Flow = Balancing.Current.DefaultPressurePipeFlow,
|
Flow = Balancing.Current.DefaultPressurePipeFlow,
|
||||||
Pressure = Balancing.Current.DefaultPressurePipePressure,
|
Pressure = Balancing.Current.DefaultPressurePipePressure,
|
||||||
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
Integrity = Math.Max(cell.Integrity, Balancing.Current.DefaultEditedPipeIntegrity),
|
||||||
PipeOpen = true
|
PipeOpen = true
|
||||||
},
|
},
|
||||||
EEditorTool.Leak => cell with {
|
EEditorTool.Leak => cell with {
|
||||||
LeakRate = Math.Max(Balancing.Current.MinimumLeakRate, cell.LeakRate),
|
LeakRate = Math.Max(Balancing.Current.MinimumLeakRate, cell.LeakRate),
|
||||||
Integrity = Math.Min(cell.Integrity, Balancing.Current.DamagedPipeIntegrity)
|
Integrity = Math.Min(cell.Integrity, Balancing.Current.DamagedPipeIntegrity)
|
||||||
},
|
},
|
||||||
EEditorTool.Repair => cell with {
|
EEditorTool.Repair => cell with {
|
||||||
LeakRate = Balancing.Current.RepairedLeakRate,
|
LeakRate = Balancing.Current.RepairedLeakRate,
|
||||||
Integrity = Balancing.Current.DefaultCellIntegrity,
|
Integrity = Balancing.Current.DefaultCellIntegrity,
|
||||||
Hazards = cell.Hazards with {
|
Hazards = cell.Hazards with {
|
||||||
Fire = false,
|
Fire = false,
|
||||||
ElectricalCharge = Balancing.Current.RepairedElectricalCharge
|
ElectricalCharge = Balancing.Current.RepairedElectricalCharge
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
EEditorTool.Heat => cell with { Hazards = cell.Hazards with { Heat = Rules.Clamp(cell.Hazards.Heat + Balancing.Current.HeatToolIncrease) } },
|
EEditorTool.Heat => cell with { Hazards = cell.Hazards with { Heat = Rules.Clamp(cell.Hazards.Heat + Balancing.Current.HeatToolIncrease) } },
|
||||||
EEditorTool.Fire => cell with {
|
EEditorTool.Fire => cell with {
|
||||||
Hazards = cell.Hazards with {
|
Hazards = cell.Hazards with {
|
||||||
Fire = !cell.Hazards.Fire,
|
Fire = !cell.Hazards.Fire,
|
||||||
Heat = Math.Max(cell.Hazards.Heat, Balancing.Current.FireToolMinimumHeat),
|
Heat = Math.Max(cell.Hazards.Heat, Balancing.Current.FireToolMinimumHeat),
|
||||||
Smoke = Math.Max(cell.Hazards.Smoke, Balancing.Current.FireToolMinimumSmoke)
|
Smoke = Math.Max(cell.Hazards.Smoke, Balancing.Current.FireToolMinimumSmoke)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => cell
|
_ => cell
|
||||||
};
|
};
|
||||||
|
|
||||||
if (cell.Terrain == ECellTerrain.Wall)
|
if (cell.Terrain == ECellTerrain.Wall)
|
||||||
cell = cell with { Hazards = new() };
|
cell = cell with { Hazards = new() };
|
||||||
|
|
||||||
return level.SetCell(position, cell);
|
return level.SetCell(position, cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,39 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public static class LevelSerializer
|
public static class LevelSerializer
|
||||||
{
|
{
|
||||||
private const int c_CurrentVersion = 1;
|
private const int c_CurrentVersion = 1;
|
||||||
|
|
||||||
public static string Serialize(LevelState level)
|
public static string Serialize(LevelState level)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(new LevelFile {
|
return JsonSerializer.Serialize(new LevelFile {
|
||||||
Version = c_CurrentVersion,
|
Version = c_CurrentVersion,
|
||||||
Level = level
|
Level = level
|
||||||
}, Options);
|
}, Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LevelState Deserialize(string json)
|
public static LevelState Deserialize(string json)
|
||||||
{
|
{
|
||||||
var file = JsonSerializer.Deserialize<LevelFile>(json, Options) ?? throw new InvalidOperationException("Level file did not contain a level.");
|
var file = JsonSerializer.Deserialize<LevelFile>(json, Options) ?? throw new InvalidOperationException("Level file did not contain a level.");
|
||||||
var level = file.Version switch {
|
var level = file.Version switch {
|
||||||
c_CurrentVersion => file.Level,
|
c_CurrentVersion => file.Level,
|
||||||
_ => throw new InvalidOperationException($"Unsupported level file version {file.Version}.")
|
_ => throw new InvalidOperationException($"Unsupported level file version {file.Version}.")
|
||||||
};
|
};
|
||||||
|
|
||||||
return level.Cells.Length != level.Width * level.Height ? throw new InvalidOperationException("Level cell count does not match its dimensions.") : level;
|
return level.Cells.Length != level.Width * level.Height ? throw new InvalidOperationException("Level cell count does not match its dimensions.") : level;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions Options = new() {
|
private static readonly JsonSerializerOptions Options = new() {
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
Converters = { new JsonStringEnumConverter() }
|
Converters = { new JsonStringEnumConverter() }
|
||||||
};
|
};
|
||||||
|
|
||||||
private sealed record LevelFile
|
private sealed record LevelFile
|
||||||
{
|
{
|
||||||
public int Version { get; init; }
|
public int Version { get; init; }
|
||||||
public LevelState Level { get; init; } = new();
|
public LevelState Level { get; init; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,181 +1,181 @@
|
|||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public enum ECellTerrain
|
public enum ECellTerrain
|
||||||
{
|
{
|
||||||
Floor,
|
Floor,
|
||||||
Wall
|
Wall
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ECellProp
|
public enum ECellProp
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Reactor,
|
Reactor,
|
||||||
CoolingPump,
|
CoolingPump,
|
||||||
Generator,
|
Generator,
|
||||||
PressureRegulator,
|
PressureRegulator,
|
||||||
DiagnosticTerminal,
|
DiagnosticTerminal,
|
||||||
ControlTerminal
|
ControlTerminal
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EPipeMedium
|
public enum EPipeMedium
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Pressure,
|
Pressure,
|
||||||
Coolant,
|
Coolant,
|
||||||
Fuel
|
Fuel
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EFailureKind
|
public enum EFailureKind
|
||||||
{
|
{
|
||||||
PipeBurst,
|
PipeBurst,
|
||||||
Ignition,
|
Ignition,
|
||||||
Meltdown,
|
Meltdown,
|
||||||
StabilityCollapse,
|
StabilityCollapse,
|
||||||
ReactorReady
|
ReactorReady
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record GridPosition(int X, int Y)
|
public sealed record GridPosition(int X, int Y)
|
||||||
{
|
{
|
||||||
public IEnumerable<GridPosition> Neighbors()
|
public IEnumerable<GridPosition> Neighbors()
|
||||||
{
|
{
|
||||||
yield return new(X - Balancing.Current.NeighborDistance, Y);
|
yield return new(X - Balancing.Current.NeighborDistance, Y);
|
||||||
yield return new(X + Balancing.Current.NeighborDistance, Y);
|
yield return new(X + Balancing.Current.NeighborDistance, Y);
|
||||||
yield return new(X, Y - Balancing.Current.NeighborDistance);
|
yield return new(X, Y - Balancing.Current.NeighborDistance);
|
||||||
yield return new(X, Y + Balancing.Current.NeighborDistance);
|
yield return new(X, Y + Balancing.Current.NeighborDistance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record HazardState
|
public sealed record HazardState
|
||||||
{
|
{
|
||||||
public HazardState Clamp()
|
public HazardState Clamp()
|
||||||
{
|
{
|
||||||
return this with {
|
return this with {
|
||||||
Heat = Rules.Clamp(Heat),
|
Heat = Rules.Clamp(Heat),
|
||||||
Smoke = Rules.Clamp(Smoke),
|
Smoke = Rules.Clamp(Smoke),
|
||||||
FuelVapor = Rules.Clamp(FuelVapor),
|
FuelVapor = Rules.Clamp(FuelVapor),
|
||||||
LiquidFuel = Rules.Clamp(LiquidFuel),
|
LiquidFuel = Rules.Clamp(LiquidFuel),
|
||||||
CoolantPooling = Rules.Clamp(CoolantPooling),
|
CoolantPooling = Rules.Clamp(CoolantPooling),
|
||||||
ElectricalCharge = Rules.Clamp(ElectricalCharge),
|
ElectricalCharge = Rules.Clamp(ElectricalCharge),
|
||||||
Stability = Rules.Clamp(Stability)
|
Stability = Rules.Clamp(Stability)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Heat { get; init; }
|
public int Heat { get; init; }
|
||||||
public int Smoke { get; init; }
|
public int Smoke { get; init; }
|
||||||
public int FuelVapor { get; init; }
|
public int FuelVapor { get; init; }
|
||||||
public int LiquidFuel { get; init; }
|
public int LiquidFuel { get; init; }
|
||||||
public int CoolantPooling { get; init; }
|
public int CoolantPooling { get; init; }
|
||||||
public int ElectricalCharge { get; init; }
|
public int ElectricalCharge { get; init; }
|
||||||
public int Stability { get; init; } = Balancing.Current.DefaultHazardStability;
|
public int Stability { get; init; } = Balancing.Current.DefaultHazardStability;
|
||||||
public bool Fire { get; init; }
|
public bool Fire { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record CellState
|
public sealed record CellState
|
||||||
{
|
{
|
||||||
public ECellTerrain Terrain { get; init; } = ECellTerrain.Floor;
|
public ECellTerrain Terrain { get; init; } = ECellTerrain.Floor;
|
||||||
public ECellProp Prop { get; init; }
|
public ECellProp Prop { get; init; }
|
||||||
public EPipeMedium Pipe { get; init; }
|
public EPipeMedium Pipe { get; init; }
|
||||||
public int Flow { get; init; }
|
public int Flow { get; init; }
|
||||||
public int Pressure { get; init; }
|
public int Pressure { get; init; }
|
||||||
public int Integrity { get; init; } = Balancing.Current.DefaultCellIntegrity;
|
public int Integrity { get; init; } = Balancing.Current.DefaultCellIntegrity;
|
||||||
public int LeakRate { get; init; }
|
public int LeakRate { get; init; }
|
||||||
public bool PipeOpen { get; init; } = true;
|
public bool PipeOpen { get; init; } = true;
|
||||||
public bool Powered { get; init; }
|
public bool Powered { get; init; }
|
||||||
public bool DoorLocked { get; init; }
|
public bool DoorLocked { get; init; }
|
||||||
public HazardState Hazards { get; init; } = new();
|
public HazardState Hazards { get; init; } = new();
|
||||||
public bool IsWalkable => Terrain != ECellTerrain.Wall;
|
public bool IsWalkable => Terrain != ECellTerrain.Wall;
|
||||||
public bool HasPipe => Pipe != EPipeMedium.None;
|
public bool HasPipe => Pipe != EPipeMedium.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record GlobalState
|
public sealed record GlobalState
|
||||||
{
|
{
|
||||||
public int Turn { get; init; }
|
public int Turn { get; init; }
|
||||||
public int ActionsPerTurn { get; init; } = Balancing.Current.DefaultActionsPerTurn;
|
public int ActionsPerTurn { get; init; } = Balancing.Current.DefaultActionsPerTurn;
|
||||||
public int CoreHeat { get; init; } = Balancing.Current.DefaultCoreHeat;
|
public int CoreHeat { get; init; } = Balancing.Current.DefaultCoreHeat;
|
||||||
public int FacilityStability { get; init; } = Balancing.Current.DefaultFacilityStability;
|
public int FacilityStability { get; init; } = Balancing.Current.DefaultFacilityStability;
|
||||||
public int Power { get; init; } = Balancing.Current.DefaultPower;
|
public int Power { get; init; } = Balancing.Current.DefaultPower;
|
||||||
public int Cooling { get; init; } = Balancing.Current.DefaultCooling;
|
public int Cooling { get; init; } = Balancing.Current.DefaultCooling;
|
||||||
public bool ReactorActivated { get; init; }
|
public bool ReactorActivated { get; init; }
|
||||||
public bool Lost { get; init; }
|
public bool Lost { get; init; }
|
||||||
public string Status { get; init; } = "STABILIZE SYSTEMS";
|
public string Status { get; init; } = "STABILIZE SYSTEMS";
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record Forecast(EFailureKind Kind, GridPosition? Position, int Turns, string Message);
|
public sealed record Forecast(EFailureKind Kind, GridPosition? Position, int Turns, string Message);
|
||||||
|
|
||||||
public sealed record LevelState
|
public sealed record LevelState
|
||||||
{
|
{
|
||||||
public static LevelState Create(string name, int width, int height)
|
public static LevelState Create(string name, int width, int height)
|
||||||
{
|
{
|
||||||
if (width < Balancing.Current.MinimumLevelSize || height < Balancing.Current.MinimumLevelSize)
|
if (width < Balancing.Current.MinimumLevelSize || height < Balancing.Current.MinimumLevelSize)
|
||||||
throw new ArgumentOutOfRangeException(nameof(width), $"Levels must be at least {Balancing.Current.MinimumLevelSize}x{Balancing.Current.MinimumLevelSize}.");
|
throw new ArgumentOutOfRangeException(nameof(width), $"Levels must be at least {Balancing.Current.MinimumLevelSize}x{Balancing.Current.MinimumLevelSize}.");
|
||||||
|
|
||||||
var cells = CreateCells(width, height);
|
var cells = CreateCells(width, height);
|
||||||
for (var y = Balancing.Current.FirstGridCoordinate; y < height; y++)
|
for (var y = Balancing.Current.FirstGridCoordinate; y < height; y++)
|
||||||
{
|
{
|
||||||
for (var x = Balancing.Current.FirstGridCoordinate; x < width; x++)
|
for (var x = Balancing.Current.FirstGridCoordinate; x < width; x++)
|
||||||
{
|
{
|
||||||
if (x == Balancing.Current.FirstGridCoordinate || y == Balancing.Current.FirstGridCoordinate || x == width - Balancing.Current.NeighborDistance || y == height - Balancing.Current.NeighborDistance)
|
if (x == Balancing.Current.FirstGridCoordinate || y == Balancing.Current.FirstGridCoordinate || x == width - Balancing.Current.NeighborDistance || y == height - Balancing.Current.NeighborDistance)
|
||||||
cells[y * width + x] = cells[y * width + x] with { Terrain = ECellTerrain.Wall };
|
cells[y * width + x] = cells[y * width + x] with { Terrain = ECellTerrain.Wall };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new() {
|
return new() {
|
||||||
Name = name,
|
Name = name,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
Cells = cells,
|
Cells = cells,
|
||||||
Robot = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate)
|
Robot = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public CellState GetCell(GridPosition position)
|
public CellState GetCell(GridPosition position)
|
||||||
{
|
{
|
||||||
EnsureInBounds(position);
|
EnsureInBounds(position);
|
||||||
return Cells[Index(position)];
|
return Cells[Index(position)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public LevelState SetCell(GridPosition position, CellState cell)
|
public LevelState SetCell(GridPosition position, CellState cell)
|
||||||
{
|
{
|
||||||
EnsureInBounds(position);
|
EnsureInBounds(position);
|
||||||
var cells = Cells.ToArray();
|
var cells = Cells.ToArray();
|
||||||
cells[Index(position)] = cell;
|
cells[Index(position)] = cell;
|
||||||
return this with { Cells = cells };
|
return this with { Cells = cells };
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InBounds(GridPosition position)
|
public bool InBounds(GridPosition position)
|
||||||
{
|
{
|
||||||
return position.X >= Balancing.Current.FirstGridCoordinate && position.Y >= Balancing.Current.FirstGridCoordinate && position.X < Width && position.Y < Height;
|
return position.X >= Balancing.Current.FirstGridCoordinate && position.Y >= Balancing.Current.FirstGridCoordinate && position.X < Width && position.Y < Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Index(GridPosition position)
|
public int Index(GridPosition position)
|
||||||
{
|
{
|
||||||
return position.Y * Width + position.X;
|
return position.Y * Width + position.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureInBounds(GridPosition position)
|
private void EnsureInBounds(GridPosition position)
|
||||||
{
|
{
|
||||||
if (!InBounds(position))
|
if (!InBounds(position))
|
||||||
throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {Width}x{Height}.");
|
throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {Width}x{Height}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CellState[] CreateCells(int width, int height)
|
private static CellState[] CreateCells(int width, int height)
|
||||||
{
|
{
|
||||||
return Enumerable.Range(Balancing.Current.FirstGridCoordinate, width * height).Select(_ => new CellState()).ToArray();
|
return Enumerable.Range(Balancing.Current.FirstGridCoordinate, width * height).Select(_ => new CellState()).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; init; } = "New Reactor";
|
public string Name { get; init; } = "New Reactor";
|
||||||
public int Width { get; init; } = Balancing.Current.DefaultLevelWidth;
|
public int Width { get; init; } = Balancing.Current.DefaultLevelWidth;
|
||||||
public int Height { get; init; } = Balancing.Current.DefaultLevelHeight;
|
public int Height { get; init; } = Balancing.Current.DefaultLevelHeight;
|
||||||
public CellState[] Cells { get; init; } = CreateCells(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight);
|
public CellState[] Cells { get; init; } = CreateCells(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight);
|
||||||
public GridPosition Robot { get; init; } = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate);
|
public GridPosition Robot { get; init; } = new(Balancing.Current.DefaultRobotCoordinate, Balancing.Current.DefaultRobotCoordinate);
|
||||||
public GlobalState Global { get; init; } = new();
|
public GlobalState Global { get; init; } = new();
|
||||||
public IReadOnlyList<Forecast> Forecasts { get; init; } = Array.Empty<Forecast>();
|
public IReadOnlyList<Forecast> Forecasts { get; init; } = Array.Empty<Forecast>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class Rules
|
internal static class Rules
|
||||||
{
|
{
|
||||||
public static int Clamp(int value)
|
public static int Clamp(int value)
|
||||||
{
|
{
|
||||||
return Math.Clamp(value, Balancing.Current.MinHazardValue, Balancing.Current.MaxHazardValue);
|
return Math.Clamp(value, Balancing.Current.MinHazardValue, Balancing.Current.MaxHazardValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,150 +1,150 @@
|
|||||||
using ReactorMaintenance.Simulation.Effects;
|
using ReactorMaintenance.Simulation.Effects;
|
||||||
using ReactorMaintenance.Simulation.Hazards;
|
using ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation;
|
namespace ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
public sealed class SimulationEngine(IEnumerable<ISimulationEffect> effects, IEnumerable<IAreaSimulationEffect> areaEffects, IEnumerable<Hazard> hazards)
|
public sealed class SimulationEngine(IEnumerable<ISimulationEffect> effects, IEnumerable<IAreaSimulationEffect> areaEffects, IEnumerable<Hazard> hazards)
|
||||||
{
|
{
|
||||||
private sealed record ForecastKey(EFailureKind Kind, GridPosition? Position);
|
private sealed record ForecastKey(EFailureKind Kind, GridPosition? Position);
|
||||||
|
|
||||||
public SimulationEngine()
|
public SimulationEngine()
|
||||||
: this(
|
: this(
|
||||||
[new PipeLeakEffect(), new MachineEffect(), new FireAndElectricalHazardEffect(), new CellIntegrityEffect()],
|
[new PipeLeakEffect(), new MachineEffect(), new FireAndElectricalHazardEffect(), new CellIntegrityEffect()],
|
||||||
[new SmokeSpreadEffect()],
|
[new SmokeSpreadEffect()],
|
||||||
[new PipeBurstHazard(), new IgnitionHazard(), new MeltdownHazard(), new StabilityCollapseHazard()])
|
[new PipeBurstHazard(), new IgnitionHazard(), new MeltdownHazard(), new StabilityCollapseHazard()])
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public LevelState AdvanceTurn(LevelState level)
|
public LevelState AdvanceTurn(LevelState level)
|
||||||
{
|
{
|
||||||
return AdvanceTurn(level, true);
|
return AdvanceTurn(level, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
||||||
{
|
{
|
||||||
var forecasts = new List<Forecast>();
|
var forecasts = new List<Forecast>();
|
||||||
var seen = new HashSet<ForecastKey>();
|
var seen = new HashSet<ForecastKey>();
|
||||||
var forecastLevel = level with { Cells = level.Cells.ToArray(), Forecasts = Array.Empty<Forecast>() };
|
var forecastLevel = level with { Cells = level.Cells.ToArray(), Forecasts = Array.Empty<Forecast>() };
|
||||||
if (forecastLevel.Global.Lost)
|
if (forecastLevel.Global.Lost)
|
||||||
AddHazardForecasts(forecasts, seen, forecastLevel, Balancing.Current.CurrentForecastTurn);
|
AddHazardForecasts(forecasts, seen, forecastLevel, Balancing.Current.CurrentForecastTurn);
|
||||||
|
|
||||||
AddReactorReadyForecast(forecasts, seen, forecastLevel, Balancing.Current.CurrentForecastTurn);
|
AddReactorReadyForecast(forecasts, seen, forecastLevel, Balancing.Current.CurrentForecastTurn);
|
||||||
|
|
||||||
if (IsReactorReady(forecastLevel) || forecastLevel.Global.Lost || forecastLevel.Global.ReactorActivated)
|
if (IsReactorReady(forecastLevel) || forecastLevel.Global.Lost || forecastLevel.Global.ReactorActivated)
|
||||||
return forecasts.OrderBy(f => f.Turns).ThenBy(f => f.Message).ToArray();
|
return forecasts.OrderBy(f => f.Turns).ThenBy(f => f.Message).ToArray();
|
||||||
|
|
||||||
for (var step = Balancing.Current.TurnIncrement; step <= Balancing.Current.MaxForecastStepCount; step++)
|
for (var step = Balancing.Current.TurnIncrement; step <= Balancing.Current.MaxForecastStepCount; step++)
|
||||||
{
|
{
|
||||||
forecastLevel = AdvanceTurn(forecastLevel, false);
|
forecastLevel = AdvanceTurn(forecastLevel, false);
|
||||||
AddHazardForecasts(forecasts, seen, forecastLevel, step);
|
AddHazardForecasts(forecasts, seen, forecastLevel, step);
|
||||||
AddReactorReadyForecast(forecasts, seen, forecastLevel, step);
|
AddReactorReadyForecast(forecasts, seen, forecastLevel, step);
|
||||||
|
|
||||||
if (forecastLevel.Global.Lost || IsReactorReady(forecastLevel) || forecastLevel.Global.ReactorActivated)
|
if (forecastLevel.Global.Lost || IsReactorReady(forecastLevel) || forecastLevel.Global.ReactorActivated)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return forecasts.OrderBy(f => f.Turns).ThenBy(f => f.Message).ToArray();
|
return forecasts.OrderBy(f => f.Turns).ThenBy(f => f.Message).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LevelState ActivateReactor(LevelState level)
|
public LevelState ActivateReactor(LevelState level)
|
||||||
{
|
{
|
||||||
if (!IsReactorReady(level))
|
if (!IsReactorReady(level))
|
||||||
return level with { Global = level.Global with { Status = "REACTOR NOT READY" } };
|
return level with { Global = level.Global with { Status = "REACTOR NOT READY" } };
|
||||||
|
|
||||||
return level with {
|
return level with {
|
||||||
Global = level.Global with {
|
Global = level.Global with {
|
||||||
ReactorActivated = true,
|
ReactorActivated = true,
|
||||||
Status = "REACTOR ONLINE"
|
Status = "REACTOR ONLINE"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private LevelState AdvanceTurn(LevelState level, bool updateForecasts)
|
private LevelState AdvanceTurn(LevelState level, bool updateForecasts)
|
||||||
{
|
{
|
||||||
var cells = level.Cells.ToArray();
|
var cells = level.Cells.ToArray();
|
||||||
|
|
||||||
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
for (var y = Balancing.Current.FirstGridCoordinate; y < level.Height; y++)
|
||||||
{
|
{
|
||||||
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
for (var x = Balancing.Current.FirstGridCoordinate; x < level.Width; x++)
|
||||||
{
|
{
|
||||||
var position = new GridPosition(x, y);
|
var position = new GridPosition(x, y);
|
||||||
var index = level.Index(position);
|
var index = level.Index(position);
|
||||||
var cell = cells[index];
|
var cell = cells[index];
|
||||||
|
|
||||||
if (!cell.IsWalkable)
|
if (!cell.IsWalkable)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var effect in m_Effects)
|
foreach (var effect in m_Effects)
|
||||||
cell = effect.Apply(cell);
|
cell = effect.Apply(cell);
|
||||||
|
|
||||||
cells[index] = cell with { Hazards = cell.Hazards.Clamp() };
|
cells[index] = cell with { Hazards = cell.Hazards.Clamp() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var areaEffect in m_AreaEffects)
|
foreach (var areaEffect in m_AreaEffects)
|
||||||
cells = areaEffect.Apply(level, cells);
|
cells = areaEffect.Apply(level, cells);
|
||||||
|
|
||||||
var global = UpdateGlobal(level, cells);
|
var global = UpdateGlobal(level, cells);
|
||||||
var next = level with {
|
var next = level with {
|
||||||
Cells = cells,
|
Cells = cells,
|
||||||
Global = global with { Turn = level.Global.Turn + Balancing.Current.TurnIncrement }
|
Global = global with { Turn = level.Global.Turn + Balancing.Current.TurnIncrement }
|
||||||
};
|
};
|
||||||
|
|
||||||
return updateForecasts ? next with { Forecasts = Forecast(next) } : next;
|
return updateForecasts ? next with { Forecasts = Forecast(next) } : next;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddHazardForecasts(List<Forecast> forecasts, HashSet<ForecastKey> seen, LevelState level, int turns)
|
private void AddHazardForecasts(List<Forecast> forecasts, HashSet<ForecastKey> seen, LevelState level, int turns)
|
||||||
{
|
{
|
||||||
foreach (var hazard in m_Hazards)
|
foreach (var hazard in m_Hazards)
|
||||||
{
|
{
|
||||||
foreach (var forecast in hazard.Predict(level, turns))
|
foreach (var forecast in hazard.Predict(level, turns))
|
||||||
AddForecast(forecasts, seen, forecast);
|
AddForecast(forecasts, seen, forecast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddReactorReadyForecast(List<Forecast> forecasts, HashSet<ForecastKey> seen, LevelState level, int turns)
|
private static void AddReactorReadyForecast(List<Forecast> forecasts, HashSet<ForecastKey> seen, LevelState level, int turns)
|
||||||
{
|
{
|
||||||
if (IsReactorReady(level))
|
if (IsReactorReady(level))
|
||||||
AddForecast(forecasts, seen, new(EFailureKind.ReactorReady, null, turns, "REACTOR READY"));
|
AddForecast(forecasts, seen, new(EFailureKind.ReactorReady, null, turns, "REACTOR READY"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddForecast(List<Forecast> forecasts, HashSet<ForecastKey> seen, Forecast forecast)
|
private static void AddForecast(List<Forecast> forecasts, HashSet<ForecastKey> seen, Forecast forecast)
|
||||||
{
|
{
|
||||||
if (seen.Add(new(forecast.Kind, forecast.Position)))
|
if (seen.Add(new(forecast.Kind, forecast.Position)))
|
||||||
forecasts.Add(forecast);
|
forecasts.Add(forecast);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GlobalState UpdateGlobal(LevelState level, CellState[] cells)
|
private static GlobalState UpdateGlobal(LevelState level, CellState[] cells)
|
||||||
{
|
{
|
||||||
var reactorHeat = cells.Where(c => c.Prop == ECellProp.Reactor).Select(c => c.Hazards.Heat).DefaultIfEmpty(level.Global.CoreHeat).Max();
|
var reactorHeat = cells.Where(c => c.Prop == ECellProp.Reactor).Select(c => c.Hazards.Heat).DefaultIfEmpty(level.Global.CoreHeat).Max();
|
||||||
var poweredGenerators = cells.Count(c => c is { Prop: ECellProp.Generator, Powered: true, Hazards.Fire: false });
|
var poweredGenerators = cells.Count(c => c is { Prop: ECellProp.Generator, Powered: true, Hazards.Fire: false });
|
||||||
var poweredPumps = cells.Count(c => c is { Prop: ECellProp.CoolingPump, Powered: true, Hazards.Fire: false });
|
var poweredPumps = cells.Count(c => c is { Prop: ECellProp.CoolingPump, Powered: true, Hazards.Fire: false });
|
||||||
var damagedCriticalCells = cells.Count(c => c.Prop is ECellProp.Reactor or ECellProp.Generator or ECellProp.CoolingPump && c.Hazards.Stability <= Balancing.Current.CriticalCellStabilityThreshold);
|
var damagedCriticalCells = cells.Count(c => c.Prop is ECellProp.Reactor or ECellProp.Generator or ECellProp.CoolingPump && c.Hazards.Stability <= Balancing.Current.CriticalCellStabilityThreshold);
|
||||||
var stability = Rules.Clamp(level.Global.FacilityStability - damagedCriticalCells);
|
var stability = Rules.Clamp(level.Global.FacilityStability - damagedCriticalCells);
|
||||||
var lost = reactorHeat >= Balancing.Current.MeltdownCoreHeatThreshold || stability <= Balancing.Current.StabilityCollapseThreshold;
|
var lost = reactorHeat >= Balancing.Current.MeltdownCoreHeatThreshold || stability <= Balancing.Current.StabilityCollapseThreshold;
|
||||||
var status = lost ? reactorHeat >= Balancing.Current.MeltdownCoreHeatThreshold ? "CORE MELTDOWN" : "FACILITY STABILITY COLLAPSE" : "STABILIZE SYSTEMS";
|
var status = lost ? reactorHeat >= Balancing.Current.MeltdownCoreHeatThreshold ? "CORE MELTDOWN" : "FACILITY STABILITY COLLAPSE" : "STABILIZE SYSTEMS";
|
||||||
var global = level.Global with {
|
var global = level.Global with {
|
||||||
CoreHeat = Rules.Clamp(reactorHeat - poweredPumps),
|
CoreHeat = Rules.Clamp(reactorHeat - poweredPumps),
|
||||||
Power = Rules.Clamp(poweredGenerators * Balancing.Current.GeneratorPowerOutput),
|
Power = Rules.Clamp(poweredGenerators * Balancing.Current.GeneratorPowerOutput),
|
||||||
Cooling = Rules.Clamp(poweredPumps * Balancing.Current.CoolingPumpOutput),
|
Cooling = Rules.Clamp(poweredPumps * Balancing.Current.CoolingPumpOutput),
|
||||||
FacilityStability = stability,
|
FacilityStability = stability,
|
||||||
Lost = lost,
|
Lost = lost,
|
||||||
Status = status
|
Status = status
|
||||||
};
|
};
|
||||||
|
|
||||||
return IsReactorReady(level with { Cells = cells, Global = global }) ? global with { Status = "REACTOR READY" } : global;
|
return IsReactorReady(level with { Cells = cells, Global = global }) ? global with { Status = "REACTOR READY" } : global;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsReactorReady(LevelState level)
|
private static bool IsReactorReady(LevelState level)
|
||||||
{
|
{
|
||||||
var hasReactor = level.Cells.Any(c => c.Prop == ECellProp.Reactor);
|
var hasReactor = level.Cells.Any(c => c.Prop == ECellProp.Reactor);
|
||||||
var hasStablePower = level.Global.Power >= Balancing.Current.ReactorReadyPowerThreshold || level.Cells.Any(c => c is { Prop: ECellProp.Generator, Powered: true, Hazards.Fire: false });
|
var hasStablePower = level.Global.Power >= Balancing.Current.ReactorReadyPowerThreshold || level.Cells.Any(c => c is { Prop: ECellProp.Generator, Powered: true, Hazards.Fire: false });
|
||||||
var hasCooling = level.Global.Cooling >= Balancing.Current.ReactorReadyCoolingThreshold || level.Cells.Any(c => c is { Prop: ECellProp.CoolingPump, Powered: true, Hazards.Fire: false });
|
var hasCooling = level.Global.Cooling >= Balancing.Current.ReactorReadyCoolingThreshold || level.Cells.Any(c => c is { Prop: ECellProp.CoolingPump, Powered: true, Hazards.Fire: false });
|
||||||
var reactorStable = level.Global.CoreHeat < Balancing.Current.ReactorReadyCoreHeatThreshold;
|
var reactorStable = level.Global.CoreHeat < Balancing.Current.ReactorReadyCoreHeatThreshold;
|
||||||
return hasReactor && hasStablePower && hasCooling && reactorStable && !level.Global.Lost;
|
return hasReactor && hasStablePower && hasCooling && reactorStable && !level.Global.Lost;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IReadOnlyList<IAreaSimulationEffect> m_AreaEffects = areaEffects.ToArray();
|
private readonly IReadOnlyList<IAreaSimulationEffect> m_AreaEffects = areaEffects.ToArray();
|
||||||
private readonly IReadOnlyList<ISimulationEffect> m_Effects = effects.ToArray();
|
private readonly IReadOnlyList<ISimulationEffect> m_Effects = effects.ToArray();
|
||||||
private readonly IReadOnlyList<Hazard> m_Hazards = hazards.ToArray();
|
private readonly IReadOnlyList<Hazard> m_Hazards = hazards.ToArray();
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<Application
|
<Application
|
||||||
x:Class="ReactorMaintenance.Win2D.App"
|
x:Class="ReactorMaintenance.Win2D.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
</Application>
|
</Application>
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Win2D;
|
namespace ReactorMaintenance.Win2D;
|
||||||
|
|
||||||
public partial class App
|
public partial class App
|
||||||
{
|
{
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
m_Window = new MainWindow();
|
m_Window = new MainWindow();
|
||||||
m_Window.Activate();
|
m_Window.Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Window? m_Window;
|
private Window? m_Window;
|
||||||
}
|
}
|
||||||
@@ -1,115 +1,115 @@
|
|||||||
<Window
|
<Window
|
||||||
x:Class="ReactorMaintenance.Win2D.MainWindow"
|
x:Class="ReactorMaintenance.Win2D.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||||
Title="Reactor Maintenance">
|
Title="Reactor Maintenance">
|
||||||
<Grid Background="#16191D">
|
<Grid Background="#16191D">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<CommandBar Grid.Row="0" DefaultLabelPosition="Right" Background="#20252A">
|
<CommandBar Grid.Row="0" DefaultLabelPosition="Right" Background="#20252A">
|
||||||
<AppBarButton Icon="Add" Label="New" Click="New_Click" />
|
<AppBarButton Icon="Add" Label="New" Click="New_Click" />
|
||||||
<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click" />
|
<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click" />
|
||||||
<AppBarButton Icon="Save" Label="Save" Click="Save_Click" />
|
<AppBarButton Icon="Save" Label="Save" Click="Save_Click" />
|
||||||
<AppBarSeparator />
|
<AppBarSeparator />
|
||||||
<AppBarButton Icon="Play" Label="Simulate" Click="Simulate_Click" />
|
<AppBarButton Icon="Play" Label="Simulate" Click="Simulate_Click" />
|
||||||
<AppBarButton Icon="Accept" Label="Activate" Click="Activate_Click" />
|
<AppBarButton Icon="Accept" Label="Activate" Click="Activate_Click" />
|
||||||
</CommandBar>
|
</CommandBar>
|
||||||
|
|
||||||
<Grid Grid.Row="1" ColumnSpacing="0">
|
<Grid Grid.Row="1" ColumnSpacing="0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="260" />
|
<ColumnDefinition Width="260" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="300" />
|
<ColumnDefinition Width="300" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<ScrollViewer Grid.Column="0" Background="#1C2126">
|
<ScrollViewer Grid.Column="0" Background="#1C2126">
|
||||||
<StackPanel Padding="12" Spacing="10">
|
<StackPanel Padding="12" Spacing="10">
|
||||||
<TextBlock Text="Tools" FontSize="18" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
<TextBlock Text="Tools" FontSize="18" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
<ItemsControl x:Name="ToolPicker">
|
<ItemsControl x:Name="ToolPicker">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<ItemsWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="2" />
|
<ItemsWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="2" />
|
||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ItemsControl.ItemsPanel>
|
</ItemsControl.ItemsPanel>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay}"
|
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay}"
|
||||||
Checked="ToolToggle_Checked" ToolTipService.ToolTip="{Binding Label}"
|
Checked="ToolToggle_Checked" ToolTipService.ToolTip="{Binding Label}"
|
||||||
Padding="5" Margin="0,0,8,8">
|
Padding="5" Margin="0,0,8,8">
|
||||||
<Image Width="96" Height="96" Source="{Binding Icon}" Stretch="Uniform" />
|
<Image Width="96" Height="96" Source="{Binding Icon}" Stretch="Uniform" />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
<TextBlock Text="Brush applies to the selected cell." Foreground="#9EA7AE" TextWrapping="Wrap" />
|
<TextBlock Text="Brush applies to the selected cell." Foreground="#9EA7AE" TextWrapping="Wrap" />
|
||||||
<TextBlock Text="Left click paints. Use Robot to set the start position." Foreground="#9EA7AE"
|
<TextBlock Text="Left click paints. Use Robot to set the start position." Foreground="#9EA7AE"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<Grid Grid.Column="1" Background="#101215">
|
<Grid Grid.Column="1" Background="#101215">
|
||||||
<canvas:CanvasControl
|
<canvas:CanvasControl
|
||||||
x:Name="LevelCanvas"
|
x:Name="LevelCanvas"
|
||||||
ClearColor="#101215"
|
ClearColor="#101215"
|
||||||
CreateResources="LevelCanvas_CreateResources"
|
CreateResources="LevelCanvas_CreateResources"
|
||||||
Draw="LevelCanvas_Draw"
|
Draw="LevelCanvas_Draw"
|
||||||
PointerPressed="LevelCanvas_PointerPressed"
|
PointerPressed="LevelCanvas_PointerPressed"
|
||||||
PointerMoved="LevelCanvas_PointerMoved"
|
PointerMoved="LevelCanvas_PointerMoved"
|
||||||
PointerReleased="LevelCanvas_PointerReleased"
|
PointerReleased="LevelCanvas_PointerReleased"
|
||||||
PointerExited="LevelCanvas_PointerExited"
|
PointerExited="LevelCanvas_PointerExited"
|
||||||
PointerWheelChanged="LevelCanvas_PointerWheelChanged" />
|
PointerWheelChanged="LevelCanvas_PointerWheelChanged" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<ScrollViewer Grid.Column="2" Background="#1C2126">
|
<ScrollViewer Grid.Column="2" Background="#1C2126">
|
||||||
<StackPanel Padding="14" Spacing="12">
|
<StackPanel Padding="14" Spacing="12">
|
||||||
<TextBlock x:Name="LevelNameText" FontSize="20" FontWeight="SemiBold" Foreground="#F4F1E8"
|
<TextBlock x:Name="LevelNameText" FontSize="20" FontWeight="SemiBold" Foreground="#F4F1E8"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<Grid ColumnSpacing="8">
|
<Grid ColumnSpacing="8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Text="Turn" Foreground="#9EA7AE" />
|
<TextBlock Text="Turn" Foreground="#9EA7AE" />
|
||||||
<TextBlock x:Name="TurnText" FontSize="22" Foreground="#F4F1E8" />
|
<TextBlock x:Name="TurnText" FontSize="22" Foreground="#F4F1E8" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1">
|
<StackPanel Grid.Column="1">
|
||||||
<TextBlock Text="Status" Foreground="#9EA7AE" />
|
<TextBlock Text="Status" Foreground="#9EA7AE" />
|
||||||
<TextBlock x:Name="StatusText" FontSize="16" Foreground="#F4F1E8" TextWrapping="Wrap" />
|
<TextBlock x:Name="StatusText" FontSize="16" Foreground="#F4F1E8" TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextBlock Text="Global Systems" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
<TextBlock Text="Global Systems" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
<TextBlock x:Name="GlobalText" Foreground="#CCD4DA" TextWrapping="Wrap" />
|
<TextBlock x:Name="GlobalText" Foreground="#CCD4DA" TextWrapping="Wrap" />
|
||||||
|
|
||||||
<TextBlock Text="Selected Cell" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
<TextBlock Text="Selected Cell" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
<TextBlock x:Name="CellText" Foreground="#CCD4DA" TextWrapping="Wrap" />
|
<TextBlock x:Name="CellText" Foreground="#CCD4DA" TextWrapping="Wrap" />
|
||||||
|
|
||||||
<TextBlock Text="Forecasts" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
<TextBlock Text="Forecasts" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
|
||||||
<ItemsControl x:Name="ForecastList">
|
<ItemsControl x:Name="ForecastList">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Border BorderBrush="#46515A" BorderThickness="1" Padding="8" Margin="0,0,0,8"
|
<Border BorderBrush="#46515A" BorderThickness="1" Padding="8" Margin="0,0,0,8"
|
||||||
CornerRadius="3">
|
CornerRadius="3">
|
||||||
<Grid ColumnSpacing="8">
|
<Grid ColumnSpacing="8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="32" />
|
<ColumnDefinition Width="32" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Image Width="28" Height="28" Source="{Binding Icon}" />
|
<Image Width="28" Height="28" Source="{Binding Icon}" />
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Message}" Foreground="#F4F1E8"
|
<TextBlock Grid.Column="1" Text="{Binding Message}" Foreground="#F4F1E8"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +1,33 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
<RootNamespace>ReactorMaintenance.Win2D</RootNamespace>
|
<RootNamespace>ReactorMaintenance.Win2D</RootNamespace>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<Platforms>x86;x64;arm64</Platforms>
|
<Platforms>x86;x64;arm64</Platforms>
|
||||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<WindowsPackageType>None</WindowsPackageType>
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.4.0" />
|
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.4.0" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1839" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1839" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260317003" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260317003" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ReactorMaintenance.Simulation\ReactorMaintenance.Simulation.csproj" />
|
<ProjectReference Include="..\ReactorMaintenance.Simulation\ReactorMaintenance.Simulation.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Update="Images\**\*.png">
|
<Content Update="Images\**\*.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
<assemblyIdentity version="1.0.0.0" name="ReactorMaintenance.Win2D.app"/>
|
<assemblyIdentity version="1.0.0.0" name="ReactorMaintenance.Win2D.app"/>
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<windowsSettings>
|
<windowsSettings>
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
</windowsSettings>
|
</windowsSettings>
|
||||||
</application>
|
</application>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
<PackageReference Include="xunit" Version="2.5.3" />
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\ReactorMaintenance.Simulation\ReactorMaintenance.Simulation.csproj" />
|
<ProjectReference Include="..\..\src\ReactorMaintenance.Simulation\ReactorMaintenance.Simulation.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,298 +1,298 @@
|
|||||||
using ReactorMaintenance.Simulation.Difficulties;
|
using ReactorMaintenance.Simulation.Difficulties;
|
||||||
using ReactorMaintenance.Simulation.Effects;
|
using ReactorMaintenance.Simulation.Effects;
|
||||||
using ReactorMaintenance.Simulation.Hazards;
|
using ReactorMaintenance.Simulation.Hazards;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Simulation.Tests;
|
namespace ReactorMaintenance.Simulation.Tests;
|
||||||
|
|
||||||
public sealed class SimulationEngineTests
|
public sealed class SimulationEngineTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FuelLeakNearPoweredGeneratorCreatesIgnitionForecast()
|
public void FuelLeakNearPoweredGeneratorCreatesIgnitionForecast()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Fuel leak", 6, 6)
|
var level = LevelState.Create("Fuel leak", 6, 6)
|
||||||
.SetCell(new(2, 2), new() {
|
.SetCell(new(2, 2), new() {
|
||||||
Prop = ECellProp.Generator,
|
Prop = ECellProp.Generator,
|
||||||
Pipe = EPipeMedium.Fuel,
|
Pipe = EPipeMedium.Fuel,
|
||||||
LeakRate = Balancing.Current.FuelVaporFireThreshold,
|
LeakRate = Balancing.Current.FuelVaporFireThreshold,
|
||||||
Pressure = Balancing.Current.PressurizedFuelLeakPressureThreshold + Balancing.Current.NeighborDistance,
|
Pressure = Balancing.Current.PressurizedFuelLeakPressureThreshold + Balancing.Current.NeighborDistance,
|
||||||
Integrity = Balancing.Current.DefaultEditedPipeIntegrity,
|
Integrity = Balancing.Current.DefaultEditedPipeIntegrity,
|
||||||
Powered = true
|
Powered = true
|
||||||
});
|
});
|
||||||
|
|
||||||
var forecasts = m_Engine.Forecast(level);
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Ignition && forecast.Position == new GridPosition(2, 2) && forecast.Turns == 1);
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Ignition && forecast.Position == new GridPosition(2, 2) && forecast.Turns == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CoolantLeakOnPoweredCellRaisesElectricalCharge()
|
public void CoolantLeakOnPoweredCellRaisesElectricalCharge()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Wet cable", 6, 6)
|
var level = LevelState.Create("Wet cable", 6, 6)
|
||||||
.SetCell(new(3, 3), new() {
|
.SetCell(new(3, 3), new() {
|
||||||
Pipe = EPipeMedium.Coolant,
|
Pipe = EPipeMedium.Coolant,
|
||||||
LeakRate = Balancing.Current.ElectrifiedCoolantPoolingThreshold,
|
LeakRate = Balancing.Current.ElectrifiedCoolantPoolingThreshold,
|
||||||
Powered = true
|
Powered = true
|
||||||
});
|
});
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
Assert.True(next.GetCell(new(3, 3)).Hazards.ElectricalCharge >= Balancing.Current.ElectricalChargeIncrease);
|
Assert.True(next.GetCell(new(3, 3)).Hazards.ElectricalCharge >= Balancing.Current.ElectricalChargeIncrease);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ActiveFireSpreadsSmokeToOpenNeighbors()
|
public void ActiveFireSpreadsSmokeToOpenNeighbors()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Smoke", 6, 6)
|
var level = LevelState.Create("Smoke", 6, 6)
|
||||||
.SetCell(new(2, 2), new() {
|
.SetCell(new(2, 2), new() {
|
||||||
Hazards = new() {
|
Hazards = new() {
|
||||||
Fire = true,
|
Fire = true,
|
||||||
Smoke = Balancing.Current.SmokeSpreadThreshold
|
Smoke = Balancing.Current.SmokeSpreadThreshold
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
|
|
||||||
Assert.True(next.GetCell(new(2, 3)).Hazards.Smoke > 0);
|
Assert.True(next.GetCell(new(2, 3)).Hazards.Smoke > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AdvanceTurnRunsConfiguredCellEffects()
|
public void AdvanceTurnRunsConfiguredCellEffects()
|
||||||
{
|
{
|
||||||
var engine = new SimulationEngine([new TestCellEffect()], [], []);
|
var engine = new SimulationEngine([new TestCellEffect()], [], []);
|
||||||
var level = LevelState.Create("Custom effect", 6, 6)
|
var level = LevelState.Create("Custom effect", 6, 6)
|
||||||
.SetCell(new(2, 2), new() {
|
.SetCell(new(2, 2), new() {
|
||||||
Hazards = new() { Heat = 1 }
|
Hazards = new() { Heat = 1 }
|
||||||
});
|
});
|
||||||
|
|
||||||
var next = engine.AdvanceTurn(level);
|
var next = engine.AdvanceTurn(level);
|
||||||
|
|
||||||
Assert.Equal(5, next.GetCell(new(2, 2)).Hazards.Heat);
|
Assert.Equal(5, next.GetCell(new(2, 2)).Hazards.Heat);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AdvanceTurnRunsConfiguredAreaEffects()
|
public void AdvanceTurnRunsConfiguredAreaEffects()
|
||||||
{
|
{
|
||||||
var engine = new SimulationEngine([], [new TestAreaEffect()], []);
|
var engine = new SimulationEngine([], [new TestAreaEffect()], []);
|
||||||
var level = LevelState.Create("Custom area effect", 6, 6);
|
var level = LevelState.Create("Custom area effect", 6, 6);
|
||||||
|
|
||||||
var next = engine.AdvanceTurn(level);
|
var next = engine.AdvanceTurn(level);
|
||||||
|
|
||||||
Assert.Equal(7, next.GetCell(new(2, 2)).Hazards.Smoke);
|
Assert.Equal(7, next.GetCell(new(2, 2)).Hazards.Smoke);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OverpressurePredictsPipeBurst()
|
public void OverpressurePredictsPipeBurst()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Pressure", 6, 6)
|
var level = LevelState.Create("Pressure", 6, 6)
|
||||||
.SetCell(new(1, 2), new() {
|
.SetCell(new(1, 2), new() {
|
||||||
Pipe = EPipeMedium.Pressure,
|
Pipe = EPipeMedium.Pressure,
|
||||||
Pressure = 10,
|
Pressure = 10,
|
||||||
Integrity = 6
|
Integrity = 6
|
||||||
});
|
});
|
||||||
|
|
||||||
var forecasts = m_Engine.Forecast(level);
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.PipeBurst && forecast.Turns == 2);
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.PipeBurst && forecast.Turns == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ForecastCapsFutureSimulationWhenNoTerminalConditionOccurs()
|
public void ForecastCapsFutureSimulationWhenNoTerminalConditionOccurs()
|
||||||
{
|
{
|
||||||
var engine = new SimulationEngine([], [], [new StepCountingHazard()]);
|
var engine = new SimulationEngine([], [], [new StepCountingHazard()]);
|
||||||
var level = LevelState.Create("Stable", 6, 6);
|
var level = LevelState.Create("Stable", 6, 6);
|
||||||
|
|
||||||
var forecasts = engine.Forecast(level);
|
var forecasts = engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Count);
|
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Count);
|
||||||
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Max(forecast => forecast.Turns));
|
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Max(forecast => forecast.Turns));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ForecastUsesCurrentBalancingProfile()
|
public void ForecastUsesCurrentBalancingProfile()
|
||||||
{
|
{
|
||||||
var previous = Balancing.Current;
|
var previous = Balancing.Current;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Balancing.Current = new TestBalancing();
|
Balancing.Current = new TestBalancing();
|
||||||
var engine = new SimulationEngine([], [], [new StepCountingHazard()]);
|
var engine = new SimulationEngine([], [], [new StepCountingHazard()]);
|
||||||
var level = LevelState.Create("Stable", 6, 6);
|
var level = LevelState.Create("Stable", 6, 6);
|
||||||
|
|
||||||
var forecasts = engine.Forecast(level);
|
var forecasts = engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Count);
|
Assert.Equal(Balancing.Current.MaxForecastStepCount, forecasts.Count);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Balancing.Current = previous;
|
Balancing.Current = previous;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ForecastPredictsMeltdownFromFutureSimulation()
|
public void ForecastPredictsMeltdownFromFutureSimulation()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Meltdown", 6, 6)
|
var level = LevelState.Create("Meltdown", 6, 6)
|
||||||
.SetCell(new(2, 2), new() {
|
.SetCell(new(2, 2), new() {
|
||||||
Prop = ECellProp.Reactor,
|
Prop = ECellProp.Reactor,
|
||||||
Hazards = new() { Heat = Balancing.Current.MeltdownCoreHeatThreshold - Balancing.Current.ReactorHeatIncrease }
|
Hazards = new() { Heat = Balancing.Current.MeltdownCoreHeatThreshold - Balancing.Current.ReactorHeatIncrease }
|
||||||
});
|
});
|
||||||
|
|
||||||
var forecasts = m_Engine.Forecast(level);
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Meltdown && forecast.Turns == 1);
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Meltdown && forecast.Turns == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ForecastReportsAlreadyLostLevelAtCurrentTurn()
|
public void ForecastReportsAlreadyLostLevelAtCurrentTurn()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Lost", 6, 6) with {
|
var level = LevelState.Create("Lost", 6, 6) with {
|
||||||
Global = new() {
|
Global = new() {
|
||||||
CoreHeat = Balancing.Current.MeltdownCoreHeatThreshold,
|
CoreHeat = Balancing.Current.MeltdownCoreHeatThreshold,
|
||||||
Lost = true,
|
Lost = true,
|
||||||
Status = "CORE MELTDOWN"
|
Status = "CORE MELTDOWN"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var forecasts = m_Engine.Forecast(level);
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Meltdown && forecast.Turns == 0);
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.Meltdown && forecast.Turns == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ForecastPredictsStabilityCollapseFromFutureSimulation()
|
public void ForecastPredictsStabilityCollapseFromFutureSimulation()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Collapse", 6, 6)
|
var level = LevelState.Create("Collapse", 6, 6)
|
||||||
.SetCell(new(2, 2), new() {
|
.SetCell(new(2, 2), new() {
|
||||||
Prop = ECellProp.Generator,
|
Prop = ECellProp.Generator,
|
||||||
Hazards = new() { Stability = Balancing.Current.CriticalCellStabilityThreshold }
|
Hazards = new() { Stability = Balancing.Current.CriticalCellStabilityThreshold }
|
||||||
}) with {
|
}) with {
|
||||||
Global = new() { FacilityStability = Balancing.Current.FireStabilityDamage }
|
Global = new() { FacilityStability = Balancing.Current.FireStabilityDamage }
|
||||||
};
|
};
|
||||||
|
|
||||||
var forecasts = m_Engine.Forecast(level);
|
var forecasts = m_Engine.Forecast(level);
|
||||||
|
|
||||||
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.StabilityCollapse && forecast.Turns == 1);
|
Assert.Contains(forecasts, forecast => forecast.Kind == EFailureKind.StabilityCollapse && forecast.Turns == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StableReactorWithPowerAndCoolingCanActivate()
|
public void StableReactorWithPowerAndCoolingCanActivate()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Ready", 8, 6)
|
var level = LevelState.Create("Ready", 8, 6)
|
||||||
.SetCell(new(2, 2), new() {
|
.SetCell(new(2, 2), new() {
|
||||||
Prop = ECellProp.Reactor,
|
Prop = ECellProp.Reactor,
|
||||||
Hazards = new() { Heat = 3 }
|
Hazards = new() { Heat = 3 }
|
||||||
})
|
})
|
||||||
.SetCell(new(3, 2), new() {
|
.SetCell(new(3, 2), new() {
|
||||||
Prop = ECellProp.Generator,
|
Prop = ECellProp.Generator,
|
||||||
Powered = true
|
Powered = true
|
||||||
})
|
})
|
||||||
.SetCell(new(4, 2), new() {
|
.SetCell(new(4, 2), new() {
|
||||||
Prop = ECellProp.CoolingPump,
|
Prop = ECellProp.CoolingPump,
|
||||||
Powered = true
|
Powered = true
|
||||||
});
|
});
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvanceTurn(level);
|
||||||
var activated = m_Engine.ActivateReactor(next);
|
var activated = m_Engine.ActivateReactor(next);
|
||||||
|
|
||||||
Assert.Equal("REACTOR ONLINE", activated.Global.Status);
|
Assert.Equal("REACTOR ONLINE", activated.Global.Status);
|
||||||
Assert.True(activated.Global.ReactorActivated);
|
Assert.True(activated.Global.ReactorActivated);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LevelSerializationRoundTripsEditableState()
|
public void LevelSerializationRoundTripsEditableState()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Round trip", 5, 5);
|
var level = LevelState.Create("Round trip", 5, 5);
|
||||||
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Reactor);
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Reactor);
|
||||||
level = LevelEditor.Apply(level, new(1, 2), EEditorTool.CoolantPipe);
|
level = LevelEditor.Apply(level, new(1, 2), EEditorTool.CoolantPipe);
|
||||||
level = LevelEditor.Apply(level, new(1, 2), EEditorTool.Leak);
|
level = LevelEditor.Apply(level, new(1, 2), EEditorTool.Leak);
|
||||||
|
|
||||||
var json = LevelSerializer.Serialize(level);
|
var json = LevelSerializer.Serialize(level);
|
||||||
var loaded = LevelSerializer.Deserialize(json);
|
var loaded = LevelSerializer.Deserialize(json);
|
||||||
|
|
||||||
Assert.Contains("\"Version\": 1", json);
|
Assert.Contains("\"Version\": 1", json);
|
||||||
Assert.Equal(level.Name, loaded.Name);
|
Assert.Equal(level.Name, loaded.Name);
|
||||||
Assert.Equal(ECellProp.Reactor, loaded.GetCell(new(2, 2)).Prop);
|
Assert.Equal(ECellProp.Reactor, loaded.GetCell(new(2, 2)).Prop);
|
||||||
Assert.Equal(EPipeMedium.Coolant, loaded.GetCell(new(1, 2)).Pipe);
|
Assert.Equal(EPipeMedium.Coolant, loaded.GetCell(new(1, 2)).Pipe);
|
||||||
Assert.Equal(1, loaded.GetCell(new(1, 2)).LeakRate);
|
Assert.Equal(1, loaded.GetCell(new(1, 2)).LeakRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LevelSerializationRejectsUnsupportedVersion()
|
public void LevelSerializationRejectsUnsupportedVersion()
|
||||||
{
|
{
|
||||||
var json = """
|
var json = """
|
||||||
{
|
{
|
||||||
"Version": 999,
|
"Version": 999,
|
||||||
"Level": {}
|
"Level": {}
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
var exception = Assert.Throws<InvalidOperationException>(() => LevelSerializer.Deserialize(json));
|
var exception = Assert.Throws<InvalidOperationException>(() => LevelSerializer.Deserialize(json));
|
||||||
|
|
||||||
Assert.Contains("Unsupported level file version 999", exception.Message);
|
Assert.Contains("Unsupported level file version 999", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WallToolClearsCellPropsPipesAndHazards()
|
public void WallToolClearsCellPropsPipesAndHazards()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Wall", 5, 5);
|
var level = LevelState.Create("Wall", 5, 5);
|
||||||
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Generator);
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Generator);
|
||||||
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.CoolantPipe);
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.CoolantPipe);
|
||||||
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Fire);
|
level = LevelEditor.Apply(level, new(2, 2), EEditorTool.Fire);
|
||||||
|
|
||||||
var edited = LevelEditor.Apply(level, new(2, 2), EEditorTool.Wall);
|
var edited = LevelEditor.Apply(level, new(2, 2), EEditorTool.Wall);
|
||||||
var cell = edited.GetCell(new(2, 2));
|
var cell = edited.GetCell(new(2, 2));
|
||||||
|
|
||||||
Assert.Equal(ECellTerrain.Wall, cell.Terrain);
|
Assert.Equal(ECellTerrain.Wall, cell.Terrain);
|
||||||
Assert.Equal(ECellProp.None, cell.Prop);
|
Assert.Equal(ECellProp.None, cell.Prop);
|
||||||
Assert.Equal(EPipeMedium.None, cell.Pipe);
|
Assert.Equal(EPipeMedium.None, cell.Pipe);
|
||||||
Assert.False(cell.Powered);
|
Assert.False(cell.Powered);
|
||||||
Assert.False(cell.Hazards.Fire);
|
Assert.False(cell.Hazards.Fire);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PropToolsKeepFloorTerrain()
|
public void PropToolsKeepFloorTerrain()
|
||||||
{
|
{
|
||||||
var level = LevelState.Create("Prop", 5, 5);
|
var level = LevelState.Create("Prop", 5, 5);
|
||||||
level = LevelEditor.Apply(level, new(1, 1), EEditorTool.Wall);
|
level = LevelEditor.Apply(level, new(1, 1), EEditorTool.Wall);
|
||||||
|
|
||||||
var edited = LevelEditor.Apply(level, new(1, 1), EEditorTool.Reactor);
|
var edited = LevelEditor.Apply(level, new(1, 1), EEditorTool.Reactor);
|
||||||
var cell = edited.GetCell(new(1, 1));
|
var cell = edited.GetCell(new(1, 1));
|
||||||
|
|
||||||
Assert.Equal(ECellTerrain.Floor, cell.Terrain);
|
Assert.Equal(ECellTerrain.Floor, cell.Terrain);
|
||||||
Assert.Equal(ECellProp.Reactor, cell.Prop);
|
Assert.Equal(ECellProp.Reactor, cell.Prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SimulationEngine m_Engine = new();
|
private readonly SimulationEngine m_Engine = new();
|
||||||
|
|
||||||
private sealed class StepCountingHazard : Hazard
|
private sealed class StepCountingHazard : Hazard
|
||||||
{
|
{
|
||||||
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
public override IEnumerable<Forecast> Predict(LevelState level, int turns)
|
||||||
{
|
{
|
||||||
yield return new(EFailureKind.PipeBurst, new(turns, 0), turns, $"STEP {turns}");
|
yield return new(EFailureKind.PipeBurst, new(turns, 0), turns, $"STEP {turns}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TestBalancing : NormalBalancing
|
private sealed class TestBalancing : NormalBalancing
|
||||||
{
|
{
|
||||||
public override int MaxForecastStepCount => 2;
|
public override int MaxForecastStepCount => 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TestCellEffect : ISimulationEffect
|
private sealed class TestCellEffect : ISimulationEffect
|
||||||
{
|
{
|
||||||
public CellState Apply(CellState cell)
|
public CellState Apply(CellState cell)
|
||||||
{
|
{
|
||||||
return cell with { Hazards = cell.Hazards with { Heat = 5 } };
|
return cell with { Hazards = cell.Hazards with { Heat = 5 } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TestAreaEffect : IAreaSimulationEffect
|
private sealed class TestAreaEffect : IAreaSimulationEffect
|
||||||
{
|
{
|
||||||
public CellState[] Apply(LevelState level, CellState[] cells)
|
public CellState[] Apply(LevelState level, CellState[] cells)
|
||||||
{
|
{
|
||||||
var next = cells.ToArray();
|
var next = cells.ToArray();
|
||||||
var position = new GridPosition(2, 2);
|
var position = new GridPosition(2, 2);
|
||||||
var cell = next[level.Index(position)];
|
var cell = next[level.Index(position)];
|
||||||
next[level.Index(position)] = cell with { Hazards = cell.Hazards with { Smoke = 7 } };
|
next[level.Index(position)] = cell with { Hazards = cell.Hazards with { Smoke = 7 } };
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user