From 0f4eec6343766916be15a00206e72ba0750d4154 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Mon, 22 Apr 2024 19:44:28 +0200 Subject: [PATCH] show tank infos in client --- tank-frontend/src/PlayerInfo.tsx | 42 ++++++++++++++----- .../TanksServer/GameLogic/CollideBullets.cs | 10 ++--- .../TanksServer/GameLogic/MapEntityManager.cs | 12 ++++-- .../TanksServer/GameLogic/MoveTanks.cs | 2 +- .../TanksServer/GameLogic/ShootFromTanks.cs | 2 +- .../Interactivity/PlayerInfoConnection.cs | 10 ++++- .../TanksServer/Interactivity/PlayerServer.cs | 5 ++- .../TanksServer/Models/PlayerInfo.cs | 14 ++++++- tanks-backend/TanksServer/Models/Tank.cs | 2 +- 9 files changed, 73 insertions(+), 26 deletions(-) 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;