New player controller.
This commit is contained in:
@@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user