Files
tetri5/backend/Program.cs

194 lines
5.7 KiB
C#

using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Collections.Concurrent;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var games = new ConcurrentDictionary<string, Game>();
app.UseWebSockets();
app.Map("/", async context =>
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = 400;
return;
}
var ws = await context.WebSockets.AcceptWebSocketAsync();
var remoteAddress = context.Connection.RemoteIpAddress?.ToString();
var player = new Player(ws, remoteAddress);
try
{
var buffer = new byte[4096];
var messageBuffer = new ArraySegment<byte>(buffer);
while (ws.State == WebSocketState.Open)
{
var message = new MemoryStream();
WebSocketReceiveResult result;
do
{
result = await ws.ReceiveAsync(messageBuffer, CancellationToken.None);
message.Write(buffer, 0, result.Count);
}
while (!result.EndOfMessage);
if (result.MessageType == WebSocketMessageType.Close)
break;
var msgText = Encoding.UTF8.GetString(message.ToArray());
if (msgText == "ping")
{
await player.SendTextAsync("pong");
continue;
}
var json = JsonDocument.Parse(msgText).RootElement;
var action = json.GetProperty("action").GetString();
var gameId = json.GetProperty("gameId").GetString();
var clientId = json.GetProperty("clientId").GetString();
switch (action)
{
case "enter_game":
await GameHandlers.EnterGame(gameId, clientId, player, games);
break;
case "send_piece":
await GameHandlers.SendToOpponent(gameId, player, "receive_piece", json.GetProperty("piece"), games);
break;
case "send_stack":
await GameHandlers.SendToOpponent(gameId, player, "receive_stack", json.GetProperty("stack"), games);
break;
case "send_stats":
await GameHandlers.SendToOpponent(gameId, player, "receive_stats", json.GetProperty("stats"), games);
break;
default:
Console.WriteLine("Unsupported action.");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
Console.WriteLine("Disconnecting player...");
await GameHandlers.ExitGame(player, games);
Console.WriteLine("Connection closed.");
}
});
app.Run();
// -------------------------
// Models
// -------------------------
record Player(WebSocket Socket, string? RemoteAddress)
{
public async Task SendTextAsync(string message)
{
var bytes = Encoding.UTF8.GetBytes(message);
await Socket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
public async Task SendJsonAsync(object obj)
{
var json = JsonSerializer.Serialize(obj);
await SendTextAsync(json);
}
}
class Game
{
public string GameId { get; }
public List<Player> Players { get; } = new();
public List<string> Clients { get; } = new();
public Game(string gameId)
{
GameId = gameId;
}
public Player? GetOpponent(Player player) => Players.FirstOrDefault(p => p != player);
}
// -------------------------
// Handlers
// -------------------------
static class GameHandlers
{
public static async Task EnterGame(string gameId, string clientId, Player player, ConcurrentDictionary<string, Game> games)
{
var game = games.GetOrAdd(gameId, _ => new Game(gameId));
if (!game.Clients.Contains(clientId))
{
game.Clients.Add(clientId);
game.Players.Add(player);
if (game.Players.Count == 1)
{
await player.SendJsonAsync(new { type = "wait_for_opponent" });
}
else if (game.Players.Count == 2)
{
foreach (var p in game.Players)
await p.SendJsonAsync(new { type = "start_game" });
}
}
else
{
Console.WriteLine("Already in game...");
}
}
public static async Task SendToOpponent(string gameId, Player sender, string type, JsonElement content, ConcurrentDictionary<string, Game> games)
{
if (!games.TryGetValue(gameId, out var game))
return;
var opponent = game.GetOpponent(sender);
if (opponent != null)
{
switch (type)
{
case "receive_piece":
await opponent.SendJsonAsync(new { type, piece = content });
break;
case "receive_stack":
await opponent.SendJsonAsync(new { type, stack = content });
break;
case "receive_stats":
await opponent.SendJsonAsync(new { type, stats = content });
break;
}
}
}
public static async Task ExitGame(Player disconnectingPlayer, ConcurrentDictionary<string, Game> games)
{
var game = games.Values.FirstOrDefault(g => g.Players.Contains(disconnectingPlayer));
if (game != null)
{
games.TryRemove(game.GameId, out _);
var opponent = game.GetOpponent(disconnectingPlayer);
if (opponent != null)
{
await opponent.SendJsonAsync(new { type = "exit_game" });
}
}
}
}