From ad211433fb452b7123da47b4c044cb34edfb06ea Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Thu, 11 Apr 2024 20:48:21 +0200 Subject: [PATCH] improve spawn position checks --- .../GameLogic/CollideBulletsWithTanks.cs | 4 +- TanksServer/GameLogic/PlayersConfiguration.cs | 6 +++ TanksServer/GameLogic/SpawnNewTanks.cs | 45 +++++++++++------ TanksServer/GameLogic/SpawnQueue.cs | 36 +++++++++++++ TanksServer/GameLogic/SpawnQueueProvider.cs | 6 --- TanksServer/GameLogic/TanksConfiguration.cs | 9 ++++ TanksServer/Interactivity/PlayerServer.cs | 4 +- TanksServer/Models/Bullet.cs | 4 +- TanksServer/Models/FloatPosition.cs | 4 +- TanksServer/Models/IMapEntity.cs | 7 +++ TanksServer/Models/Tank.cs | 2 +- TanksServer/Models/TanksConfiguration.cs | 9 ---- TanksServer/Program.cs | 14 +++++- .../SendToServicePointDisplay.cs | 4 +- TanksServer/appsettings.json | 50 +++++++++++-------- tank-frontend/src/ClientScreen.tsx | 23 +++------ 16 files changed, 147 insertions(+), 80 deletions(-) create mode 100644 TanksServer/GameLogic/PlayersConfiguration.cs create mode 100644 TanksServer/GameLogic/SpawnQueue.cs delete mode 100644 TanksServer/GameLogic/SpawnQueueProvider.cs create mode 100644 TanksServer/GameLogic/TanksConfiguration.cs create mode 100644 TanksServer/Models/IMapEntity.cs delete mode 100644 TanksServer/Models/TanksConfiguration.cs diff --git a/TanksServer/GameLogic/CollideBulletsWithTanks.cs b/TanksServer/GameLogic/CollideBulletsWithTanks.cs index 837a5e8..bd1ac06 100644 --- a/TanksServer/GameLogic/CollideBulletsWithTanks.cs +++ b/TanksServer/GameLogic/CollideBulletsWithTanks.cs @@ -1,7 +1,7 @@ namespace TanksServer.GameLogic; internal sealed class CollideBulletsWithTanks( - BulletManager bullets, TankManager tanks, SpawnQueueProvider spawnQueueProvider + BulletManager bullets, TankManager tanks, SpawnQueue spawnQueue ) : ITickStep { public Task TickAsync() @@ -24,7 +24,7 @@ internal sealed class CollideBulletsWithTanks( tank.Owner.Deaths++; tanks.Remove(tank); - spawnQueueProvider.Queue.Enqueue(tank.Owner); + spawnQueue.EnqueueForDelayedSpawn(tank.Owner); return true; } diff --git a/TanksServer/GameLogic/PlayersConfiguration.cs b/TanksServer/GameLogic/PlayersConfiguration.cs new file mode 100644 index 0000000..a4fb337 --- /dev/null +++ b/TanksServer/GameLogic/PlayersConfiguration.cs @@ -0,0 +1,6 @@ +namespace TanksServer.GameLogic; + +public class PlayersConfiguration +{ + public int SpawnDelayMs { get; set; } +} \ No newline at end of file diff --git a/TanksServer/GameLogic/SpawnNewTanks.cs b/TanksServer/GameLogic/SpawnNewTanks.cs index b6e93e0..fea1ae5 100644 --- a/TanksServer/GameLogic/SpawnNewTanks.cs +++ b/TanksServer/GameLogic/SpawnNewTanks.cs @@ -1,25 +1,29 @@ namespace TanksServer.GameLogic; -internal sealed class SpawnNewTanks(TankManager tanks, MapService map, SpawnQueueProvider queueProvider) : ITickStep +internal sealed class SpawnNewTanks( + TankManager tanks, + MapService map, + SpawnQueue queue, + BulletManager bullets +) : ITickStep { public Task TickAsync() { - while (queueProvider.Queue.TryDequeue(out var player)) + if (!queue.TryDequeueNext(out var player)) + return Task.CompletedTask; + + tanks.Add(new Tank(player, ChooseSpawnPosition()) { - var tank = new Tank(player, ChooseSpawnPosition()) - { - Rotation = Random.Shared.Next(0, 16) - }; - tanks.Add(tank); - } + Rotation = Random.Shared.Next(0, 16) + }); return Task.CompletedTask; } private FloatPosition ChooseSpawnPosition() { - List candidates = new(); - + Dictionary candidates = []; + for (var x = 0; x < MapService.TilesPerRow; x++) for (var y = 0; y < MapService.TilesPerColumn; y++) { @@ -27,15 +31,24 @@ internal sealed class SpawnNewTanks(TankManager tanks, MapService map, SpawnQueu if (map.IsCurrentlyWall(tile)) continue; + + var tilePixelCenter = tile.GetPixelRelative(4, 4); + + var minDistance = bullets.GetAll() + .Cast() + .Concat(tanks) + .Select(entity => Math.Sqrt( + Math.Pow(entity.Position.X - tilePixelCenter.X, 2) + + Math.Pow(entity.Position.Y - tilePixelCenter.Y, 2))) + .Aggregate(double.MaxValue, Math.Min); - // TODO: check tanks and bullets - candidates.Add(tile); + candidates.Add(tile, minDistance); } - var chosenTile = candidates[Random.Shared.Next(candidates.Count)]; + var min = candidates.MaxBy(kvp => kvp.Value).Key; return new FloatPosition( - chosenTile.X * MapService.TileSize, - chosenTile.Y * MapService.TileSize + min.X * MapService.TileSize, + min.Y * MapService.TileSize ); } -} +} \ No newline at end of file diff --git a/TanksServer/GameLogic/SpawnQueue.cs b/TanksServer/GameLogic/SpawnQueue.cs new file mode 100644 index 0000000..56e39ae --- /dev/null +++ b/TanksServer/GameLogic/SpawnQueue.cs @@ -0,0 +1,36 @@ +using System.Diagnostics.CodeAnalysis; + +namespace TanksServer.GameLogic; + +internal sealed class SpawnQueue( + IOptions options +) +{ + private ConcurrentQueue _queue = new(); + private ConcurrentDictionary _spawnTimes = new(); + private readonly TimeSpan _spawnDelay = TimeSpan.FromMilliseconds(options.Value.SpawnDelayMs); + + public void EnqueueForImmediateSpawn(Player player) + { + _queue.Enqueue(player); + } + + public void EnqueueForDelayedSpawn(Player player) + { + _queue.Enqueue(player); + _spawnTimes.AddOrUpdate(player, DateTime.MinValue, (_, _) => DateTime.Now + _spawnDelay); + } + + public bool TryDequeueNext([MaybeNullWhen(false)] out Player player) + { + if (!_queue.TryDequeue(out player)) + return false; + + var now = DateTime.Now; + if (_spawnTimes.GetOrAdd(player, DateTime.MinValue) <= now) + return true; + + _queue.Enqueue(player); + return false; + } +} \ No newline at end of file diff --git a/TanksServer/GameLogic/SpawnQueueProvider.cs b/TanksServer/GameLogic/SpawnQueueProvider.cs deleted file mode 100644 index c45b13a..0000000 --- a/TanksServer/GameLogic/SpawnQueueProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace TanksServer.GameLogic; - -internal sealed class SpawnQueueProvider -{ - public ConcurrentQueue Queue { get; } = new(); -} diff --git a/TanksServer/GameLogic/TanksConfiguration.cs b/TanksServer/GameLogic/TanksConfiguration.cs new file mode 100644 index 0000000..4c02ff2 --- /dev/null +++ b/TanksServer/GameLogic/TanksConfiguration.cs @@ -0,0 +1,9 @@ +namespace TanksServer.GameLogic; + +public class TanksConfiguration +{ + public double MoveSpeed { get; set; } + public double TurnSpeed { get; set; } + public double ShootDelayMs { get; set; } + public double BulletSpeed { get; set; } +} \ No newline at end of file diff --git a/TanksServer/Interactivity/PlayerServer.cs b/TanksServer/Interactivity/PlayerServer.cs index aa28529..19956a5 100644 --- a/TanksServer/Interactivity/PlayerServer.cs +++ b/TanksServer/Interactivity/PlayerServer.cs @@ -3,7 +3,7 @@ using TanksServer.GameLogic; namespace TanksServer.Interactivity; -internal sealed class PlayerServer(ILogger logger, SpawnQueueProvider spawnQueueProvider) +internal sealed class PlayerServer(ILogger logger, SpawnQueue spawnQueue) { private readonly ConcurrentDictionary _players = new(); @@ -33,7 +33,7 @@ internal sealed class PlayerServer(ILogger logger, SpawnQueueProvi private Player AddAndSpawn(string name) { var player = new Player(name); - spawnQueueProvider.Queue.Enqueue(player); + spawnQueue.EnqueueForImmediateSpawn(player); return player; } } diff --git a/TanksServer/Models/Bullet.cs b/TanksServer/Models/Bullet.cs index 00f6125..efb5674 100644 --- a/TanksServer/Models/Bullet.cs +++ b/TanksServer/Models/Bullet.cs @@ -1,10 +1,10 @@ namespace TanksServer.Models; -internal sealed class Bullet(Player tankOwner, FloatPosition position, double rotation) +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; -} +} \ No newline at end of file diff --git a/TanksServer/Models/FloatPosition.cs b/TanksServer/Models/FloatPosition.cs index b4763df..2f58845 100644 --- a/TanksServer/Models/FloatPosition.cs +++ b/TanksServer/Models/FloatPosition.cs @@ -1,5 +1,3 @@ namespace TanksServer.Models; -internal readonly record struct FloatPosition(double X, double Y) -{ -} +internal readonly record struct FloatPosition(double X, double Y); diff --git a/TanksServer/Models/IMapEntity.cs b/TanksServer/Models/IMapEntity.cs new file mode 100644 index 0000000..bc3ce6f --- /dev/null +++ b/TanksServer/Models/IMapEntity.cs @@ -0,0 +1,7 @@ +namespace TanksServer.Models; + +internal interface IMapEntity +{ + FloatPosition Position { get; set; } + double Rotation { get; set; } +} \ No newline at end of file diff --git a/TanksServer/Models/Tank.cs b/TanksServer/Models/Tank.cs index eb472a8..bc45529 100644 --- a/TanksServer/Models/Tank.cs +++ b/TanksServer/Models/Tank.cs @@ -2,7 +2,7 @@ using TanksServer.GameLogic; namespace TanksServer.Models; -internal sealed class Tank(Player player, FloatPosition spawnPosition) +internal sealed class Tank(Player player, FloatPosition spawnPosition): IMapEntity { private double _rotation; diff --git a/TanksServer/Models/TanksConfiguration.cs b/TanksServer/Models/TanksConfiguration.cs deleted file mode 100644 index b067d04..0000000 --- a/TanksServer/Models/TanksConfiguration.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace TanksServer.Models; - -public class TanksConfiguration -{ - public double MoveSpeed { get; set; } = 1.4; - public double TurnSpeed { get; set; } = 0.4; - public double ShootDelayMs { get; set; } = 0.4 * 1000; - public double BulletSpeed { get; set; } = 8; -} diff --git a/TanksServer/Program.cs b/TanksServer/Program.cs index e3cf71f..f404bf1 100644 --- a/TanksServer/Program.cs +++ b/TanksServer/Program.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Xml; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -59,6 +60,13 @@ public static class Program { var builder = WebApplication.CreateSlimBuilder(args); + builder.Logging.AddSimpleConsole(options => + { + options.SingleLine = true; + options.IncludeScopes = true; + options.TimestampFormat = "HH:mm:ss "; + }); + builder.Services.AddCors(options => options .AddDefaultPolicy(policy => policy .AllowAnyHeader() @@ -80,7 +88,7 @@ public static class Program builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); @@ -103,6 +111,10 @@ public static class Program builder.Services.Configure( builder.Configuration.GetSection("ServicePointDisplay")); + builder.Services.Configure( + builder.Configuration.GetSection("Tanks")); + builder.Services.Configure( + builder.Configuration.GetSection("Players")); var app = builder.Build(); diff --git a/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs b/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs index 003afb0..24ab1ab 100644 --- a/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs +++ b/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs @@ -40,8 +40,8 @@ internal sealed class SendToServicePointDisplay : ITickStep, IDisposable { Rows = { - [0] = "== TANKS! ==", - [1] = "-- scores --", + [00] = "== TANKS! ==", + [01] = "-- scores --", [17] = "-- join --", [18] = string.Join('.', localIp[..2]), [19] = string.Join('.', localIp[2..]) diff --git a/TanksServer/appsettings.json b/TanksServer/appsettings.json index 0266752..2832663 100644 --- a/TanksServer/appsettings.json +++ b/TanksServer/appsettings.json @@ -1,24 +1,32 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "TanksServer": "Debug", - "Microsoft.AspNetCore.HttpLogging": "Debug" - } - }, - "AllowedHosts": "*", - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://localhost:3000" - } - } - }, - "ServicePointDisplay": { - "Enable": true, - //"Hostname": "172.23.42.29", - "Hostname": "localhost", - "Port": 2342 + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "TanksServer": "Debug", + "Microsoft.AspNetCore.HttpLogging": "Information" } + }, + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:3000" + } + } + }, + "ServicePointDisplay": { + "Enable": false, + "Hostname": "172.23.42.29", + "Port": 2342 + }, + "Tanks": { + "MoveSpeed": 1.4, + "TurnSpeed": 0.4, + "ShootDelayMs": 400, + "BulletSpeed": 8 + }, + "Players": { + "SpawnDelayMs": 3000 + } } diff --git a/tank-frontend/src/ClientScreen.tsx b/tank-frontend/src/ClientScreen.tsx index 3386524..0a5e1b2 100644 --- a/tank-frontend/src/ClientScreen.tsx +++ b/tank-frontend/src/ClientScreen.tsx @@ -6,6 +6,9 @@ import {statusTextForReadyState} from './statusTextForReadyState.tsx'; const pixelsPerRow = 352; const pixelsPerCol = 160; +const onColor = [0, 180, 0, 255]; +const offColor = [0, 0, 0, 255]; + function getIndexes(bitIndex: number) { return { byteIndex: 10 + Math.floor(bitIndex / 8), @@ -26,28 +29,18 @@ function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement) { for (let x = 0; x < canvas.width; x++) { const pixelIndex = rowStartPixelIndex + x; const {byteIndex, bitInByteIndex} = getIndexes(pixelIndex); - const byte = pixels[byteIndex]; const mask = (1 << bitInByteIndex); - const bitCheck = byte & mask; - const isOn = bitCheck !== 0; + const isOn = (pixels[byteIndex] & mask) !== 0; + const color = isOn ? onColor : offColor; - const dataIndex = pixelIndex * 4; - if (isOn) { - data[dataIndex] = 0; // r - data[dataIndex + 1] = 180; // g - data[dataIndex + 2] = 0; // b - data[dataIndex + 3] = 255; // a - } else { - data[dataIndex] = 0; // r - data[dataIndex + 1] = 0; // g - data[dataIndex + 2] = 0; // b - data[dataIndex + 3] = 255; // a - } + for (let colorChannel of [0, 1, 2, 3]) + data[pixelIndex * 4 + colorChannel] = color[colorChannel]; } } drawContext.putImageData(imageData, 0, 0); } + export default function ClientScreen({}: {}) { const canvasRef = useRef(null);