Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch and Debug Standalone Blazor WebAssembly App",
"type": "blazorwasm",
"request": "launch",
"cwd": "${workspaceFolder}/CSharpElevatorSaga"
}
]
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
2 changes: 1 addition & 1 deletion CSharpElevatorSaga.Model/CSharpElevatorSaga.Model.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions CSharpElevatorSaga.Model/README.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 5 additions & 5 deletions CSharpElevatorSaga.UnitTests/CSharpElevatorSaga.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

Expand All @@ -10,13 +10,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
2 changes: 2 additions & 0 deletions CSharpElevatorSaga.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Usings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
18 changes: 13 additions & 5 deletions CSharpElevatorSaga/CSharpElevatorSaga.csproj
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);RS2008</NoWarn>
<NoWarn>$(NoWarn);RS2008</NoWarn>
<InvariantGlobalization>true</InvariantGlobalization><!-- Disable globalization to reduce size -->

<!-- TODO It may be possible to link required assemblies https://github.com/unoplatform/uno/discussions/10564#discussioncomment-4437362 -->
<!-- Webcil is new assembly format, stored in .wasm size. It seems that these assemblies cannot be loaded dynamically unlike DLLs. I need these assemblies to compile code correctly -->
<WasmEnableWebcil>false</WasmEnableWebcil>

<!-- .NET 9 compresses assemblies, and adds fingerprint to them. We need to load assemblies at runtime - this way it doesn't interfere -->
<StaticWebAssetFingerprintingEnabled>false</StaticWebAssetFingerprintingEnabled>
<StaticWebAssetsFingerprintContent>false</StaticWebAssetsFingerprintContent>
<DisableBuildCompression>true</DisableBuildCompression>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
</ItemGroup>

<ItemGroup>
Expand Down
22 changes: 11 additions & 11 deletions CSharpElevatorSaga/Compilation/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class Compiler
private readonly IAssemblyResolver _assemblyResolver;
private int _assemblyNumber;
private bool _warmedUp;
private static IReadOnlyList<string> _assemblyFileNames = new string[]
private static readonly IReadOnlyList<string> AssemblyFileNames = new[]
{
"System.Collections.dll",
"System.Console.dll",
Expand All @@ -40,15 +40,15 @@ public async Task PreloadAssemblies()

_warmedUp = true;
var metadata = new List<MetadataReference>();
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
{
Expand Down Expand Up @@ -78,15 +78,15 @@ public async Task<CompileResult> CompileCodeAsync(string code)
}
var diagnosticsConfiguration = new List<KeyValuePair<string, ReportDiagnostic>>()
{
new KeyValuePair<string, ReportDiagnostic>("CS8321", ReportDiagnostic.Hidden)
new("CS8321", ReportDiagnostic.Hidden)
};
CSharpCompilation compilation = CSharpCompilation.Create(
"Solution" + _assemblyNumber++,
new[] { syntaxTree, usingStringSyntaxTree },
_assemblyMetadata,
new CSharpCompilationOptions(OutputKind.ConsoleApplication, false, specificDiagnosticOptions: diagnosticsConfiguration, concurrentBuild: false));

using (MemoryStream stream = new MemoryStream())
using (MemoryStream stream = new())
{
EmitResult result = compilation.Emit(stream);

Expand Down Expand Up @@ -121,7 +121,7 @@ private static void AddDiagnostic(Diagnostic diagnostic, List<Diagnostic> diagno

private static SyntaxTree MakeUsingSyntaxTree()
{
var usings = new string[] {
var usings = new[] {
"System",
"System.Linq",
"System.Collections",
Expand Down Expand Up @@ -160,10 +160,10 @@ private static CompileResult LoadAssembly(MemoryStream stream, List<Diagnostic>
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
Expand All @@ -175,9 +175,9 @@ public CompileResult(bool success, IReadOnlyList<Diagnostic> diagnostics, Progra
Program = program;
}

public static CompileResult Fail(IReadOnlyList<Diagnostic> diagnostics) => new CompileResult(false, diagnostics, null);
public static CompileResult Fail(IReadOnlyList<Diagnostic> diagnostics) => new(false, diagnostics, null);

public static CompileResult Compiled(IReadOnlyList<Diagnostic> diagnostics, ProgramEntryPoint.RunProgram program) => new CompileResult(true, diagnostics, program);
public static CompileResult Compiled(IReadOnlyList<Diagnostic> diagnostics, ProgramEntryPoint.RunProgram program) => new(true, diagnostics, program);

[MemberNotNullWhen(true, nameof(Program))]
public bool Success { get; }
Expand Down
103 changes: 81 additions & 22 deletions CSharpElevatorSaga/Game/ElevatorController.cs
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -28,6 +30,9 @@ internal void Tick()
case ElevatorState.Idle:
HandleIdle();
break;
case ElevatorState.Stopped:
HandleStopped();
break;
}
}

Expand All @@ -45,54 +50,83 @@ 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;
UpdateElevatorPosition(Controls.TargetFloor, progress);

bool reachedFloor = Controls.ActivityTicks >= _building.Properties.TicksPerStory;
if (!reachedFloor)
if (reachedFloor)
{
return;
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;

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.RemovePassengersGoingTo(_elevator.Floor);
var leavingPassengers = _elevator.Cargo.Passengers.Where(p => p.TargetFloor == _elevator.Floor).ToList();
foreach (var passenger in leavingPassengers)
{
_elevator.Cargo.RemovePassenger(passenger);
newFloor.AddPersonToOutputLine(passenger);
}

newFloor.OutputLine.AddRange(leavingPassengers);
_scoring.PersonTransported(leavingPassengers.Count);

while (_elevator.Controls.DestinationQueue.Remove(_elevator.Floor) == true)
while (_elevator.Controls.DestinationQueue.Remove(_elevator.Floor))
{
;
}
Transition(ElevatorState.Idle);

Transition(ElevatorState.Stopped);
_elevator.Proxy.RaiseOnStopped();
}
}

private void HandleIdle()
private void HandleStopped()
{
while (_floor.WaitingLine.Any() && _elevator.Cargo.CanTakePassenger)
if(TryLoadPassengers())
{
var newPassenger = _floor.WaitingLine.Dequeue();
_elevator.Cargo.TakePassenger(newPassenger);

PressButtonInElevator(newPassenger.TargetFloor);
return;
}


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)
Expand All @@ -110,13 +144,38 @@ 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))
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)
Expand Down
Loading
Loading