Add resign button

This commit is contained in:
sepia 2025-07-21 22:54:03 -05:00
parent 10a4bd64d2
commit 3093754bd4
6 changed files with 79 additions and 28 deletions

View File

@ -18,9 +18,9 @@
</head>
<body>
<div id="game-container" hx-ext="ws">
<div id="title-box" class="title-box"></div>
<div id="game-board" class="game-board-grid"></div>
<div id="messages"></div>
<div id="title-box"></div>
<div id="game-board"></div>
<div id="button-box"></div>
</div>
<div id="game-link-container">
<button id="copy-link-button" onclick="copyGameLink()">

View File

@ -3,11 +3,17 @@ document.addEventListener('htmx:wsConfigSend', function (e) {
const row = parseInt(e.target.dataset.row);
const col = parseInt(e.target.dataset.col);
console.log(e.target.id);
// Set the custom JSON data
e.detail.parameters = {
type: 'make_move',
row: row,
col: col,
};
} else if (e.target.id == 'resign-button') {
e.detail.parameters = {
type: 'resign'
};
}
});

View File

@ -102,7 +102,7 @@ body {
padding-bottom: 0.25em;
}
.game-board-grid {
#game-board {
position: relative;
width: calc(14 * 30px);
height: calc(14 * 30px);
@ -110,7 +110,7 @@ body {
border: 2px solid var(--color-neutral-700);
}
.game-board-grid::before {
#game-board::before {
content: '';
position: absolute;
top: 0;
@ -221,8 +221,15 @@ body {
margin-top: 20px;
}
#copy-link-button {
position: absolute;
img.icon {
width: 1em;
height: 1em;
vertical-align: middle;
}
button {
background-color: var(--color-primary);
color: var(--color-on-primary);
display: flex;
justify-content: center;
align-items: center;
@ -230,27 +237,38 @@ body {
padding: 8px 15px;
border-radius: 5px;
white-space: nowrap;
}
#copy-link-button {
background-color: var(--color-primary);
color: var(--color-on-primary);
border: none;
cursor: pointer;
gap: 8px;
font-size: 1em;
}
#copy-link-button:hover {
button:hover {
background-color: var(--color-primary-light);
}
#button-box {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
#resign-button {
background-color: var(--color-error);
color: var(--color-on-primary);
}
#resign-button:hover {
background-color: var(--color-error-light);
}
#copy-link-button {
background-color: var(--color-primary);
color: var(--color-on-primary);
}
#copy-link-button.copied-state {
background-color: var(--color-success);
}
img.icon {
width: 1em;
height: 1em;
vertical-align: middle;
}

View File

@ -76,6 +76,12 @@ export class GomokuGame {
return { success: true };
}
public resign(resigningPlayerColor: PlayerColor) {
this.winnerColor = resigningPlayerColor === 'white' ? 'black' : 'white';
this.status = 'finished';
this.currentPlayerColor = null;
}
private checkWin(row: number, col: number, color: PlayerColor): boolean {
const directions = [
[1, 0], // vertical

View File

@ -1,7 +1,5 @@
export interface Message {
type: 'make_move' | 'resign';
gameId: string;
playerId: string;
}
export interface MakeMoveMessage extends Message {

View File

@ -118,7 +118,11 @@ class GameServer {
}
public broadcastButtonsToPlayer(conn: PlayerConnection) {
// TODO
let buttonsHtml;
if (this.gomoku.status == 'playing' && this.getPlayerColor(conn)) {
buttonsHtml = <button id="resign-button" ws-send="click">Resign</button>;
}
conn.ws.send(<div id="button-box">{buttonsHtml}</div>);
console.log(`Sent buttons for game ${this.id} to player ${conn.id}`);
}
@ -136,6 +140,9 @@ class GameServer {
return;
}
console.log(
`Handling ${message.type} message in game ${this.id} from player ${conn.id}: ${JSON.stringify(message)}`,
);
switch (message.type) {
case 'make_move': {
this.handleMakeMove(conn, message as MakeMoveMessage);
@ -159,9 +166,6 @@ class GameServer {
}
private handleMakeMove(conn: PlayerConnection, message: MakeMoveMessage): void {
console.log(
`Handling make_move message in game ${this.id} from player ${conn.id}: ${{ message }}`,
);
const { row, col } = message;
var playerColor;
@ -180,7 +184,6 @@ class GameServer {
const stateBeforeMove = this.gomoku.status;
const result = this.gomoku.makeMove(playerColor, row, col);
console.log(result);
if (result.success) {
this.broadcastBoard();
this.broadcastTitle();
@ -196,8 +199,28 @@ class GameServer {
}
}
private handleResignation(conn: PlayerConnection, _message: ResignationMessage): void {
// TODO
private handleResignation(conn: PlayerConnection, message: ResignationMessage): void {
console.log(
`Handling resign message in game ${this.id} from player ${conn.id}: ${{ message }}`,
);
if (this.gomoku.status !== 'playing') {
conn.sendMessage('error', 'You can only resign from an active game.');
return;
}
const resigningPlayerColor = this.getPlayerColor(conn);
if (!resigningPlayerColor) {
conn.sendMessage('error', 'You are not a player in this game.');
return;
}
this.gomoku.resign(resigningPlayerColor);
this.broadcastBoard();
this.broadcastTitle();
this.broadcastButtons();
console.log(`Player ${conn.id} resigned from game ${this.id}`);
}
}