commit
88f2708d94
|
@ -74,6 +74,8 @@ export default function PlayerInfo({player}: { player: string }) {
|
||||||
|
|
||||||
<ScoreRow name="walls destroyed" value={lastJsonMessage.scores.wallsDestroyed}/>
|
<ScoreRow name="walls destroyed" value={lastJsonMessage.scores.wallsDestroyed}/>
|
||||||
<ScoreRow name="bullets fired" value={lastJsonMessage.scores.shotsFired}/>
|
<ScoreRow name="bullets fired" value={lastJsonMessage.scores.shotsFired}/>
|
||||||
|
<ScoreRow name="power ups collected" value={lastJsonMessage.scores.powerUpsCollected}/>
|
||||||
|
<ScoreRow name="pixels moved" value={lastJsonMessage.scores.pixelsMoved}/>
|
||||||
|
|
||||||
<ScoreRow name="score" value={lastJsonMessage.scores.overallScore}/>
|
<ScoreRow name="score" value={lastJsonMessage.scores.overallScore}/>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -9,7 +9,7 @@ function numberSorter(a: number, b: number) {
|
||||||
export default function Scoreboard({}: {}) {
|
export default function Scoreboard({}: {}) {
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: ['scores'],
|
queryKey: ['scores'],
|
||||||
refetchInterval: 1000,
|
refetchInterval: 5000,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const url = makeApiUrl('/scores');
|
const url = makeApiUrl('/scores');
|
||||||
const response = await fetch(url, {method: 'GET'});
|
const response = await fetch(url, {method: 'GET'});
|
||||||
|
@ -53,6 +53,16 @@ export default function Scoreboard({}: {}) {
|
||||||
visualize: p => p.scores.shotsFired.toString(),
|
visualize: p => p.scores.shotsFired.toString(),
|
||||||
sorter: (a, b) => numberSorter(a.scores.shotsFired, b.scores.shotsFired)
|
sorter: (a, b) => numberSorter(a.scores.shotsFired, b.scores.shotsFired)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'powerUps',
|
||||||
|
visualize: p => p.scores.powerUpsCollected.toString(),
|
||||||
|
sorter: (a, b) => numberSorter(a.scores.powerUpsCollected, b.scores.powerUpsCollected)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'distance',
|
||||||
|
visualize: p => p.scores.pixelsMoved.toString(),
|
||||||
|
sorter: (a, b) => numberSorter(a.scores.pixelsMoved, b.scores.pixelsMoved)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'score',
|
field: 'score',
|
||||||
visualize: p => p.scores.overallScore.toString(),
|
visualize: p => p.scores.overallScore.toString(),
|
||||||
|
|
|
@ -8,6 +8,8 @@ export type Scores = {
|
||||||
readonly wallsDestroyed: number;
|
readonly wallsDestroyed: number;
|
||||||
readonly shotsFired: number;
|
readonly shotsFired: number;
|
||||||
readonly overallScore: number;
|
readonly overallScore: number;
|
||||||
|
readonly powerUpsCollected: number;
|
||||||
|
readonly pixelsMoved: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Player = {
|
export type Player = {
|
||||||
|
|
|
@ -20,8 +20,9 @@ internal sealed class CollectPowerUp(
|
||||||
position.Y < topLeft.Y || position.Y > bottomRight.Y)
|
position.Y < topLeft.Y || position.Y > bottomRight.Y)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// this works because now the tank overlaps the power up
|
// now the tank overlaps the power up by at least 0.5 tiles
|
||||||
tank.ExplosiveBullets += 10;
|
tank.ExplosiveBullets += 10;
|
||||||
|
tank.Owner.Scores.PowerUpsCollected++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ internal sealed class MoveTanks(
|
||||||
if (HitsTank(tank, newPosition))
|
if (HitsTank(tank, newPosition))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
tank.Owner.Scores.DistanceMoved += newPosition.Distance(tank.Position);
|
||||||
tank.Position = newPosition;
|
tank.Position = newPosition;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace TanksServer.GameLogic;
|
||||||
internal sealed class TankSpawnQueue(
|
internal sealed class TankSpawnQueue(
|
||||||
IOptions<GameRules> options,
|
IOptions<GameRules> options,
|
||||||
MapEntityManager entityManager
|
MapEntityManager entityManager
|
||||||
): ITickStep
|
) : ITickStep
|
||||||
{
|
{
|
||||||
private readonly ConcurrentQueue<Player> _queue = new();
|
private readonly ConcurrentQueue<Player> _queue = new();
|
||||||
private readonly ConcurrentDictionary<Player, DateTime> _spawnTimes = new();
|
private readonly ConcurrentDictionary<Player, DateTime> _spawnTimes = new();
|
||||||
|
@ -16,31 +16,8 @@ internal sealed class TankSpawnQueue(
|
||||||
|
|
||||||
public void EnqueueForDelayedSpawn(Player player)
|
public void EnqueueForDelayedSpawn(Player player)
|
||||||
{
|
{
|
||||||
_queue.Enqueue(player);
|
|
||||||
_spawnTimes.AddOrUpdate(player, DateTime.MinValue, (_, _) => DateTime.Now + _spawnDelay);
|
_spawnTimes.AddOrUpdate(player, DateTime.MinValue, (_, _) => DateTime.Now + _spawnDelay);
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryDequeueNext([MaybeNullWhen(false)] out Player player)
|
|
||||||
{
|
|
||||||
if (!_queue.TryDequeue(out player))
|
|
||||||
return false; // no one on queue
|
|
||||||
|
|
||||||
if (player.LastInput + _idleTimeout < DateTime.Now)
|
|
||||||
{
|
|
||||||
// player idle
|
|
||||||
_queue.Enqueue(player);
|
_queue.Enqueue(player);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = DateTime.Now;
|
|
||||||
if (_spawnTimes.GetOrAdd(player, DateTime.MinValue) > now)
|
|
||||||
{
|
|
||||||
// spawn delay
|
|
||||||
_queue.Enqueue(player);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask TickAsync(TimeSpan _)
|
public ValueTask TickAsync(TimeSpan _)
|
||||||
|
@ -51,4 +28,29 @@ internal sealed class TankSpawnQueue(
|
||||||
entityManager.SpawnTank(player);
|
entityManager.SpawnTank(player);
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryDequeueNext([MaybeNullWhen(false)] out Player player)
|
||||||
|
{
|
||||||
|
if (!_queue.TryDequeue(out player))
|
||||||
|
return false; // no one on queue
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
if (player.LastInput + _idleTimeout < now)
|
||||||
|
{
|
||||||
|
// player idle
|
||||||
|
_queue.Enqueue(player);
|
||||||
|
player = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_spawnTimes.GetOrAdd(player, DateTime.MinValue) > now)
|
||||||
|
{
|
||||||
|
// spawn delay
|
||||||
|
_queue.Enqueue(player);
|
||||||
|
player = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,6 @@ namespace TanksServer.Interactivity;
|
||||||
|
|
||||||
[JsonSerializable(typeof(Player))]
|
[JsonSerializable(typeof(Player))]
|
||||||
[JsonSerializable(typeof(IEnumerable<Player>))]
|
[JsonSerializable(typeof(IEnumerable<Player>))]
|
||||||
[JsonSerializable(typeof(Guid))]
|
|
||||||
[JsonSerializable(typeof(NameId))]
|
|
||||||
[JsonSerializable(typeof(IEnumerable<string>))]
|
[JsonSerializable(typeof(IEnumerable<string>))]
|
||||||
[JsonSerializable(typeof(PlayerInfo))]
|
[JsonSerializable(typeof(PlayerInfo))]
|
||||||
internal sealed partial class AppSerializerContext : JsonSerializerContext;
|
internal sealed partial class AppSerializerContext : JsonSerializerContext;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace TanksServer.Models;
|
namespace TanksServer.Models;
|
||||||
|
|
||||||
internal sealed record class Scores
|
internal sealed record class Scores
|
||||||
|
@ -22,5 +24,17 @@ internal sealed record class Scores
|
||||||
|
|
||||||
public int ShotsFired { get; set; }
|
public int ShotsFired { get; set; }
|
||||||
|
|
||||||
public int OverallScore => Math.Max(0, 10000 * Kills - 1000 * Deaths + 10 * ShotsFired + 10 * WallsDestroyed);
|
public int PowerUpsCollected { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public double DistanceMoved { get; set; }
|
||||||
|
|
||||||
|
public int PixelsMoved => (int)DistanceMoved;
|
||||||
|
|
||||||
|
public int OverallScore => Math.Max(0,
|
||||||
|
10000 * Kills
|
||||||
|
- 1000 * Deaths
|
||||||
|
+ 100 * PowerUpsCollected
|
||||||
|
+ 10 * (ShotsFired + WallsDestroyed)
|
||||||
|
+ PixelsMoved
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@ using TanksServer.Interactivity;
|
||||||
|
|
||||||
namespace TanksServer;
|
namespace TanksServer;
|
||||||
|
|
||||||
internal sealed record class NameId(string Name, Guid Id);
|
|
||||||
|
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static async Task Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
|
|
Loading…
Reference in a new issue