diff --git a/.gitignore b/.gitignore
index cb1cef4..8a0cf38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
bin
obj
.idea
+client
+
diff --git a/DisplayCommands/ByteGrid.cs b/DisplayCommands/ByteGrid.cs
index 889d119..2572098 100644
--- a/DisplayCommands/ByteGrid.cs
+++ b/DisplayCommands/ByteGrid.cs
@@ -22,4 +22,6 @@ public class ByteGrid(ushort width, ushort height)
Debug.Assert(y < Height);
return x + y * Width;
}
+
+ public void Clear() => Data.Span.Clear();
}
diff --git a/DisplayCommands/GlobalUsings.cs b/DisplayCommands/GlobalUsings.cs
index 129a986..b124d17 100644
--- a/DisplayCommands/GlobalUsings.cs
+++ b/DisplayCommands/GlobalUsings.cs
@@ -1,4 +1,2 @@
-// Global using directives
-
global using System;
-global using System.Threading.Tasks;
\ No newline at end of file
+global using System.Threading.Tasks;
diff --git a/DisplayCommands/IDisplayConnection.cs b/DisplayCommands/IDisplayConnection.cs
index 5354390..db53477 100644
--- a/DisplayCommands/IDisplayConnection.cs
+++ b/DisplayCommands/IDisplayConnection.cs
@@ -3,11 +3,22 @@ namespace DisplayCommands;
public interface IDisplayConnection
{
ValueTask SendClearAsync();
-
+
ValueTask SendCp437DataAsync(ushort x, ushort y, Cp437Grid grid);
ValueTask SendCharBrightnessAsync(ushort x, ushort y, ByteGrid luma);
+
ValueTask SendBrightnessAsync(byte brightness);
+
ValueTask SendHardResetAsync();
+
ValueTask SendFadeOutAsync(byte loops);
+
+ public ValueTask SendBitmapLinearWindowAsync(ushort x, ushort y, PixelGrid pixels);
+
+ ///
+ /// Returns the IPv4 address that is associated with the interface with which the display is reachable.
+ ///
+ /// IPv4 as text
+ public string GetLocalIPv4();
}
\ No newline at end of file
diff --git a/DisplayCommands/Internals/DisplayConnection.cs b/DisplayCommands/Internals/DisplayConnection.cs
index 0e04969..5083b8b 100644
--- a/DisplayCommands/Internals/DisplayConnection.cs
+++ b/DisplayCommands/Internals/DisplayConnection.cs
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Diagnostics;
+using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Options;
@@ -74,6 +75,27 @@ internal sealed class DisplayConnection(IOptions options)
_arrayPool.Return(payloadBuffer);
}
+ public ValueTask SendBitmapLinearWindowAsync(ushort x, ushort y, PixelGrid pixels)
+ {
+ var header = new HeaderWindow
+ {
+ Command = DisplayCommand.BitmapLinearWin,
+ PosX = x, PosY = y,
+ Width = pixels.Width,
+ Height = pixels.Height
+ };
+
+ return SendAsync(header, pixels.Data);
+ }
+
+ public string GetLocalIPv4()
+ {
+ using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0);
+ socket.Connect(options.Value.Hostname, options.Value.Port);
+ var endPoint = socket.LocalEndPoint as IPEndPoint ?? throw new NotSupportedException();
+ return endPoint.Address.ToString();
+ }
+
private async ValueTask SendAsync(HeaderWindow header, Memory payload)
{
int headerSize;
diff --git a/DisplayCommands/Internals/DisplaySubCommand.cs b/DisplayCommands/Internals/DisplaySubCommand.cs
index 66e0691..57fe8f3 100644
--- a/DisplayCommands/Internals/DisplaySubCommand.cs
+++ b/DisplayCommands/Internals/DisplaySubCommand.cs
@@ -1,6 +1,6 @@
namespace DisplayCommands.Internals;
-internal enum DisplaySubCommand
+internal enum DisplaySubCommand : ushort
{
BitmapNormal = 0x0,
BitmapCompressZ = 0x677a,
diff --git a/DisplayCommands/Internals/HeaderBitmap.cs b/DisplayCommands/Internals/HeaderBitmap.cs
new file mode 100644
index 0000000..e3bdd43
--- /dev/null
+++ b/DisplayCommands/Internals/HeaderBitmap.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+
+namespace DisplayCommands.Internals;
+
+[StructLayout(LayoutKind.Sequential, Pack = 16, Size = 10)]
+internal struct HeaderBitmap
+{
+ public DisplayCommand Command;
+
+ public ushort Offset;
+
+ public ushort Length;
+
+ public DisplaySubCommand SubCommand;
+
+ public ushort Reserved;
+}
\ No newline at end of file
diff --git a/DisplayCommands/PixelGrid.cs b/DisplayCommands/PixelGrid.cs
new file mode 100644
index 0000000..e84c84e
--- /dev/null
+++ b/DisplayCommands/PixelGrid.cs
@@ -0,0 +1,47 @@
+using System.Diagnostics;
+
+namespace DisplayCommands;
+
+public sealed class PixelGrid(ushort width, ushort height)
+{
+ private readonly ByteGrid _byteGrid = new((ushort)(width / 8u), height);
+
+ public ushort Width { get; } = width;
+
+ public ushort Height { get; } = height;
+
+ public Memory Data => _byteGrid.Data;
+
+ public bool this[ushort x, ushort y]
+ {
+ get
+ {
+ Debug.Assert(y < Height);
+ var (byteIndex, bitInByteMask) = GetIndexes(x);
+ var byteVal = _byteGrid[byteIndex, y];
+ return (byteVal & bitInByteMask) != 0;
+ }
+ set
+ {
+ Debug.Assert(y < Height);
+ var (byteIndex, bitInByteMask) = GetIndexes(x);
+ if (value)
+ _byteGrid[byteIndex, y] |= bitInByteMask;
+ else
+ _byteGrid[byteIndex, y] &= (byte)(ushort.MaxValue ^ bitInByteMask);
+ }
+ }
+
+ public void Clear() => _byteGrid.Clear();
+
+ private (ushort byteIndex, byte bitInByteMask) GetIndexes(int x)
+ {
+ Debug.Assert(x < Width);
+ var byteIndex = (ushort)(x / 8);
+ Debug.Assert(byteIndex < Width);
+ var bitInByteIndex = (byte)(7 - x % 8);
+ Debug.Assert(bitInByteIndex < 8);
+ var bitInByteMask = (byte)(1 << bitInByteIndex);
+ return (byteIndex, bitInByteMask);
+ }
+}
\ No newline at end of file
diff --git a/TanksServer/Graphics/BulletDrawer.cs b/TanksServer/Graphics/BulletDrawer.cs
index fcfa562..bce2b36 100644
--- a/TanksServer/Graphics/BulletDrawer.cs
+++ b/TanksServer/Graphics/BulletDrawer.cs
@@ -1,13 +1,16 @@
+using DisplayCommands;
using TanksServer.GameLogic;
-using TanksServer.ServicePointDisplay;
namespace TanksServer.Graphics;
-internal sealed class BulletDrawer(BulletManager bullets): IDrawStep
+internal sealed class BulletDrawer(BulletManager bullets) : IDrawStep
{
- public void Draw(PixelDisplayBufferView buffer)
+ public void Draw(PixelGrid buffer)
{
foreach (var bullet in bullets.GetAll())
- buffer.Pixels[bullet.Position.ToPixelPosition()] = true;
+ {
+ var pos = bullet.Position.ToPixelPosition();
+ buffer[pos.X, pos.Y] = true;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/TanksServer/Graphics/DrawStateToFrame.cs b/TanksServer/Graphics/DrawStateToFrame.cs
index a793322..dba1a4a 100644
--- a/TanksServer/Graphics/DrawStateToFrame.cs
+++ b/TanksServer/Graphics/DrawStateToFrame.cs
@@ -1,5 +1,5 @@
+using DisplayCommands;
using TanksServer.GameLogic;
-using TanksServer.ServicePointDisplay;
namespace TanksServer.Graphics;
@@ -8,13 +8,14 @@ internal sealed class DrawStateToFrame(
) : ITickStep
{
private readonly List _drawSteps = drawSteps.ToList();
+ private readonly PixelGrid _drawGrid = new(MapService.PixelsPerRow, MapService.PixelsPerColumn);
public Task TickAsync()
{
- var buffer = PixelDisplayBufferView.New(0, 0, MapService.TilesPerRow, MapService.PixelsPerColumn);
+ _drawGrid.Clear();
foreach (var step in _drawSteps)
- step.Draw(buffer);
- lastFrameProvider.LastFrame = buffer;
+ step.Draw(_drawGrid);
+ lastFrameProvider.LastFrame = _drawGrid;
return Task.CompletedTask;
}
}
diff --git a/TanksServer/Graphics/IDrawStep.cs b/TanksServer/Graphics/IDrawStep.cs
index 9366adf..0067912 100644
--- a/TanksServer/Graphics/IDrawStep.cs
+++ b/TanksServer/Graphics/IDrawStep.cs
@@ -1,8 +1,8 @@
-using TanksServer.ServicePointDisplay;
+using DisplayCommands;
namespace TanksServer.Graphics;
internal interface IDrawStep
{
- void Draw(PixelDisplayBufferView buffer);
+ void Draw(PixelGrid buffer);
}
diff --git a/TanksServer/Graphics/LastFinishedFrameProvider.cs b/TanksServer/Graphics/LastFinishedFrameProvider.cs
index 70c101d..eb58723 100644
--- a/TanksServer/Graphics/LastFinishedFrameProvider.cs
+++ b/TanksServer/Graphics/LastFinishedFrameProvider.cs
@@ -1,12 +1,12 @@
-using TanksServer.ServicePointDisplay;
+using DisplayCommands;
namespace TanksServer.Graphics;
internal sealed class LastFinishedFrameProvider
{
- private PixelDisplayBufferView? _lastFrame;
+ private PixelGrid? _lastFrame;
- public PixelDisplayBufferView LastFrame
+ public PixelGrid LastFrame
{
get => _lastFrame ?? throw new InvalidOperationException("first frame not yet drawn");
set => _lastFrame = value;
diff --git a/TanksServer/Graphics/MapDrawer.cs b/TanksServer/Graphics/MapDrawer.cs
index d40fef6..df0d786 100644
--- a/TanksServer/Graphics/MapDrawer.cs
+++ b/TanksServer/Graphics/MapDrawer.cs
@@ -1,11 +1,11 @@
+using DisplayCommands;
using TanksServer.GameLogic;
-using TanksServer.ServicePointDisplay;
namespace TanksServer.Graphics;
internal sealed class MapDrawer(MapService map) : IDrawStep
{
- public void Draw(PixelDisplayBufferView buffer)
+ public void Draw(PixelGrid buffer)
{
for (var tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
for (var tileX = 0; tileX < MapService.TilesPerRow; tileX++)
@@ -18,8 +18,8 @@ internal sealed class MapDrawer(MapService map) : IDrawStep
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)
{
var position = tile.GetPixelRelative(pixelInTileX, pixelInTileY);
- buffer.Pixels[position] = pixelInTileX % 2 == pixelInTileY % 2;
+ buffer[position.X, position.Y] = pixelInTileX % 2 == pixelInTileY % 2;
}
}
}
-}
+}
\ No newline at end of file
diff --git a/TanksServer/Graphics/TankDrawer.cs b/TanksServer/Graphics/TankDrawer.cs
index 61e3d0c..706b4b1 100644
--- a/TanksServer/Graphics/TankDrawer.cs
+++ b/TanksServer/Graphics/TankDrawer.cs
@@ -1,7 +1,7 @@
+using DisplayCommands;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using TanksServer.GameLogic;
-using TanksServer.ServicePointDisplay;
namespace TanksServer.Graphics;
@@ -29,21 +29,21 @@ internal sealed class TankDrawer : IDrawStep
_tankSpriteWidth = tankImage.Width;
}
- public void Draw(PixelDisplayBufferView buffer)
+ public void Draw(PixelGrid buffer)
{
foreach (var tank in _tanks)
{
var pos = tank.Position.ToPixelPosition();
var rotationVariant = (int)Math.Round(tank.Rotation) % 16;
-
+
for (var dy = 0; dy < MapService.TileSize; dy++)
for (var dx = 0; dx < MapService.TileSize; dx++)
{
if (!TankSpriteAt(dx, dy, rotationVariant))
continue;
- var position = new PixelPosition(pos.X + dx, pos.Y + dy);
- buffer.Pixels[position] = true;
+ var position = new PixelPosition((ushort)(pos.X + dx), (ushort)(pos.Y + dy));
+ buffer[position.X, position.Y] = true;
}
}
}
diff --git a/TanksServer/Interactivity/ByteChannelWebSocket.cs b/TanksServer/Interactivity/ByteChannelWebSocket.cs
index 0408fc0..67d4e56 100644
--- a/TanksServer/Interactivity/ByteChannelWebSocket.cs
+++ b/TanksServer/Interactivity/ByteChannelWebSocket.cs
@@ -7,15 +7,15 @@ namespace TanksServer.Interactivity;
///
/// Hacky class for easier semantics
///
-internal sealed class ByteChannelWebSocket : Channel
+internal sealed class ByteChannelWebSocket : Channel>
{
private readonly ILogger _logger;
private readonly WebSocket _socket;
private readonly Task _backgroundDone;
private readonly byte[] _buffer;
- private readonly Channel _outgoing = Channel.CreateUnbounded();
- private readonly Channel _incoming = Channel.CreateUnbounded();
+ private readonly Channel> _outgoing = Channel.CreateUnbounded>();
+ private readonly Channel> _incoming = Channel.CreateUnbounded>();
public ByteChannelWebSocket(WebSocket socket, ILogger logger, int messageSize)
{
diff --git a/TanksServer/Interactivity/ClientScreenServer.cs b/TanksServer/Interactivity/ClientScreenServer.cs
index 7c8c1db..efbf76d 100644
--- a/TanksServer/Interactivity/ClientScreenServer.cs
+++ b/TanksServer/Interactivity/ClientScreenServer.cs
@@ -1,8 +1,8 @@
using System.Diagnostics;
using System.Net.WebSockets;
using System.Threading.Channels;
+using DisplayCommands;
using Microsoft.Extensions.Hosting;
-using TanksServer.ServicePointDisplay;
namespace TanksServer.Interactivity;
@@ -44,10 +44,10 @@ internal sealed class ClientScreenServer(
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
private void Remove(ClientScreenServerConnection connection) => _connections.TryRemove(connection, out _);
-
+
public IEnumerable GetConnections() => _connections.Keys;
- internal sealed class ClientScreenServerConnection: IDisposable
+ internal sealed class ClientScreenServerConnection : IDisposable
{
private readonly ByteChannelWebSocket _channel;
private readonly SemaphoreSlim _wantedFrames = new(1);
@@ -64,7 +64,7 @@ internal sealed class ClientScreenServer(
Done = ReceiveAsync();
}
- public async Task SendAsync(PixelDisplayBufferView buf)
+ public async Task SendAsync(PixelGrid buf)
{
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
{
@@ -85,9 +85,9 @@ internal sealed class ClientScreenServer(
private async Task ReceiveAsync()
{
- await foreach (var _ in _channel.Reader.ReadAllAsync())
+ await foreach (var _ in _channel.Reader.ReadAllAsync())
_wantedFrames.Release();
-
+
_logger.LogTrace("done receiving");
_server.Remove(this);
}
@@ -106,4 +106,4 @@ internal sealed class ClientScreenServer(
Done.Dispose();
}
}
-}
+}
\ No newline at end of file
diff --git a/TanksServer/Interactivity/ControlsServer.cs b/TanksServer/Interactivity/ControlsServer.cs
index a279a2e..f23b66a 100644
--- a/TanksServer/Interactivity/ControlsServer.cs
+++ b/TanksServer/Interactivity/ControlsServer.cs
@@ -71,8 +71,8 @@ internal sealed class ControlsServer(ILogger logger, ILoggerFact
{
await foreach (var buffer in _binaryWebSocket.Reader.ReadAllAsync())
{
- var type = (MessageType)buffer[0];
- var control = (InputType)buffer[1];
+ var type = (MessageType)buffer.Span[0];
+ var control = (InputType)buffer.Span[1];
_logger.LogTrace("player input {} {} {}", _player.Id, type, control);
diff --git a/TanksServer/Interactivity/SendToServicePointDisplay.cs b/TanksServer/Interactivity/SendToServicePointDisplay.cs
new file mode 100644
index 0000000..0061a02
--- /dev/null
+++ b/TanksServer/Interactivity/SendToServicePointDisplay.cs
@@ -0,0 +1,87 @@
+using System.Diagnostics;
+using System.Net.Sockets;
+using DisplayCommands;
+using TanksServer.GameLogic;
+using TanksServer.Graphics;
+
+namespace TanksServer.Interactivity;
+
+internal sealed class SendToServicePointDisplay : ITickStep
+{
+ private readonly LastFinishedFrameProvider _lastFinishedFrameProvider;
+ private readonly Cp437Grid _scoresBuffer;
+ private readonly PlayerServer _players;
+ private readonly ILogger _logger;
+ private readonly IDisplayConnection _displayConnection;
+
+ private DateTime _nextFailLog = DateTime.Now;
+
+ private const int ScoresWidth = 12;
+ private const int ScoresHeight = 20;
+ private const int ScoresPlayerRows = ScoresHeight - 5;
+
+ public SendToServicePointDisplay(
+ LastFinishedFrameProvider lastFinishedFrameProvider,
+ PlayerServer players,
+ ILogger logger,
+ IDisplayConnection displayConnection
+ )
+ {
+ _lastFinishedFrameProvider = lastFinishedFrameProvider;
+ _players = players;
+ _logger = logger;
+ _displayConnection = displayConnection;
+
+ var localIp = _displayConnection.GetLocalIPv4().Split('.');
+ Debug.Assert(localIp.Length == 4);
+ _scoresBuffer = new Cp437Grid(12, 20)
+ {
+ [00] = "== TANKS! ==",
+ [01] = "-- scores --",
+ [17] = "-- join --",
+ [18] = string.Join('.', localIp[..2]),
+ [19] = string.Join('.', localIp[2..])
+ };
+ }
+
+ public async Task TickAsync()
+ {
+ RefreshScores();
+ try
+ {
+ await _displayConnection.SendCp437DataAsync(MapService.TilesPerRow, 0, _scoresBuffer);
+ await _displayConnection.SendBitmapLinearWindowAsync(0, 0, _lastFinishedFrameProvider.LastFrame);
+ }
+ catch (SocketException ex)
+ {
+ if (DateTime.Now > _nextFailLog)
+ {
+ _logger.LogWarning("could not send data to service point display: {}", ex.Message);
+ _nextFailLog = DateTime.Now + TimeSpan.FromSeconds(5);
+ }
+ }
+ }
+
+ private void RefreshScores()
+ {
+ var playersToDisplay = _players.GetAll()
+ .OrderByDescending(p => p.Kills)
+ .Take(ScoresPlayerRows);
+
+ ushort row = 2;
+ foreach (var p in playersToDisplay)
+ {
+ var score = p.Kills.ToString();
+ var nameLength = Math.Min(p.Name.Length, ScoresWidth - score.Length - 1);
+
+ var name = p.Name[..nameLength];
+ var spaces = new string(' ', ScoresWidth - score.Length - nameLength);
+
+ _scoresBuffer[row] = name + spaces + score;
+ row++;
+ }
+
+ for (; row < 17; row++)
+ _scoresBuffer[row] = string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/TanksServer/Models/PixelPosition.cs b/TanksServer/Models/PixelPosition.cs
index 116ac0e..106525d 100644
--- a/TanksServer/Models/PixelPosition.cs
+++ b/TanksServer/Models/PixelPosition.cs
@@ -1,3 +1,3 @@
namespace TanksServer.Models;
-internal record struct PixelPosition(int X, int Y);
+internal record struct PixelPosition(ushort X, ushort Y);
\ No newline at end of file
diff --git a/TanksServer/Models/PositionHelpers.cs b/TanksServer/Models/PositionHelpers.cs
index ae15bf0..8c4e02c 100644
--- a/TanksServer/Models/PositionHelpers.cs
+++ b/TanksServer/Models/PositionHelpers.cs
@@ -10,15 +10,15 @@ internal static class PositionHelpers
Debug.Assert(subX < 8);
Debug.Assert(subY < 8);
return new PixelPosition(
- X: position.X * MapService.TileSize + subX,
- Y: position.Y * MapService.TileSize + subY
+ X: (ushort)(position.X * MapService.TileSize + subX),
+ Y: (ushort)(position.Y * MapService.TileSize + subY)
);
}
public static PixelPosition ToPixelPosition(this FloatPosition position) => new(
- X: (int)position.X % MapService.PixelsPerRow,
- Y: (int)position.Y % MapService.PixelsPerRow
+ X: (ushort)((int)position.X % MapService.PixelsPerRow),
+ Y: (ushort)((int)position.Y % MapService.PixelsPerRow)
);
public static TilePosition ToTilePosition(this PixelPosition position) => new(
diff --git a/TanksServer/Program.cs b/TanksServer/Program.cs
index 72ea92c..611889f 100644
--- a/TanksServer/Program.cs
+++ b/TanksServer/Program.cs
@@ -8,7 +8,6 @@ using Microsoft.Extensions.FileProviders;
using TanksServer.GameLogic;
using TanksServer.Graphics;
using TanksServer.Interactivity;
-using TanksServer.ServicePointDisplay;
namespace TanksServer;
@@ -109,8 +108,6 @@ public static class Program
builder.Services.AddSingleton();
builder.Services.AddSingleton();
- builder.Services.Configure(
- builder.Configuration.GetSection("ServicePointDisplay"));
builder.Services.Configure(
builder.Configuration.GetSection("Tanks"));
builder.Services.Configure(
diff --git a/TanksServer/ServicePointDisplay/DisplayBufferView.cs b/TanksServer/ServicePointDisplay/DisplayBufferView.cs
deleted file mode 100644
index 8d1dde8..0000000
--- a/TanksServer/ServicePointDisplay/DisplayBufferView.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-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);
- }
-}
\ No newline at end of file
diff --git a/TanksServer/ServicePointDisplay/FixedSizeBitGridView.cs b/TanksServer/ServicePointDisplay/FixedSizeBitGridView.cs
deleted file mode 100644
index bcd598f..0000000
--- a/TanksServer/ServicePointDisplay/FixedSizeBitGridView.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Diagnostics;
-
-namespace TanksServer.ServicePointDisplay;
-
-internal sealed class FixedSizeBitGridView(Memory data, int columns, int rows)
-{
- private readonly FixedSizeBitRowView _bits = new(data);
-
- public bool this[PixelPosition position]
- {
- get => _bits[ToPixelIndex(position)];
- set => _bits[ToPixelIndex(position)] = value;
- }
-
- private int ToPixelIndex(PixelPosition position)
- {
- Debug.Assert(position.X < columns);
- Debug.Assert(position.Y < rows);
- var index = position.Y * columns + position.X;
- ArgumentOutOfRangeException.ThrowIfNegative(index, nameof(position));
- return index;
- }
-}
\ No newline at end of file
diff --git a/TanksServer/ServicePointDisplay/FixedSizeBitRowView.cs b/TanksServer/ServicePointDisplay/FixedSizeBitRowView.cs
deleted file mode 100644
index b3ccbdf..0000000
--- a/TanksServer/ServicePointDisplay/FixedSizeBitRowView.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System.Collections;
-
-namespace TanksServer.ServicePointDisplay;
-
-internal sealed class FixedSizeBitRowView(Memory data) : IList
-{
- public int Count => data.Length * 8;
- public bool IsReadOnly => false;
-
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
-
- public IEnumerator GetEnumerator()
- {
- return Enumerable().GetEnumerator();
-
- IEnumerable Enumerable()
- {
- for (var i = 0; i < Count; i++)
- yield return this[i];
- }
- }
-
- public void Clear()
- {
- var span = data.Span;
- for (var i = 0; i < data.Length; i++)
- span[i] = 0;
- }
-
- public void CopyTo(bool[] array, int arrayIndex)
- {
- for (var i = 0; i < Count && i + arrayIndex < array.Length; i++)
- array[i + arrayIndex] = this[i];
- }
-
- private (int byteIndex, int bitInByteIndex) GetIndexes(int bitIndex)
- {
- var byteIndex = 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);
- }
-
- public bool this[int bitIndex]
- {
- get
- {
- var (byteIndex, bitInByteIndex) = GetIndexes(bitIndex);
- var bitInByteMask = (byte)(1 << bitInByteIndex);
- return (data.Span[byteIndex] & bitInByteMask) != 0;
- }
-
- set
- {
- var (byteIndex, bitInByteIndex) = GetIndexes(bitIndex);
- var bitInByteMask = (byte)(1 << bitInByteIndex);
-
- if (value)
- {
- data.Span[byteIndex] |= bitInByteMask;
- }
- else
- {
- var withoutBitMask = (byte)(ushort.MaxValue ^ bitInByteMask);
- data.Span[byteIndex] &= withoutBitMask;
- }
- }
- }
-
- public void Add(bool item) => throw new NotSupportedException();
- public bool Contains(bool item) => throw new NotSupportedException();
- public bool Remove(bool item) => throw new NotSupportedException();
- public int IndexOf(bool item) => throw new NotSupportedException();
- public void Insert(int index, bool item) => throw new NotSupportedException();
- public void RemoveAt(int index) => throw new NotSupportedException();
-}
\ No newline at end of file
diff --git a/TanksServer/ServicePointDisplay/FixedSizeCharGridView.cs b/TanksServer/ServicePointDisplay/FixedSizeCharGridView.cs
deleted file mode 100644
index 9f1dae8..0000000
--- a/TanksServer/ServicePointDisplay/FixedSizeCharGridView.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Text;
-
-namespace TanksServer.ServicePointDisplay;
-
-internal sealed class FixedSizeCharGridView(Memory 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;
- }
-}
\ No newline at end of file
diff --git a/TanksServer/ServicePointDisplay/PixelDisplayBufferView.cs b/TanksServer/ServicePointDisplay/PixelDisplayBufferView.cs
deleted file mode 100644
index 3532a7c..0000000
--- a/TanksServer/ServicePointDisplay/PixelDisplayBufferView.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using TanksServer.GameLogic;
-
-namespace TanksServer.ServicePointDisplay;
-
-internal sealed class PixelDisplayBufferView : DisplayBufferView
-{
- private PixelDisplayBufferView(byte[] data, int columns, int pixelRows) : base(data)
- {
- Pixels = new FixedSizeBitGridView(Data.AsMemory(10), columns, pixelRows);
- }
-
- // ReSharper disable once CollectionNeverQueried.Global (setting values in collection updates underlying byte array)
- public FixedSizeBitGridView 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], widthInTiles * MapService.TileSize, pixelRows)
- {
- Mode = 19,
- TileX = x,
- TileY = y,
- WidthInTiles = widthInTiles,
- RowCount = pixelRows
- };
- }
-}
\ No newline at end of file
diff --git a/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs b/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs
deleted file mode 100644
index 24ab1ab..0000000
--- a/TanksServer/ServicePointDisplay/SendToServicePointDisplay.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-using System.Diagnostics;
-using System.Net;
-using System.Net.Sockets;
-using TanksServer.GameLogic;
-using TanksServer.Graphics;
-using TanksServer.Interactivity;
-
-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 _logger;
- private DateTime _nextFailLog = DateTime.Now;
-
- private const int ScoresWidth = 12;
- private const int ScoresHeight = 20;
- private const int ScoresPlayerRows = ScoresHeight - 5;
-
- public SendToServicePointDisplay(
- IOptions options,
- LastFinishedFrameProvider lastFinishedFrameProvider,
- PlayerServer players,
- ILogger logger
- )
- {
- _lastFinishedFrameProvider = lastFinishedFrameProvider;
- _players = players;
- _logger = logger;
- _udpClient = options.Value.Enable
- ? new UdpClient(options.Value.Hostname, options.Value.Port)
- : null;
-
- var localIp = GetLocalIp(options.Value.Hostname, options.Value.Port).Split('.');
- Debug.Assert(localIp.Length == 4); // were talking legacy ip
- _scoresBuffer = new TextDisplayBuffer(new TilePosition(MapService.TilesPerRow, 0), 12, 20)
- {
- Rows =
- {
- [00] = "== TANKS! ==",
- [01] = "-- scores --",
- [17] = "-- join --",
- [18] = string.Join('.', localIp[..2]),
- [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)
- {
- if (DateTime.Now > _nextFailLog)
- {
- _logger.LogWarning("could not send data to service point display: {}", ex.Message);
- _nextFailLog = DateTime.Now + TimeSpan.FromSeconds(5);
- }
- }
- }
- }
-
- 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 = Math.Min(p.Name.Length, ScoresWidth - score.Length - 1);
-
- var name = p.Name[..nameLength];
- var spaces = new string(' ', ScoresWidth - score.Length - nameLength);
-
- _scoresBuffer.Rows[row] = name + spaces + score;
- row++;
- }
-
- for (; row < 17; row++)
- _scoresBuffer.Rows[row] = string.Empty;
- }
-
- public void Dispose()
- {
- _udpClient?.Dispose();
- }
-}
\ No newline at end of file
diff --git a/TanksServer/ServicePointDisplay/ServicePointDisplayConfiguration.cs b/TanksServer/ServicePointDisplay/ServicePointDisplayConfiguration.cs
deleted file mode 100644
index 6a71db9..0000000
--- a/TanksServer/ServicePointDisplay/ServicePointDisplayConfiguration.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace TanksServer.ServicePointDisplay;
-
-internal sealed class ServicePointDisplayConfiguration
-{
- public bool Enable { get; set; } = true;
- public string Hostname { get; set; } = string.Empty;
- public int Port { get; set; }
-}
diff --git a/TanksServer/ServicePointDisplay/TextDisplayBuffer.cs b/TanksServer/ServicePointDisplay/TextDisplayBuffer.cs
deleted file mode 100644
index ace76a7..0000000
--- a/TanksServer/ServicePointDisplay/TextDisplayBuffer.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-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; }
-}
\ No newline at end of file
diff --git a/tank-frontend/.env b/tank-frontend/.env
index d8123a6..d835d34 100644
--- a/tank-frontend/.env
+++ b/tank-frontend/.env
@@ -1,3 +1,7 @@
+#VITE_TANK_SCREEN_URL=ws://172.23.43.79/screen
+#VITE_TANK_CONTROLS_URL=ws://172.23.43.79/controls
+#VITE_TANK_PLAYER_URL=http://172.23.43.79/player
+
VITE_TANK_SCREEN_URL=ws://vinzenz-lpt2/screen
VITE_TANK_CONTROLS_URL=ws://vinzenz-lpt2/controls
VITE_TANK_PLAYER_URL=http://vinzenz-lpt2/player
diff --git a/tank-frontend/src/ClientScreen.tsx b/tank-frontend/src/ClientScreen.tsx
index 0a5e1b2..e5353a0 100644
--- a/tank-frontend/src/ClientScreen.tsx
+++ b/tank-frontend/src/ClientScreen.tsx
@@ -11,7 +11,7 @@ const offColor = [0, 0, 0, 255];
function getIndexes(bitIndex: number) {
return {
- byteIndex: 10 + Math.floor(bitIndex / 8),
+ byteIndex: Math.floor(bitIndex / 8),
bitInByteIndex: 7 - bitIndex % 8
};
}