improve spawn position checks
This commit is contained in:
parent
91ab911f9c
commit
ad211433fb
|
@ -1,7 +1,7 @@
|
||||||
namespace TanksServer.GameLogic;
|
namespace TanksServer.GameLogic;
|
||||||
|
|
||||||
internal sealed class CollideBulletsWithTanks(
|
internal sealed class CollideBulletsWithTanks(
|
||||||
BulletManager bullets, TankManager tanks, SpawnQueueProvider spawnQueueProvider
|
BulletManager bullets, TankManager tanks, SpawnQueue spawnQueue
|
||||||
) : ITickStep
|
) : ITickStep
|
||||||
{
|
{
|
||||||
public Task TickAsync()
|
public Task TickAsync()
|
||||||
|
@ -24,7 +24,7 @@ internal sealed class CollideBulletsWithTanks(
|
||||||
tank.Owner.Deaths++;
|
tank.Owner.Deaths++;
|
||||||
|
|
||||||
tanks.Remove(tank);
|
tanks.Remove(tank);
|
||||||
spawnQueueProvider.Queue.Enqueue(tank.Owner);
|
spawnQueue.EnqueueForDelayedSpawn(tank.Owner);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
6
TanksServer/GameLogic/PlayersConfiguration.cs
Normal file
6
TanksServer/GameLogic/PlayersConfiguration.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace TanksServer.GameLogic;
|
||||||
|
|
||||||
|
public class PlayersConfiguration
|
||||||
|
{
|
||||||
|
public int SpawnDelayMs { get; set; }
|
||||||
|
}
|
|
@ -1,24 +1,28 @@
|
||||||
namespace TanksServer.GameLogic;
|
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()
|
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)
|
||||||
{
|
});
|
||||||
Rotation = Random.Shared.Next(0, 16)
|
|
||||||
};
|
|
||||||
tanks.Add(tank);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FloatPosition ChooseSpawnPosition()
|
private FloatPosition ChooseSpawnPosition()
|
||||||
{
|
{
|
||||||
List<TilePosition> candidates = new();
|
Dictionary<TilePosition, double> candidates = [];
|
||||||
|
|
||||||
for (var x = 0; x < MapService.TilesPerRow; x++)
|
for (var x = 0; x < MapService.TilesPerRow; x++)
|
||||||
for (var y = 0; y < MapService.TilesPerColumn; y++)
|
for (var y = 0; y < MapService.TilesPerColumn; y++)
|
||||||
|
@ -28,14 +32,23 @@ internal sealed class SpawnNewTanks(TankManager tanks, MapService map, SpawnQueu
|
||||||
if (map.IsCurrentlyWall(tile))
|
if (map.IsCurrentlyWall(tile))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// TODO: check tanks and bullets
|
var tilePixelCenter = tile.GetPixelRelative(4, 4);
|
||||||
candidates.Add(tile);
|
|
||||||
|
var minDistance = bullets.GetAll()
|
||||||
|
.Cast<IMapEntity>()
|
||||||
|
.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);
|
||||||
|
|
||||||
|
candidates.Add(tile, minDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
var chosenTile = candidates[Random.Shared.Next(candidates.Count)];
|
var min = candidates.MaxBy(kvp => kvp.Value).Key;
|
||||||
return new FloatPosition(
|
return new FloatPosition(
|
||||||
chosenTile.X * MapService.TileSize,
|
min.X * MapService.TileSize,
|
||||||
chosenTile.Y * MapService.TileSize
|
min.Y * MapService.TileSize
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
36
TanksServer/GameLogic/SpawnQueue.cs
Normal file
36
TanksServer/GameLogic/SpawnQueue.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace TanksServer.GameLogic;
|
||||||
|
|
||||||
|
internal sealed class SpawnQueue(
|
||||||
|
IOptions<PlayersConfiguration> options
|
||||||
|
)
|
||||||
|
{
|
||||||
|
private ConcurrentQueue<Player> _queue = new();
|
||||||
|
private ConcurrentDictionary<Player, DateTime> _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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
namespace TanksServer.GameLogic;
|
|
||||||
|
|
||||||
internal sealed class SpawnQueueProvider
|
|
||||||
{
|
|
||||||
public ConcurrentQueue<Player> Queue { get; } = new();
|
|
||||||
}
|
|
9
TanksServer/GameLogic/TanksConfiguration.cs
Normal file
9
TanksServer/GameLogic/TanksConfiguration.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ using TanksServer.GameLogic;
|
||||||
|
|
||||||
namespace TanksServer.Interactivity;
|
namespace TanksServer.Interactivity;
|
||||||
|
|
||||||
internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnQueueProvider spawnQueueProvider)
|
internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnQueue spawnQueue)
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, Player> _players = new();
|
private readonly ConcurrentDictionary<string, Player> _players = new();
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnQueueProvi
|
||||||
private Player AddAndSpawn(string name)
|
private Player AddAndSpawn(string name)
|
||||||
{
|
{
|
||||||
var player = new Player(name);
|
var player = new Player(name);
|
||||||
spawnQueueProvider.Queue.Enqueue(player);
|
spawnQueue.EnqueueForImmediateSpawn(player);
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
namespace TanksServer.Models;
|
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 Player Owner { get; } = tankOwner;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
namespace TanksServer.Models;
|
namespace TanksServer.Models;
|
||||||
|
|
||||||
internal readonly record struct FloatPosition(double X, double Y)
|
internal readonly record struct FloatPosition(double X, double Y);
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
7
TanksServer/Models/IMapEntity.cs
Normal file
7
TanksServer/Models/IMapEntity.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace TanksServer.Models;
|
||||||
|
|
||||||
|
internal interface IMapEntity
|
||||||
|
{
|
||||||
|
FloatPosition Position { get; set; }
|
||||||
|
double Rotation { get; set; }
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ using TanksServer.GameLogic;
|
||||||
|
|
||||||
namespace TanksServer.Models;
|
namespace TanksServer.Models;
|
||||||
|
|
||||||
internal sealed class Tank(Player player, FloatPosition spawnPosition)
|
internal sealed class Tank(Player player, FloatPosition spawnPosition): IMapEntity
|
||||||
{
|
{
|
||||||
private double _rotation;
|
private double _rotation;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -59,6 +60,13 @@ public static class Program
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateSlimBuilder(args);
|
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
|
builder.Services.AddCors(options => options
|
||||||
.AddDefaultPolicy(policy => policy
|
.AddDefaultPolicy(policy => policy
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
|
@ -80,7 +88,7 @@ public static class Program
|
||||||
builder.Services.AddSingleton<PlayerServer>();
|
builder.Services.AddSingleton<PlayerServer>();
|
||||||
builder.Services.AddSingleton<ClientScreenServer>();
|
builder.Services.AddSingleton<ClientScreenServer>();
|
||||||
builder.Services.AddSingleton<LastFinishedFrameProvider>();
|
builder.Services.AddSingleton<LastFinishedFrameProvider>();
|
||||||
builder.Services.AddSingleton<SpawnQueueProvider>();
|
builder.Services.AddSingleton<SpawnQueue>();
|
||||||
|
|
||||||
builder.Services.AddHostedService<GameTickWorker>();
|
builder.Services.AddHostedService<GameTickWorker>();
|
||||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>());
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<ControlsServer>());
|
||||||
|
@ -103,6 +111,10 @@ public static class Program
|
||||||
|
|
||||||
builder.Services.Configure<ServicePointDisplayConfiguration>(
|
builder.Services.Configure<ServicePointDisplayConfiguration>(
|
||||||
builder.Configuration.GetSection("ServicePointDisplay"));
|
builder.Configuration.GetSection("ServicePointDisplay"));
|
||||||
|
builder.Services.Configure<TanksConfiguration>(
|
||||||
|
builder.Configuration.GetSection("Tanks"));
|
||||||
|
builder.Services.Configure<PlayersConfiguration>(
|
||||||
|
builder.Configuration.GetSection("Players"));
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,8 @@ internal sealed class SendToServicePointDisplay : ITickStep, IDisposable
|
||||||
{
|
{
|
||||||
Rows =
|
Rows =
|
||||||
{
|
{
|
||||||
[0] = "== TANKS! ==",
|
[00] = "== TANKS! ==",
|
||||||
[1] = "-- scores --",
|
[01] = "-- scores --",
|
||||||
[17] = "-- join --",
|
[17] = "-- join --",
|
||||||
[18] = string.Join('.', localIp[..2]),
|
[18] = string.Join('.', localIp[..2]),
|
||||||
[19] = string.Join('.', localIp[2..])
|
[19] = string.Join('.', localIp[2..])
|
||||||
|
|
|
@ -1,24 +1,32 @@
|
||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning",
|
"Microsoft.AspNetCore": "Warning",
|
||||||
"TanksServer": "Debug",
|
"TanksServer": "Debug",
|
||||||
"Microsoft.AspNetCore.HttpLogging": "Debug"
|
"Microsoft.AspNetCore.HttpLogging": "Information"
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*",
|
|
||||||
"Kestrel": {
|
|
||||||
"Endpoints": {
|
|
||||||
"Http": {
|
|
||||||
"Url": "http://localhost:3000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ServicePointDisplay": {
|
|
||||||
"Enable": true,
|
|
||||||
//"Hostname": "172.23.42.29",
|
|
||||||
"Hostname": "localhost",
|
|
||||||
"Port": 2342
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ import {statusTextForReadyState} from './statusTextForReadyState.tsx';
|
||||||
const pixelsPerRow = 352;
|
const pixelsPerRow = 352;
|
||||||
const pixelsPerCol = 160;
|
const pixelsPerCol = 160;
|
||||||
|
|
||||||
|
const onColor = [0, 180, 0, 255];
|
||||||
|
const offColor = [0, 0, 0, 255];
|
||||||
|
|
||||||
function getIndexes(bitIndex: number) {
|
function getIndexes(bitIndex: number) {
|
||||||
return {
|
return {
|
||||||
byteIndex: 10 + Math.floor(bitIndex / 8),
|
byteIndex: 10 + Math.floor(bitIndex / 8),
|
||||||
|
@ -26,28 +29,18 @@ function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement) {
|
||||||
for (let x = 0; x < canvas.width; x++) {
|
for (let x = 0; x < canvas.width; x++) {
|
||||||
const pixelIndex = rowStartPixelIndex + x;
|
const pixelIndex = rowStartPixelIndex + x;
|
||||||
const {byteIndex, bitInByteIndex} = getIndexes(pixelIndex);
|
const {byteIndex, bitInByteIndex} = getIndexes(pixelIndex);
|
||||||
const byte = pixels[byteIndex];
|
|
||||||
const mask = (1 << bitInByteIndex);
|
const mask = (1 << bitInByteIndex);
|
||||||
const bitCheck = byte & mask;
|
const isOn = (pixels[byteIndex] & mask) !== 0;
|
||||||
const isOn = bitCheck !== 0;
|
const color = isOn ? onColor : offColor;
|
||||||
|
|
||||||
const dataIndex = pixelIndex * 4;
|
for (let colorChannel of [0, 1, 2, 3])
|
||||||
if (isOn) {
|
data[pixelIndex * 4 + colorChannel] = color[colorChannel];
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawContext.putImageData(imageData, 0, 0);
|
drawContext.putImageData(imageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ClientScreen({}: {}) {
|
export default function ClientScreen({}: {}) {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue