load maps from png files
This commit is contained in:
parent
51334af8c3
commit
6bed7d918f
|
@ -9,5 +9,5 @@ internal sealed class CollideBulletsWithMap(BulletManager bullets, MapService ma
|
|||
}
|
||||
|
||||
private bool BulletHitsWall(Bullet bullet) =>
|
||||
map.IsCurrentlyWall(bullet.Position.ToPixelPosition().ToTilePosition());
|
||||
map.Current.IsCurrentlyWall(bullet.Position.ToPixelPosition());
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.IO;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace TanksServer.GameLogic;
|
||||
|
||||
|
@ -9,35 +11,65 @@ internal sealed class MapService
|
|||
public const ushort TileSize = 8;
|
||||
public const ushort PixelsPerRow = TilesPerRow * TileSize;
|
||||
public const ushort PixelsPerColumn = TilesPerColumn * TileSize;
|
||||
private readonly string _map;
|
||||
private readonly ILogger<MapService> _logger;
|
||||
|
||||
private string[] LoadMaps() => Directory.EnumerateFiles("./assets/maps/", "*.txt")
|
||||
.Select(LoadMap)
|
||||
.Where(s => s != null)
|
||||
.Select(s => s!)
|
||||
.ToArray();
|
||||
public Map Current { get; }
|
||||
|
||||
private string? LoadMap(string file)
|
||||
public MapService()
|
||||
{
|
||||
var text = File.ReadAllText(file).ReplaceLineEndings(string.Empty).Trim();
|
||||
if (text.Length != TilesPerColumn * TilesPerRow)
|
||||
var textMaps = Directory.EnumerateFiles("./assets/maps/", "*.txt").Select(LoadMapString);
|
||||
var pngMaps = Directory.EnumerateFiles("./assets/maps/", "*.png").Select(LoadMapPng);
|
||||
|
||||
var maps = textMaps.Concat(pngMaps).ToList();
|
||||
Current = maps[Random.Shared.Next(maps.Count)];
|
||||
}
|
||||
|
||||
private static Map LoadMapPng(string file)
|
||||
{
|
||||
var dict = new Dictionary<PixelPosition, bool>();
|
||||
using var image = Image.Load<Rgba32>(file);
|
||||
|
||||
if (image.Width != PixelsPerRow || image.Height != PixelsPerColumn)
|
||||
throw new FileLoadException($"invalid image size in file {file}");
|
||||
|
||||
var whitePixel = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
|
||||
for (var y = 0; y < image.Height; y++)
|
||||
for (var x = 0; x < image.Width; x++)
|
||||
{
|
||||
_logger.LogWarning("cannot load map {}: invalid length", file);
|
||||
return null;
|
||||
if (image[x, y] == whitePixel)
|
||||
dict[new PixelPosition(x, y)] = true;
|
||||
}
|
||||
|
||||
return text;
|
||||
return new Map(dict);
|
||||
}
|
||||
|
||||
public MapService(ILogger<MapService> logger)
|
||||
private static Map LoadMapString(string file)
|
||||
{
|
||||
_logger = logger;
|
||||
var maps = LoadMaps();
|
||||
_map = maps[Random.Shared.Next(0, maps.Length)];
|
||||
var map = File.ReadAllText(file).ReplaceLineEndings(string.Empty).Trim();
|
||||
if (map.Length != TilesPerColumn * TilesPerRow)
|
||||
throw new FileLoadException($"cannot load map {file}: invalid length");
|
||||
|
||||
var dict = new Dictionary<PixelPosition, bool>();
|
||||
|
||||
for (ushort tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
|
||||
for (ushort tileX = 0; tileX < MapService.TilesPerRow; tileX++)
|
||||
{
|
||||
var tile = new TilePosition(tileX, tileY);
|
||||
if (map[tileX + tileY * TilesPerRow] != '#')
|
||||
continue;
|
||||
|
||||
for (byte pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++)
|
||||
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)
|
||||
{
|
||||
var pixel = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY);
|
||||
dict[pixel] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new Map(dict);
|
||||
}
|
||||
|
||||
private char this[int tileX, int tileY] => _map[tileX + tileY * TilesPerRow];
|
||||
|
||||
public bool IsCurrentlyWall(TilePosition position) => this[position.X, position.Y] == '#';
|
||||
}
|
||||
|
||||
internal sealed class Map(IReadOnlyDictionary<PixelPosition, bool> walls)
|
||||
{
|
||||
public bool IsCurrentlyWall(PixelPosition position) => walls.TryGetValue(position, out var value) && value;
|
||||
}
|
||||
|
|
|
@ -63,14 +63,16 @@ internal sealed class MoveTanks(
|
|||
|
||||
private bool HitsWall(FloatPosition newPosition)
|
||||
{
|
||||
var (topLeft, bottomRight) = Tank.GetBoundsForCenter(newPosition);
|
||||
TilePosition[] positions =
|
||||
[
|
||||
topLeft.ToTilePosition(),
|
||||
new PixelPosition(bottomRight.X, topLeft.Y).ToTilePosition(),
|
||||
new PixelPosition(topLeft.X, bottomRight.Y).ToTilePosition(),
|
||||
bottomRight.ToTilePosition()
|
||||
];
|
||||
return positions.Any(map.IsCurrentlyWall);
|
||||
var (topLeft, _) = Tank.GetBoundsForCenter(newPosition);
|
||||
|
||||
for (short y = 0; y < MapService.TileSize; y++)
|
||||
for (short x = 0; x < MapService.TileSize; x++)
|
||||
{
|
||||
var pixelToCheck = topLeft.GetPixelRelative(x, y);
|
||||
if (map.Current.IsCurrentlyWall(pixelToCheck))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ internal sealed class SpawnNewTanks(
|
|||
{
|
||||
var tile = new TilePosition(x, y);
|
||||
|
||||
if (map.IsCurrentlyWall(tile))
|
||||
// TODO: implement lookup for non tile aligned walls
|
||||
if (map.Current.IsCurrentlyWall(tile.ToPixelPosition()))
|
||||
continue;
|
||||
|
||||
var tilePixelCenter = tile.ToPixelPosition().GetPixelRelative(4, 4).ToFloatPosition();
|
||||
|
|
|
@ -7,19 +7,13 @@ internal sealed class DrawMapStep(MapService map) : IDrawStep
|
|||
{
|
||||
public void Draw(PixelGrid buffer)
|
||||
{
|
||||
for (ushort tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
|
||||
for (ushort tileX = 0; tileX < MapService.TilesPerRow; tileX++)
|
||||
for (ushort y = 0; y < MapService.PixelsPerColumn; y++)
|
||||
for (ushort x = 0; x < MapService.PixelsPerRow; x++)
|
||||
{
|
||||
var tile = new TilePosition(tileX, tileY);
|
||||
if (!map.IsCurrentlyWall(tile))
|
||||
var pixel = new PixelPosition(x, y);
|
||||
if (!map.Current.IsCurrentlyWall(pixel))
|
||||
continue;
|
||||
|
||||
for (byte pixelInTileY = 0; pixelInTileY < MapService.TileSize; pixelInTileY++)
|
||||
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)
|
||||
{
|
||||
var (x, y) = tile.ToPixelPosition().GetPixelRelative(pixelInTileX, pixelInTileY);
|
||||
buffer[(ushort)x, (ushort)y] = pixelInTileX % 2 == pixelInTileY % 2;
|
||||
}
|
||||
buffer[x, y] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,14 @@ internal sealed class GeneratePixelsTickStep(
|
|||
) : ITickStep
|
||||
{
|
||||
private readonly List<IDrawStep> _drawSteps = drawSteps.ToList();
|
||||
private readonly PixelGrid _drawGrid = new(MapService.PixelsPerRow, MapService.PixelsPerColumn);
|
||||
|
||||
public Task TickAsync()
|
||||
{
|
||||
var drawGrid = new PixelGrid(MapService.PixelsPerRow, MapService.PixelsPerColumn);
|
||||
_drawGrid.Clear();
|
||||
foreach (var step in _drawSteps)
|
||||
step.Draw(drawGrid);
|
||||
lastFrameProvider.LastFrame = drawGrid;
|
||||
step.Draw(_drawGrid);
|
||||
lastFrameProvider.LastFrame = _drawGrid;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@ internal sealed class ClientScreenServer(
|
|||
private readonly ILogger<ClientScreenServerConnection> _logger;
|
||||
private readonly ClientScreenServer _server;
|
||||
private readonly SemaphoreSlim _wantedFrames = new(1);
|
||||
private PixelGrid? _lastSentPixels;
|
||||
|
||||
public ClientScreenServerConnection(WebSocket webSocket,
|
||||
ILogger<ClientScreenServerConnection> logger,
|
||||
|
@ -78,9 +77,6 @@ internal sealed class ClientScreenServer(
|
|||
|
||||
public async Task SendAsync(PixelGrid pixels)
|
||||
{
|
||||
if (_lastSentPixels == pixels)
|
||||
return;
|
||||
|
||||
if (!await _wantedFrames.WaitAsync(TimeSpan.Zero))
|
||||
{
|
||||
_logger.LogTrace("client does not want a frame yet");
|
||||
|
@ -91,7 +87,6 @@ internal sealed class ClientScreenServer(
|
|||
try
|
||||
{
|
||||
await _channel.SendAsync(pixels.Data);
|
||||
_lastSentPixels = pixels;
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"TanksServer": "Debug",
|
||||
"Microsoft.AspNetCore.HttpLogging": "Information",
|
||||
"TanksServer.GameLogic.RotateTanks": "Trace"
|
||||
"Microsoft.AspNetCore.HttpLogging": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
................#..##...#...#..............#
|
||||
................#...#...#..#...............#
|
||||
........................###................#
|
||||
........................#..#......###......#
|
||||
........................#..#.....####......#
|
||||
........................#...#...#..........#
|
||||
.................................###.......#
|
||||
....................................#...#..#
|
||||
|
|
BIN
TanksServer/assets/maps/test2.png
Normal file
BIN
TanksServer/assets/maps/test2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -28,7 +28,7 @@ export default function App() {
|
|||
if (isLoggedIn)
|
||||
return;
|
||||
const result = await postPlayer(nameId);
|
||||
setLoggedIn(result !== null);
|
||||
setLoggedIn(result.ok);
|
||||
}, [nameId, isLoggedIn])();
|
||||
|
||||
return <Column className='flex-grow'>
|
||||
|
@ -43,11 +43,10 @@ export default function App() {
|
|||
</Row>
|
||||
<ClientScreen logout={logout} theme={theme}/>
|
||||
{nameId.name === '' && <JoinForm setNameId={setNameId} clientId={nameId.id}/>}
|
||||
{isLoggedIn && <Row className='GadgetRows'>
|
||||
<Controls playerId={nameId.id} logout={logout}/>
|
||||
<PlayerInfo playerId={nameId.id} logout={logout}/>
|
||||
<Row className='GadgetRows'>
|
||||
{isLoggedIn && <Controls playerId={nameId.id} logout={logout}/>}
|
||||
{isLoggedIn && <PlayerInfo playerId={nameId.id} logout={logout}/>}
|
||||
<Scoreboard/>
|
||||
</Row>
|
||||
}
|
||||
</Column>;
|
||||
}
|
||||
|
|
|
@ -55,7 +55,8 @@ export default function ClientScreen({logout, theme}: { logout: () => void, them
|
|||
sendMessage,
|
||||
getWebSocket
|
||||
} = useWebSocket(import.meta.env.VITE_TANK_SCREEN_URL, {
|
||||
onError: logout
|
||||
onError: logout,
|
||||
shouldReconnect: () => true,
|
||||
});
|
||||
|
||||
const socket = getWebSocket();
|
||||
|
|
|
@ -13,7 +13,8 @@ export default function Controls({playerId, logout}: {
|
|||
getWebSocket,
|
||||
readyState
|
||||
} = useWebSocket(url.toString(), {
|
||||
onError: logout
|
||||
onError: logout,
|
||||
shouldReconnect: () => true,
|
||||
});
|
||||
|
||||
const socket = getWebSocket();
|
||||
|
|
Loading…
Reference in a new issue