Pure websocket game.

This commit is contained in:
Anduin Xue
2024-01-02 06:37:00 +00:00
parent 91b01b6341
commit e67b4907ac
4 changed files with 146 additions and 121 deletions
@@ -35,10 +35,31 @@ public class GamesController : Controller
public async Task GetWebSocket([FromRoute] int id)
{
var pusher = await HttpContext.AcceptWebSocketClient();
var subscription = _database
.GetOrAddGame(id)
var game = _database.GetOrAddGame(id);
var outSub = game
.FenChangedChannel
.Subscribe(t => pusher.Send(t, HttpContext.RequestAborted));
var inSub = pusher.Subscribe(async message =>
{
if (message.Length < 2)
{
return;
}
var player = message[..1];
var move = message[1..];
lock (game.MovePieceLock)
{
if (game.Board.IsValidMove(move) && !game.Board.IsEndGame &&
game.Board.Turn.AsChar.ToString() == player)
{
game.Board.Move(move);
}
}
await game.FenChangedChannel.BroadcastAsync(game.Board.ToFen());
});
try
{
await pusher.Listen(HttpContext.RequestAborted);
@@ -50,7 +71,8 @@ public class GamesController : Controller
finally
{
await pusher.Close(HttpContext.RequestAborted);
subscription.UnRegister();
outSub.UnRegister();
inSub.UnRegister();
}
}
@@ -80,22 +102,4 @@ public class GamesController : Controller
var game = _database.GetOrAddGame(id);
return Ok(game.Board.ToPgn());
}
[HttpPost]
[Route("{id:int}/move/{player}/{move}")]
public async Task<IActionResult> Move([FromRoute] int id, [FromRoute] string player, [FromRoute] string move)
{
var game = _database.GetOrAddGame(id);
lock (game.MovePieceLock)
{
if (!game.Board.IsValidMove(move) || game.Board.IsEndGame || game.Board.Turn.AsChar.ToString() != player)
{
return BadRequest();
}
game.Board.Move(move);
}
var fen = game.Board.ToFen();
await game.FenChangedChannel.BroadcastAsync(fen);
return Ok(fen);
}
}
+4 -1
View File
@@ -1,6 +1,9 @@
using System.Diagnostics.CodeAnalysis;
namespace Aiursoft.ChessServer;
public class Program
[ExcludeFromCodeCoverage]
public abstract class Program
{
public static async Task Main(string[] args)
{
@@ -1,101 +1,102 @@
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) {
fetch(`/games/${gameId}.fen`)
.then(response => response.text())
.then(fen => {
let board = null;
let game = null;
fetch(`/games/${gameId}.fen`)
.then(response => response.text())
.then(fen => {
let board = null;
let game = null;
const wsScheme = window.location.protocol === "https:" ? "wss://" : "ws://";
const socket = new WebSocket(
`${wsScheme}${window.location.host}/games/${gameId}.ws`
);
function onDragStart(source, piece, position, _) {
if (game.turn() !== player) {
return false;
}
function onDragStart(source, piece, position, _) {
if (game.turn() !== player) {
return false;
}
if (game.isGameOver()) return false;
if (game.isGameOver()) return false;
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;
fetch(`/games/${gameId}/move/${player}/${lastMove}`, { method: 'POST' });
} catch (e) {
return "snapback";
}
}
function onSnapEnd() {
board.position(game.fen());
}
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`;
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;
socket.send(player + lastMove);
} catch (e) {
return "snapback";
}
}
}
statusControl.innerHTML = 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");
refresh(fen);
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;
}
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);
};
const config = {
orientation: player === "w" ? "white" : "black",
draggable: true,
dragoffBoard: "snapback",
position: fen,
onDragStart: onDragStart,
onSnapEnd: onSnapEnd,
onDrop: onDrop
};
board = ChessBoard("board", config);
socket.onclose = function () {
setTimeout(function () {
initGameBoard(player, gameId);
}, 1000);
};
});
function refresh(newFen) {
game = new Chess(newFen);
board.position(newFen);
console.log(`Got fen ${newFen}. refreshing board...`);
updateStatusText();
}
refresh(fen);
socket.onmessage = function (event) {
refresh(event.data);
};
socket.onclose = function () {
setTimeout(function () {
initGameBoard(player, gameId);
}, 1000);
};
});
};
export default initGameBoard;