From 46cd28ef5dbeb7ac95e9fd82d7c94643be2adb4d Mon Sep 17 00:00:00 2001 From: ShadowDancer Date: Sat, 2 Sep 2023 11:10:20 +0200 Subject: [PATCH 1/3] Change position system to absolute --- .vscode/launch.json | 11 +++ .vscode/tasks.json | 41 +++++++++++ CSharpElevatorSaga.sln.DotSettings | 2 + CSharpElevatorSaga/Compilation/Compiler.cs | 22 +++--- CSharpElevatorSaga/Game/ElevatorController.cs | 49 ++++++++----- CSharpElevatorSaga/Game/GameController.cs | 28 ++++---- CSharpElevatorSaga/Game/IPeopleGenerator.cs | 8 +++ CSharpElevatorSaga/Game/IScoring.cs | 10 +++ CSharpElevatorSaga/Game/Level.cs | 20 ++++++ CSharpElevatorSaga/Game/Model/Building.cs | 27 ++++++-- .../Game/Model/BuildingProperties.cs | 4 +- CSharpElevatorSaga/Game/Model/Elevator.cs | 27 ++++++-- .../Game/Model/ElevatorCargo.cs | 34 ++++++---- .../Game/Model/ElevatorControls.cs | 4 +- .../Game/Model/ElevatorState.cs | 2 +- CSharpElevatorSaga/Game/Model/Floor.cs | 48 ++++++++++--- CSharpElevatorSaga/Game/Model/FloorButtons.cs | 2 +- CSharpElevatorSaga/Game/Model/Person.cs | 23 ++++++- CSharpElevatorSaga/Game/Model/Position.cs | 15 ++++ .../Game/Proxy/ElevatorProxy.cs | 4 +- .../Game/Proxy/FloorButtonsProxy.cs | 2 +- CSharpElevatorSaga/Game/Proxy/FloorProxy.cs | 2 +- CSharpElevatorSaga/Game/ScoringStub.cs | 16 +++++ .../Game/UniformPeopleGenerator.cs | 68 +++++++++++++++++++ CSharpElevatorSaga/Pages/BuildingView.razor | 16 ++--- CSharpElevatorSaga/Pages/EditorView.razor | 12 ++-- CSharpElevatorSaga/Pages/ElevatorView.razor | 37 ++-------- .../Pages/ElevatorView.razor.css | 4 -- CSharpElevatorSaga/Pages/FloorView.razor | 22 +----- CSharpElevatorSaga/Pages/FloorView.razor.css | 12 +++- CSharpElevatorSaga/Pages/Index.razor | 1 + CSharpElevatorSaga/Pages/PersonView.razor | 21 +++++- CSharpElevatorSaga/Pages/PersonView.razor.css | 9 +++ CSharpElevatorSaga/Program.cs | 2 +- CSharpElevatorSaga/_Imports.razor | 2 - 35 files changed, 447 insertions(+), 160 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 CSharpElevatorSaga.sln.DotSettings create mode 100644 CSharpElevatorSaga/Game/IPeopleGenerator.cs create mode 100644 CSharpElevatorSaga/Game/IScoring.cs create mode 100644 CSharpElevatorSaga/Game/Level.cs create mode 100644 CSharpElevatorSaga/Game/Model/Position.cs create mode 100644 CSharpElevatorSaga/Game/ScoringStub.cs create mode 100644 CSharpElevatorSaga/Game/UniformPeopleGenerator.cs create mode 100644 CSharpElevatorSaga/Pages/PersonView.razor.css diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..83daaef --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch and Debug Standalone Blazor WebAssembly App", + "type": "blazorwasm", + "request": "launch", + "cwd": "${workspaceFolder}/CSharpElevatorSaga" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..d5678f6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/CSharpElevatorSaga.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/CSharpElevatorSaga.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/CSharpElevatorSaga.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/CSharpElevatorSaga.sln.DotSettings b/CSharpElevatorSaga.sln.DotSettings new file mode 100644 index 0000000..db9ce64 --- /dev/null +++ b/CSharpElevatorSaga.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/CSharpElevatorSaga/Compilation/Compiler.cs b/CSharpElevatorSaga/Compilation/Compiler.cs index bab3afd..5d70cc6 100644 --- a/CSharpElevatorSaga/Compilation/Compiler.cs +++ b/CSharpElevatorSaga/Compilation/Compiler.cs @@ -13,7 +13,7 @@ public class Compiler private readonly IAssemblyResolver _assemblyResolver; private int _assemblyNumber; private bool _warmedUp; - private static IReadOnlyList _assemblyFileNames = new string[] + private static readonly IReadOnlyList AssemblyFileNames = new[] { "System.Collections.dll", "System.Console.dll", @@ -40,15 +40,15 @@ public async Task PreloadAssemblies() _warmedUp = true; var metadata = new List(); - foreach (var assemblyFileName in _assemblyFileNames) + foreach (var assemblyFileName in AssemblyFileNames) { try { #pragma warning disable CS8774 // Member must have a non-null value when exiting. - MetadataReference? refernce = await _assemblyResolver.GetAssembly(assemblyFileName); + MetadataReference reference = await _assemblyResolver.GetAssembly(assemblyFileName); #pragma warning restore CS8774 // Member must have a non-null value when exiting. - metadata.Add(refernce); + metadata.Add(reference); } catch { @@ -78,7 +78,7 @@ public async Task CompileCodeAsync(string code) } var diagnosticsConfiguration = new List>() { - new KeyValuePair("CS8321", ReportDiagnostic.Hidden) + new("CS8321", ReportDiagnostic.Hidden) }; CSharpCompilation compilation = CSharpCompilation.Create( "Solution" + _assemblyNumber++, @@ -86,7 +86,7 @@ public async Task CompileCodeAsync(string code) _assemblyMetadata, new CSharpCompilationOptions(OutputKind.ConsoleApplication, false, specificDiagnosticOptions: diagnosticsConfiguration, concurrentBuild: false)); - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = new()) { EmitResult result = compilation.Emit(stream); @@ -121,7 +121,7 @@ private static void AddDiagnostic(Diagnostic diagnostic, List diagno private static SyntaxTree MakeUsingSyntaxTree() { - var usings = new string[] { + var usings = new[] { "System", "System.Linq", "System.Collections", @@ -160,10 +160,10 @@ private static CompileResult LoadAssembly(MemoryStream stream, List return CompileResult.Compiled(diagnostics, programDelegate); } - internal static DiagnosticDescriptor NoRunMethodRule = new DiagnosticDescriptor("ES0001", "Could not find Run", "Program does not contain static void Run(IElevator[] elevators, IFloor[] floors) function", + internal static DiagnosticDescriptor NoRunMethodRule = new("ES0001", "Could not find Run", "Program does not contain static void Run(IElevator[] elevators, IFloor[] floors) function", "Elevator Saga", DiagnosticSeverity.Error, isEnabledByDefault: true); - internal static DiagnosticDescriptor RunMethodHasInvalidSignatureRule = new DiagnosticDescriptor("ES0002", "Invalid Run method", "Function Run should have signature static void Run(IElevator[] elevators, IFloor[] floors)", + internal static DiagnosticDescriptor RunMethodHasInvalidSignatureRule = new("ES0002", "Invalid Run method", "Function Run should have signature static void Run(IElevator[] elevators, IFloor[] floors)", "Elevator Saga", DiagnosticSeverity.Error, isEnabledByDefault: true); public class CompileResult @@ -175,9 +175,9 @@ public CompileResult(bool success, IReadOnlyList diagnostics, Progra Program = program; } - public static CompileResult Fail(IReadOnlyList diagnostics) => new CompileResult(false, diagnostics, null); + public static CompileResult Fail(IReadOnlyList diagnostics) => new(false, diagnostics, null); - public static CompileResult Compiled(IReadOnlyList diagnostics, ProgramEntryPoint.RunProgram program) => new CompileResult(true, diagnostics, program); + public static CompileResult Compiled(IReadOnlyList diagnostics, ProgramEntryPoint.RunProgram program) => new(true, diagnostics, program); [MemberNotNullWhen(true, nameof(Program))] public bool Success { get; } diff --git a/CSharpElevatorSaga/Game/ElevatorController.cs b/CSharpElevatorSaga/Game/ElevatorController.cs index c818c18..8f81bbc 100644 --- a/CSharpElevatorSaga/Game/ElevatorController.cs +++ b/CSharpElevatorSaga/Game/ElevatorController.cs @@ -1,20 +1,22 @@ -using CSharpElevatorSaga.Implementation.Model; +using CSharpElevatorSaga.Game.Model; -namespace CSharpElevatorSaga.Implementation; +namespace CSharpElevatorSaga.Game; public class ElevatorController { private readonly Elevator _elevator; private readonly Floor _floor; private readonly Building _building; + private readonly IScoring _scoring; private ElevatorControls Controls => _elevator.Controls; - public ElevatorController(Elevator elevator, Floor floor, Building building) + public ElevatorController(Elevator elevator, Floor floor, Building building, IScoring scoring) { _elevator = elevator; _floor = floor; _building = building; + _scoring = scoring; } internal void Tick() @@ -45,33 +47,43 @@ private void HandleMoving() if (oldDirection != 0 && directionVector != 0 && directionVector != oldDirection) { - // Because activity ticks always go up, we need to recompute it when changing direction Controls.ActivityTicks = _building.Properties.TicksPerStory - Controls.ActivityTicks; } + float progress = (float)Controls.ActivityTicks / _building.Properties.TicksPerStory; + _elevator.UpdatePosition(Controls.TargetFloor, progress); + bool reachedFloor = Controls.ActivityTicks >= _building.Properties.TicksPerStory; - if (!reachedFloor) + if (reachedFloor) { - return; + OnFloorReached(directionVector); } + } + private void OnFloorReached(int directionVector) + { Controls.ActivityTicks = 0; var nextFloorNumber = Math.Clamp(_elevator.Floor + directionVector, 0, _building.Floors.Length); var newFloor = _building.Floors[nextFloorNumber]; _elevator.Proxy.Floor = newFloor.Proxy; - if (_elevator.Floor == Controls.TargetFloor) { - var leavingPassengers = _elevator.Cargo.RemovePassengersGoingTo(_elevator.Floor); + var leavingPassengers = _elevator.Cargo.Passengers.Where(p => p.TargetFloor == _elevator.Floor).ToList(); + Console.WriteLine($"Leaving passengers: " + string.Join(", ", leavingPassengers.Select(p => p.Id))); - newFloor.OutputLine.AddRange(leavingPassengers); + foreach (var passenger in leavingPassengers) + { + newFloor.AddPersonToOutputLine(passenger); + } + + _scoring.PersonTransported(leavingPassengers.Count); - while (_elevator.Controls.DestinationQueue.Remove(_elevator.Floor) == true) + while (_elevator.Controls.DestinationQueue.Remove(_elevator.Floor)) { - ; } + _scoring.ElevatorMoved(); Transition(ElevatorState.Idle); _elevator.Proxy.RaiseOnStopped(); @@ -80,13 +92,16 @@ private void HandleMoving() private void HandleIdle() { - while (_floor.WaitingLine.Any() && _elevator.Cargo.CanTakePassenger) + var waitingPeople = _floor.WaitingLine.ToList(); + while (waitingPeople.Any() && _elevator.Cargo.CanTakePassenger) { - var newPassenger = _floor.WaitingLine.Dequeue(); + var newPassenger = waitingPeople.First(); _elevator.Cargo.TakePassenger(newPassenger); PressButtonInElevator(newPassenger.TargetFloor); + waitingPeople.RemoveAt(0); } + _floor.UpdateWaitingLinePositions(); bool minimumStayTimeElapsing = Controls.ActivityTicks < _building.Properties.MinimumStayTicks; if (minimumStayTimeElapsing) @@ -112,11 +127,13 @@ private void HandleIdle() private void PressButtonInElevator(int targetFloor) { - if (!_elevator.RequestedFloors.Contains(targetFloor)) + if (_elevator.RequestedFloors.Contains(targetFloor)) { - _elevator.RequestedFloors.Add(targetFloor); - _elevator.Proxy.RaiseOnButtonPressed(targetFloor); + return; } + + _elevator.RequestedFloors.Add(targetFloor); + _elevator.Proxy.RaiseOnButtonPressed(targetFloor); } private void Transition(ElevatorState newState) diff --git a/CSharpElevatorSaga/Game/GameController.cs b/CSharpElevatorSaga/Game/GameController.cs index c1aab4d..c246014 100644 --- a/CSharpElevatorSaga/Game/GameController.cs +++ b/CSharpElevatorSaga/Game/GameController.cs @@ -1,7 +1,7 @@ -using CSharpElevatorSaga.Implementation.Model; +using CSharpElevatorSaga.Game.Model; using CSharpElevatorSaga.Model; -namespace CSharpElevatorSaga.Implementation; +namespace CSharpElevatorSaga.Game; public class GameController { @@ -9,8 +9,11 @@ public class GameController public GameController() { - Building = new Building(3, 1); - Timer = new Timer(GameTickHandler, 0, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(16)); + const int Fps = 60; + + Level = new Level(3, 1, new ScoringStub(), new UniformPeopleGenerator(60)); + Building = new Building(3, 1, Level.Scoring); + Timer = new Timer(GameTickHandler, 0, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000.0/Fps)); } public delegate void OnGameTickCompleted(); @@ -22,15 +25,13 @@ private void GameTickHandler(object? state) for (int i = 0; i < _gameSpeed; i++) { Building.Tick(); + Level.Scoring.Tick(); + Level.PeopleGenerator.Tick(Building); } OnTickCompleted?.Invoke(); } - public int Floors { get; } = 3; - - public int Elevators { get; } = 1; - /// /// Number of game ticks per frame /// @@ -40,15 +41,18 @@ public int GameSpeed set => Math.Clamp(value, 0, 16); } - public Building Building { get; set; } + public Level Level { get; } + + public Building Building { get; private set; } + // ReSharper disable once UnusedAutoPropertyAccessor.Local private Timer Timer { get; } - public void RunProgram(ProgramEntryPoint.RunProgram program) + public void RunProgram(ProgramEntryPoint.RunProgram? program) { - Building = new Building(Floors, Elevators); + Building = new Building(Level.Stories, Level.Elevators, Level.Scoring); var elevators = Building.Elevators.Select(elevator => (IElevator)elevator.Proxy).ToArray(); var floors = Building.Floors.Select(floor => (IFloor)floor.Proxy).ToArray(); program?.Invoke(elevators, floors); } -} +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Game/IPeopleGenerator.cs b/CSharpElevatorSaga/Game/IPeopleGenerator.cs new file mode 100644 index 0000000..a0161f3 --- /dev/null +++ b/CSharpElevatorSaga/Game/IPeopleGenerator.cs @@ -0,0 +1,8 @@ +using CSharpElevatorSaga.Game.Model; + +namespace CSharpElevatorSaga.Game; + +public interface IPeopleGenerator +{ + public void Tick(Building building); +} diff --git a/CSharpElevatorSaga/Game/IScoring.cs b/CSharpElevatorSaga/Game/IScoring.cs new file mode 100644 index 0000000..a83f656 --- /dev/null +++ b/CSharpElevatorSaga/Game/IScoring.cs @@ -0,0 +1,10 @@ +namespace CSharpElevatorSaga.Game; + +public interface IScoring +{ + public void PersonTransported(int count); + + public void ElevatorMoved(); + + public void Tick(); +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Game/Level.cs b/CSharpElevatorSaga/Game/Level.cs new file mode 100644 index 0000000..5ee8e5a --- /dev/null +++ b/CSharpElevatorSaga/Game/Level.cs @@ -0,0 +1,20 @@ +namespace CSharpElevatorSaga.Game; + +public class Level +{ + public Level(int stories, int elevators, IScoring scoring, IPeopleGenerator peopleGenerator) + { + Stories = stories; + Elevators = elevators; + Scoring = scoring; + PeopleGenerator = peopleGenerator; + } + + public int Stories { get; } + + public int Elevators { get; } + + public IScoring Scoring { get; } + + public IPeopleGenerator PeopleGenerator { get; } +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Game/Model/Building.cs b/CSharpElevatorSaga/Game/Model/Building.cs index e1103c6..c88fc0b 100644 --- a/CSharpElevatorSaga/Game/Model/Building.cs +++ b/CSharpElevatorSaga/Game/Model/Building.cs @@ -1,22 +1,35 @@ using System.Collections.Immutable; +using System.Collections.Generic; -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class Building { - public Building(int stories, int elevators) - { - Floors = Enumerable.Range(0, stories).Select(n => new Floor(new Proxy.FloorProxy(n))).ToImmutableArray(); + private readonly IScoring _scoring; + + private int NextPersonId { get; set; } = 0; - Elevators = Enumerable.Range(0, elevators).Select(n => new Elevator(new Proxy.ElevatorProxy(Floors[0].Proxy))).ToImmutableArray(); + public Building(int stories, int elevators, IScoring scoring) + { + _scoring = scoring; + Floors = Enumerable.Range(0, stories).Select(floorNumber => new Floor(new Proxy.FloorProxy(floorNumber), People.Where(p => p.CurrentFloor == floorNumber))).ToImmutableArray(); + Elevators = Enumerable.Range(0, elevators).Select(_ => new Elevator(new Proxy.ElevatorProxy(Floors[0].Proxy), this)).ToImmutableArray(); } public ImmutableArray Elevators { get; } public ImmutableArray Floors { get; } - public BuildingProperties Properties { get; } = new BuildingProperties(); + public BuildingProperties Properties { get; } = new(); + public List People { get; } = new(); + + public Person CreatePerson(int floor){ + var person = new Person(NextPersonId++, floor); + People.Add(person); + Floors[floor].AddPersonToWaitingLine(person); + return person; + } public void Tick() { @@ -29,6 +42,6 @@ public void Tick() private void UpdateElevator(Elevator elevator) { var elevatorFloor = Floors[elevator.Floor]; - new ElevatorController(elevator, elevatorFloor, this).Tick(); + new ElevatorController(elevator, elevatorFloor, this, _scoring).Tick(); } } diff --git a/CSharpElevatorSaga/Game/Model/BuildingProperties.cs b/CSharpElevatorSaga/Game/Model/BuildingProperties.cs index 7698d48..e11cd01 100644 --- a/CSharpElevatorSaga/Game/Model/BuildingProperties.cs +++ b/CSharpElevatorSaga/Game/Model/BuildingProperties.cs @@ -1,4 +1,4 @@ -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class BuildingProperties { @@ -7,4 +7,6 @@ public class BuildingProperties public int TicksToMoveDoors { get; } = 2; public int MinimumStayTicks { get; } = 20; + + public int FloorHeight { get; } = 100; } diff --git a/CSharpElevatorSaga/Game/Model/Elevator.cs b/CSharpElevatorSaga/Game/Model/Elevator.cs index c22fca7..ad788d2 100644 --- a/CSharpElevatorSaga/Game/Model/Elevator.cs +++ b/CSharpElevatorSaga/Game/Model/Elevator.cs @@ -1,19 +1,23 @@ -using CSharpElevatorSaga.Proxy; +using CSharpElevatorSaga.Game.Proxy; -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class Elevator { - - public Elevator(ElevatorProxy proxy) + public Elevator(ElevatorProxy proxy, Building building) { Proxy = proxy; - Controls = new(proxy); + Controls = new ElevatorControls(proxy); DirectionIndicators = proxy.DirectionIndicators; - Cargo = new(proxy); + Position = new Position(200f, proxy.Floor.Number * 100f); + Cargo = new ElevatorCargo(proxy, this, building); } public int Floor => Proxy.Floor.Number; + + public Position Position { get; set; } + + public float Height { get; } = 100f; public ElevatorProxy Proxy { get; } @@ -24,4 +28,15 @@ public Elevator(ElevatorProxy proxy) public ElevatorCargo Cargo { get; } public List RequestedFloors => Proxy.RequestedFloors; + + public void UpdatePosition(int targetFloor, float progress) + { + float sourceY = Floor * 100f; + int directionVector = Math.Sign(targetFloor - Floor); + int nextFloor = Floor + directionVector; + float nextFloorY = nextFloor * 100f; + Position = new Position(Position.X, sourceY + (nextFloorY - sourceY) * progress); + + Cargo.UpdatePassengerPositions(); + } } diff --git a/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs b/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs index 94dcb87..1d15220 100644 --- a/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs +++ b/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs @@ -1,36 +1,40 @@ -using CSharpElevatorSaga.Proxy; +using CSharpElevatorSaga.Game.Proxy; -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class ElevatorCargo { - private ElevatorProxy _proxy; + private readonly ElevatorProxy _proxy; + private readonly Elevator _elevator; + private readonly Building _building; + private const float PersonSpacing = 15f; - public ElevatorCargo(ElevatorProxy proxy) + public ElevatorCargo(ElevatorProxy proxy, Elevator elevator, Building building) { _proxy = proxy; + _elevator = elevator; + _building = building; } - + public int MaxPassengers { get; set; } = 3; - public List Passengers { get; } = new List(); + public IEnumerable Passengers => _building.People.Where(p => p.State == PersonState.InElevator); - public bool CanTakePassenger => Passengers.Count < MaxPassengers; + public bool CanTakePassenger => Passengers.Count() < MaxPassengers; public void TakePassenger(Person person) { - Passengers.Add(person); + person.State = PersonState.InElevator; + UpdatePassengerPositions(); } - internal List RemovePassengersGoingTo(int floor) + public void UpdatePassengerPositions() { - var leavingPassengers = Passengers.Where(n => n.TargetFloor == floor).ToList(); - - foreach (var passenger in leavingPassengers) + var passengers = Passengers.ToList(); + for (int i = 0; i < passengers.Count; i++) { - Passengers.Remove(passenger); + var xPos = _elevator.Position.X + (i * PersonSpacing); + passengers[i].Position = new Position(xPos, _elevator.Position.Y); } - - return leavingPassengers; } } diff --git a/CSharpElevatorSaga/Game/Model/ElevatorControls.cs b/CSharpElevatorSaga/Game/Model/ElevatorControls.cs index e3de3b5..9c090b4 100644 --- a/CSharpElevatorSaga/Game/Model/ElevatorControls.cs +++ b/CSharpElevatorSaga/Game/Model/ElevatorControls.cs @@ -1,6 +1,6 @@ -using CSharpElevatorSaga.Proxy; +using CSharpElevatorSaga.Game.Proxy; -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class ElevatorControls { diff --git a/CSharpElevatorSaga/Game/Model/ElevatorState.cs b/CSharpElevatorSaga/Game/Model/ElevatorState.cs index 3178daa..d50d79f 100644 --- a/CSharpElevatorSaga/Game/Model/ElevatorState.cs +++ b/CSharpElevatorSaga/Game/Model/ElevatorState.cs @@ -1,4 +1,4 @@ -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public enum ElevatorState { diff --git a/CSharpElevatorSaga/Game/Model/Floor.cs b/CSharpElevatorSaga/Game/Model/Floor.cs index 00e1386..31f820a 100644 --- a/CSharpElevatorSaga/Game/Model/Floor.cs +++ b/CSharpElevatorSaga/Game/Model/Floor.cs @@ -1,23 +1,53 @@ -using CSharpElevatorSaga.Proxy; +using CSharpElevatorSaga.Game.Proxy; -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class Floor { + private readonly IEnumerable _peopleOnFloor; + public FloorProxy Proxy { get; } + + public float Y { get; } + + public float WaitingLineX { get; } = 50f; + + public float OutputLineX { get; } = 300f; + + public float PersonSpacing { get; } = 25f; - public Floor(FloorProxy floorProxy) + public Floor(FloorProxy floorProxy, IEnumerable peopleOnFloor) { Proxy = floorProxy; - for (int i = 0; i < 5; i++) - { - WaitingLine.Enqueue(new Person(0)); - } + Y = floorProxy.Number * 100f; + _peopleOnFloor = peopleOnFloor; } public FloorButtons Buttons { get; } = new(); - public Queue WaitingLine { get; } = new(); + public IEnumerable WaitingLine => _peopleOnFloor.Where(p => p.State == PersonState.Waiting); - public List OutputLine { get; } = new(); + public IEnumerable OutputLine => _peopleOnFloor.Where(p => p.State == PersonState.Exited); + + public void AddPersonToWaitingLine(Person person) + { + person.State = PersonState.Waiting; + person.CurrentFloor = Proxy.Number; + UpdateWaitingLinePositions(); + } + + public void UpdateWaitingLinePositions() + { + var waitingLine = WaitingLine.ToList(); + for (int i = 0; i < waitingLine.Count; i++){ + waitingLine[i].Position = new Position(WaitingLineX + (i * PersonSpacing), Y); + } + } + + public void AddPersonToOutputLine(Person person) + { + person.State = PersonState.Exited; + person.CurrentFloor = Proxy.Number; + person.Position = new Position(OutputLineX + (OutputLine.Count() * PersonSpacing), Y); + } } diff --git a/CSharpElevatorSaga/Game/Model/FloorButtons.cs b/CSharpElevatorSaga/Game/Model/FloorButtons.cs index 79904ac..b61591d 100644 --- a/CSharpElevatorSaga/Game/Model/FloorButtons.cs +++ b/CSharpElevatorSaga/Game/Model/FloorButtons.cs @@ -1,6 +1,6 @@ using CSharpElevatorSaga.Model; -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; public class FloorButtons : IFloorButtons { diff --git a/CSharpElevatorSaga/Game/Model/Person.cs b/CSharpElevatorSaga/Game/Model/Person.cs index de209b0..fdcf4cb 100644 --- a/CSharpElevatorSaga/Game/Model/Person.cs +++ b/CSharpElevatorSaga/Game/Model/Person.cs @@ -1,11 +1,30 @@ -namespace CSharpElevatorSaga.Implementation.Model; +namespace CSharpElevatorSaga.Game.Model; + +public enum PersonState +{ + Waiting, + InElevator, + Exited +} public class Person { - public Person(int targetFloor) + public Person(int id, int targetFloor) { + Id = id; TargetFloor = targetFloor; + Position = Position.Zero; + State = PersonState.Waiting; + CurrentFloor = 0; } + public int Id { get; } + public int TargetFloor { get; } + + public Position Position { get; set; } + + public PersonState State { get; set; } + + public int CurrentFloor { get; set; } } diff --git a/CSharpElevatorSaga/Game/Model/Position.cs b/CSharpElevatorSaga/Game/Model/Position.cs new file mode 100644 index 0000000..34a346b --- /dev/null +++ b/CSharpElevatorSaga/Game/Model/Position.cs @@ -0,0 +1,15 @@ +namespace CSharpElevatorSaga.Game.Model; + +public struct Position +{ + public Position(float x, float y) + { + X = x; + Y = y; + } + + public float X { get; set; } + public float Y { get; set; } + + public static Position Zero => new Position(0, 0); +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Game/Proxy/ElevatorProxy.cs b/CSharpElevatorSaga/Game/Proxy/ElevatorProxy.cs index 4bb89b5..2154358 100644 --- a/CSharpElevatorSaga/Game/Proxy/ElevatorProxy.cs +++ b/CSharpElevatorSaga/Game/Proxy/ElevatorProxy.cs @@ -1,6 +1,6 @@ using CSharpElevatorSaga.Model; -namespace CSharpElevatorSaga.Proxy; +namespace CSharpElevatorSaga.Game.Proxy; public class ElevatorProxy : IElevator { @@ -9,7 +9,7 @@ public ElevatorProxy(IFloor startingFloor) Floor = startingFloor; } - public IFloor Floor { get; set; } = null!; + public IFloor Floor { get; set; } public IList DestinationQueue { get; } = new List(); diff --git a/CSharpElevatorSaga/Game/Proxy/FloorButtonsProxy.cs b/CSharpElevatorSaga/Game/Proxy/FloorButtonsProxy.cs index c3b0631..28d96bc 100644 --- a/CSharpElevatorSaga/Game/Proxy/FloorButtonsProxy.cs +++ b/CSharpElevatorSaga/Game/Proxy/FloorButtonsProxy.cs @@ -1,6 +1,6 @@ using CSharpElevatorSaga.Model; -namespace CSharpElevatorSaga.Proxy; +namespace CSharpElevatorSaga.Game.Proxy; public class FloorButtonsProxy : IFloorButtons { diff --git a/CSharpElevatorSaga/Game/Proxy/FloorProxy.cs b/CSharpElevatorSaga/Game/Proxy/FloorProxy.cs index 00cc7dc..a4a88af 100644 --- a/CSharpElevatorSaga/Game/Proxy/FloorProxy.cs +++ b/CSharpElevatorSaga/Game/Proxy/FloorProxy.cs @@ -1,6 +1,6 @@ using CSharpElevatorSaga.Model; -namespace CSharpElevatorSaga.Proxy; +namespace CSharpElevatorSaga.Game.Proxy; public class FloorProxy : IFloor { diff --git a/CSharpElevatorSaga/Game/ScoringStub.cs b/CSharpElevatorSaga/Game/ScoringStub.cs new file mode 100644 index 0000000..a75cce9 --- /dev/null +++ b/CSharpElevatorSaga/Game/ScoringStub.cs @@ -0,0 +1,16 @@ +namespace CSharpElevatorSaga.Game; + +public class ScoringStub : IScoring +{ + public void PersonTransported(int count) + { + } + + public void ElevatorMoved() + { + } + + public void Tick() + { + } +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs b/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs new file mode 100644 index 0000000..9df6253 --- /dev/null +++ b/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs @@ -0,0 +1,68 @@ +using CSharpElevatorSaga.Game.Model; + +namespace CSharpElevatorSaga.Game; + +public class UniformPeopleGenerator : IPeopleGenerator +{ + private const int MaxPeoplePerFloor = 5; + private int _floorNumber; + private int _ticks; + private readonly int _ticksPerPerson; + + public UniformPeopleGenerator(int ticksPerPerson) + { + _ticksPerPerson = ticksPerPerson; + } + + public void Tick(Building building) + { + _ticks++; + if (_ticks > _ticksPerPerson) + { + _ticks = 0; + GeneratePerson(building); + } + } + + private void GeneratePerson(Building building) + { + FindEmptyFloor(building); + + if (building.Floors[_floorNumber].WaitingLine.Count() >= MaxPeoplePerFloor) + { + return; + } + + var targetFloor = new Random().Next(0, building.Floors.Length - 1); + if (targetFloor >= _floorNumber) + { + targetFloor++; + } + + building.CreatePerson(targetFloor); + NextFloor(building); + } + + private void FindEmptyFloor(Building building) + { + int attempts = 0; + while (building.Floors[_floorNumber].WaitingLine.Count() > MaxPeoplePerFloor) + { + NextFloor(building); + attempts++; + if (attempts > building.Floors.Length) + { + break; + } + } + } + + private void NextFloor(Building building) + { + _floorNumber++; + if (_floorNumber >= building.Floors.Length) + { + _floorNumber = 0; + } + } +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Pages/BuildingView.razor b/CSharpElevatorSaga/Pages/BuildingView.razor index 2ab30b0..2041fd5 100644 --- a/CSharpElevatorSaga/Pages/BuildingView.razor +++ b/CSharpElevatorSaga/Pages/BuildingView.razor @@ -1,4 +1,5 @@ - +@using CSharpElevatorSaga.Game.Model +
@foreach (var floor in Building.Floors.Reverse()) @@ -6,19 +7,18 @@ } - @{ - var i = 0; - } - @foreach (var elevator in Building.Elevators) { - - i++; + + } + + @foreach (var person in Building.People) + { + }
- @code { [Parameter, EditorRequired] public Building Building { get; set; } = null!; diff --git a/CSharpElevatorSaga/Pages/EditorView.razor b/CSharpElevatorSaga/Pages/EditorView.razor index 3c1d914..f525fef 100644 --- a/CSharpElevatorSaga/Pages/EditorView.razor +++ b/CSharpElevatorSaga/Pages/EditorView.razor @@ -1,5 +1,6 @@ @using Microsoft.CodeAnalysis @inject IJSRuntime JS +@using CSharpElevatorSaga.Game @inject Compiler Compiler
@@ -9,8 +10,8 @@ Compile
- - + + @@ -24,7 +25,9 @@ var elevator = elevators.First(); elevator.OnIdle += () => { + elevator.DestinationQueue.Add(0); elevator.DestinationQueue.Add(1); + elevator.DestinationQueue.Add(2); }; // Put code here @@ -41,7 +44,7 @@ CompilationSuccess = result.Success; Diagnostics = result.Diagnostics; - if (result.Success == true) + if (result.Success) { GameController.RunProgram(result.Program); } @@ -58,5 +61,4 @@ [Parameter, EditorRequired] public GameController GameController { get; set; } = null!; - -} +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Pages/ElevatorView.razor b/CSharpElevatorSaga/Pages/ElevatorView.razor index d884691..aa9a216 100644 --- a/CSharpElevatorSaga/Pages/ElevatorView.razor +++ b/CSharpElevatorSaga/Pages/ElevatorView.razor @@ -1,45 +1,20 @@ - +@using CSharpElevatorSaga.Game.Model + + +
-
-
- @foreach (var person in Elevator.Cargo.Passengers) - { - - } -
@code { protected override void OnParametersSet() { base.OnParametersSet(); - - var control = Elevator.Controls; - - int height = (Elevator.Floor) * 100; - if (control.State == ElevatorState.Moving) - { - const int totalHeight = 100; - - int heightPercentage = (100 * control.ActivityTicks / 40); - - if (control.TargetFloor >= Elevator.Floor) - { - height += (int)(totalHeight * heightPercentage / 100.0f); - } - else - { - height -= (int)(totalHeight * heightPercentage / 100.0f); - } - } - - style = $"bottom: {height}px; left: 200px;"; + Style = $"position: absolute; bottom: {Elevator.Position.Y}px; left: {Elevator.Position.X}px;"; } [Parameter, EditorRequired] public Elevator Elevator { get; set; } = null!; - [Parameter, EditorRequired] - public string? style { get; set; } + public string? Style { get; set; } } \ No newline at end of file diff --git a/CSharpElevatorSaga/Pages/ElevatorView.razor.css b/CSharpElevatorSaga/Pages/ElevatorView.razor.css index 03c9bcb..ead66ae 100644 --- a/CSharpElevatorSaga/Pages/ElevatorView.razor.css +++ b/CSharpElevatorSaga/Pages/ElevatorView.razor.css @@ -8,8 +8,4 @@ background: linear-gradient(180deg, rgba(112,20,232,1) 30%, rgba(59,51,221,1) 100%); position: absolute; display: flex; -}t - -.people { - margin-top: auto; } \ No newline at end of file diff --git a/CSharpElevatorSaga/Pages/FloorView.razor b/CSharpElevatorSaga/Pages/FloorView.razor index 16f7c61..c9ec144 100644 --- a/CSharpElevatorSaga/Pages/FloorView.razor +++ b/CSharpElevatorSaga/Pages/FloorView.razor @@ -1,4 +1,5 @@ - +@using CSharpElevatorSaga.Game.Model +
@@ -7,26 +8,7 @@
-
-
- @foreach (var person in Floor.WaitingLine) - { - - } - -
-
-
- -
-
- @foreach (var person in Floor.OutputLine) - { - - } -
-
diff --git a/CSharpElevatorSaga/Pages/FloorView.razor.css b/CSharpElevatorSaga/Pages/FloorView.razor.css index f2e8554..e715985 100644 --- a/CSharpElevatorSaga/Pages/FloorView.razor.css +++ b/CSharpElevatorSaga/Pages/FloorView.razor.css @@ -28,4 +28,14 @@ } .floor-line { height: 1px; -} \ No newline at end of file +} + +.input-queue{ +/* position: absolute; */ + bottom: 0; +} + +.output-queue{ +/* position: absolute; */ + bottom: 0; +} diff --git a/CSharpElevatorSaga/Pages/Index.razor b/CSharpElevatorSaga/Pages/Index.razor index 457157c..c3b852d 100644 --- a/CSharpElevatorSaga/Pages/Index.razor +++ b/CSharpElevatorSaga/Pages/Index.razor @@ -1,4 +1,5 @@ @page "/" +@using CSharpElevatorSaga.Game C# Elevator Saga
diff --git a/CSharpElevatorSaga/Pages/PersonView.razor b/CSharpElevatorSaga/Pages/PersonView.razor index eb4efcd..a976b1d 100644 --- a/CSharpElevatorSaga/Pages/PersonView.razor +++ b/CSharpElevatorSaga/Pages/PersonView.razor @@ -1 +1,20 @@ -🧍 \ No newline at end of file +@using CSharpElevatorSaga.Game.Model + +🧍 + +@code { + [Parameter, EditorRequired] + public Person Person { get; set; } = null!; + + [Parameter] + public string? Style { get; set; } + + public int Id { get; set; } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + + Style = $"left: {Person.Position.X}px; bottom: {Person.Position.Y}px;"; + } +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Pages/PersonView.razor.css b/CSharpElevatorSaga/Pages/PersonView.razor.css new file mode 100644 index 0000000..ca35bbd --- /dev/null +++ b/CSharpElevatorSaga/Pages/PersonView.razor.css @@ -0,0 +1,9 @@ +.person { + font-size: 1.5rem; + z-index: 10; + position: absolute; + display: inline-block; + width: 10px; + + text-align: center; +} \ No newline at end of file diff --git a/CSharpElevatorSaga/Program.cs b/CSharpElevatorSaga/Program.cs index 8410f27..7aea459 100644 --- a/CSharpElevatorSaga/Program.cs +++ b/CSharpElevatorSaga/Program.cs @@ -8,7 +8,7 @@ builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddSingleton(); diff --git a/CSharpElevatorSaga/_Imports.razor b/CSharpElevatorSaga/_Imports.razor index 2adf8fc..19fcf43 100644 --- a/CSharpElevatorSaga/_Imports.razor +++ b/CSharpElevatorSaga/_Imports.razor @@ -8,6 +8,4 @@ @using Microsoft.JSInterop @using CSharpElevatorSaga @using CSharpElevatorSaga.Pages -@using CSharpElevatorSaga.Implementation -@using CSharpElevatorSaga.Implementation.Model @using CSharpElevatorSaga.Compilation \ No newline at end of file From d652d7036c7cdb1363dd35be5f133f2362df0e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Sun, 30 Mar 2025 20:02:55 +0200 Subject: [PATCH 2/3] Add animations for passenger pickup/dropoff --- CSharpElevatorSaga/Game/ElevatorController.cs | 72 +++++++++++++++---- CSharpElevatorSaga/Game/GameController.cs | 2 +- CSharpElevatorSaga/Game/Model/Building.cs | 20 ++++-- .../Game/Model/BuildingProperties.cs | 15 ++++ CSharpElevatorSaga/Game/Model/Elevator.cs | 18 ++--- .../Game/Model/ElevatorCargo.cs | 44 ++++++++---- .../Game/Model/ElevatorState.cs | 4 ++ CSharpElevatorSaga/Game/Model/Floor.cs | 34 +++++---- CSharpElevatorSaga/Game/Model/Person.cs | 58 ++++++++++++--- .../Game/UniformPeopleGenerator.cs | 17 ++--- CSharpElevatorSaga/Pages/FloorView.razor | 1 - .../Properties/launchSettings.json | 16 ----- 12 files changed, 200 insertions(+), 101 deletions(-) diff --git a/CSharpElevatorSaga/Game/ElevatorController.cs b/CSharpElevatorSaga/Game/ElevatorController.cs index 8f81bbc..11ea3e9 100644 --- a/CSharpElevatorSaga/Game/ElevatorController.cs +++ b/CSharpElevatorSaga/Game/ElevatorController.cs @@ -30,6 +30,9 @@ internal void Tick() case ElevatorState.Idle: HandleIdle(); break; + case ElevatorState.Stopped: + HandleStopped(); + break; } } @@ -51,15 +54,27 @@ private void HandleMoving() } float progress = (float)Controls.ActivityTicks / _building.Properties.TicksPerStory; - _elevator.UpdatePosition(Controls.TargetFloor, progress); + UpdateElevatorPosition(Controls.TargetFloor, progress); bool reachedFloor = Controls.ActivityTicks >= _building.Properties.TicksPerStory; if (reachedFloor) { + Transition(ElevatorState.Stopped); OnFloorReached(directionVector); } } + private void UpdateElevatorPosition(int targetFloor, float progress) + { + float sourceY = _elevator.Floor * 100f; + int directionVector = Math.Sign(targetFloor - _elevator.Floor); + int nextFloor = _elevator.Floor + directionVector; + float nextFloorY = nextFloor * 100f; + _elevator.Position = new Position(_elevator.Position.X, sourceY + (nextFloorY - sourceY) * progress); + + _elevator.Cargo.UpdatePassengerPositions(); + } + private void OnFloorReached(int directionVector) { Controls.ActivityTicks = 0; @@ -67,14 +82,14 @@ private void OnFloorReached(int directionVector) var nextFloorNumber = Math.Clamp(_elevator.Floor + directionVector, 0, _building.Floors.Length); var newFloor = _building.Floors[nextFloorNumber]; _elevator.Proxy.Floor = newFloor.Proxy; + _scoring.ElevatorMoved(); if (_elevator.Floor == Controls.TargetFloor) { var leavingPassengers = _elevator.Cargo.Passengers.Where(p => p.TargetFloor == _elevator.Floor).ToList(); - Console.WriteLine($"Leaving passengers: " + string.Join(", ", leavingPassengers.Select(p => p.Id))); - foreach (var passenger in leavingPassengers) { + _elevator.Cargo.RemovePassenger(passenger); newFloor.AddPersonToOutputLine(passenger); } @@ -83,31 +98,35 @@ private void OnFloorReached(int directionVector) while (_elevator.Controls.DestinationQueue.Remove(_elevator.Floor)) { } - _scoring.ElevatorMoved(); - Transition(ElevatorState.Idle); + Transition(ElevatorState.Stopped); _elevator.Proxy.RaiseOnStopped(); } } - - private void HandleIdle() + + private void HandleStopped() { - var waitingPeople = _floor.WaitingLine.ToList(); - while (waitingPeople.Any() && _elevator.Cargo.CanTakePassenger) + if(TryLoadPassengers()) { - var newPassenger = waitingPeople.First(); - _elevator.Cargo.TakePassenger(newPassenger); - - PressButtonInElevator(newPassenger.TargetFloor); - waitingPeople.RemoveAt(0); + return; } - _floor.UpdateWaitingLinePositions(); + bool minimumStayTimeElapsing = Controls.ActivityTicks < _building.Properties.MinimumStayTicks; if (minimumStayTimeElapsing) { return; } + + Transition(ElevatorState.Idle); + } + + private void HandleIdle() + { + if(TryLoadPassengers()) + { + return; + } // Remove current floor so we do not start to move on current floor while (Controls.DestinationQueue.Count > 0 && Controls.DestinationQueue.First() == _elevator.Floor) @@ -125,6 +144,29 @@ private void HandleIdle() _elevator.Proxy.RaiseOnIdle(); } + private bool TryLoadPassengers() + { + bool loadedPassenger = false; + var waitingPeople = _floor.ElevatorQueue.ToList(); + for (int i = 0; i < waitingPeople.Count && _elevator.Cargo.CanTakePassenger; i++) + { + var newPassenger = waitingPeople[i]; + _elevator.Cargo.TakePassenger(newPassenger); + + PressButtonInElevator(newPassenger.TargetFloor); + loadedPassenger = true; + } + + if(loadedPassenger) + { + _floor.UpdateWaitingLinePositions(); + Transition(ElevatorState.Stopped); + } + + + return loadedPassenger; + } + private void PressButtonInElevator(int targetFloor) { if (_elevator.RequestedFloors.Contains(targetFloor)) diff --git a/CSharpElevatorSaga/Game/GameController.cs b/CSharpElevatorSaga/Game/GameController.cs index c246014..ce66adf 100644 --- a/CSharpElevatorSaga/Game/GameController.cs +++ b/CSharpElevatorSaga/Game/GameController.cs @@ -11,7 +11,7 @@ public GameController() { const int Fps = 60; - Level = new Level(3, 1, new ScoringStub(), new UniformPeopleGenerator(60)); + Level = new Level(3, 1, new ScoringStub(), new UniformPeopleGenerator(30)); Building = new Building(3, 1, Level.Scoring); Timer = new Timer(GameTickHandler, 0, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000.0/Fps)); } diff --git a/CSharpElevatorSaga/Game/Model/Building.cs b/CSharpElevatorSaga/Game/Model/Building.cs index c88fc0b..85de75a 100644 --- a/CSharpElevatorSaga/Game/Model/Building.cs +++ b/CSharpElevatorSaga/Game/Model/Building.cs @@ -7,13 +7,13 @@ public class Building { private readonly IScoring _scoring; - private int NextPersonId { get; set; } = 0; + private int NextPersonId { get; set; } public Building(int stories, int elevators, IScoring scoring) { _scoring = scoring; - Floors = Enumerable.Range(0, stories).Select(floorNumber => new Floor(new Proxy.FloorProxy(floorNumber), People.Where(p => p.CurrentFloor == floorNumber))).ToImmutableArray(); - Elevators = Enumerable.Range(0, elevators).Select(_ => new Elevator(new Proxy.ElevatorProxy(Floors[0].Proxy), this)).ToImmutableArray(); + Floors = Enumerable.Range(0, stories).Select(floorNumber => new Floor(new Proxy.FloorProxy(floorNumber), People.Where(p => p.CurrentFloor == floorNumber), Properties)).ToImmutableArray(); + Elevators = Enumerable.Range(0, elevators).Select(n => new Elevator(new Proxy.ElevatorProxy(Floors[0].Proxy), this, n)).ToImmutableArray(); } public ImmutableArray Elevators { get; } @@ -24,15 +24,23 @@ public Building(int stories, int elevators, IScoring scoring) public List People { get; } = new(); - public Person CreatePerson(int floor){ - var person = new Person(NextPersonId++, floor); + public Person CreatePerson(int floorNumber){ + var person = new Person(NextPersonId++, floorNumber, Properties.PersonWidth); People.Add(person); - Floors[floor].AddPersonToWaitingLine(person); + var floor = Floors[floorNumber]; + person.SetPosition(new Position(-Properties.PersonWidth, floor.Y), 0); + floor.AddPersonToWaitingLine(person); + return person; } public void Tick() { + foreach (var person in People) + { + person.Tick(); + } + foreach (var elevator in Elevators) { UpdateElevator(elevator); diff --git a/CSharpElevatorSaga/Game/Model/BuildingProperties.cs b/CSharpElevatorSaga/Game/Model/BuildingProperties.cs index e11cd01..e2ed274 100644 --- a/CSharpElevatorSaga/Game/Model/BuildingProperties.cs +++ b/CSharpElevatorSaga/Game/Model/BuildingProperties.cs @@ -7,6 +7,21 @@ public class BuildingProperties public int TicksToMoveDoors { get; } = 2; public int MinimumStayTicks { get; } = 20; + public int QueueRepositionTicks { get; } = 10; public int FloorHeight { get; } = 100; + + public int PersonWidth { get; } = 10; + public int ElevatorQueueStartX { get; internal set; } = 300; + public int OutputLineStart { get; internal set; } = 600; + public int ElevatorWidth { get; internal set; } = 50; + /// + /// Distance between elevators + /// + public int ElevatorSpacing { get; internal set; } = 25; + /// + /// Distance between waiting line and elevator + /// + public int QueueSpacing { get; internal set; } = 100; + } diff --git a/CSharpElevatorSaga/Game/Model/Elevator.cs b/CSharpElevatorSaga/Game/Model/Elevator.cs index ad788d2..8aa158a 100644 --- a/CSharpElevatorSaga/Game/Model/Elevator.cs +++ b/CSharpElevatorSaga/Game/Model/Elevator.cs @@ -4,13 +4,14 @@ namespace CSharpElevatorSaga.Game.Model; public class Elevator { - public Elevator(ElevatorProxy proxy, Building building) + public Elevator(ElevatorProxy proxy, Building building, int position) { Proxy = proxy; Controls = new ElevatorControls(proxy); DirectionIndicators = proxy.DirectionIndicators; - Position = new Position(200f, proxy.Floor.Number * 100f); - Cargo = new ElevatorCargo(proxy, this, building); + var properties = building.Properties; + Position = new Position(properties.ElevatorQueueStartX + properties.QueueSpacing + position * (properties.ElevatorWidth + properties.ElevatorSpacing), proxy.Floor.Number * properties.FloorHeight); + Cargo = new ElevatorCargo(proxy, this, properties); } public int Floor => Proxy.Floor.Number; @@ -28,15 +29,4 @@ public Elevator(ElevatorProxy proxy, Building building) public ElevatorCargo Cargo { get; } public List RequestedFloors => Proxy.RequestedFloors; - - public void UpdatePosition(int targetFloor, float progress) - { - float sourceY = Floor * 100f; - int directionVector = Math.Sign(targetFloor - Floor); - int nextFloor = Floor + directionVector; - float nextFloorY = nextFloor * 100f; - Position = new Position(Position.X, sourceY + (nextFloorY - sourceY) * progress); - - Cargo.UpdatePassengerPositions(); - } } diff --git a/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs b/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs index 1d15220..5c5e460 100644 --- a/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs +++ b/CSharpElevatorSaga/Game/Model/ElevatorCargo.cs @@ -6,35 +6,55 @@ public class ElevatorCargo { private readonly ElevatorProxy _proxy; private readonly Elevator _elevator; - private readonly Building _building; - private const float PersonSpacing = 15f; + private readonly BuildingProperties _buildingProperties; + private readonly Person?[] _slots; - public ElevatorCargo(ElevatorProxy proxy, Elevator elevator, Building building) + public ElevatorCargo(ElevatorProxy proxy, Elevator elevator, BuildingProperties buildingProperties) { _proxy = proxy; _elevator = elevator; - _building = building; + _buildingProperties = buildingProperties; + _slots = new Person?[MaxPassengers]; } public int MaxPassengers { get; set; } = 3; - public IEnumerable Passengers => _building.People.Where(p => p.State == PersonState.InElevator); + public IEnumerable Passengers => _slots.Where(p => p != null)!; - public bool CanTakePassenger => Passengers.Count() < MaxPassengers; + public bool CanTakePassenger => _slots.Any(slot => slot == null); public void TakePassenger(Person person) { + var emptySlot = Array.IndexOf(_slots, null); + if (emptySlot == -1) + { + throw new InvalidOperationException("No empty slots in elevator"); + } + + _slots[emptySlot] = person; person.State = PersonState.InElevator; - UpdatePassengerPositions(); + UpdatePassengerPositions(_buildingProperties.MinimumStayTicks); + } + + public void RemovePassenger(Person person) + { + var slotIndex = Array.IndexOf(_slots, person); + if (slotIndex != -1) + { + _slots[slotIndex] = null; + } } - public void UpdatePassengerPositions() + public void UpdatePassengerPositions(int ticks = 0) { - var passengers = Passengers.ToList(); - for (int i = 0; i < passengers.Count; i++) + for (int i = 0; i < _slots.Length; i++) { - var xPos = _elevator.Position.X + (i * PersonSpacing); - passengers[i].Position = new Position(xPos, _elevator.Position.Y); + var person = _slots[i]; + if (person != null) + { + var slotX = _elevator.Position.X + (i * _buildingProperties.PersonWidth); + person.SetPosition(new Position(slotX, _elevator.Position.Y), ticks); + } } } } diff --git a/CSharpElevatorSaga/Game/Model/ElevatorState.cs b/CSharpElevatorSaga/Game/Model/ElevatorState.cs index d50d79f..3c24cb0 100644 --- a/CSharpElevatorSaga/Game/Model/ElevatorState.cs +++ b/CSharpElevatorSaga/Game/Model/ElevatorState.cs @@ -4,4 +4,8 @@ public enum ElevatorState { Idle, Moving, + /// + /// Elevator is stopped at a floor and waiting for passengers to get on or off + /// + Stopped, } diff --git a/CSharpElevatorSaga/Game/Model/Floor.cs b/CSharpElevatorSaga/Game/Model/Floor.cs index 31f820a..35c6104 100644 --- a/CSharpElevatorSaga/Game/Model/Floor.cs +++ b/CSharpElevatorSaga/Game/Model/Floor.cs @@ -10,44 +10,48 @@ public class Floor public float Y { get; } - public float WaitingLineX { get; } = 50f; - public float OutputLineX { get; } = 300f; - - public float PersonSpacing { get; } = 25f; + private BuildingProperties _buildingProperties; - public Floor(FloorProxy floorProxy, IEnumerable peopleOnFloor) + public Floor(FloorProxy floorProxy, IEnumerable peopleOnFloor, BuildingProperties buildingProperties) { Proxy = floorProxy; - Y = floorProxy.Number * 100f; + Y = floorProxy.Number * buildingProperties.FloorHeight; _peopleOnFloor = peopleOnFloor; + _buildingProperties = buildingProperties; } public FloorButtons Buttons { get; } = new(); - public IEnumerable WaitingLine => _peopleOnFloor.Where(p => p.State == PersonState.Waiting); + public IEnumerable ElevatorQueue => _peopleOnFloor.Where(p => p.State == PersonState.Waiting); - public IEnumerable OutputLine => _peopleOnFloor.Where(p => p.State == PersonState.Exited); + public IEnumerable Exiting => _peopleOnFloor.Where(p => p.State == PersonState.Exiting); public void AddPersonToWaitingLine(Person person) { person.State = PersonState.Waiting; person.CurrentFloor = Proxy.Number; - UpdateWaitingLinePositions(); + person.SetPosition(QueuePositionToWorldPosition(ElevatorQueue.Count()-1), _buildingProperties.MinimumStayTicks); } public void UpdateWaitingLinePositions() { - var waitingLine = WaitingLine.ToList(); - for (int i = 0; i < waitingLine.Count; i++){ - waitingLine[i].Position = new Position(WaitingLineX + (i * PersonSpacing), Y); - } + var waitingLine = ElevatorQueue.ToList(); + for (int i = 0; i < waitingLine.Count; i++) + { + waitingLine[i].SetPosition(QueuePositionToWorldPosition(i), _buildingProperties.QueueRepositionTicks); + } + } + + private Position QueuePositionToWorldPosition(int i) + { + return new Position(_buildingProperties.ElevatorQueueStartX - (i * _buildingProperties.PersonWidth), Y); } public void AddPersonToOutputLine(Person person) { - person.State = PersonState.Exited; + person.State = PersonState.Exiting; person.CurrentFloor = Proxy.Number; - person.Position = new Position(OutputLineX + (OutputLine.Count() * PersonSpacing), Y); + person.SetPosition(new Position(_buildingProperties.OutputLineStart + (Exiting.Count() * _buildingProperties.PersonWidth), Y), _buildingProperties.MinimumStayTicks); } } diff --git a/CSharpElevatorSaga/Game/Model/Person.cs b/CSharpElevatorSaga/Game/Model/Person.cs index fdcf4cb..f01ab54 100644 --- a/CSharpElevatorSaga/Game/Model/Person.cs +++ b/CSharpElevatorSaga/Game/Model/Person.cs @@ -4,27 +4,65 @@ public enum PersonState { Waiting, InElevator, - Exited + Exiting } public class Person { - public Person(int id, int targetFloor) + private Position _position; + private Position _targetPosition; + private int _transitionTimeRemaining; + private int _totalTransitionTime; + + public Person(int id, int targetFloor, int width) { Id = id; TargetFloor = targetFloor; - Position = Position.Zero; - State = PersonState.Waiting; - CurrentFloor = 0; + _position = Position.Zero; + _targetPosition = Position.Zero; + Width = width; } public int Id { get; } - public int TargetFloor { get; } - - public Position Position { get; set; } + public int CurrentFloor { get; set; } + public PersonState State { get; set; } = PersonState.Waiting; + public Position Position => _position; - public PersonState State { get; set; } + public int Width { get; } + public Position TargetPosition => _targetPosition; - public int CurrentFloor { get; set; } + public void SetPosition(Position newPosition, int transitionTime) + { + _targetPosition = newPosition; + if (transitionTime == 0) + { + _position = newPosition; + _transitionTimeRemaining = 0; + _totalTransitionTime = 0; + } + else + { + _transitionTimeRemaining = transitionTime; + _totalTransitionTime = transitionTime; + } + } + + public void Tick() + { + if (_transitionTimeRemaining > 0) + { + float progress = 1f - (_transitionTimeRemaining / (float)_totalTransitionTime); + _position = new Position( + _position.X + (_targetPosition.X - _position.X) * progress, + _position.Y + (_targetPosition.Y - _position.Y) * progress + ); + _transitionTimeRemaining--; + + if (_transitionTimeRemaining == 0) + { + _position = _targetPosition; + } + } + } } diff --git a/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs b/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs index 9df6253..68b9fe5 100644 --- a/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs +++ b/CSharpElevatorSaga/Game/UniformPeopleGenerator.cs @@ -26,31 +26,26 @@ public void Tick(Building building) private void GeneratePerson(Building building) { - FindEmptyFloor(building); + FindFloorWithSpace(building); - if (building.Floors[_floorNumber].WaitingLine.Count() >= MaxPeoplePerFloor) + if (building.Floors[_floorNumber].ElevatorQueue.Count() >= MaxPeoplePerFloor) { return; } - var targetFloor = new Random().Next(0, building.Floors.Length - 1); - if (targetFloor >= _floorNumber) - { - targetFloor++; - } - building.CreatePerson(targetFloor); + building.CreatePerson(_floorNumber); NextFloor(building); } - private void FindEmptyFloor(Building building) + private void FindFloorWithSpace(Building building) { int attempts = 0; - while (building.Floors[_floorNumber].WaitingLine.Count() > MaxPeoplePerFloor) + while (building.Floors[_floorNumber].ElevatorQueue.Count() >= MaxPeoplePerFloor) { NextFloor(building); attempts++; - if (attempts > building.Floors.Length) + if (attempts >= building.Floors.Length) { break; } diff --git a/CSharpElevatorSaga/Pages/FloorView.razor b/CSharpElevatorSaga/Pages/FloorView.razor index c9ec144..05994d2 100644 --- a/CSharpElevatorSaga/Pages/FloorView.razor +++ b/CSharpElevatorSaga/Pages/FloorView.razor @@ -8,7 +8,6 @@
-
diff --git a/CSharpElevatorSaga/Properties/launchSettings.json b/CSharpElevatorSaga/Properties/launchSettings.json index cbe236a..d915e4b 100644 --- a/CSharpElevatorSaga/Properties/launchSettings.json +++ b/CSharpElevatorSaga/Properties/launchSettings.json @@ -1,12 +1,4 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:14693", - "sslPort": 0 - } - }, "profiles": { "CSharpElevatorSaga": { "commandName": "Project", @@ -17,14 +9,6 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } } } From bac0e2719e10600d79c11e87c2deb4b5ef340df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Sun, 30 Mar 2025 23:19:24 +0200 Subject: [PATCH 3/3] Update to net9 --- .../CSharpElevatorSaga.Model.csproj | 2 +- CSharpElevatorSaga.Model/README.md | 1 + .../CSharpElevatorSaga.UnitTests.csproj | 10 +++++----- CSharpElevatorSaga/CSharpElevatorSaga.csproj | 18 +++++++++++++----- 4 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 CSharpElevatorSaga.Model/README.md diff --git a/CSharpElevatorSaga.Model/CSharpElevatorSaga.Model.csproj b/CSharpElevatorSaga.Model/CSharpElevatorSaga.Model.csproj index 132c02c..0d2df0d 100644 --- a/CSharpElevatorSaga.Model/CSharpElevatorSaga.Model.csproj +++ b/CSharpElevatorSaga.Model/CSharpElevatorSaga.Model.csproj @@ -1,7 +1,7 @@ - net6.0 + net9.0 enable enable diff --git a/CSharpElevatorSaga.Model/README.md b/CSharpElevatorSaga.Model/README.md new file mode 100644 index 0000000..5ab6b0f --- /dev/null +++ b/CSharpElevatorSaga.Model/README.md @@ -0,0 +1 @@ +This project contains interface which is exposed to compiler in browser. This way player can operate on elevators, while not having access to internal properies, like positions etc. diff --git a/CSharpElevatorSaga.UnitTests/CSharpElevatorSaga.UnitTests.csproj b/CSharpElevatorSaga.UnitTests/CSharpElevatorSaga.UnitTests.csproj index 4e17b5a..59a5d87 100644 --- a/CSharpElevatorSaga.UnitTests/CSharpElevatorSaga.UnitTests.csproj +++ b/CSharpElevatorSaga.UnitTests/CSharpElevatorSaga.UnitTests.csproj @@ -1,7 +1,7 @@ - net6.0 + net9.0 enable enable @@ -10,13 +10,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/CSharpElevatorSaga/CSharpElevatorSaga.csproj b/CSharpElevatorSaga/CSharpElevatorSaga.csproj index 9da5e67..5fe4335 100644 --- a/CSharpElevatorSaga/CSharpElevatorSaga.csproj +++ b/CSharpElevatorSaga/CSharpElevatorSaga.csproj @@ -1,18 +1,26 @@  - net6.0 + net9.0 enable enable - $(NoWarn);RS2008 + $(NoWarn);RS2008 true + + + false + + + false + false + true - - - + + +