position is now center, socket improvements
This commit is contained in:
		
							parent
							
								
									40eba7a7c7
								
							
						
					
					
						commit
						d4d0abd013
					
				
					 18 changed files with 134 additions and 106 deletions
				
			
		|  | @ -1,7 +1,9 @@ | |||
| namespace TanksServer.GameLogic; | ||||
| 
 | ||||
| internal sealed class CollideBulletsWithTanks( | ||||
|     BulletManager bullets, TankManager tanks, SpawnQueue spawnQueue | ||||
|     BulletManager bullets, | ||||
|     TankManager tanks, | ||||
|     SpawnQueue spawnQueue | ||||
| ) : ITickStep | ||||
| { | ||||
|     public Task TickAsync() | ||||
|  | @ -14,7 +16,7 @@ internal sealed class CollideBulletsWithTanks( | |||
|     { | ||||
|         foreach (var tank in tanks) | ||||
|         { | ||||
|             var (topLeft, bottomRight) = TankManager.GetTankBounds(tank.Position.ToPixelPosition()); | ||||
|             var (topLeft, bottomRight) = tank.Bounds; | ||||
|             if (bullet.Position.X < topLeft.X || bullet.Position.X > bottomRight.X || | ||||
|                 bullet.Position.Y < topLeft.Y || bullet.Position.Y > bottomRight.Y) | ||||
|                 continue; | ||||
|  |  | |||
|  | @ -47,9 +47,7 @@ internal sealed class MoveTanks( | |||
| 
 | ||||
|     private bool TryMoveTankTo(Tank tank, FloatPosition newPosition) | ||||
|     { | ||||
|         var (topLeft, bottomRight) = TankManager.GetTankBounds(newPosition.ToPixelPosition()); | ||||
| 
 | ||||
|         if (HitsWall(topLeft, bottomRight)) | ||||
|         if (HitsWall(newPosition)) | ||||
|             return false; | ||||
|         if (HitsTank(tank, newPosition)) | ||||
|             return false; | ||||
|  | @ -63,15 +61,16 @@ internal sealed class MoveTanks( | |||
|             .Where(otherTank => otherTank != tank) | ||||
|             .Any(otherTank => newPosition.Distance(otherTank.Position) < MapService.TileSize); | ||||
| 
 | ||||
|     private bool HitsWall(PixelPosition topLeft, PixelPosition bottomRight) | ||||
|     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(), | ||||
|             bottomRight.ToTilePosition() | ||||
|         ]; | ||||
|         return positions.Any(map.IsCurrentlyWall); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -27,10 +27,10 @@ internal sealed class ShootFromTanks( | |||
| 
 | ||||
|         var angle = tank.Rotation * 2 * Math.PI; | ||||
|         var position = new FloatPosition( | ||||
|             x: tank.Position.X + MapService.TileSize / 2d + Math.Sin(angle) * _config.BulletSpeed, | ||||
|             y: tank.Position.Y + MapService.TileSize / 2d - Math.Cos(angle) * _config.BulletSpeed | ||||
|             tank.Position.X + Math.Sin(angle) * _config.BulletSpeed, | ||||
|             tank.Position.Y - Math.Cos(angle) * _config.BulletSpeed | ||||
|         ); | ||||
| 
 | ||||
|         bulletManager.Spawn(new Bullet(tank.Owner, position, tank.Rotation)); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -44,9 +44,6 @@ internal sealed class SpawnNewTanks( | |||
|         } | ||||
| 
 | ||||
|         var min = candidates.MaxBy(kvp => kvp.Value).Key; | ||||
|         return new FloatPosition( | ||||
|             min.X * MapService.TileSize, | ||||
|             min.Y * MapService.TileSize | ||||
|         ); | ||||
|         return min.ToPixelPosition().GetPixelRelative(4, 4).ToFloatPosition(); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -20,12 +20,4 @@ internal sealed class TankManager(ILogger<TankManager> logger) : IEnumerable<Tan | |||
|         logger.LogInformation("Tank removed for player {}", tank.Owner.Id); | ||||
|         _tanks.Remove(tank, out _); | ||||
|     } | ||||
| 
 | ||||
|     public static (PixelPosition TopLeft, PixelPosition BottomRight) GetTankBounds(PixelPosition tankPosition) | ||||
|     { | ||||
|         return (tankPosition, new PixelPosition( | ||||
|             (ushort)(tankPosition.X + MapService.TileSize - 1), | ||||
|             (ushort)(tankPosition.Y + MapService.TileSize - 1) | ||||
|         )); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1,16 +0,0 @@ | |||
| using DisplayCommands; | ||||
| using TanksServer.GameLogic; | ||||
| 
 | ||||
| namespace TanksServer.Graphics; | ||||
| 
 | ||||
| internal sealed class BulletDrawer(BulletManager bullets) : IDrawStep | ||||
| { | ||||
|     public void Draw(PixelGrid buffer) | ||||
|     { | ||||
|         foreach (var bullet in bullets.GetAll()) | ||||
|         { | ||||
|             var pos = bullet.Position.ToPixelPosition(); | ||||
|             buffer[pos.X, pos.Y] = true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								TanksServer/Graphics/DrawBulletsStep.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								TanksServer/Graphics/DrawBulletsStep.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| using DisplayCommands; | ||||
| using TanksServer.GameLogic; | ||||
| 
 | ||||
| namespace TanksServer.Graphics; | ||||
| 
 | ||||
| internal sealed class DrawBulletsStep(BulletManager bullets) : IDrawStep | ||||
| { | ||||
|     public void Draw(PixelGrid buffer) | ||||
|     { | ||||
|         foreach (var position in bullets.GetAll().Select(b => b.Position.ToPixelPosition())) | ||||
|             buffer[position.X, position.Y] = true; | ||||
|     } | ||||
| } | ||||
|  | @ -3,7 +3,7 @@ using TanksServer.GameLogic; | |||
| 
 | ||||
| namespace TanksServer.Graphics; | ||||
| 
 | ||||
| internal sealed class MapDrawer(MapService map) : IDrawStep | ||||
| internal sealed class DrawMapStep(MapService map) : IDrawStep | ||||
| { | ||||
|     public void Draw(PixelGrid buffer) | ||||
|     { | ||||
|  | @ -22,4 +22,4 @@ internal sealed class MapDrawer(MapService map) : IDrawStep | |||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | @ -5,13 +5,13 @@ using TanksServer.GameLogic; | |||
| 
 | ||||
| namespace TanksServer.Graphics; | ||||
| 
 | ||||
| internal sealed class TankDrawer : IDrawStep | ||||
| internal sealed class DrawTanksStep : IDrawStep | ||||
| { | ||||
|     private readonly TankManager _tanks; | ||||
|     private readonly bool[] _tankSprite; | ||||
|     private readonly int _tankSpriteWidth; | ||||
| 
 | ||||
|     public TankDrawer(TankManager tanks) | ||||
|     public DrawTanksStep(TankManager tanks) | ||||
|     { | ||||
|         _tanks = tanks; | ||||
| 
 | ||||
|  | @ -22,9 +22,7 @@ internal sealed class TankDrawer : IDrawStep | |||
|         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; | ||||
|     } | ||||
|  | @ -33,7 +31,7 @@ internal sealed class TankDrawer : IDrawStep | |||
|     { | ||||
|         foreach (var tank in _tanks) | ||||
|         { | ||||
|             var tankPosition = tank.Position.ToPixelPosition(); | ||||
|             var tankPosition = tank.Bounds.TopLeft; | ||||
|             var orientation = (int)Math.Round(tank.Rotation * 16d) % 16; | ||||
| 
 | ||||
|             for (byte dy = 0; dy < MapService.TileSize; dy++) | ||||
|  | @ -56,4 +54,4 @@ internal sealed class TankDrawer : IDrawStep | |||
| 
 | ||||
|         return _tankSprite[index]; | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | @ -3,8 +3,9 @@ using TanksServer.GameLogic; | |||
| 
 | ||||
| namespace TanksServer.Graphics; | ||||
| 
 | ||||
| internal sealed class DrawStateToFrame( | ||||
|     IEnumerable<IDrawStep> drawSteps, LastFinishedFrameProvider lastFrameProvider | ||||
| internal sealed class GeneratePixelsTickStep( | ||||
|     IEnumerable<IDrawStep> drawSteps, | ||||
|     LastFinishedFrameProvider lastFrameProvider | ||||
| ) : ITickStep | ||||
| { | ||||
|     private readonly List<IDrawStep> _drawSteps = drawSteps.ToList(); | ||||
|  | @ -12,30 +12,10 @@ internal sealed class ByteChannelWebSocket(WebSocket socket, ILogger logger, int | |||
| 
 | ||||
|     public async IAsyncEnumerable<Memory<byte>> ReadAllAsync() | ||||
|     { | ||||
|         while (true) | ||||
|         while (socket.State is WebSocketState.Open or WebSocketState.CloseSent) | ||||
|         { | ||||
|             if (socket.State is not (WebSocketState.Open or WebSocketState.CloseSent)) | ||||
|                 break; | ||||
| 
 | ||||
|             var response = await socket.ReceiveAsync(_buffer, CancellationToken.None); | ||||
|             if (response.MessageType == WebSocketMessageType.Close) | ||||
|             { | ||||
|                 if (socket.State == WebSocketState.CloseReceived) | ||||
|                     await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, | ||||
|                         CancellationToken.None); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if (response.Count != _buffer.Length) | ||||
|             { | ||||
|                 await socket.CloseOutputAsync( | ||||
|                     WebSocketCloseStatus.InvalidPayloadData, | ||||
|                     "response has unexpected size", | ||||
|                     CancellationToken.None); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             yield return _buffer.ToArray(); | ||||
|             if (await TryReadAsync()) | ||||
|                 yield return _buffer.ToArray(); | ||||
|         } | ||||
| 
 | ||||
|         if (socket.State != WebSocketState.Closed) | ||||
|  | @ -44,7 +24,45 @@ internal sealed class ByteChannelWebSocket(WebSocket socket, ILogger logger, int | |||
| 
 | ||||
|     public async Task CloseAsync() | ||||
|     { | ||||
|         logger.LogDebug("closing socket"); | ||||
|         await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); | ||||
|         if (socket.State != WebSocketState.Open) | ||||
|             return; | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); | ||||
|         } | ||||
|         catch (WebSocketException socketException) | ||||
|         { | ||||
|             logger.LogDebug(socketException, "could not close socket properly"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     private async Task<bool> TryReadAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var response = await socket.ReceiveAsync(_buffer, CancellationToken.None); | ||||
|             if (response.MessageType == WebSocketMessageType.Close) | ||||
|             { | ||||
|                 await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, | ||||
|                     CancellationToken.None); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             if (response.Count != _buffer.Length) | ||||
|             { | ||||
|                 await socket.CloseOutputAsync(WebSocketCloseStatus.InvalidPayloadData, | ||||
|                     "response has unexpected size", | ||||
|                     CancellationToken.None); | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         } | ||||
|         catch (WebSocketException socketException) | ||||
|         { | ||||
|             logger.LogDebug(socketException, "could not read"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,12 @@ | |||
| namespace TanksServer.Models; | ||||
| 
 | ||||
| internal sealed class Bullet(Player tankOwner, FloatPosition position, double rotation): IMapEntity | ||||
| internal sealed class Bullet(Player tankOwner, FloatPosition position, double rotation) : IMapEntity | ||||
| { | ||||
|     public Player Owner { get; } = tankOwner; | ||||
|      | ||||
|     public FloatPosition Position { get; set; } = position; | ||||
|      | ||||
| 
 | ||||
|     public double Rotation { get; set; } = rotation; | ||||
| } | ||||
| 
 | ||||
|     public FloatPosition Position { get; set; } = position; | ||||
| 
 | ||||
|     public PixelBounds Bounds => new (Position.ToPixelPosition(), Position.ToPixelPosition()); | ||||
| } | ||||
|  |  | |||
|  | @ -3,4 +3,6 @@ namespace TanksServer.Models; | |||
| internal interface IMapEntity | ||||
| { | ||||
|     FloatPosition Position { get; set; } | ||||
| } | ||||
| 
 | ||||
|     PixelBounds Bounds { get; } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										3
									
								
								TanksServer/Models/PixelBounds.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								TanksServer/Models/PixelBounds.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| namespace TanksServer.Models; | ||||
| 
 | ||||
| internal record struct PixelBounds(PixelPosition TopLeft, PixelPosition BottomRight); | ||||
|  | @ -10,8 +10,8 @@ internal static class PositionHelpers | |||
|         Debug.Assert(subX < 8); | ||||
|         Debug.Assert(subY < 8); | ||||
|         return new PixelPosition( | ||||
|             x: (ushort)(position.X * MapService.TileSize + subX), | ||||
|             y: (ushort)(position.Y * MapService.TileSize + subY) | ||||
|             (ushort)(position.X * MapService.TileSize + subX), | ||||
|             (ushort)(position.Y * MapService.TileSize + subY) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|  | @ -23,21 +23,26 @@ internal static class PositionHelpers | |||
|     } | ||||
| 
 | ||||
|     public static PixelPosition ToPixelPosition(this FloatPosition position) => new( | ||||
|         x: (ushort)((int)position.X % MapService.PixelsPerRow), | ||||
|         y: (ushort)((int)position.Y % MapService.PixelsPerRow) | ||||
|         (ushort)((int)position.X % MapService.PixelsPerRow), | ||||
|         (ushort)((int)position.Y % MapService.PixelsPerRow) | ||||
|     ); | ||||
| 
 | ||||
|     public static PixelPosition ToPixelPosition(this TilePosition position) => new( | ||||
|         (ushort)(position.X * MapService.TileSize), | ||||
|         (ushort)(position.Y * MapService.TileSize) | ||||
|     ); | ||||
| 
 | ||||
|     public static TilePosition ToTilePosition(this PixelPosition position) => new( | ||||
|         x: (ushort)(position.X / MapService.TileSize), | ||||
|         y: (ushort)(position.Y / MapService.TileSize) | ||||
|         (ushort)(position.X / MapService.TileSize), | ||||
|         (ushort)(position.Y / MapService.TileSize) | ||||
|     ); | ||||
| 
 | ||||
|     public static FloatPosition ToFloatPosition(this PixelPosition position) => new(position.X, position.Y); | ||||
| 
 | ||||
| 
 | ||||
|     public static double Distance(this FloatPosition p1, FloatPosition p2) | ||||
|         => Math.Sqrt( | ||||
|     public static double Distance(this FloatPosition p1, FloatPosition p2) => | ||||
|         Math.Sqrt( | ||||
|             Math.Pow(p1.X - p2.X, 2) + | ||||
|             Math.Pow(p1.Y - p2.Y, 2) | ||||
|         ); | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -20,9 +20,21 @@ internal sealed class Tank(Player player, FloatPosition spawnPosition) : IMapEnt | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public FloatPosition Position { get; set; } = spawnPosition; | ||||
| 
 | ||||
|     public DateTime NextShotAfter { get; set; } | ||||
| 
 | ||||
|     public bool Moved { get; set; } | ||||
| } | ||||
| 
 | ||||
|     public FloatPosition Position { get; set; } = spawnPosition; | ||||
| 
 | ||||
|     public PixelBounds Bounds => GetBoundsForCenter(Position); | ||||
| 
 | ||||
|     public static PixelBounds GetBoundsForCenter(FloatPosition position) => new( | ||||
|         new PixelPosition( | ||||
|             (ushort)(position.X - MapService.TileSize / 2d), | ||||
|             (ushort)(position.Y - MapService.TileSize / 2d) | ||||
|         ), new PixelPosition( | ||||
|             (ushort)(position.X + MapService.TileSize / 2d - 1d), | ||||
|             (ushort)(position.Y + MapService.TileSize / 2d - 1d) | ||||
|         ) | ||||
|     ); | ||||
| } | ||||
|  |  | |||
|  | @ -100,13 +100,13 @@ public static class Program | |||
|         builder.Services.AddSingleton<ITickStep, MoveTanks>(); | ||||
|         builder.Services.AddSingleton<ITickStep, ShootFromTanks>(); | ||||
|         builder.Services.AddSingleton<ITickStep, SpawnNewTanks>(); | ||||
|         builder.Services.AddSingleton<ITickStep, DrawStateToFrame>(); | ||||
|         builder.Services.AddSingleton<ITickStep, GeneratePixelsTickStep>(); | ||||
|         builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>(); | ||||
|         builder.Services.AddSingleton<ITickStep, SendToClientScreen>(); | ||||
| 
 | ||||
|         builder.Services.AddSingleton<IDrawStep, MapDrawer>(); | ||||
|         builder.Services.AddSingleton<IDrawStep, TankDrawer>(); | ||||
|         builder.Services.AddSingleton<IDrawStep, BulletDrawer>(); | ||||
|         builder.Services.AddSingleton<IDrawStep, DrawMapStep>(); | ||||
|         builder.Services.AddSingleton<IDrawStep, DrawTanksStep>(); | ||||
|         builder.Services.AddSingleton<IDrawStep, DrawBulletsStep>(); | ||||
| 
 | ||||
|         builder.Services.Configure<TanksConfiguration>( | ||||
|             builder.Configuration.GetSection("Tanks")); | ||||
|  |  | |||
|  | @ -19,7 +19,6 @@ | |||
|         <PublishAot>true</PublishAot> | ||||
| 
 | ||||
|         <IlcDisableReflection>false</IlcDisableReflection> | ||||
|         <InvariantGlobalization>true</InvariantGlobalization> | ||||
|         <StaticExecutable>true</StaticExecutable> | ||||
|         <StripSymbols>true</StripSymbols> | ||||
|         <StaticallyLinked>true</StaticallyLinked> | ||||
|  | @ -31,19 +30,20 @@ | |||
|     </ItemGroup> | ||||
| 
 | ||||
|     <ItemGroup> | ||||
|         <None Include="./assets/tank.png" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always" /> | ||||
|          | ||||
|         <None Include="./assets/tank.png" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always"/> | ||||
| 
 | ||||
|         <Content Include="../Dockerfile"> | ||||
|             <Link>..\Dockerfile</Link> | ||||
|             <Link>Dockerfile</Link> | ||||
|         </Content> | ||||
|         <Content Include="../.dockerignore"> | ||||
|             <Link>../Dockerfile</Link> | ||||
|             <Link>Dockerfile</Link> | ||||
|         </Content> | ||||
|         <Content Include="../Makefile" /> | ||||
|         <Content Include="../Makefile"/> | ||||
|         <Content Include="../.editorconfig"/> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
|     <ItemGroup> | ||||
|       <ProjectReference Include="..\DisplayCommands\DisplayCommands.csproj" /> | ||||
|         <ProjectReference Include="../DisplayCommands/DisplayCommands.csproj"/> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter