From 6bed7d918fd9076dc942ac2781c11f9eee6433a2 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 14 Apr 2024 21:10:21 +0200 Subject: [PATCH] load maps from png files --- .../GameLogic/CollideBulletsWithMap.cs | 2 +- TanksServer/GameLogic/MapService.cs | 74 +++++++++++++----- TanksServer/GameLogic/MoveTanks.cs | 20 ++--- TanksServer/GameLogic/SpawnNewTanks.cs | 3 +- TanksServer/Graphics/DrawMapStep.cs | 16 ++-- .../Graphics/GeneratePixelsTickStep.cs | 7 +- .../Interactivity/ClientScreenServer.cs | 5 -- TanksServer/appsettings.json | 3 +- TanksServer/assets/maps/tanks.txt | 2 +- TanksServer/assets/maps/test2.png | Bin 0 -> 1959 bytes tank-frontend/src/App.tsx | 9 +-- tank-frontend/src/ClientScreen.tsx | 3 +- tank-frontend/src/Controls.tsx | 3 +- 13 files changed, 86 insertions(+), 61 deletions(-) create mode 100644 TanksServer/assets/maps/test2.png diff --git a/TanksServer/GameLogic/CollideBulletsWithMap.cs b/TanksServer/GameLogic/CollideBulletsWithMap.cs index faf0bf6..3c3113e 100644 --- a/TanksServer/GameLogic/CollideBulletsWithMap.cs +++ b/TanksServer/GameLogic/CollideBulletsWithMap.cs @@ -9,5 +9,5 @@ internal sealed class CollideBulletsWithMap(BulletManager bullets, MapService ma } private bool BulletHitsWall(Bullet bullet) => - map.IsCurrentlyWall(bullet.Position.ToPixelPosition().ToTilePosition()); + map.Current.IsCurrentlyWall(bullet.Position.ToPixelPosition()); } diff --git a/TanksServer/GameLogic/MapService.cs b/TanksServer/GameLogic/MapService.cs index 4699e85..6337e20 100644 --- a/TanksServer/GameLogic/MapService.cs +++ b/TanksServer/GameLogic/MapService.cs @@ -1,4 +1,6 @@ using System.IO; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace TanksServer.GameLogic; @@ -9,35 +11,65 @@ internal sealed class MapService public const ushort TileSize = 8; public const ushort PixelsPerRow = TilesPerRow * TileSize; public const ushort PixelsPerColumn = TilesPerColumn * TileSize; - private readonly string _map; - private readonly ILogger _logger; - private string[] LoadMaps() => Directory.EnumerateFiles("./assets/maps/", "*.txt") - .Select(LoadMap) - .Where(s => s != null) - .Select(s => s!) - .ToArray(); + public Map Current { get; } - private string? LoadMap(string file) + public MapService() { - var text = File.ReadAllText(file).ReplaceLineEndings(string.Empty).Trim(); - if (text.Length != TilesPerColumn * TilesPerRow) + var textMaps = Directory.EnumerateFiles("./assets/maps/", "*.txt").Select(LoadMapString); + var pngMaps = Directory.EnumerateFiles("./assets/maps/", "*.png").Select(LoadMapPng); + + var maps = textMaps.Concat(pngMaps).ToList(); + Current = maps[Random.Shared.Next(maps.Count)]; + } + + private static Map LoadMapPng(string file) + { + var dict = new Dictionary(); + using var image = Image.Load(file); + + if (image.Width != PixelsPerRow || image.Height != PixelsPerColumn) + throw new FileLoadException($"invalid image size in file {file}"); + + var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + for (var y = 0; y < image.Height; y++) + for (var x = 0; x < image.Width; x++) { - _logger.LogWarning("cannot load map {}: invalid length", file); - return null; + if (image[x, y] == whitePixel) + dict[new PixelPosition(x, y)] = true; } - return text; + return new Map(dict); } - public MapService(ILogger logger) + private static Map LoadMapString(string file) { - _logger = logger; - var maps = LoadMaps(); - _map = maps[Random.Shared.Next(0, maps.Length)]; + var map = File.ReadAllText(file).ReplaceLineEndings(string.Empty).Trim(); + if (map.Length != TilesPerColumn * TilesPerRow) + throw new FileLoadException($"cannot load map {file}: invalid length"); + + var dict = new Dictionary(); + + for (ushort tileY = 0; tileY < MapService.TilesPerColumn; tileY++) + for (ushort tileX = 0; tileX < MapService.TilesPerRow; tileX++) + { + var tile = new TilePosition(tileX, tileY); + if (map[tileX + tileY * TilesPerRow] != '#') + continue; + + for (byte pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++) + for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++) + { + var pixel = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY); + dict[pixel] = true; + } + } + + return new Map(dict); } - - private char this[int tileX, int tileY] => _map[tileX + tileY * TilesPerRow]; - - public bool IsCurrentlyWall(TilePosition position) => this[position.X, position.Y] == '#'; +} + +internal sealed class Map(IReadOnlyDictionary walls) +{ + public bool IsCurrentlyWall(PixelPosition position) => walls.TryGetValue(position, out var value) && value; } diff --git a/TanksServer/GameLogic/MoveTanks.cs b/TanksServer/GameLogic/MoveTanks.cs index 0bd0656..31da8f0 100644 --- a/TanksServer/GameLogic/MoveTanks.cs +++ b/TanksServer/GameLogic/MoveTanks.cs @@ -63,14 +63,16 @@ internal sealed class MoveTanks( private bool HitsWall(FloatPosition newPosition) { - var (topLeft, bottomRight) = Tank.GetBoundsForCenter(newPosition); - TilePosition[] positions = - [ - topLeft.ToTilePosition(), - new PixelPosition(bottomRight.X, topLeft.Y).ToTilePosition(), - new PixelPosition(topLeft.X, bottomRight.Y).ToTilePosition(), - bottomRight.ToTilePosition() - ]; - return positions.Any(map.IsCurrentlyWall); + var (topLeft, _) = Tank.GetBoundsForCenter(newPosition); + + for (short y = 0; y < MapService.TileSize; y++) + for (short x = 0; x < MapService.TileSize; x++) + { + var pixelToCheck = topLeft.GetPixelRelative(x, y); + if (map.Current.IsCurrentlyWall(pixelToCheck)) + return true; + } + + return false; } } diff --git a/TanksServer/GameLogic/SpawnNewTanks.cs b/TanksServer/GameLogic/SpawnNewTanks.cs index ccf9cc9..b523810 100644 --- a/TanksServer/GameLogic/SpawnNewTanks.cs +++ b/TanksServer/GameLogic/SpawnNewTanks.cs @@ -29,7 +29,8 @@ internal sealed class SpawnNewTanks( { var tile = new TilePosition(x, y); - if (map.IsCurrentlyWall(tile)) + // TODO: implement lookup for non tile aligned walls + if (map.Current.IsCurrentlyWall(tile.ToPixelPosition())) continue; var tilePixelCenter = tile.ToPixelPosition().GetPixelRelative(4, 4).ToFloatPosition(); diff --git a/TanksServer/Graphics/DrawMapStep.cs b/TanksServer/Graphics/DrawMapStep.cs index b153b26..8214612 100644 --- a/TanksServer/Graphics/DrawMapStep.cs +++ b/TanksServer/Graphics/DrawMapStep.cs @@ -7,19 +7,13 @@ internal sealed class DrawMapStep(MapService map) : IDrawStep { public void Draw(PixelGrid buffer) { - for (ushort tileY = 0; tileY < MapService.TilesPerColumn; tileY++) - for (ushort tileX = 0; tileX < MapService.TilesPerRow; tileX++) + for (ushort y = 0; y < MapService.PixelsPerColumn; y++) + for (ushort x = 0; x < MapService.PixelsPerRow; x++) { - var tile = new TilePosition(tileX, tileY); - if (!map.IsCurrentlyWall(tile)) + var pixel = new PixelPosition(x, y); + if (!map.Current.IsCurrentlyWall(pixel)) continue; - - for (byte pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++) - for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++) - { - var (x, y) = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY); - buffer[(ushort)x, (ushort)y] = pixelInTileX % 2 == pixelInTileY % 2; - } + buffer[x, y] = true; } } } diff --git a/TanksServer/Graphics/GeneratePixelsTickStep.cs b/TanksServer/Graphics/GeneratePixelsTickStep.cs index 442ce83..d6135ab 100644 --- a/TanksServer/Graphics/GeneratePixelsTickStep.cs +++ b/TanksServer/Graphics/GeneratePixelsTickStep.cs @@ -9,13 +9,14 @@ internal sealed class GeneratePixelsTickStep( ) : ITickStep { private readonly List _drawSteps = drawSteps.ToList(); + private readonly PixelGrid _drawGrid = new(MapService.PixelsPerRow, MapService.PixelsPerColumn); public Task TickAsync() { - var drawGrid = new PixelGrid(MapService.PixelsPerRow, MapService.PixelsPerColumn); + _drawGrid.Clear(); foreach (var step in _drawSteps) - step.Draw(drawGrid); - lastFrameProvider.LastFrame = drawGrid; + step.Draw(_drawGrid); + lastFrameProvider.LastFrame = _drawGrid; return Task.CompletedTask; } } diff --git a/TanksServer/Interactivity/ClientScreenServer.cs b/TanksServer/Interactivity/ClientScreenServer.cs index b6a00a6..6c79f15 100644 --- a/TanksServer/Interactivity/ClientScreenServer.cs +++ b/TanksServer/Interactivity/ClientScreenServer.cs @@ -56,7 +56,6 @@ internal sealed class ClientScreenServer( private readonly ILogger _logger; private readonly ClientScreenServer _server; private readonly SemaphoreSlim _wantedFrames = new(1); - private PixelGrid? _lastSentPixels; public ClientScreenServerConnection(WebSocket webSocket, ILogger logger, @@ -78,9 +77,6 @@ internal sealed class ClientScreenServer( public async Task SendAsync(PixelGrid pixels) { - if (_lastSentPixels == pixels) - return; - if (!await _wantedFrames.WaitAsync(TimeSpan.Zero)) { _logger.LogTrace("client does not want a frame yet"); @@ -91,7 +87,6 @@ internal sealed class ClientScreenServer( try { await _channel.SendAsync(pixels.Data); - _lastSentPixels = pixels; } catch (WebSocketException ex) { diff --git a/TanksServer/appsettings.json b/TanksServer/appsettings.json index 2d31e8b..54fa331 100644 --- a/TanksServer/appsettings.json +++ b/TanksServer/appsettings.json @@ -4,8 +4,7 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning", "TanksServer": "Debug", - "Microsoft.AspNetCore.HttpLogging": "Information", - "TanksServer.GameLogic.RotateTanks": "Trace" + "Microsoft.AspNetCore.HttpLogging": "Information" } }, "AllowedHosts": "*", diff --git a/TanksServer/assets/maps/tanks.txt b/TanksServer/assets/maps/tanks.txt index 25fa716..ff5fe3d 100644 --- a/TanksServer/assets/maps/tanks.txt +++ b/TanksServer/assets/maps/tanks.txt @@ -10,7 +10,7 @@ ................#..##...#...#..............# ................#...#...#..#...............# ........................###................# -........................#..#......###......# +........................#..#.....####......# ........................#...#...#..........# .................................###.......# ....................................#...#..# diff --git a/TanksServer/assets/maps/test2.png b/TanksServer/assets/maps/test2.png new file mode 100644 index 0000000000000000000000000000000000000000..9c4ba800908f92b6178022e6fc1fa7fefd4f4a85 GIT binary patch literal 1959 zcmXw3X;c(f8huq(Lp2RjfTU&IinS4&MfQj$4%tG45zFk$cbVbr#(EYy^} z6Dy4C6}D!$oAJi`ew+1N-!2X@=`+=$H}zfU_PXNxwh=+t^Qrqr^h>*<91&!Of|#7q zgc%Yf^_cpB^iGd+GXZ8C+NVb|Y>5ral?jd^93qHGL!itCVfcHz>w)jf9hqf(V5CDGKFqbgy|qWRH5vulR~6`h;a`z^rLJsX z5PbY&J12d6Wm!#39SUn~b0NrC^1_1K5`I?3o9hrv7~Znj&JqxMI5g_@g$M}vQB)?~ z7z}2poq^v#ye}|k2x9r=U8WG3NeM0Fy2a`+{4pwYsrm~5&yr-;*e!F)N&MzKAClJS zY|1xkTD(chL2^qh#MMWf&e<_}2-omGt2FNu%fGTYT>o5q{pzcEeQhnCs}3TEKedJ` ze8-`%{5Cm|pww;{U~QDGi@X=k96t714v|oHl6Ay`7l&%W1~9}pL;ac_u@a*HM7N;D zDQT`n`k`e3(IY3S&8D6mL_HgaYCz*whT$ba-vD1>G5B0~>U~I!nq@g@iJ?yT3h-oc z^cSWqPWHvfnYHj^75oPJrxooxbBP>POPnrbjT~Ai?L#1c#G2)%_IRuz$OO^KIsQBJ zGCOFp8Y!nqvOQO_N-kX5sb?IMx|;kH;&o{BGDOjd<-$KCCS;fm#pvW+#ZjcZC!(~^ zWq6*VkBMo)Jsa9Bvy(#~%u_ZGdjDghsXf=98dt|dJrcLM>caX8ZW|pe*6A^2_>j$d z&@*fzM%SyWN?|($>R;5)0>7(ZPgbOxP7ZXA^I8jks9P4em$5qlw&eh4YgYi~GGOj_ z=s*x`+Y&_A)$I&H28!4JdR?E=etdG%If{+~r1H)bXNa6F%c*v^Z4)z1(4TAeUP+`G z?@bu38cP4sl#KwO*okkt88-6>XgKxgpe=3y>SqUy_FH1X9;zO7(L991fD_-mY}$|G zKPDe68m@2JFwtN?9YCm#FWd83c^z%ws1ybm6vSIWM)LEyV5^hYkzbarV~((NZt?`w)2 zaYHkQ(jR(j>~KRbhhC0tG-ud5Q5boBUi0}oxv>9so~Nr7jD}x+SJWb90oZ$Kp!dv| z;TtjI@B$S}(lmh`f{~06Bv38lNloV2G zGu^nzhD;@fPaeQ$Icc%#twgxqPR5dRlscfK>IDcM2QhKoR9f&d@$ZnWb ztzZA-NRS2KT*D5Bzar)T>0x6c$)R^p_P=aXkHPw`W(!^S15V{pJEPYRGq-rj23^Nh zkx4CnDBSzfZWcEj=Xauae||SJUR1gYI)=%L5|a9M9ZG0n``Zyb!5@YBpY==5N9e!z CAk+*1 literal 0 HcmV?d00001 diff --git a/tank-frontend/src/App.tsx b/tank-frontend/src/App.tsx index eab496e..7579689 100644 --- a/tank-frontend/src/App.tsx +++ b/tank-frontend/src/App.tsx @@ -28,7 +28,7 @@ export default function App() { if (isLoggedIn) return; const result = await postPlayer(nameId); - setLoggedIn(result !== null); + setLoggedIn(result.ok); }, [nameId, isLoggedIn])(); return @@ -43,11 +43,10 @@ export default function App() { {nameId.name === '' && } - {isLoggedIn && - - + + {isLoggedIn && } + {isLoggedIn && } - } ; } diff --git a/tank-frontend/src/ClientScreen.tsx b/tank-frontend/src/ClientScreen.tsx index 0169590..67d27a9 100644 --- a/tank-frontend/src/ClientScreen.tsx +++ b/tank-frontend/src/ClientScreen.tsx @@ -55,7 +55,8 @@ export default function ClientScreen({logout, theme}: { logout: () => void, them sendMessage, getWebSocket } = useWebSocket(import.meta.env.VITE_TANK_SCREEN_URL, { - onError: logout + onError: logout, + shouldReconnect: () => true, }); const socket = getWebSocket(); diff --git a/tank-frontend/src/Controls.tsx b/tank-frontend/src/Controls.tsx index 15a3b46..5ee9027 100644 --- a/tank-frontend/src/Controls.tsx +++ b/tank-frontend/src/Controls.tsx @@ -13,7 +13,8 @@ export default function Controls({playerId, logout}: { getWebSocket, readyState } = useWebSocket(url.toString(), { - onError: logout + onError: logout, + shouldReconnect: () => true, }); const socket = getWebSocket();