From ea7f12b0c26172f7f2be4bbf4646ca4ce3e48f2f Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sat, 6 Apr 2024 13:46:34 +0200 Subject: [PATCH] initial project, implement binary format of display --- .dockerignore | 25 +++++ .editorconfig | 10 ++ .gitignore | 3 + TanksServer.sln | 16 +++ TanksServer/DisplayPixelBuffer.cs | 55 ++++++++++ TanksServer/Dockerfile | 20 ++++ TanksServer/FixedSizeBitFieldView.cs | 101 ++++++++++++++++++ TanksServer/GlobalUsings.cs | 3 + TanksServer/MapDrawer.cs | 40 +++++++ TanksServer/MapService.cs | 41 +++++++ TanksServer/Program.cs | 41 +++++++ TanksServer/Properties/launchSettings.json | 12 +++ TanksServer/ServicePointDisplay.cs | 14 +++ .../ServicePointDisplayConfiguration.cs | 7 ++ TanksServer/TanksServer.csproj | 18 ++++ TanksServer/appsettings.Development.json | 8 ++ TanksServer/appsettings.json | 9 ++ global.json | 7 ++ 18 files changed, 430 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 TanksServer.sln create mode 100644 TanksServer/DisplayPixelBuffer.cs create mode 100644 TanksServer/Dockerfile create mode 100644 TanksServer/FixedSizeBitFieldView.cs create mode 100644 TanksServer/GlobalUsings.cs create mode 100644 TanksServer/MapDrawer.cs create mode 100644 TanksServer/MapService.cs create mode 100644 TanksServer/Program.cs create mode 100644 TanksServer/Properties/launchSettings.json create mode 100644 TanksServer/ServicePointDisplay.cs create mode 100644 TanksServer/ServicePointDisplayConfiguration.cs create mode 100644 TanksServer/TanksServer.csproj create mode 100644 TanksServer/appsettings.Development.json create mode 100644 TanksServer/appsettings.json create mode 100644 global.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..38bece4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b175ed5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb1cef4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin +obj +.idea diff --git a/TanksServer.sln b/TanksServer.sln new file mode 100644 index 0000000..8f630dd --- /dev/null +++ b/TanksServer.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TanksServer", "TanksServer\TanksServer.csproj", "{D88BF376-47A4-4C72-ADD1-983F9285C351}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D88BF376-47A4-4C72-ADD1-983F9285C351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D88BF376-47A4-4C72-ADD1-983F9285C351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D88BF376-47A4-4C72-ADD1-983F9285C351}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/TanksServer/DisplayPixelBuffer.cs b/TanksServer/DisplayPixelBuffer.cs new file mode 100644 index 0000000..ea688c2 --- /dev/null +++ b/TanksServer/DisplayPixelBuffer.cs @@ -0,0 +1,55 @@ +namespace TanksServer; + +public sealed class DisplayPixelBuffer(byte[] data) +{ + public byte[] Data => data; + + public byte Magic1 + { + get => data[0]; + set => data[0] = value; + } + + public byte Magic2 + { + get => data[1]; + set => data[1] = value; + } + + public ushort X + { + get => GetTwoBytes(2); + set => SetTwoBytes(2, value); + } + + public ushort Y + { + get => GetTwoBytes(4); + set => SetTwoBytes(4, value); + } + + public ushort WidthInTiles + { + get => GetTwoBytes(6); + set => SetTwoBytes(6, value); + } + + public ushort HeightInPixels + { + get => GetTwoBytes(8); + set => SetTwoBytes(8, value); + } + + public FixedSizeBitFieldView Pixels { get; } = new(data.AsMemory(10)); + + private ushort GetTwoBytes(int index) + { + return (ushort)(data[index] * byte.MaxValue + data[index + 1]); + } + + private void SetTwoBytes(int index, ushort value) + { + data[index] = (byte)(value / byte.MaxValue); + data[index + 1] = (byte)(value % byte.MaxValue); + } +} diff --git a/TanksServer/Dockerfile b/TanksServer/Dockerfile new file mode 100644 index 0000000..9d92e08 --- /dev/null +++ b/TanksServer/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src +COPY ["TanksServer/TanksServer.csproj", "TanksServer/"] +RUN dotnet restore "TanksServer/TanksServer.csproj" +COPY . . +WORKDIR "/src/TanksServer" +RUN dotnet build "TanksServer.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "TanksServer.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "TanksServer.dll"] diff --git a/TanksServer/FixedSizeBitFieldView.cs b/TanksServer/FixedSizeBitFieldView.cs new file mode 100644 index 0000000..fcd9671 --- /dev/null +++ b/TanksServer/FixedSizeBitFieldView.cs @@ -0,0 +1,101 @@ +using System.Collections; + +namespace TanksServer; + +public sealed class FixedSizeBitFieldView(Memory data) : IList +{ + public int Count => data.Length * 8; + public bool IsReadOnly => false; + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return Enumerable().GetEnumerator(); + + IEnumerable Enumerable() + { + for (var i = 0; i < Count; i++) + yield return this[i]; + } + } + + public void Clear() + { + var span = data.Span; + for (var i = 0; i < data.Length; i++) + span[i] = 0; + } + + public void CopyTo(bool[] array, int arrayIndex) + { + for (var i = 0; i < Count && i + arrayIndex < array.Length; i++) + array[i + arrayIndex] = this[i]; + } + + private static (int byteIndex, int bitInByteIndex) GetIndexes(int bitIndex) + { + var byteIndex = bitIndex / 8; + var bitInByteIndex = 7 - bitIndex % 8; + return (byteIndex, bitInByteIndex); + } + + public bool this[int bitIndex] + { + get + { + var (byteIndex, bitInByteIndex) = GetIndexes(bitIndex); + var bitInByteMask = (byte)(1 << bitInByteIndex); + return (data.Span[byteIndex] & bitInByteMask) != 0; + } + + set + { + var (byteIndex, bitInByteIndex) = GetIndexes(bitIndex); + var bitInByteMask = (byte)(1 << bitInByteIndex); + + if (value) + { + data.Span[byteIndex] |= bitInByteMask; + } + else + { + var withoutBitMask = (byte)(ushort.MaxValue ^ bitInByteMask); + data.Span[byteIndex] &= withoutBitMask; + } + } + } + + public void Add(bool item) + { + throw new NotSupportedException(); + } + + public bool Contains(bool item) + { + throw new NotSupportedException(); + } + + public bool Remove(bool item) + { + throw new NotSupportedException(); + } + + public int IndexOf(bool item) + { + throw new NotSupportedException(); + } + + public void Insert(int index, bool item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } +} diff --git a/TanksServer/GlobalUsings.cs b/TanksServer/GlobalUsings.cs new file mode 100644 index 0000000..cc5fbc3 --- /dev/null +++ b/TanksServer/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; diff --git a/TanksServer/MapDrawer.cs b/TanksServer/MapDrawer.cs new file mode 100644 index 0000000..f601859 --- /dev/null +++ b/TanksServer/MapDrawer.cs @@ -0,0 +1,40 @@ +namespace TanksServer; + +public class MapDrawer(MapService map) +{ + private const uint GameFieldPixelCount = MapService.PixelsPerRow * MapService.PixelsPerColumn; + + public void DrawInto(DisplayPixelBuffer buf) + { + for (var tileY = 0; tileY < MapService.TilesPerColumn; tileY++) + for (var tileX = 0; tileX < MapService.TilesPerRow; tileX++) + { + if (!map.IsCurrentlyWall(tileX, tileY)) + continue; + + var absoluteTilePixelY = tileY * MapService.TileSize; + for (var pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++) + { + var absoluteRowStartPixelIndex = (absoluteTilePixelY + pixelInTileY) * MapService.PixelsPerRow + + tileX * MapService.TileSize; + for (var pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++) + buf.Pixels[absoluteRowStartPixelIndex + pixelInTileX] = pixelInTileX % 2 == pixelInTileY % 2; + } + } + } + + public DisplayPixelBuffer CreateGameFieldPixelBuffer() + { + var data = new byte[10 + GameFieldPixelCount / 8]; + var result = new DisplayPixelBuffer(data) + { + Magic1 = 0, + Magic2 = 19, + X = 0, + Y = 0, + WidthInTiles = MapService.TilesPerRow, + HeightInPixels = MapService.PixelsPerColumn + }; + return result; + } +} diff --git a/TanksServer/MapService.cs b/TanksServer/MapService.cs new file mode 100644 index 0000000..03671b7 --- /dev/null +++ b/TanksServer/MapService.cs @@ -0,0 +1,41 @@ +namespace TanksServer; + +public class MapService +{ + public const int TilesPerRow = 44; + public const int TilesPerColumn = 20; + public const int TileSize = 8; + public const int PixelsPerRow = TilesPerRow * TileSize; + public const int PixelsPerColumn = TilesPerColumn * TileSize; + + private string _map = """ + ############################################ + #...................##.....................# + #...................##.....................# + #.....####......................####.......# + #..........................................# + #............###...........###.............# + #............#...............#.............# + #...##.......#...............#......##.....# + #....#..............................#......# + #....#..##......................##..#......# + #....#..##......................##..#......# + #....#..............................#......# + #...##.......#...............#......##.....# + #............#...............#.............# + #............###...........###.............# + #..........................................# + #.....####......................####.......# + #...................##.....................# + #...................##.....................# + ############################################ + """ + .ReplaceLineEndings(string.Empty); + + private char this[int tileX, int tileY] => _map[tileX + tileY * TilesPerRow]; + + public bool IsCurrentlyWall(int tileX, int tileY) + { + return this[tileX, tileY] == '#'; + } +} diff --git a/TanksServer/Program.cs b/TanksServer/Program.cs new file mode 100644 index 0000000..85fc25c --- /dev/null +++ b/TanksServer/Program.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace TanksServer; + +internal static class Program +{ + public static async Task Main(string[] args) + { + var app = Configure(args); + + var display = app.Services.GetRequiredService(); + var mapDrawer = app.Services.GetRequiredService(); + + var buffer = mapDrawer.CreateGameFieldPixelBuffer(); + mapDrawer.DrawInto(buffer); + await display.Send(buffer); + + app.UseWebSockets(); + + app.Map("/screen", async context => + { + if (!context.WebSockets.IsWebSocketRequest) + return; + using var ws = await context.WebSockets.AcceptWebSocketAsync(); + }); + + await app.RunAsync(); + } + + private static WebApplication Configure(string[] args) + { + var builder = WebApplication.CreateSlimBuilder(args); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + return builder.Build(); + } +} diff --git a/TanksServer/Properties/launchSettings.json b/TanksServer/Properties/launchSettings.json new file mode 100644 index 0000000..14d1823 --- /dev/null +++ b/TanksServer/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/TanksServer/ServicePointDisplay.cs b/TanksServer/ServicePointDisplay.cs new file mode 100644 index 0000000..cf1220d --- /dev/null +++ b/TanksServer/ServicePointDisplay.cs @@ -0,0 +1,14 @@ +using System.Net.Sockets; +using Microsoft.Extensions.Options; + +namespace TanksServer; + +public class ServicePointDisplay(IOptions options) +{ + private readonly UdpClient _udpClient = new(options.Value.Hostname, options.Value.Port); + + public ValueTask Send(DisplayPixelBuffer buffer) + { + return _udpClient.SendAsync(buffer.Data); + } +} diff --git a/TanksServer/ServicePointDisplayConfiguration.cs b/TanksServer/ServicePointDisplayConfiguration.cs new file mode 100644 index 0000000..8165a68 --- /dev/null +++ b/TanksServer/ServicePointDisplayConfiguration.cs @@ -0,0 +1,7 @@ +namespace TanksServer; + +public class ServicePointDisplayConfiguration +{ + public string Hostname { get; set; } = string.Empty; + public int Port { get; set; } +} diff --git a/TanksServer/TanksServer.csproj b/TanksServer/TanksServer.csproj new file mode 100644 index 0000000..ef4cbb5 --- /dev/null +++ b/TanksServer/TanksServer.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + disable + true + true + Linux + + + + + .dockerignore + + + + diff --git a/TanksServer/appsettings.Development.json b/TanksServer/appsettings.Development.json new file mode 100644 index 0000000..a34cd70 --- /dev/null +++ b/TanksServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/TanksServer/appsettings.json b/TanksServer/appsettings.json new file mode 100644 index 0000000..23160a4 --- /dev/null +++ b/TanksServer/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/global.json b/global.json new file mode 100644 index 0000000..dad2db5 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file