New player controller.

This commit is contained in:
AnduinXue
2023-12-13 07:58:57 +00:00
parent 7821e363ad
commit 7fb88b3bdc
7 changed files with 148 additions and 96 deletions
@@ -35,7 +35,10 @@ public class GamesController : Controller
public async Task GetWebSocket([FromRoute] int id)
{
var pusher = await HttpContext.AcceptWebSocketClient();
var subscription = _database.GetOrAddGame(id).BoardChannel.Subscribe(t => pusher.Send(t, HttpContext.RequestAborted));
var subscription = _database
.GetOrAddGame(id)
.FenChangedChannel
.Subscribe(t => pusher.Send(t, HttpContext.RequestAborted));
try
{
await pusher.Listen(HttpContext.RequestAborted);
@@ -88,7 +91,7 @@ public class GamesController : Controller
game.Board.Move(move);
}
var fen = game.Board.ToFen();
await game.BoardChannel.BroadcastAsync(fen);
await game.FenChangedChannel.BroadcastAsync(fen);
return Ok(fen);
}
}
@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using Aiursoft.ChessServer.Data;
using Microsoft.AspNetCore.Mvc;
namespace Aiursoft.ChessServer.Controllers;
[Route("players")]
public class PlayersController : ControllerBase
{
private readonly InMemoryDatabase _database;
public PlayersController(InMemoryDatabase database)
{
_database = database;
}
[Route("{id:guid}")]
public IActionResult Me([Required]Guid id)
{
var me = _database.GetOrAddPlayer(id);
return Ok(me);
}
[HttpPut]
[Route("{id:guid}/name/{nickname}")]
public IActionResult ChangeNickname([Required]Guid id, [Required]string nickname)
{
var me = _database.GetOrAddPlayer(id);
me.NickName = nickname;
return Ok(me);
}
}
@@ -7,6 +7,8 @@ namespace Aiursoft.ChessServer.Data;
public class InMemoryDatabase : ISingletonDependency
{
private ConcurrentDictionary<int, Game> Games { get; } = new();
private ConcurrentDictionary<Guid, Player> Players { get; } = new();
public GameContext[] GetActiveGames()
{
@@ -15,9 +17,20 @@ public class InMemoryDatabase : ISingletonDependency
public Game GetOrAddGame(int id)
{
lock (this)
lock (Games)
{
return Games.GetOrAdd(id, _ => new Game());
}
}
public Player GetOrAddPlayer(Guid id)
{
lock (Players)
{
return Players.GetOrAdd(id, _ => new Player(id)
{
NickName = "Anonymous " + new Random().Next(1000, 9999)
});
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ public class Game
{
public ChessBoard Board { get; } = new();
public AsyncObservable<string> BoardChannel { get; } = new();
public AsyncObservable<string> FenChangedChannel { get; } = new();
public object MovePieceLock { get; } = new();
}
+8 -1
View File
@@ -2,6 +2,13 @@
public class Player
{
public Guid Id { get; }
public Player(Guid id)
{
Id = id;
}
public string NickName { get; set; } = "Anonymous";
public Guid Id { get; set; } = Guid.NewGuid();
}
@@ -23,7 +23,7 @@ public class GameContext
{ "websocket", $"games/{id}.ws" },
{ "move-post", $"games/{id}/move/{{player}}/{{move_algebraic_notation}}" }
};
Listeners = game.BoardChannel.GetListenerCount();
Listeners = game.FenChangedChannel.GetListenerCount();
}
@@ -1,104 +1,101 @@
import { Chess } from "/node_modules/chess.js/dist/esm/chess.js";
import { Chess } from "../node_modules/chess.js/dist/esm/chess.js";
const initGameBoard = function (player, gameId) {
$.get("/games/" + gameId + ".fen", function (fen) {
let board = null;
let game = null;
fetch(`/games/${gameId}.fen`)
.then(response => response.text())
.then(fen => {
let board = null;
let game = null;
// Happens when a player picks up a piece.
function onDragStart(source, piece, position, _) {
// only allow moving players own pieces
if (game.turn() !== player) {
return false;
}
function onDragStart(source, piece, position, _) {
if (game.turn() !== player) {
return false;
}
// do not pick up pieces if the game is over
if (game.isGameOver()) return false;
if (game.isGameOver()) return false;
// only pick up pieces for the side to move
if (
(game.turn() === "w" && piece.search(/^b/) !== -1) ||
(game.turn() === "b" && piece.search(/^w/) !== -1)) {
return false
}
}
function onDrop(source, target) {
try {
const move = game.move({
from: source,
to: target,
promotion: "q",
});
if (move === null) {
return "snapback";
if (
(game.turn() === "w" && piece.search(/^b/) !== -1) ||
(game.turn() === "b" && piece.search(/^w/) !== -1)) {
return false
}
}
const lastMove = game.history({ verbose: true }).pop().san;
$.post("/games/" + gameId + "/move/" + player + "/" + lastMove);
} catch (e) {
return "snapback";
}
}
// Hack to make sure castling\promotion works.
function onSnapEnd() {
board.position(game.fen());
}
const statusControl = $("#status");
function updateStatusText() {
let status;
let moveColor = "White";
if (game.turn() === "b") {
moveColor = "Black";
} if (game.isCheckmate()) {
status = "Game over, " + moveColor + " is in checkmate, and winner is " + (game.turn() === "w" ? "Black" : "White");
} else if (game.isDraw()) {
status = "Game over, drawn position";
} else {
status = moveColor + " to move";
if (game.isCheck()) {
status += ", " + moveColor + " is in check";
function onDrop(source, target) {
try {
const move = game.move({
from: source,
to: target,
promotion: "q",
});
if (move === null) {
return "snapback";
}
const lastMove = game.history({ verbose: true }).pop().san;
fetch(`/games/${gameId}/move/${player}/${lastMove}`, { method: 'POST' });
} catch (e) {
return "snapback";
}
}
}
statusControl.html(status);
}
const config = {
orientation: player === "w" ? "white" : "black",
draggable: true,
dragoffBoard: "snapback",
position: fen,
onDragStart: onDragStart,
onSnapEnd: onSnapEnd,
onDrop: onDrop
};
board = ChessBoard("board", config);
function onSnapEnd() {
board.position(game.fen());
}
function refresh(newFen) {
game = new Chess(newFen);
board.position(newFen);
console.log("Got fen " + newFen + ". refreshing board...");
updateStatusText();
}
const statusControl = document.getElementById("status");
function updateStatusText() {
let status;
let moveColor = "White";
if (game.turn() === "b") {
moveColor = "Black";
}
if (game.isCheckmate()) {
status = `Game over, ${moveColor} is in checkmate, and winner is ${game.turn() === "w" ? "Black" : "White"}`;
} else if (game.isDraw()) {
status = "Game over, drawn position";
} else {
status = `${moveColor} to move`;
if (game.isCheck()) {
status += `, ${moveColor} is in check`;
}
}
statusControl.innerHTML = status;
}
refresh(fen);
const config = {
orientation: player === "w" ? "white" : "black",
draggable: true,
dragoffBoard: "snapback",
position: fen,
onDragStart: onDragStart,
onSnapEnd: onSnapEnd,
onDrop: onDrop
};
board = ChessBoard("board", config);
const wsScheme = window.location.protocol === "https:" ? "wss://" : "ws://";
const socket = new WebSocket(
wsScheme + window.location.host + "/games/" + gameId + ".ws"
);
socket.onmessage = function (event) {
refresh(event.data);
};
function refresh(newFen) {
game = new Chess(newFen);
board.position(newFen);
console.log(`Got fen ${newFen}. refreshing board...`);
updateStatusText();
}
// Auto reconnect.
socket.onclose = function () {
setTimeout(function () {
initGameBoard(player, gameId);
}, 1000);
};
});
refresh(fen);
const wsScheme = window.location.protocol === "https:" ? "wss://" : "ws://";
const socket = new WebSocket(
`${wsScheme}${window.location.host}/games/${gameId}.ws`
);
socket.onmessage = function (event) {
refresh(event.data);
};
socket.onclose = function () {
setTimeout(function () {
initGameBoard(player, gameId);
}, 1000);
};
});
};
// noinspection JSUnusedGlobalSymbols
export default initGameBoard;
export default initGameBoard;