tanks can spawn and get rendered
This commit is contained in:
		
							parent
							
								
									151cad4cee
								
							
						
					
					
						commit
						a3bd582b2e
					
				
					 20 changed files with 286 additions and 108 deletions
				
			
		|  | @ -9,7 +9,7 @@ namespace TanksServer; | |||
| internal sealed class ClientScreenServer( | ||||
|     ILogger<ClientScreenServer> logger, | ||||
|     ILoggerFactory loggerFactory, | ||||
|     MapDrawer drawer | ||||
|     PixelDrawer drawer | ||||
| ) : IHostedLifecycleService, ITickStep | ||||
| { | ||||
|     private readonly List<ClientScreenServerConnection> _connections = new(); | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ using System.Net.WebSockets; | |||
| using Microsoft.Extensions.Hosting; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using TanksServer.Helpers; | ||||
| using TanksServer.Models; | ||||
| 
 | ||||
| namespace TanksServer; | ||||
| 
 | ||||
|  | @ -35,8 +36,9 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact | |||
|         _connections.Remove(connection); | ||||
|     } | ||||
| 
 | ||||
|     private sealed class ControlsServerConnection(WebSocket socket, ILogger logger, ControlsServer server, | ||||
|             Player player) | ||||
|     private sealed class ControlsServerConnection( | ||||
|             WebSocket socket, ILogger<ControlsServerConnection> logger, | ||||
|             ControlsServer server, Player player) | ||||
|         : EasyWebSocket(socket, logger, new byte[2]) | ||||
|     { | ||||
|         private enum MessageType : byte | ||||
|  | @ -58,8 +60,8 @@ internal sealed class ControlsServer(ILogger<ControlsServer> logger, ILoggerFact | |||
|         { | ||||
|             var type = (MessageType)buffer[0]; | ||||
|             var control = (InputType)buffer[1]; | ||||
|              | ||||
|             logger.LogTrace("player input {} {} {}", player.Id, type, control); | ||||
| 
 | ||||
|             Logger.LogTrace("player input {} {} {}", player.Id, type, control); | ||||
| 
 | ||||
|             var isEnable = type switch | ||||
|             { | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| using System.Text.Json.Serialization; | ||||
| using TanksServer.Models; | ||||
| 
 | ||||
| namespace TanksServer; | ||||
| namespace TanksServer.Helpers; | ||||
| 
 | ||||
| [JsonSerializable(typeof(Player))] | ||||
| internal partial class AppSerializerContext: JsonSerializerContext; | ||||
| internal sealed partial class AppSerializerContext: JsonSerializerContext; | ||||
|  |  | |||
|  | @ -1,6 +1,4 @@ | |||
| using TanksServer.Helpers; | ||||
| 
 | ||||
| namespace TanksServer; | ||||
| namespace TanksServer.Helpers; | ||||
| 
 | ||||
| internal sealed class DisplayPixelBuffer(byte[] data) | ||||
| { | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ internal abstract class EasyWebSocket | |||
| { | ||||
|     private readonly TaskCompletionSource _completionSource = new(); | ||||
| 
 | ||||
|     private readonly ILogger _logger; | ||||
|     protected readonly ILogger Logger; | ||||
|     private readonly WebSocket _socket; | ||||
|     private readonly Task _readLoop; | ||||
|     private readonly ArraySegment<byte> _buffer; | ||||
|  | @ -19,7 +19,7 @@ internal abstract class EasyWebSocket | |||
|     protected EasyWebSocket(WebSocket socket, ILogger logger, ArraySegment<byte> buffer) | ||||
|     { | ||||
|         _socket = socket; | ||||
|         _logger = logger; | ||||
|         Logger = logger; | ||||
|         _buffer = buffer; | ||||
|         _readLoop = ReadLoopAsync(); | ||||
|     } | ||||
|  | @ -46,7 +46,7 @@ internal abstract class EasyWebSocket | |||
|         if (_socket.State != WebSocketState.Open) | ||||
|             await CloseAsync(); | ||||
| 
 | ||||
|         _logger.LogTrace("sending {} bytes of data", _buffer.Count); | ||||
|         Logger.LogTrace("sending {} bytes of data", _buffer.Count); | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|  | @ -54,7 +54,7 @@ internal abstract class EasyWebSocket | |||
|         } | ||||
|         catch (WebSocketException wsEx) | ||||
|         { | ||||
|             _logger.LogDebug(wsEx, "send failed"); | ||||
|             Logger.LogDebug(wsEx, "send failed"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -65,7 +65,7 @@ internal abstract class EasyWebSocket | |||
|     { | ||||
|         if (Interlocked.Exchange(ref _closed, 1) == 1) | ||||
|             return; | ||||
|         _logger.LogDebug("closing socket"); | ||||
|         Logger.LogDebug("closing socket"); | ||||
|         await _socket.CloseAsync(status, description, CancellationToken.None); | ||||
|         await _readLoop; | ||||
|         await ClosingAsync(); | ||||
|  |  | |||
							
								
								
									
										3
									
								
								TanksServer/Models/PixelPosition.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								TanksServer/Models/PixelPosition.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| namespace TanksServer.Models; | ||||
| 
 | ||||
| internal record struct PixelPosition(int X, int Y); | ||||
							
								
								
									
										19
									
								
								TanksServer/Models/Player.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								TanksServer/Models/Player.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| namespace TanksServer.Models; | ||||
| 
 | ||||
| internal sealed class Player(string name) | ||||
| { | ||||
|     public string Name => name; | ||||
| 
 | ||||
|     public Guid Id { get; } = Guid.NewGuid(); | ||||
| 
 | ||||
|     public PlayerControls Controls { get; } = new(); | ||||
| } | ||||
| 
 | ||||
| internal sealed class PlayerControls | ||||
| { | ||||
|     public bool Forward { get; set; } | ||||
|     public bool Backward { get; set; } | ||||
|     public bool TurnLeft { get; set; } | ||||
|     public bool TurnRight { get; set; } | ||||
|     public bool Shoot { get; set; } | ||||
| } | ||||
							
								
								
									
										8
									
								
								TanksServer/Models/Tank.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								TanksServer/Models/Tank.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| namespace TanksServer.Models; | ||||
| 
 | ||||
| internal sealed class Tank(Player player, PixelPosition spawnPosition) | ||||
| { | ||||
|     public Player Owner { get; } = player; | ||||
|     public int Rotation { get; set; } | ||||
|     public PixelPosition Position { get; set; } = spawnPosition; | ||||
| } | ||||
							
								
								
									
										3
									
								
								TanksServer/Models/TilePosition.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								TanksServer/Models/TilePosition.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| namespace TanksServer.Models; | ||||
| 
 | ||||
| internal record struct TilePosition(int X, int Y); | ||||
|  | @ -1,16 +1,18 @@ | |||
| using System.Collections.Concurrent; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using TanksServer.Models; | ||||
| using TanksServer.Services; | ||||
| 
 | ||||
| namespace TanksServer; | ||||
| 
 | ||||
| internal sealed class PlayerServer(ILogger<PlayerServer> logger) | ||||
| internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnQueue spawnQueue) | ||||
| { | ||||
|     private readonly ConcurrentDictionary<string, Player> _players = new(); | ||||
| 
 | ||||
|     public Player GetOrAdd(string name) | ||||
|     { | ||||
|         var player = _players.GetOrAdd(name, _ => new Player(name)); | ||||
|         var player = _players.GetOrAdd(name, AddAndSpawn); | ||||
|         logger.LogInformation("player {} (re)joined", player.Id); | ||||
|         return player; | ||||
|     } | ||||
|  | @ -28,22 +30,11 @@ internal sealed class PlayerServer(ILogger<PlayerServer> logger) | |||
|         foundPlayer = null; | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| internal sealed class Player(string name) | ||||
| { | ||||
|     public string Name => name; | ||||
| 
 | ||||
|     public Guid Id { get; } = Guid.NewGuid(); | ||||
|      | ||||
|     public PlayerControls Controls { get; } = new(); | ||||
| } | ||||
| 
 | ||||
| internal sealed class PlayerControls | ||||
| { | ||||
|     public bool Forward { get; set; } | ||||
|     public bool Backward { get; set; } | ||||
|     public bool TurnLeft { get; set; } | ||||
|     public bool TurnRight { get; set; } | ||||
|     public bool Shoot { get; set; } | ||||
|     private Player AddAndSpawn(string name) | ||||
|     { | ||||
|         var player = new Player(name); | ||||
|         spawnQueue.SpawnTankForPlayer(player); | ||||
|         return player; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; | |||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.FileProviders; | ||||
| using TanksServer.Helpers; | ||||
| using TanksServer.Services; | ||||
| 
 | ||||
| namespace TanksServer; | ||||
|  | @ -26,7 +27,7 @@ internal static class Program | |||
|         app.UseStaticFiles(new StaticFileOptions { FileProvider = clientFileProvider }); | ||||
| 
 | ||||
|         app.MapGet("/player", playerService.GetOrAdd); | ||||
|          | ||||
| 
 | ||||
|         app.Map("/screen", async context => | ||||
|         { | ||||
|             if (!context.WebSockets.IsWebSocketRequest) | ||||
|  | @ -46,7 +47,7 @@ internal static class Program | |||
| 
 | ||||
|             if (!playerService.TryGet(playerId, out var player)) | ||||
|                 return Results.NotFound(); | ||||
|              | ||||
| 
 | ||||
|             using var ws = await context.WebSockets.AcceptWebSocketAsync(); | ||||
|             await controlsServer.HandleClient(ws, player); | ||||
|             return Results.Empty; | ||||
|  | @ -73,18 +74,23 @@ internal static class Program | |||
| 
 | ||||
|         builder.Services.AddSingleton<ServicePointDisplay>(); | ||||
|         builder.Services.AddSingleton<MapService>(); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<MapDrawer>(); | ||||
|         builder.Services.AddSingleton<ITickStep, MapDrawer>(sp => sp.GetRequiredService<MapDrawer>()); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<ClientScreenServer>(); | ||||
|         builder.Services.AddHostedService<ClientScreenServer>(sp => sp.GetRequiredService<ClientScreenServer>()); | ||||
|         builder.Services.AddSingleton<ITickStep, ClientScreenServer>(sp => sp.GetRequiredService<ClientScreenServer>()); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<ControlsServer>(); | ||||
|         builder.Services.AddSingleton<TankManager>(); | ||||
| 
 | ||||
|         builder.Services.AddHostedService<GameTickService>(); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<SpawnQueue>(); | ||||
|         builder.Services.AddSingleton<ITickStep>(sp => sp.GetRequiredService<SpawnQueue>()); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<PixelDrawer>(); | ||||
|         builder.Services.AddSingleton<ITickStep, PixelDrawer>(sp => sp.GetRequiredService<PixelDrawer>()); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<ClientScreenServer>(); | ||||
|         builder.Services.AddHostedService(sp => sp.GetRequiredService<ClientScreenServer>()); | ||||
|         builder.Services.AddSingleton<ITickStep, ClientScreenServer>(sp => sp.GetRequiredService<ClientScreenServer>()); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<ControlsServer>(); | ||||
|         builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>()); | ||||
|          | ||||
|         builder.Services.AddSingleton<PlayerServer>(); | ||||
| 
 | ||||
|         return builder.Build(); | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ using Microsoft.Extensions.Hosting; | |||
| 
 | ||||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal sealed class GameTickService(IEnumerable<ITickStep> steps) : IHostedService | ||||
| internal sealed class GameTickService(IEnumerable<ITickStep> steps) : IHostedService, IDisposable | ||||
| { | ||||
|     private readonly CancellationTokenSource _cancellation = new(); | ||||
|     private readonly List<ITickStep> _steps = steps.ToList(); | ||||
|  | @ -29,6 +29,12 @@ internal sealed class GameTickService(IEnumerable<ITickStep> steps) : IHostedSer | |||
|         await _cancellation.CancelAsync(); | ||||
|         if (_run != null) await _run; | ||||
|     } | ||||
| 
 | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _cancellation.Dispose(); | ||||
|         _run?.Dispose(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| public interface ITickStep | ||||
|  |  | |||
|  | @ -1,56 +0,0 @@ | |||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal class MapDrawer(MapService map):ITickStep | ||||
| { | ||||
|     private const uint GameFieldPixelCount = MapService.PixelsPerRow * MapService.PixelsPerColumn; | ||||
| 
 | ||||
|     private 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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private 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; | ||||
|     } | ||||
| 
 | ||||
|     private DisplayPixelBuffer? _lastFrame; | ||||
| 
 | ||||
|     public DisplayPixelBuffer LastFrame | ||||
|     { | ||||
|         get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn"); | ||||
|         private set => _lastFrame = value; | ||||
|     } | ||||
| 
 | ||||
|     public Task TickAsync() | ||||
|     { | ||||
|         var buffer = CreateGameFieldPixelBuffer(); | ||||
|         DrawInto(buffer); | ||||
|         LastFrame = buffer; | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| } | ||||
|  | @ -1,6 +1,8 @@ | |||
| using TanksServer.Models; | ||||
| 
 | ||||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal class MapService | ||||
| internal sealed class MapService | ||||
| { | ||||
|     public const int TilesPerRow = 44; | ||||
|     public const int TilesPerColumn = 20; | ||||
|  | @ -35,8 +37,8 @@ internal class MapService | |||
| 
 | ||||
|     private char this[int tileX, int tileY] => _map[tileX + tileY * TilesPerRow]; | ||||
| 
 | ||||
|     public bool IsCurrentlyWall(int tileX, int tileY) | ||||
|     public bool IsCurrentlyWall(TilePosition position) | ||||
|     { | ||||
|         return this[tileX, tileY] == '#'; | ||||
|         return this[position.X, position.Y] == '#'; | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										112
									
								
								TanksServer/Services/PixelDrawer.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								TanksServer/Services/PixelDrawer.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | |||
| using System.Diagnostics; | ||||
| using System.Net.Mime; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using SixLabors.ImageSharp; | ||||
| using SixLabors.ImageSharp.PixelFormats; | ||||
| using TanksServer.Helpers; | ||||
| using TanksServer.Models; | ||||
| 
 | ||||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal sealed class PixelDrawer : ITickStep | ||||
| { | ||||
|     private const uint GameFieldPixelCount = MapService.PixelsPerRow * MapService.PixelsPerColumn; | ||||
|     private DisplayPixelBuffer? _lastFrame; | ||||
|     private readonly MapService _map; | ||||
|     private readonly TankManager _tanks; | ||||
|     private readonly bool[] _tankSprite; | ||||
|     private readonly int _tankSpriteWidth; | ||||
| 
 | ||||
|     public PixelDrawer(MapService map, TankManager tanks, ILogger<PixelDrawer> logger) | ||||
|     { | ||||
|         _map = map; | ||||
|         _tanks = tanks; | ||||
| 
 | ||||
|         using var tankImage = Image.Load<Rgba32>("assets/tank.png"); | ||||
|         _tankSprite = new bool[tankImage.Height * tankImage.Width]; | ||||
| 
 | ||||
|         var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); | ||||
|         var i = 0; | ||||
|         for (var y = 0; y < tankImage.Height; y++) | ||||
|         for (var x = 0; x < tankImage.Width; x++, i++) | ||||
|         { | ||||
|             _tankSprite[i] = tankImage[x, y] == whitePixel; | ||||
|         } | ||||
| 
 | ||||
|         _tankSpriteWidth = tankImage.Width; | ||||
|     } | ||||
| 
 | ||||
|     public DisplayPixelBuffer LastFrame | ||||
|     { | ||||
|         get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn"); | ||||
|         private set => _lastFrame = value; | ||||
|     } | ||||
| 
 | ||||
|     public Task TickAsync() | ||||
|     { | ||||
|         var buffer = CreateGameFieldPixelBuffer(); | ||||
|         DrawMap(buffer); | ||||
|         DrawTanks(buffer); | ||||
|         LastFrame = buffer; | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| 
 | ||||
|     private void DrawMap(DisplayPixelBuffer buf) | ||||
|     { | ||||
|         for (var tileY = 0; tileY < MapService.TilesPerColumn; tileY++) | ||||
|         for (var tileX = 0; tileX < MapService.TilesPerRow; tileX++) | ||||
|         { | ||||
|             var tile = new TilePosition(tileX, tileY); | ||||
|             if (!_map.IsCurrentlyWall(tile)) | ||||
|                 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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void DrawTanks(DisplayPixelBuffer buf) | ||||
|     { | ||||
|         foreach (var tank in _tanks) | ||||
|         { | ||||
|             for (var dy = 0; dy < MapService.TileSize; dy++) | ||||
|             { | ||||
|                 var rowStartIndex = (tank.Position.Y + dy) * MapService.PixelsPerRow; | ||||
| 
 | ||||
|                 for (var dx = 0; dx < MapService.TileSize; dx++) | ||||
|                 { | ||||
|                     var i = rowStartIndex + tank.Position.X + dx; | ||||
|                     buf.Pixels[i] = TankSpriteAt(dx, dy, tank.Rotation); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private bool TankSpriteAt(int dx, int dy, int tankRotation) | ||||
|     { | ||||
|         var x = tankRotation % 4 * (MapService.TileSize + 1); | ||||
|         var y = tankRotation / 4 * (MapService.TileSize + 1); | ||||
|         return _tankSprite[(y + dy) * _tankSpriteWidth + x + dx]; | ||||
|     } | ||||
| 
 | ||||
|     private static 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; | ||||
|     } | ||||
| } | ||||
|  | @ -3,13 +3,21 @@ using Microsoft.Extensions.Options; | |||
| 
 | ||||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal sealed class ServicePointDisplay(IOptions<ServicePointDisplayConfiguration> options) | ||||
| internal sealed class ServicePointDisplay( | ||||
|     IOptions<ServicePointDisplayConfiguration> options,  | ||||
|     PixelDrawer drawer | ||||
| ) : ITickStep, IDisposable | ||||
| { | ||||
|     private readonly UdpClient _udpClient = new(options.Value.Hostname, options.Value.Port); | ||||
| 
 | ||||
|     public ValueTask<int> Send(DisplayPixelBuffer buffer) | ||||
|     public Task TickAsync() | ||||
|     { | ||||
|         return _udpClient.SendAsync(buffer.Data); | ||||
|         return _udpClient.SendAsync(drawer.LastFrame.Data).AsTask(); | ||||
|     } | ||||
| 
 | ||||
|     public void Dispose() | ||||
|     { | ||||
|         _udpClient.Dispose(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										51
									
								
								TanksServer/Services/SpawnQueue.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								TanksServer/Services/SpawnQueue.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| using System.Collections.Concurrent; | ||||
| using TanksServer.Models; | ||||
| 
 | ||||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal sealed class SpawnQueue(TankManager tanks, MapService map) : ITickStep | ||||
| { | ||||
|     private readonly ConcurrentQueue<Player> _playersToSpawn = new(); | ||||
| 
 | ||||
|     public Task TickAsync() | ||||
|     { | ||||
|         while (_playersToSpawn.TryDequeue(out var player)) | ||||
|         { | ||||
|             var tank = new Tank(player, ChooseSpawnPosition()) | ||||
|             { | ||||
|                 Rotation = Random.Shared.Next(0, 16) | ||||
|             }; | ||||
|             tanks.Add(tank); | ||||
|         } | ||||
| 
 | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| 
 | ||||
|     private PixelPosition ChooseSpawnPosition() | ||||
|     { | ||||
|         List<TilePosition> candidates = new(); | ||||
|          | ||||
|         for (var x = 0; x < MapService.TilesPerRow; x++) | ||||
|         for (var y = 0; y < MapService.TilesPerColumn; y++) | ||||
|         { | ||||
|             var tile = new TilePosition(x, y); | ||||
| 
 | ||||
|             if (map.IsCurrentlyWall(tile)) | ||||
|                 continue; | ||||
|              | ||||
|             // TODO: check tanks | ||||
|             candidates.Add(tile); | ||||
|         } | ||||
| 
 | ||||
|         var chosenTile = candidates[Random.Shared.Next(candidates.Count)]; | ||||
|         return new PixelPosition( | ||||
|             chosenTile.X * MapService.TileSize, | ||||
|             chosenTile.Y * MapService.TileSize | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public void SpawnTankForPlayer(Player player) | ||||
|     { | ||||
|         _playersToSpawn.Enqueue(player); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								TanksServer/Services/TankManager.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								TanksServer/Services/TankManager.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| using System.Collections; | ||||
| using System.Collections.Concurrent; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using TanksServer.Models; | ||||
| 
 | ||||
| namespace TanksServer.Services; | ||||
| 
 | ||||
| internal sealed class TankManager(ILogger<TankManager> logger) : IEnumerable<Tank> | ||||
| { | ||||
|     private readonly ConcurrentBag<Tank> _tanks = new(); | ||||
| 
 | ||||
|     public void Add(Tank tank) | ||||
|     { | ||||
|         logger.LogInformation("Tank added"); | ||||
|         _tanks.Add(tank); | ||||
|     } | ||||
| 
 | ||||
|     IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||||
|     public IEnumerator<Tank> GetEnumerator() => _tanks.GetEnumerator(); | ||||
| } | ||||
|  | @ -14,6 +14,10 @@ | |||
|             <Link>.dockerignore</Link> | ||||
|         </Content> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
|     <ItemGroup> | ||||
|       <PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" /> | ||||
|     </ItemGroup> | ||||
|      | ||||
|     <PropertyGroup> | ||||
|         <AnalysisMode>Recommended</AnalysisMode> | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								TanksServer/assets/tank.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								TanksServer/assets/tank.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 355 B | 
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter