add spectator

This commit is contained in:
dvorak
2023-12-08 16:14:17 +08:00
parent 8e48c1c47d
commit 5512103ffd
3 changed files with 184 additions and 95 deletions
+3 -2
View File
@@ -12,8 +12,9 @@ ChessServer is just a simple chess server for [Aiursoft](https://www.aiursoft.co
Requirements about how to run Requirements about how to run
1. [.NET 7 SDK](http://dot.net/) 1. [.NET 7 SDK](http://dot.net/)
2. Execute `dotnet run` to run the app 2. Run `npm i` at directory `/src/Aiursoft.ChessServer/wwwroot/`
3. Use your browser to view [http://localhost:5000](http://localhost:5000) 3. Execute `dotnet run` to run the app
4. Use your browser to view [http://localhost:5000](http://localhost:5000)
## Run in Microsoft Visual Studio ## Run in Microsoft Visual Studio
@@ -15,19 +15,50 @@
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
@* Make the following p auto warp *@ @* Make the following p auto warp *@
<p id="status" class="text-center text-wrap text-primary mt-1"></p> <p id="status" class="text-center text-wrap text-primary mt-1"></p>
<p id="fen" class="text-wrap text-muted hidden"></p> <p id="fen" class="text-wrap text-muted hidden"></p>
</div> </div>
</div> </div>
<div class="modal fade" id="chooseCharacter" tabindex="-1" aria-labelledby="chooseCharacter" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Choose your character</h5>
</div>
<div class="modal-body">
<button type="button" id="chooseWhite" class="btn">
<img src="/img/chesspieces/wikipedia/wK.png" alt="White">
</button>
<button type="button" id="chooseBlack" class="btn">
<img src="/img/chesspieces/wikipedia/bK.png" alt="Black">
</button>
<button type="button" data-bs-dismiss="modal" id="choosePeek" class="btn btn-primary">Peek~👀</button>
</div>
</div>
</div>
</div>
@section Scripts { @section Scripts {
<script type="module" src="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.js"></script> <script type="module" src="~/node_modules/@@chrisoakman/chessboardjs/dist/chessboard-1.0.0.min.js"></script>
<script type="module" src="~/site.js"></script> <script type="module" src="~/site.js"></script>
<script type="module"> <script type="module">
// Ask the user to enter w or b: // Ask the user to enter w or b:
import initGameBoard from "/site.js"; import Game from "/site.js";
const player = prompt("Please enter your color (w or b):"); let game = new Game(@Model);
initGameBoard(player, @Model); game.chooseCharacter();
$("#chooseWhite").on("click", function () {
game.setPlayer('w');
game.startGame();
});
$("#chooseBlack").on("click", function () {
game.setPlayer('b');
game.startGame();
});
$('#choosePeek').on('click', function () {
game.startGame();
});
</script> </script>
} }
+146 -89
View File
@@ -1,107 +1,164 @@
import { Chess } from "/node_modules/chess.js/dist/esm/chess.js"; import { Chess } from "/node_modules/chess.js/dist/esm/chess.js";
const statusControl = $('#status'); const WHITE = "w";
const fenControl = $('#fen'); const BLACK = "b";
const SPECTATOR = "";
/**
* representing current Game
*
* ## example
* ```
* let gameId = 0;
* let game = new Game(gameId);
* ```
*
* @param {number} gameId current id of game
*/
function Game(gameId) {
this.gameId = gameId;
this.player = "";
this.chooseCharacterModal = new bootstrap.Modal(
document.getElementById("chooseCharacter")
);
this.chooseCharacter = () => {
this.chooseCharacterModal.show();
};
this.setPlayer = (player) => {
if ([WHITE, BLACK].includes(player)) {
this.player = player;
} else {
this.player = SPECTATOR;
}
};
/**
* let's start to play!
* but before, you need to choose a character
*/
this.startGame = () => {
this.chooseCharacterModal.hide();
initGameBoard(this.player, this.gameId);
if (this.player === SPECTATOR) {
document.getElementById("board").style.cursor = "not-allowed";
}
};
}
const statusControl = $("#status");
const fenControl = $("#fen");
const initGameBoard = function (player, gameId) { const initGameBoard = function (player, gameId) {
$.get("/games/" + gameId + ".fen", function (fen) { $.get("/games/" + gameId + ".fen", function (fen) {
let board = null; let board = null;
let game = null; let game = null;
// Happens when a player picks up a piece. // Happens when a player picks up a piece.
function onDragStart(source, piece, position, _) { function onDragStart(source, piece, position, _) {
// only allow moving players own pieces // only allow moving players own pieces
if ((game.turn() !== player)) { if (game.turn() !== player) {
return false return false;
} }
// do not pick up pieces if the game is over // 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 // only pick up pieces for the side to move
if ((game.turn() === 'w' && piece.search(/^b/) !== -1) || if (
(game.turn() === 'b' && piece.search(/^w/) !== -1)) { (game.turn() === "w" && piece.search(/^b/) !== -1) ||
return false (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";
} }
const lastMove = game.history({ verbose: true }).pop().san;
$.post("/games/" + gameId + "/move/" + player + "/" + lastMove);
} catch (e) {
return "snapback";
}
}
function onDrop(source, target) { // Hack to make sure castling\promotion works.
try { function onSnapEnd() {
const move = game.move({ board.position(game.fen());
from: source, }
to: target,
promotion: 'q' function updateStatusText() {
}) let status;
if (move === null) { let moveColor = "White";
return 'snapback' if (game.turn() === "b") {
} moveColor = "Black";
const lastMove = game.history({verbose: true}).pop().san; }
$.post("/games/" + gameId + "/move/" + player + "/" + lastMove); if (game.isCheckmate()) {
} catch (e) { status =
return 'snapback'; "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.html(status);
fenControl.html(game.fen());
}
// Hack to make sure castling\promotion works. const config = {
function onSnapEnd() { orientation: player === "w" ? "white" : "black",
board.position(game.fen()) draggable: true,
} dragoffBoard: "snapback",
position: fen,
onDragStart: onDragStart,
onSnapEnd: onSnapEnd,
onDrop: onDrop,
};
board = ChessBoard("board", config);
function updateStatusText() { function refresh(newFen) {
let status; game = new Chess(newFen);
let moveColor = 'White'; board.position(newFen);
if (game.turn() === 'b') { console.log("Got fen " + newFen + ". refreshing board...");
moveColor = 'Black'; updateStatusText();
} 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.html(status);
fenControl.html(game.fen());
}
const config = { refresh(fen);
orientation: player === "w" ? "white" : "black",
draggable: true,
dragoffBoard: 'snapback',
position: fen,
onDragStart: onDragStart,
onSnapEnd: onSnapEnd,
onDrop: onDrop
};
board = ChessBoard('board', config);
function refresh(newFen) { const wsScheme = window.location.protocol === "https:" ? "wss://" : "ws://";
game = new Chess(newFen); const socket = new WebSocket(
board.position(newFen); wsScheme + window.location.host + "/games/" + gameId + ".ws"
console.log("Got fen " + newFen + ". refreshing board..."); );
updateStatusText(); socket.onmessage = function (event) {
} refresh(event.data);
};
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);
};
// Auto reconnect.
socket.onclose = function () {
alert("Socket closed. Reconnecting...");
setTimeout(function () {
initGameBoard(player, gameId);
}, 1000);
};
});
// Auto reconnect.
socket.onclose = function () {
alert("Socket closed. Reconnecting...");
setTimeout(function () {
initGameBoard(player, gameId);
}, 1000);
};
});
}; };
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
export default initGameBoard; export default Game;