add endpoint for requesting map data
This commit is contained in:
parent
079b096c16
commit
5f5e9fb716
|
@ -24,14 +24,18 @@ internal sealed class Endpoints(
|
|||
app.Map("/controls", ConnectControlsAsync);
|
||||
app.MapGet("/map", () => mapService.MapNames);
|
||||
app.MapPost("/map", PostMap);
|
||||
app.MapGet("/map/{name}", GetMapByName);
|
||||
}
|
||||
|
||||
private Results<BadRequest<string>, NotFound<string>, Ok> PostMap([FromQuery] string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return TypedResults.BadRequest("invalid map name");
|
||||
if (!mapService.TryGetMapByName(name, out var map))
|
||||
|
||||
name = name.Trim().ToUpperInvariant();
|
||||
if (!mapService.TryGetPrototype(name, out var map))
|
||||
return TypedResults.NotFound("map with name not found");
|
||||
|
||||
changeToRequestedMap.Request(map);
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
@ -88,4 +92,19 @@ internal sealed class Endpoints(
|
|||
var player = playerService.GetOrAdd(name);
|
||||
return TypedResults.Ok(player.Name);
|
||||
}
|
||||
|
||||
private Results<Ok<MapInfo>, NotFound, BadRequest<string>> GetMapByName(string name)
|
||||
{
|
||||
name = name.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return TypedResults.BadRequest("map name cannot be empty");
|
||||
|
||||
if (!mapService.TryGetPrototype(name, out var prototype))
|
||||
return TypedResults.NotFound();
|
||||
if (!mapService.TryGetPreview(name, out var preview))
|
||||
return TypedResults.NotFound();
|
||||
|
||||
var mapInfo = new MapInfo(prototype.Name, prototype.GetType().Name, preview.Data);
|
||||
return TypedResults.Ok(mapInfo);
|
||||
}
|
||||
}
|
||||
|
|
8
tanks-backend/TanksServer/GameLogic/MapPrototype.cs
Normal file
8
tanks-backend/TanksServer/GameLogic/MapPrototype.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace TanksServer.GameLogic;
|
||||
|
||||
internal abstract class MapPrototype
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public abstract Map CreateInstance();
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using DisplayCommands;
|
||||
using TanksServer.Graphics;
|
||||
|
||||
namespace TanksServer.GameLogic;
|
||||
|
||||
internal abstract class MapPrototype
|
||||
{
|
||||
public abstract Map CreateInstance();
|
||||
}
|
||||
|
||||
internal sealed class MapService
|
||||
{
|
||||
public const ushort TilesPerRow = 44;
|
||||
|
@ -17,9 +13,10 @@ internal sealed class MapService
|
|||
public const ushort PixelsPerRow = TilesPerRow * TileSize;
|
||||
public const ushort PixelsPerColumn = TilesPerColumn * TileSize;
|
||||
|
||||
private readonly Dictionary<string, MapPrototype> _maps = new();
|
||||
private readonly Dictionary<string, MapPrototype> _mapPrototypes = new();
|
||||
private readonly Dictionary<string, PixelGrid> _mapPreviews = new();
|
||||
|
||||
public IEnumerable<string> MapNames => _maps.Keys;
|
||||
public IEnumerable<string> MapNames => _mapPrototypes.Keys;
|
||||
|
||||
public Map Current { get; private set; }
|
||||
|
||||
|
@ -29,29 +26,49 @@ internal sealed class MapService
|
|||
LoadMapString(file);
|
||||
foreach (var file in Directory.EnumerateFiles("./assets/maps/", "*.png"))
|
||||
LoadMapPng(file);
|
||||
|
||||
var chosenMapIndex = Random.Shared.Next(_maps.Count);
|
||||
var chosenMapName = _maps.Keys.Skip(chosenMapIndex).First();
|
||||
Current = _maps[chosenMapName].CreateInstance();
|
||||
Current = GetRandomMap();
|
||||
}
|
||||
|
||||
public bool TryGetMapByName(string name, [MaybeNullWhen(false)] out MapPrototype map)
|
||||
=> _maps.TryGetValue(name, out map);
|
||||
public bool TryGetPrototype(string name, [MaybeNullWhen(false)] out MapPrototype map)
|
||||
=> _mapPrototypes.TryGetValue(name, out map);
|
||||
|
||||
public void SwitchTo(MapPrototype prototype) => Current = prototype.CreateInstance();
|
||||
|
||||
public bool TryGetPreview(string name, [MaybeNullWhen(false)] out PixelGrid pixelGrid)
|
||||
{
|
||||
if (_mapPreviews.TryGetValue(name, out pixelGrid))
|
||||
return true; // already generated
|
||||
if (!_mapPrototypes.TryGetValue(name, out var prototype))
|
||||
return false; // name not found
|
||||
|
||||
pixelGrid = new PixelGrid(PixelsPerRow, PixelsPerColumn);
|
||||
DrawMapStep.Draw(pixelGrid, prototype.CreateInstance());
|
||||
|
||||
_mapPreviews.TryAdd(name, pixelGrid); // another thread may have added the map already
|
||||
return true; // new preview
|
||||
}
|
||||
|
||||
private void LoadMapPng(string file)
|
||||
{
|
||||
var name = Path.GetFileName(file);
|
||||
var name = MapNameFromFilePath(file);
|
||||
var prototype = new SpriteMapPrototype(name, Sprite.FromImageFile(file));
|
||||
_maps.Add(Path.GetFileName(file), prototype);
|
||||
_mapPrototypes.Add(name, prototype);
|
||||
}
|
||||
|
||||
private void LoadMapString(string file)
|
||||
{
|
||||
var name = MapNameFromFilePath(file);
|
||||
var map = File.ReadAllText(file).ReplaceLineEndings(string.Empty).Trim();
|
||||
var name = Path.GetFileName(file);
|
||||
var prototype = new TextMapPrototype(name, map);
|
||||
_maps.Add(name, prototype);
|
||||
_mapPrototypes.Add(name, prototype);
|
||||
}
|
||||
|
||||
private static string MapNameFromFilePath(string filePath) => Path.GetFileName(filePath).ToUpperInvariant();
|
||||
|
||||
private Map GetRandomMap()
|
||||
{
|
||||
var chosenMapIndex = Random.Shared.Next(_mapPrototypes.Count);
|
||||
var chosenMapName = _mapPrototypes.Keys.Skip(chosenMapIndex).First();
|
||||
return _mapPrototypes[chosenMapName].CreateInstance();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,18 @@ namespace TanksServer.GameLogic;
|
|||
|
||||
internal sealed class SpriteMapPrototype : MapPrototype
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly Sprite _sprite;
|
||||
public override string Name { get; }
|
||||
|
||||
public Sprite Sprite { get; }
|
||||
|
||||
public SpriteMapPrototype(string name, Sprite sprite)
|
||||
{
|
||||
if (sprite.Width != MapService.PixelsPerRow || sprite.Height != MapService.PixelsPerColumn)
|
||||
throw new FileLoadException($"invalid image size in file {_name}");
|
||||
throw new FileLoadException($"invalid image size in file {Name}");
|
||||
|
||||
_name = name;
|
||||
_sprite = sprite;
|
||||
Name = name;
|
||||
Sprite = sprite;
|
||||
}
|
||||
|
||||
public override Map CreateInstance() => new(_name, _sprite.ToBoolArray());
|
||||
public override Map CreateInstance() => new(Name, Sprite.ToBoolArray());
|
||||
}
|
||||
|
|
|
@ -2,17 +2,19 @@ namespace TanksServer.GameLogic;
|
|||
|
||||
internal sealed class TextMapPrototype : MapPrototype
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly string _text;
|
||||
public override string Name { get; }
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
public TextMapPrototype(string name, string text)
|
||||
{
|
||||
if (text.Length != MapService.TilesPerColumn * MapService.TilesPerRow)
|
||||
throw new ArgumentException($"cannot load map {name}: invalid length");
|
||||
_name = name;
|
||||
_text = text;
|
||||
Name = name;
|
||||
Text = text;
|
||||
}
|
||||
|
||||
|
||||
public override Map CreateInstance()
|
||||
{
|
||||
var walls = new bool[MapService.PixelsPerRow, MapService.PixelsPerColumn];
|
||||
|
@ -21,7 +23,7 @@ internal sealed class TextMapPrototype : MapPrototype
|
|||
for (ushort tileY = 0; tileY < MapService.TilesPerColumn; tileY++)
|
||||
{
|
||||
var tile = new TilePosition(tileX, tileY);
|
||||
if (_text[tileX + tileY * MapService.TilesPerRow] != '#')
|
||||
if (Text[tileX + tileY * MapService.TilesPerRow] != '#')
|
||||
continue;
|
||||
|
||||
for (byte pixelInTileX = 0; pixelInTileX < MapService.TileSize; pixelInTileX++)
|
||||
|
@ -32,6 +34,6 @@ internal sealed class TextMapPrototype : MapPrototype
|
|||
}
|
||||
}
|
||||
|
||||
return new Map(_name, walls);
|
||||
return new Map(Name, walls);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
using DisplayCommands;
|
||||
using TanksServer.GameLogic;
|
||||
|
||||
namespace TanksServer.Graphics;
|
||||
|
||||
internal sealed class DrawMapStep(MapService map) : IDrawStep
|
||||
{
|
||||
public void Draw(GamePixelGrid pixels)
|
||||
public void Draw(GamePixelGrid pixels) => Draw(pixels, map.Current);
|
||||
|
||||
private static void Draw(GamePixelGrid pixels, Map map)
|
||||
{
|
||||
for (ushort y = 0; y < MapService.PixelsPerColumn; y++)
|
||||
for (ushort x = 0; x < MapService.PixelsPerRow; x++)
|
||||
{
|
||||
if (!map.Current.IsWall(x, y))
|
||||
if (!map.IsWall(x, y))
|
||||
continue;
|
||||
|
||||
pixels[x, y].EntityType = GamePixelEntityType.Wall;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Draw(PixelGrid pixels, Map map)
|
||||
{
|
||||
for (ushort y = 0; y < MapService.PixelsPerColumn; y++)
|
||||
for (ushort x = 0; x < MapService.PixelsPerRow; x++)
|
||||
{
|
||||
if (!map.IsWall(x, y))
|
||||
continue;
|
||||
pixels[x, y] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,5 +7,6 @@ namespace TanksServer.Interactivity;
|
|||
[JsonSerializable(typeof(IEnumerable<Player>))]
|
||||
[JsonSerializable(typeof(IEnumerable<string>))]
|
||||
[JsonSerializable(typeof(PlayerInfo))]
|
||||
[JsonSerializable(typeof(MapInfo))]
|
||||
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web)]
|
||||
internal sealed partial class AppSerializerContext : JsonSerializerContext;
|
||||
|
|
3
tanks-backend/TanksServer/Models/MapInfo.cs
Normal file
3
tanks-backend/TanksServer/Models/MapInfo.cs
Normal file
|
@ -0,0 +1,3 @@
|
|||
namespace TanksServer.Models;
|
||||
|
||||
public record MapInfo(string Name, string TypeName, Memory<byte> Preview);
|
Loading…
Reference in a new issue