diff --git a/tank-frontend/src/PlayerInfo.tsx b/tank-frontend/src/PlayerInfo.tsx
index 188c6dd..441f2d9 100644
--- a/tank-frontend/src/PlayerInfo.tsx
+++ b/tank-frontend/src/PlayerInfo.tsx
@@ -6,11 +6,19 @@ import {useEffect, useState} from 'react';
function ScoreRow({name, value}: {
name: string;
- value?: any;
+ value?: string | any;
}) {
+ let valueStr;
+ if (value === undefined)
+ valueStr = '?';
+ else if (typeof value === 'string' || value instanceof String)
+ valueStr = value;
+ else
+ valueStr = JSON.stringify(value);
+
return
{name} |
- {value ?? '?'} |
+ {valueStr} |
;
}
@@ -22,35 +30,45 @@ type Controls = {
readonly shoot: boolean;
}
+type TankInfo = {
+ readonly explosiveBullets: number;
+ readonly position: { x: number; y: number };
+ readonly orientation: number;
+ readonly moving: boolean;
+}
+
+
type PlayerInfoMessage = {
readonly name: string;
readonly scores: Scores;
readonly controls: Controls;
+ readonly tank?: TankInfo;
}
function controlsString(controls: Controls) {
- let str = "";
+ let str = '';
if (controls.forward)
- str += "▲";
+ str += '▲';
if (controls.backward)
- str += "▼";
+ str += '▼';
if (controls.turnLeft)
- str += "◄";
+ str += '◄';
if (controls.turnRight)
- str += "►";
+ str += '►';
if (controls.shoot)
- str += "•";
+ str += '•';
return str;
}
export default function PlayerInfo({playerId}: { playerId: Guid }) {
- const [shouldSendMessage, setShouldSendMessage] = useState(true);
+ const [shouldSendMessage, setShouldSendMessage] = useState(false);
const url = makeApiUrl('/player');
url.searchParams.set('id', playerId);
const {lastJsonMessage, readyState, sendMessage} = useWebSocket(url.toString(), {
- onMessage: () => setShouldSendMessage(true)
+ onMessage: () => setShouldSendMessage(true),
+ shouldReconnect: () => true,
});
useEffect(() => {
@@ -73,6 +91,10 @@ export default function PlayerInfo({playerId}: { playerId: Guid }) {
+
+
+
+
;
diff --git a/tanks-backend/TanksServer/GameLogic/CollideBullets.cs b/tanks-backend/TanksServer/GameLogic/CollideBullets.cs
index 5368ed6..9e56338 100644
--- a/tanks-backend/TanksServer/GameLogic/CollideBullets.cs
+++ b/tanks-backend/TanksServer/GameLogic/CollideBullets.cs
@@ -11,13 +11,13 @@ internal sealed class CollideBullets(
public Task TickAsync(TimeSpan _)
{
- entityManager.RemoveBulletsWhere(BulletHitsTank);
- entityManager.RemoveBulletsWhere(TryHitAndDestroyWall);
- entityManager.RemoveBulletsWhere(TimeoutBullet);
+ entityManager.RemoveWhere(BulletHitsTank);
+ entityManager.RemoveWhere(BulletHitsWall);
+ entityManager.RemoveWhere(BulletTimesOut);
return Task.CompletedTask;
}
- private bool TimeoutBullet(Bullet bullet)
+ private bool BulletTimesOut(Bullet bullet)
{
if (bullet.Timeout > DateTime.Now)
return false;
@@ -27,7 +27,7 @@ internal sealed class CollideBullets(
return true;
}
- private bool TryHitAndDestroyWall(Bullet bullet)
+ private bool BulletHitsWall(Bullet bullet)
{
var pixel = bullet.Position.ToPixelPosition();
if (!map.Current.IsWall(pixel))
diff --git a/tanks-backend/TanksServer/GameLogic/MapEntityManager.cs b/tanks-backend/TanksServer/GameLogic/MapEntityManager.cs
index 46d6fc4..9146166 100644
--- a/tanks-backend/TanksServer/GameLogic/MapEntityManager.cs
+++ b/tanks-backend/TanksServer/GameLogic/MapEntityManager.cs
@@ -9,6 +9,7 @@ internal sealed class MapEntityManager(
private readonly HashSet _bullets = [];
private readonly HashSet _tanks = [];
private readonly HashSet _powerUps = [];
+ private readonly Dictionary _playerTanks = [];
private readonly TimeSpan _bulletTimeout = TimeSpan.FromMilliseconds(options.Value.BulletTimeoutMs);
public IEnumerable Bullets => _bullets;
@@ -31,14 +32,16 @@ internal sealed class MapEntityManager(
OwnerCollisionAfter = DateTime.Now + TimeSpan.FromSeconds(1),
});
- public void RemoveBulletsWhere(Predicate predicate) => _bullets.RemoveWhere(predicate);
+ public void RemoveWhere(Predicate predicate) => _bullets.RemoveWhere(predicate);
public void SpawnTank(Player player)
{
- _tanks.Add(new Tank(player, ChooseSpawnPosition())
+ var tank = new Tank(player, ChooseSpawnPosition())
{
Rotation = Random.Shared.NextDouble()
- });
+ };
+ _tanks.Add(tank);
+ _playerTanks[player] = tank;
logger.LogInformation("Tank added for player {}", player.Id);
}
@@ -50,6 +53,7 @@ internal sealed class MapEntityManager(
{
logger.LogInformation("Tank removed for player {}", tank.Owner.Id);
_tanks.Remove(tank);
+ _playerTanks.Remove(tank.Owner);
}
public FloatPosition ChooseSpawnPosition()
@@ -75,4 +79,6 @@ internal sealed class MapEntityManager(
var min = candidates.MaxBy(pair => pair.Value).Key;
return min.ToPixelPosition().GetPixelRelative(4, 4).ToFloatPosition();
}
+
+ public Tank? GetCurrentTankOfPlayer(Player player) => _playerTanks.GetValueOrDefault(player);
}
diff --git a/tanks-backend/TanksServer/GameLogic/MoveTanks.cs b/tanks-backend/TanksServer/GameLogic/MoveTanks.cs
index 04777ed..a9e71d9 100644
--- a/tanks-backend/TanksServer/GameLogic/MoveTanks.cs
+++ b/tanks-backend/TanksServer/GameLogic/MoveTanks.cs
@@ -11,7 +11,7 @@ internal sealed class MoveTanks(
public Task TickAsync(TimeSpan delta)
{
foreach (var tank in entityManager.Tanks)
- tank.Moved = TryMoveTank(tank, delta);
+ tank.Moving = TryMoveTank(tank, delta);
return Task.CompletedTask;
}
diff --git a/tanks-backend/TanksServer/GameLogic/ShootFromTanks.cs b/tanks-backend/TanksServer/GameLogic/ShootFromTanks.cs
index 9920c72..23f93ef 100644
--- a/tanks-backend/TanksServer/GameLogic/ShootFromTanks.cs
+++ b/tanks-backend/TanksServer/GameLogic/ShootFromTanks.cs
@@ -11,7 +11,7 @@ internal sealed class ShootFromTanks(
public Task TickAsync(TimeSpan _)
{
- foreach (var tank in entityManager.Tanks.Where(t => !t.Moved))
+ foreach (var tank in entityManager.Tanks.Where(t => !t.Moving))
Shoot(tank);
return Task.CompletedTask;
diff --git a/tanks-backend/TanksServer/Interactivity/PlayerInfoConnection.cs b/tanks-backend/TanksServer/Interactivity/PlayerInfoConnection.cs
index e6acd67..4e06987 100644
--- a/tanks-backend/TanksServer/Interactivity/PlayerInfoConnection.cs
+++ b/tanks-backend/TanksServer/Interactivity/PlayerInfoConnection.cs
@@ -1,12 +1,14 @@
using System.Net.WebSockets;
using System.Text.Json;
+using TanksServer.GameLogic;
namespace TanksServer.Interactivity;
internal sealed class PlayerInfoConnection(
Player player,
ILogger logger,
- WebSocket rawSocket
+ WebSocket rawSocket,
+ MapEntityManager entityManager
) : WebsocketServerConnection(logger, new ByteChannelWebSocket(rawSocket, logger, 0)), IDisposable
{
private readonly SemaphoreSlim _wantedFrames = new(1);
@@ -45,7 +47,11 @@ internal sealed class PlayerInfoConnection(
private byte[]? GetMessageToSend()
{
- var info = new PlayerInfo(player.Name, player.Scores, player.Controls);
+ var tank = entityManager.GetCurrentTankOfPlayer(player);
+ var tankInfo = tank != null
+ ? new TankInfo(tank.Orientation, tank.ExplosiveBullets, tank.Position.ToPixelPosition(), tank.Moving)
+ : null;
+ var info = new PlayerInfo(player.Name, player.Scores, player.Controls, tankInfo);
var response = JsonSerializer.SerializeToUtf8Bytes(info, _context.PlayerInfo);
if (response.SequenceEqual(_lastMessage))
diff --git a/tanks-backend/TanksServer/Interactivity/PlayerServer.cs b/tanks-backend/TanksServer/Interactivity/PlayerServer.cs
index 03546a3..d99a2af 100644
--- a/tanks-backend/TanksServer/Interactivity/PlayerServer.cs
+++ b/tanks-backend/TanksServer/Interactivity/PlayerServer.cs
@@ -7,7 +7,8 @@ namespace TanksServer.Interactivity;
internal sealed class PlayerServer(
ILogger logger,
ILogger connectionLogger,
- TankSpawnQueue tankSpawnQueue
+ TankSpawnQueue tankSpawnQueue,
+ MapEntityManager entityManager
) : WebsocketServer(logger), ITickStep
{
private readonly ConcurrentDictionary _players = new();
@@ -46,7 +47,7 @@ internal sealed class PlayerServer(
public IEnumerable GetAll() => _players.Values;
public Task HandleClientAsync(WebSocket webSocket, Player player)
- => HandleClientAsync(new PlayerInfoConnection(player, connectionLogger, webSocket));
+ => HandleClientAsync(new PlayerInfoConnection(player, connectionLogger, webSocket, entityManager));
public Task TickAsync(TimeSpan delta)
=> ParallelForEachConnectionAsync(connection => connection.OnGameTickAsync());
diff --git a/tanks-backend/TanksServer/Models/PlayerInfo.cs b/tanks-backend/TanksServer/Models/PlayerInfo.cs
index a5cd3f5..ece5955 100644
--- a/tanks-backend/TanksServer/Models/PlayerInfo.cs
+++ b/tanks-backend/TanksServer/Models/PlayerInfo.cs
@@ -1,3 +1,15 @@
namespace TanksServer.Models;
-internal sealed record class PlayerInfo(string Name, Scores Scores, PlayerControls Controls);
+internal sealed record class TankInfo(
+ int Orientation,
+ byte ExplosiveBullets,
+ PixelPosition Position,
+ bool Moving
+);
+
+internal sealed record class PlayerInfo(
+ string Name,
+ Scores Scores,
+ PlayerControls Controls,
+ TankInfo? Tank
+);
diff --git a/tanks-backend/TanksServer/Models/Tank.cs b/tanks-backend/TanksServer/Models/Tank.cs
index 74d236c..5f64a81 100644
--- a/tanks-backend/TanksServer/Models/Tank.cs
+++ b/tanks-backend/TanksServer/Models/Tank.cs
@@ -22,7 +22,7 @@ internal sealed class Tank(Player player, FloatPosition spawnPosition) : IMapEnt
public DateTime NextShotAfter { get; set; }
- public bool Moved { get; set; }
+ public bool Moving { get; set; }
public FloatPosition Position { get; set; } = spawnPosition;