position is now center, socket improvements

This commit is contained in:
Vinzenz Schroeter 2024-04-13 14:07:14 +02:00
parent 40eba7a7c7
commit d4d0abd013
18 changed files with 134 additions and 106 deletions

View file

@ -1,7 +1,9 @@
namespace TanksServer.GameLogic; namespace TanksServer.GameLogic;
internal sealed class CollideBulletsWithTanks( internal sealed class CollideBulletsWithTanks(
BulletManager bullets, TankManager tanks, SpawnQueue spawnQueue BulletManager bullets,
TankManager tanks,
SpawnQueue spawnQueue
) : ITickStep ) : ITickStep
{ {
public Task TickAsync() public Task TickAsync()
@ -14,7 +16,7 @@ internal sealed class CollideBulletsWithTanks(
{ {
foreach (var tank in tanks) 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 || if (bullet.Position.X < topLeft.X || bullet.Position.X > bottomRight.X ||
bullet.Position.Y < topLeft.Y || bullet.Position.Y > bottomRight.Y) bullet.Position.Y < topLeft.Y || bullet.Position.Y > bottomRight.Y)
continue; continue;

View file

@ -47,9 +47,7 @@ internal sealed class MoveTanks(
private bool TryMoveTankTo(Tank tank, FloatPosition newPosition) private bool TryMoveTankTo(Tank tank, FloatPosition newPosition)
{ {
var (topLeft, bottomRight) = TankManager.GetTankBounds(newPosition.ToPixelPosition()); if (HitsWall(newPosition))
if (HitsWall(topLeft, bottomRight))
return false; return false;
if (HitsTank(tank, newPosition)) if (HitsTank(tank, newPosition))
return false; return false;
@ -63,15 +61,16 @@ internal sealed class MoveTanks(
.Where(otherTank => otherTank != tank) .Where(otherTank => otherTank != tank)
.Any(otherTank => newPosition.Distance(otherTank.Position) < MapService.TileSize); .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 = TilePosition[] positions =
[ [
topLeft.ToTilePosition(), topLeft.ToTilePosition(),
new PixelPosition(bottomRight.X, topLeft.Y).ToTilePosition(), new PixelPosition(bottomRight.X, topLeft.Y).ToTilePosition(),
new PixelPosition(topLeft.X, bottomRight.Y).ToTilePosition(), new PixelPosition(topLeft.X, bottomRight.Y).ToTilePosition(),
bottomRight.ToTilePosition(), bottomRight.ToTilePosition()
]; ];
return positions.Any(map.IsCurrentlyWall); return positions.Any(map.IsCurrentlyWall);
} }
} }

View file

@ -27,10 +27,10 @@ internal sealed class ShootFromTanks(
var angle = tank.Rotation * 2 * Math.PI; var angle = tank.Rotation * 2 * Math.PI;
var position = new FloatPosition( var position = new FloatPosition(
x: tank.Position.X + MapService.TileSize / 2d + Math.Sin(angle) * _config.BulletSpeed, tank.Position.X + Math.Sin(angle) * _config.BulletSpeed,
y: tank.Position.Y + MapService.TileSize / 2d - Math.Cos(angle) * _config.BulletSpeed tank.Position.Y - Math.Cos(angle) * _config.BulletSpeed
); );
bulletManager.Spawn(new Bullet(tank.Owner, position, tank.Rotation)); bulletManager.Spawn(new Bullet(tank.Owner, position, tank.Rotation));
} }
} }

View file

@ -44,9 +44,6 @@ internal sealed class SpawnNewTanks(
} }
var min = candidates.MaxBy(kvp => kvp.Value).Key; var min = candidates.MaxBy(kvp => kvp.Value).Key;
return new FloatPosition( return min.ToPixelPosition().GetPixelRelative(4, 4).ToFloatPosition();
min.X * MapService.TileSize,
min.Y * MapService.TileSize
);
} }
} }

View file

@ -20,12 +20,4 @@ internal sealed class TankManager(ILogger<TankManager> logger) : IEnumerable<Tan
logger.LogInformation("Tank removed for player {}", tank.Owner.Id); logger.LogInformation("Tank removed for player {}", tank.Owner.Id);
_tanks.Remove(tank, out _); _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)
));
}
}

View file

@ -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;
}
}
}

View 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;
}
}

View file

@ -3,7 +3,7 @@ using TanksServer.GameLogic;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
internal sealed class MapDrawer(MapService map) : IDrawStep internal sealed class DrawMapStep(MapService map) : IDrawStep
{ {
public void Draw(PixelGrid buffer) public void Draw(PixelGrid buffer)
{ {
@ -22,4 +22,4 @@ internal sealed class MapDrawer(MapService map) : IDrawStep
} }
} }
} }
} }

View file

@ -5,13 +5,13 @@ using TanksServer.GameLogic;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
internal sealed class TankDrawer : IDrawStep internal sealed class DrawTanksStep : IDrawStep
{ {
private readonly TankManager _tanks; private readonly TankManager _tanks;
private readonly bool[] _tankSprite; private readonly bool[] _tankSprite;
private readonly int _tankSpriteWidth; private readonly int _tankSpriteWidth;
public TankDrawer(TankManager tanks) public DrawTanksStep(TankManager tanks)
{ {
_tanks = tanks; _tanks = tanks;
@ -22,9 +22,7 @@ internal sealed class TankDrawer : IDrawStep
var i = 0; var i = 0;
for (var y = 0; y < tankImage.Height; y++) for (var y = 0; y < tankImage.Height; y++)
for (var x = 0; x < tankImage.Width; x++, i++) for (var x = 0; x < tankImage.Width; x++, i++)
{
_tankSprite[i] = tankImage[x, y] == whitePixel; _tankSprite[i] = tankImage[x, y] == whitePixel;
}
_tankSpriteWidth = tankImage.Width; _tankSpriteWidth = tankImage.Width;
} }
@ -33,7 +31,7 @@ internal sealed class TankDrawer : IDrawStep
{ {
foreach (var tank in _tanks) foreach (var tank in _tanks)
{ {
var tankPosition = tank.Position.ToPixelPosition(); var tankPosition = tank.Bounds.TopLeft;
var orientation = (int)Math.Round(tank.Rotation * 16d) % 16; var orientation = (int)Math.Round(tank.Rotation * 16d) % 16;
for (byte dy = 0; dy < MapService.TileSize; dy++) for (byte dy = 0; dy < MapService.TileSize; dy++)
@ -56,4 +54,4 @@ internal sealed class TankDrawer : IDrawStep
return _tankSprite[index]; return _tankSprite[index];
} }
} }

View file

@ -3,8 +3,9 @@ using TanksServer.GameLogic;
namespace TanksServer.Graphics; namespace TanksServer.Graphics;
internal sealed class DrawStateToFrame( internal sealed class GeneratePixelsTickStep(
IEnumerable<IDrawStep> drawSteps, LastFinishedFrameProvider lastFrameProvider IEnumerable<IDrawStep> drawSteps,
LastFinishedFrameProvider lastFrameProvider
) : ITickStep ) : ITickStep
{ {
private readonly List<IDrawStep> _drawSteps = drawSteps.ToList(); private readonly List<IDrawStep> _drawSteps = drawSteps.ToList();

View file

@ -12,30 +12,10 @@ internal sealed class ByteChannelWebSocket(WebSocket socket, ILogger logger, int
public async IAsyncEnumerable<Memory<byte>> ReadAllAsync() 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)) if (await TryReadAsync())
break; yield return _buffer.ToArray();
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 (socket.State != WebSocketState.Closed) if (socket.State != WebSocketState.Closed)
@ -44,7 +24,45 @@ internal sealed class ByteChannelWebSocket(WebSocket socket, ILogger logger, int
public async Task CloseAsync() public async Task CloseAsync()
{ {
logger.LogDebug("closing socket"); if (socket.State != WebSocketState.Open)
await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); 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;
}
}
}

View file

@ -1,10 +1,12 @@
namespace TanksServer.Models; 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 Player Owner { get; } = tankOwner;
public FloatPosition Position { get; set; } = position;
public double Rotation { get; set; } = rotation; public double Rotation { get; set; } = rotation;
}
public FloatPosition Position { get; set; } = position;
public PixelBounds Bounds => new (Position.ToPixelPosition(), Position.ToPixelPosition());
}

View file

@ -3,4 +3,6 @@ namespace TanksServer.Models;
internal interface IMapEntity internal interface IMapEntity
{ {
FloatPosition Position { get; set; } FloatPosition Position { get; set; }
}
PixelBounds Bounds { get; }
}

View file

@ -0,0 +1,3 @@
namespace TanksServer.Models;
internal record struct PixelBounds(PixelPosition TopLeft, PixelPosition BottomRight);

View file

@ -10,8 +10,8 @@ internal static class PositionHelpers
Debug.Assert(subX < 8); Debug.Assert(subX < 8);
Debug.Assert(subY < 8); Debug.Assert(subY < 8);
return new PixelPosition( return new PixelPosition(
x: (ushort)(position.X * MapService.TileSize + subX), (ushort)(position.X * MapService.TileSize + subX),
y: (ushort)(position.Y * MapService.TileSize + subY) (ushort)(position.Y * MapService.TileSize + subY)
); );
} }
@ -23,21 +23,26 @@ internal static class PositionHelpers
} }
public static PixelPosition ToPixelPosition(this FloatPosition position) => new( public static PixelPosition ToPixelPosition(this FloatPosition position) => new(
x: (ushort)((int)position.X % MapService.PixelsPerRow), (ushort)((int)position.X % MapService.PixelsPerRow),
y: (ushort)((int)position.Y % 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( public static TilePosition ToTilePosition(this PixelPosition position) => new(
x: (ushort)(position.X / MapService.TileSize), (ushort)(position.X / MapService.TileSize),
y: (ushort)(position.Y / MapService.TileSize) (ushort)(position.Y / MapService.TileSize)
); );
public static FloatPosition ToFloatPosition(this PixelPosition position) => new(position.X, position.Y); public static FloatPosition ToFloatPosition(this PixelPosition position) => new(position.X, position.Y);
public static double Distance(this FloatPosition p1, FloatPosition p2) public static double Distance(this FloatPosition p1, FloatPosition p2) =>
=> Math.Sqrt( Math.Sqrt(
Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.X - p2.X, 2) +
Math.Pow(p1.Y - p2.Y, 2) Math.Pow(p1.Y - p2.Y, 2)
); );
} }

View file

@ -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 DateTime NextShotAfter { get; set; }
public bool Moved { 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)
)
);
}

View file

@ -100,13 +100,13 @@ public static class Program
builder.Services.AddSingleton<ITickStep, MoveTanks>(); builder.Services.AddSingleton<ITickStep, MoveTanks>();
builder.Services.AddSingleton<ITickStep, ShootFromTanks>(); builder.Services.AddSingleton<ITickStep, ShootFromTanks>();
builder.Services.AddSingleton<ITickStep, SpawnNewTanks>(); builder.Services.AddSingleton<ITickStep, SpawnNewTanks>();
builder.Services.AddSingleton<ITickStep, DrawStateToFrame>(); builder.Services.AddSingleton<ITickStep, GeneratePixelsTickStep>();
builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>(); builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>();
builder.Services.AddSingleton<ITickStep, SendToClientScreen>(); builder.Services.AddSingleton<ITickStep, SendToClientScreen>();
builder.Services.AddSingleton<IDrawStep, MapDrawer>(); builder.Services.AddSingleton<IDrawStep, DrawMapStep>();
builder.Services.AddSingleton<IDrawStep, TankDrawer>(); builder.Services.AddSingleton<IDrawStep, DrawTanksStep>();
builder.Services.AddSingleton<IDrawStep, BulletDrawer>(); builder.Services.AddSingleton<IDrawStep, DrawBulletsStep>();
builder.Services.Configure<TanksConfiguration>( builder.Services.Configure<TanksConfiguration>(
builder.Configuration.GetSection("Tanks")); builder.Configuration.GetSection("Tanks"));

View file

@ -19,7 +19,6 @@
<PublishAot>true</PublishAot> <PublishAot>true</PublishAot>
<IlcDisableReflection>false</IlcDisableReflection> <IlcDisableReflection>false</IlcDisableReflection>
<InvariantGlobalization>true</InvariantGlobalization>
<StaticExecutable>true</StaticExecutable> <StaticExecutable>true</StaticExecutable>
<StripSymbols>true</StripSymbols> <StripSymbols>true</StripSymbols>
<StaticallyLinked>true</StaticallyLinked> <StaticallyLinked>true</StaticallyLinked>
@ -31,19 +30,20 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="./assets/tank.png" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always" /> <None Include="./assets/tank.png" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="Always"/>
<Content Include="../Dockerfile"> <Content Include="../Dockerfile">
<Link>..\Dockerfile</Link> <Link>Dockerfile</Link>
</Content> </Content>
<Content Include="../.dockerignore"> <Content Include="../.dockerignore">
<Link>../Dockerfile</Link> <Link>Dockerfile</Link>
</Content> </Content>
<Content Include="../Makefile" /> <Content Include="../Makefile"/>
<Content Include="../.editorconfig"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DisplayCommands\DisplayCommands.csproj" /> <ProjectReference Include="../DisplayCommands/DisplayCommands.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>