194 lines
5.7 KiB
C#
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" });
|
|
}
|
|
}
|
|
}
|
|
}
|