send scores to big display
This commit is contained in:
parent
a89392beb8
commit
7f00160780
|
@ -1,11 +1,12 @@
|
||||||
using TanksServer.Helpers;
|
using TanksServer.Helpers;
|
||||||
|
using TanksServer.ServicePointDisplay;
|
||||||
using TanksServer.Services;
|
using TanksServer.Services;
|
||||||
|
|
||||||
namespace TanksServer.DrawSteps;
|
namespace TanksServer.DrawSteps;
|
||||||
|
|
||||||
internal sealed class BulletDrawer(BulletManager bullets): IDrawStep
|
internal sealed class BulletDrawer(BulletManager bullets): IDrawStep
|
||||||
{
|
{
|
||||||
public void Draw(DisplayPixelBuffer buffer)
|
public void Draw(PixelDisplayBufferView buffer)
|
||||||
{
|
{
|
||||||
foreach (var bullet in bullets.GetAll())
|
foreach (var bullet in bullets.GetAll())
|
||||||
buffer.Pixels[bullet.Position.ToPixelPosition().ToPixelIndex()] = true;
|
buffer.Pixels[bullet.Position.ToPixelPosition().ToPixelIndex()] = true;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using TanksServer.Helpers;
|
using TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
namespace TanksServer.DrawSteps;
|
namespace TanksServer.DrawSteps;
|
||||||
|
|
||||||
internal interface IDrawStep
|
internal interface IDrawStep
|
||||||
{
|
{
|
||||||
void Draw(DisplayPixelBuffer buffer);
|
void Draw(PixelDisplayBufferView buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
using TanksServer.Helpers;
|
using TanksServer.Helpers;
|
||||||
using TanksServer.Models;
|
using TanksServer.Models;
|
||||||
|
using TanksServer.ServicePointDisplay;
|
||||||
using TanksServer.Services;
|
using TanksServer.Services;
|
||||||
|
|
||||||
namespace TanksServer.DrawSteps;
|
namespace TanksServer.DrawSteps;
|
||||||
|
|
||||||
internal sealed class MapDrawer(MapService map) : IDrawStep
|
internal sealed class MapDrawer(MapService map) : IDrawStep
|
||||||
{
|
{
|
||||||
public void Draw(DisplayPixelBuffer buffer)
|
public void Draw(PixelDisplayBufferView buffer)
|
||||||
{
|
{
|
||||||
for (var tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
|
for (var tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
|
||||||
for (var tileX = 0; tileX < MapService.TilesPerRow; tileX++)
|
for (var tileX = 0; tileX < MapService.TilesPerRow; tileX++)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using TanksServer.Helpers;
|
using TanksServer.Helpers;
|
||||||
|
using TanksServer.ServicePointDisplay;
|
||||||
using TanksServer.Services;
|
using TanksServer.Services;
|
||||||
|
|
||||||
namespace TanksServer.DrawSteps;
|
namespace TanksServer.DrawSteps;
|
||||||
|
@ -29,7 +30,7 @@ internal sealed class TankDrawer : IDrawStep
|
||||||
_tankSpriteWidth = tankImage.Width;
|
_tankSpriteWidth = tankImage.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(DisplayPixelBuffer buffer)
|
public void Draw(PixelDisplayBufferView buffer)
|
||||||
{
|
{
|
||||||
foreach (var tank in _tanks)
|
foreach (var tank in _tanks)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
namespace TanksServer.Helpers;
|
|
||||||
|
|
||||||
internal sealed class DisplayPixelBuffer(byte[] data)
|
|
||||||
{
|
|
||||||
public byte[] Data => data;
|
|
||||||
|
|
||||||
public byte Magic1
|
|
||||||
{
|
|
||||||
get => data[0];
|
|
||||||
set => data[0] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte Magic2
|
|
||||||
{
|
|
||||||
get => data[1];
|
|
||||||
set => data[1] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ushort X
|
|
||||||
{
|
|
||||||
get => GetTwoBytes(2);
|
|
||||||
set => SetTwoBytes(2, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ushort Y
|
|
||||||
{
|
|
||||||
get => GetTwoBytes(4);
|
|
||||||
set => SetTwoBytes(4, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ushort WidthInTiles
|
|
||||||
{
|
|
||||||
get => GetTwoBytes(6);
|
|
||||||
set => SetTwoBytes(6, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ushort HeightInPixels
|
|
||||||
{
|
|
||||||
get => GetTwoBytes(8);
|
|
||||||
set => SetTwoBytes(8, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FixedSizeBitFieldView Pixels { get; } = new(data.AsMemory(10));
|
|
||||||
|
|
||||||
private ushort GetTwoBytes(int index)
|
|
||||||
{
|
|
||||||
return (ushort)(data[index] * byte.MaxValue + data[index + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetTwoBytes(int index, ushort value)
|
|
||||||
{
|
|
||||||
data[index] = (byte)(value / byte.MaxValue);
|
|
||||||
data[index + 1] = (byte)(value % byte.MaxValue);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using TanksServer.DrawSteps;
|
using TanksServer.DrawSteps;
|
||||||
using TanksServer.Helpers;
|
using TanksServer.Helpers;
|
||||||
using TanksServer.Models;
|
|
||||||
using TanksServer.Servers;
|
using TanksServer.Servers;
|
||||||
|
using TanksServer.ServicePointDisplay;
|
||||||
using TanksServer.Services;
|
using TanksServer.Services;
|
||||||
using TanksServer.TickSteps;
|
using TanksServer.TickSteps;
|
||||||
|
|
||||||
|
@ -81,7 +81,6 @@ internal static class Program
|
||||||
builder.Services.AddSingleton<MapService>();
|
builder.Services.AddSingleton<MapService>();
|
||||||
builder.Services.AddSingleton<BulletManager>();
|
builder.Services.AddSingleton<BulletManager>();
|
||||||
builder.Services.AddSingleton<TankManager>();
|
builder.Services.AddSingleton<TankManager>();
|
||||||
builder.Services.AddSingleton<SpawnNewTanks>();
|
|
||||||
builder.Services.AddSingleton<ControlsServer>();
|
builder.Services.AddSingleton<ControlsServer>();
|
||||||
builder.Services.AddSingleton<PlayerServer>();
|
builder.Services.AddSingleton<PlayerServer>();
|
||||||
builder.Services.AddSingleton<ClientScreenServer>();
|
builder.Services.AddSingleton<ClientScreenServer>();
|
||||||
|
@ -98,7 +97,7 @@ internal static class Program
|
||||||
builder.Services.AddSingleton<ITickStep, RotateTanks>();
|
builder.Services.AddSingleton<ITickStep, RotateTanks>();
|
||||||
builder.Services.AddSingleton<ITickStep, MoveTanks>();
|
builder.Services.AddSingleton<ITickStep, MoveTanks>();
|
||||||
builder.Services.AddSingleton<ITickStep, ShootFromTanks>();
|
builder.Services.AddSingleton<ITickStep, ShootFromTanks>();
|
||||||
builder.Services.AddSingleton<ITickStep>(sp => sp.GetRequiredService<SpawnNewTanks>());
|
builder.Services.AddSingleton<ITickStep, SpawnNewTanks>();
|
||||||
builder.Services.AddSingleton<ITickStep, DrawStateToFrame>();
|
builder.Services.AddSingleton<ITickStep, DrawStateToFrame>();
|
||||||
builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>();
|
builder.Services.AddSingleton<ITickStep, SendToServicePointDisplay>();
|
||||||
builder.Services.AddSingleton<ITickStep, SendToClientScreen>();
|
builder.Services.AddSingleton<ITickStep, SendToClientScreen>();
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading.Channels;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TanksServer.Helpers;
|
using TanksServer.Helpers;
|
||||||
|
using TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
namespace TanksServer.Servers;
|
namespace TanksServer.Servers;
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ internal sealed class ClientScreenServer(
|
||||||
Done = ReceiveAsync();
|
Done = ReceiveAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(DisplayPixelBuffer buf)
|
public async Task SendAsync(PixelDisplayBufferView buf)
|
||||||
{
|
{
|
||||||
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
|
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,6 +30,8 @@ internal sealed class PlayerServer(ILogger<PlayerServer> logger, SpawnQueueProvi
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Player> GetAll() => _players.Values;
|
||||||
|
|
||||||
private Player AddAndSpawn(string name)
|
private Player AddAndSpawn(string name)
|
||||||
{
|
{
|
||||||
var player = new Player(name);
|
var player = new Player(name);
|
||||||
|
|
64
TanksServer/ServicePointDisplay/DisplayBufferView.cs
Normal file
64
TanksServer/ServicePointDisplay/DisplayBufferView.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using TanksServer.Models;
|
||||||
|
|
||||||
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
|
internal class DisplayBufferView(byte[] data)
|
||||||
|
{
|
||||||
|
public byte[] Data => data;
|
||||||
|
|
||||||
|
public ushort Mode
|
||||||
|
{
|
||||||
|
get => GetTwoBytes(0);
|
||||||
|
set => SetTwoBytes(0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort TileX
|
||||||
|
{
|
||||||
|
get => GetTwoBytes(2);
|
||||||
|
set => SetTwoBytes(2, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort TileY
|
||||||
|
{
|
||||||
|
get => GetTwoBytes(4);
|
||||||
|
set => SetTwoBytes(4, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort WidthInTiles
|
||||||
|
{
|
||||||
|
get => GetTwoBytes(6);
|
||||||
|
set => SetTwoBytes(6, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort RowCount
|
||||||
|
{
|
||||||
|
get => GetTwoBytes(8);
|
||||||
|
set => SetTwoBytes(8, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TilePosition Position
|
||||||
|
{
|
||||||
|
get => new(TileX, TileY);
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(value.X, ushort.MaxValue);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Y, ushort.MaxValue);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(value.X);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(value.Y);
|
||||||
|
|
||||||
|
TileX = (ushort)value.X;
|
||||||
|
TileY = (ushort)value.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ushort GetTwoBytes(int index)
|
||||||
|
{
|
||||||
|
return (ushort)(data[index] * byte.MaxValue + data[index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTwoBytes(int index, ushort value)
|
||||||
|
{
|
||||||
|
data[index] = (byte)(value / byte.MaxValue);
|
||||||
|
data[index + 1] = (byte)(value % byte.MaxValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
namespace TanksServer.Helpers;
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
internal sealed class FixedSizeBitFieldView(Memory<byte> data) : IList<bool>
|
internal sealed class FixedSizeBitFieldView(Memory<byte> data) : IList<bool>
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ internal sealed class FixedSizeBitFieldView(Memory<byte> data) : IList<bool>
|
||||||
public bool IsReadOnly => false;
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
public IEnumerator<bool> GetEnumerator()
|
public IEnumerator<bool> GetEnumerator()
|
||||||
{
|
{
|
||||||
return Enumerable().GetEnumerator();
|
return Enumerable().GetEnumerator();
|
||||||
|
@ -32,10 +33,17 @@ internal sealed class FixedSizeBitFieldView(Memory<byte> data) : IList<bool>
|
||||||
array[i + arrayIndex] = this[i];
|
array[i + arrayIndex] = this[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (int byteIndex, int bitInByteIndex) GetIndexes(int bitIndex)
|
private (int byteIndex, int bitInByteIndex) GetIndexes(int bitIndex)
|
||||||
{
|
{
|
||||||
var byteIndex = bitIndex / 8;
|
var byteIndex = bitIndex / 8;
|
||||||
var bitInByteIndex = 7 - bitIndex % 8;
|
var bitInByteIndex = 7 - bitIndex % 8;
|
||||||
|
if (byteIndex >= data.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bitIndex),
|
||||||
|
$"accessing this bit field at position {bitIndex} would result in an access to byte " +
|
||||||
|
$"{byteIndex} but byte length is {data.Length}");
|
||||||
|
}
|
||||||
|
|
||||||
return (byteIndex, bitInByteIndex);
|
return (byteIndex, bitInByteIndex);
|
||||||
}
|
}
|
||||||
|
|
39
TanksServer/ServicePointDisplay/FixedSizeCharGridView.cs
Normal file
39
TanksServer/ServicePointDisplay/FixedSizeCharGridView.cs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
|
internal sealed class FixedSizeCharGridView(Memory<byte> data, ushort rowLength, ushort rowCount)
|
||||||
|
{
|
||||||
|
public char this[int x, int y]
|
||||||
|
{
|
||||||
|
get => (char)data.Span[x + y * rowLength];
|
||||||
|
set => data.Span[x + y * rowLength] = CharToByte(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string this[int row]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var rowStart = row * rowLength;
|
||||||
|
return Encoding.UTF8.GetString(data[rowStart..(rowStart + rowLength)].Span);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(row, rowCount, nameof(row));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(value.Length, rowLength, nameof(value));
|
||||||
|
var x = 0;
|
||||||
|
for (; x < value.Length; x++)
|
||||||
|
this[x, row] = value[x];
|
||||||
|
for (; x < rowLength; x++)
|
||||||
|
this[x, row] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte CharToByte(char c)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(c);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(c, (char)byte.MaxValue, nameof(c));
|
||||||
|
// c# strings are UTF-16
|
||||||
|
return (byte)c;
|
||||||
|
}
|
||||||
|
}
|
29
TanksServer/ServicePointDisplay/PixelDisplayBufferView.cs
Normal file
29
TanksServer/ServicePointDisplay/PixelDisplayBufferView.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using TanksServer.Helpers;
|
||||||
|
using TanksServer.Services;
|
||||||
|
|
||||||
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
|
internal sealed class PixelDisplayBufferView : DisplayBufferView
|
||||||
|
{
|
||||||
|
private PixelDisplayBufferView(byte[] data) : base(data)
|
||||||
|
{
|
||||||
|
Pixels = new FixedSizeBitFieldView(Data.AsMemory(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once CollectionNeverQueried.Global (setting values in collection updates underlying byte array)
|
||||||
|
public FixedSizeBitFieldView Pixels { get; }
|
||||||
|
|
||||||
|
public static PixelDisplayBufferView New(ushort x, ushort y, ushort widthInTiles, ushort pixelRows)
|
||||||
|
{
|
||||||
|
// 10 bytes header, one byte per tile row (with one bit each pixel) after that
|
||||||
|
var size = 10 + widthInTiles * pixelRows;
|
||||||
|
return new PixelDisplayBufferView(new byte[size])
|
||||||
|
{
|
||||||
|
Mode = 19,
|
||||||
|
TileX = x,
|
||||||
|
TileY = y,
|
||||||
|
WidthInTiles = widthInTiles,
|
||||||
|
RowCount = pixelRows
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
103
TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs
Normal file
103
TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using TanksServer.Servers;
|
||||||
|
using TanksServer.Services;
|
||||||
|
using TanksServer.TickSteps;
|
||||||
|
|
||||||
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
|
internal sealed class SendToServicePointDisplay : ITickStep, IDisposable
|
||||||
|
{
|
||||||
|
private readonly UdpClient? _udpClient;
|
||||||
|
private readonly LastFinishedFrameProvider _lastFinishedFrameProvider;
|
||||||
|
private readonly TextDisplayBuffer _scoresBuffer;
|
||||||
|
private readonly PlayerServer _players;
|
||||||
|
private readonly ILogger<SendToServicePointDisplay> _logger;
|
||||||
|
|
||||||
|
private const int ScoresWidth = 12;
|
||||||
|
private const int ScoresHeight = 20;
|
||||||
|
private const int ScoresPlayerRows = ScoresHeight - 5;
|
||||||
|
|
||||||
|
public SendToServicePointDisplay(
|
||||||
|
IOptions<ServicePointDisplayConfiguration> options,
|
||||||
|
LastFinishedFrameProvider lastFinishedFrameProvider,
|
||||||
|
PlayerServer players,
|
||||||
|
ILogger<SendToServicePointDisplay> logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_lastFinishedFrameProvider = lastFinishedFrameProvider;
|
||||||
|
_players = players;
|
||||||
|
_logger = logger;
|
||||||
|
_udpClient = options.Value.Enable
|
||||||
|
? new UdpClient(options.Value.Hostname, options.Value.Port)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
_scoresBuffer = new(new(MapService.TilesPerRow, 0), 12, 20);
|
||||||
|
_scoresBuffer.Rows[0] = "== TANKS! ==";
|
||||||
|
_scoresBuffer.Rows[1] = "-- scores --";
|
||||||
|
|
||||||
|
_scoresBuffer.Rows[17] = "-- join --";
|
||||||
|
|
||||||
|
var localIp = GetLocalIp(options.Value.Hostname, options.Value.Port).Split('.');
|
||||||
|
Debug.Assert(localIp.Length == 4); // were talking legacy ip
|
||||||
|
_scoresBuffer.Rows[18] = string.Join('.', localIp[..2]);
|
||||||
|
_scoresBuffer.Rows[19] = string.Join('.', localIp[2..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLocalIp(string host, int port)
|
||||||
|
{
|
||||||
|
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0);
|
||||||
|
socket.Connect(host, port);
|
||||||
|
var endPoint = socket.LocalEndPoint as IPEndPoint ?? throw new NotSupportedException();
|
||||||
|
return endPoint.Address.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task TickAsync()
|
||||||
|
{
|
||||||
|
return _udpClient == null ? Task.CompletedTask : Core();
|
||||||
|
|
||||||
|
async Task Core()
|
||||||
|
{
|
||||||
|
RefreshScores();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _udpClient.SendAsync(_scoresBuffer.Data);
|
||||||
|
await _udpClient.SendAsync(_lastFinishedFrameProvider.LastFrame.Data);
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "could not send data to service point display");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshScores()
|
||||||
|
{
|
||||||
|
var playersToDisplay = _players.GetAll()
|
||||||
|
.OrderByDescending(p => p.Kills)
|
||||||
|
.Take(ScoresPlayerRows);
|
||||||
|
|
||||||
|
var row = 2;
|
||||||
|
foreach (var p in playersToDisplay)
|
||||||
|
{
|
||||||
|
var score = p.Kills.ToString();
|
||||||
|
var nameLength = ScoresWidth - score.Length;
|
||||||
|
|
||||||
|
var name = p.Name[..nameLength];
|
||||||
|
var spaces = new string(' ', nameLength - name.Length + 1);
|
||||||
|
|
||||||
|
_scoresBuffer.Rows[row] = name + spaces + score;
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; row < 17; row++)
|
||||||
|
_scoresBuffer.Rows[row] = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_udpClient?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace TanksServer.Models;
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
internal sealed class ServicePointDisplayConfiguration
|
internal sealed class ServicePointDisplayConfiguration
|
||||||
{
|
{
|
18
TanksServer/ServicePointDisplay/TextDisplayBuffer.cs
Normal file
18
TanksServer/ServicePointDisplay/TextDisplayBuffer.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using TanksServer.Models;
|
||||||
|
|
||||||
|
namespace TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
|
internal sealed class TextDisplayBuffer : DisplayBufferView
|
||||||
|
{
|
||||||
|
public TextDisplayBuffer(TilePosition position, ushort charsPerRow, ushort rows)
|
||||||
|
: base(new byte[10 + charsPerRow * rows])
|
||||||
|
{
|
||||||
|
Mode = 3;
|
||||||
|
WidthInTiles = charsPerRow;
|
||||||
|
RowCount = rows;
|
||||||
|
Position = position;
|
||||||
|
Rows = new FixedSizeCharGridView(Data.AsMemory(10), charsPerRow, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedSizeCharGridView Rows { get; set; }
|
||||||
|
}
|
|
@ -3,12 +3,16 @@ using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TanksServer.TickSteps;
|
using TanksServer.TickSteps;
|
||||||
|
|
||||||
namespace TanksServer;
|
namespace TanksServer.Services;
|
||||||
|
|
||||||
internal sealed class GameTickWorker(
|
internal sealed class GameTickWorker(
|
||||||
IEnumerable<ITickStep> steps, IHostApplicationLifetime lifetime, ILogger<GameTickWorker> logger
|
IEnumerable<ITickStep> steps,
|
||||||
|
IHostApplicationLifetime lifetime,
|
||||||
|
ILogger<GameTickWorker> logger
|
||||||
) : IHostedService, IDisposable
|
) : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
|
private const int TicksPerSecond = 25;
|
||||||
|
private static readonly TimeSpan TickPacing = TimeSpan.FromMilliseconds((int)(1000 / TicksPerSecond));
|
||||||
private readonly CancellationTokenSource _cancellation = new();
|
private readonly CancellationTokenSource _cancellation = new();
|
||||||
private readonly List<ITickStep> _steps = steps.ToList();
|
private readonly List<ITickStep> _steps = steps.ToList();
|
||||||
private Task? _run;
|
private Task? _run;
|
||||||
|
@ -32,7 +36,9 @@ internal sealed class GameTickWorker(
|
||||||
foreach (var step in _steps)
|
foreach (var step in _steps)
|
||||||
await step.TickAsync();
|
await step.TickAsync();
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(1000 / 25) - sw.Elapsed);
|
var wantedDelay = TickPacing - sw.Elapsed;
|
||||||
|
if (wantedDelay.Ticks > 0)
|
||||||
|
await Task.Delay(wantedDelay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
|
@ -1,12 +1,12 @@
|
||||||
using TanksServer.Helpers;
|
using TanksServer.ServicePointDisplay;
|
||||||
|
|
||||||
namespace TanksServer.Services;
|
namespace TanksServer.Services;
|
||||||
|
|
||||||
internal sealed class LastFinishedFrameProvider
|
internal sealed class LastFinishedFrameProvider
|
||||||
{
|
{
|
||||||
private DisplayPixelBuffer? _lastFrame;
|
private PixelDisplayBufferView? _lastFrame;
|
||||||
|
|
||||||
public DisplayPixelBuffer LastFrame
|
public PixelDisplayBufferView LastFrame
|
||||||
{
|
{
|
||||||
get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn");
|
get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn");
|
||||||
set => _lastFrame = value;
|
set => _lastFrame = value;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using TanksServer.DrawSteps;
|
using TanksServer.DrawSteps;
|
||||||
using TanksServer.Helpers;
|
using TanksServer.ServicePointDisplay;
|
||||||
using TanksServer.Services;
|
using TanksServer.Services;
|
||||||
|
|
||||||
namespace TanksServer.TickSteps;
|
namespace TanksServer.TickSteps;
|
||||||
|
@ -8,30 +8,14 @@ internal sealed class DrawStateToFrame(
|
||||||
IEnumerable<IDrawStep> drawSteps, LastFinishedFrameProvider lastFrameProvider
|
IEnumerable<IDrawStep> drawSteps, LastFinishedFrameProvider lastFrameProvider
|
||||||
) : ITickStep
|
) : ITickStep
|
||||||
{
|
{
|
||||||
private const uint GameFieldPixelCount = MapService.PixelsPerRow * MapService.PixelsPerColumn;
|
|
||||||
private readonly List<IDrawStep> _drawSteps = drawSteps.ToList();
|
private readonly List<IDrawStep> _drawSteps = drawSteps.ToList();
|
||||||
|
|
||||||
public Task TickAsync()
|
public Task TickAsync()
|
||||||
{
|
{
|
||||||
var buffer = CreateGameFieldPixelBuffer();
|
var buffer = PixelDisplayBufferView.New(0, 0, MapService.TilesPerRow, MapService.PixelsPerColumn);
|
||||||
foreach (var step in _drawSteps)
|
foreach (var step in _drawSteps)
|
||||||
step.Draw(buffer);
|
step.Draw(buffer);
|
||||||
lastFrameProvider.LastFrame = buffer;
|
lastFrameProvider.LastFrame = buffer;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DisplayPixelBuffer CreateGameFieldPixelBuffer()
|
|
||||||
{
|
|
||||||
var data = new byte[10 + GameFieldPixelCount / 8];
|
|
||||||
var result = new DisplayPixelBuffer(data)
|
|
||||||
{
|
|
||||||
Magic1 = 0,
|
|
||||||
Magic2 = 19,
|
|
||||||
X = 0,
|
|
||||||
Y = 0,
|
|
||||||
WidthInTiles = MapService.TilesPerRow,
|
|
||||||
HeightInPixels = MapService.PixelsPerColumn
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
using System.Net.Sockets;
|
|
||||||
using TanksServer.Models;
|
|
||||||
using TanksServer.Services;
|
|
||||||
|
|
||||||
namespace TanksServer.TickSteps;
|
|
||||||
|
|
||||||
internal sealed class SendToServicePointDisplay(
|
|
||||||
IOptions<ServicePointDisplayConfiguration> options,
|
|
||||||
LastFinishedFrameProvider lastFinishedFrameProvider
|
|
||||||
) : ITickStep, IDisposable
|
|
||||||
{
|
|
||||||
private readonly UdpClient? _udpClient = options.Value.Enable
|
|
||||||
? new(options.Value.Hostname, options.Value.Port)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
public Task TickAsync()
|
|
||||||
{
|
|
||||||
return _udpClient?.SendAsync(lastFinishedFrameProvider.LastFrame.Data).AsTask() ?? Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_udpClient?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,9 @@ using TanksServer.Services;
|
||||||
namespace TanksServer.TickSteps;
|
namespace TanksServer.TickSteps;
|
||||||
|
|
||||||
internal sealed class ShootFromTanks(
|
internal sealed class ShootFromTanks(
|
||||||
TankManager tanks, IOptions<TanksConfiguration> options, BulletManager bulletManager
|
TankManager tanks,
|
||||||
|
IOptions<TanksConfiguration> options,
|
||||||
|
BulletManager bulletManager
|
||||||
) : ITickStep
|
) : ITickStep
|
||||||
{
|
{
|
||||||
private readonly TanksConfiguration _config = options.Value;
|
private readonly TanksConfiguration _config = options.Value;
|
||||||
|
|
|
@ -15,8 +15,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ServicePointDisplay": {
|
"ServicePointDisplay": {
|
||||||
"Enable": false,
|
"Enable": true,
|
||||||
"Hostname": "172.23.42.29",
|
//"Hostname": "172.23.42.29",
|
||||||
|
"Hostname": "localhost",
|
||||||
"Port": 2342
|
"Port": 2342
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
VITE_TANK_SCREEN_URL=ws://localhost:3000/screen
|
VITE_TANK_SCREEN_URL=ws://vinzenz-lpt2/screen
|
||||||
VITE_TANK_CONTROLS_URL=ws://localhost:3000/controls
|
VITE_TANK_CONTROLS_URL=ws://vinzenz-lpt2/controls
|
||||||
VITE_TANK_PLAYER_URL=http://localhost:3000/player
|
VITE_TANK_PLAYER_URL=http://vinzenz-lpt2/player
|
||||||
|
|
Loading…
Reference in a new issue