Add draw requests and rematches
This commit is contained in:
parent
1a221bf680
commit
2f46d86947
|
@ -35,5 +35,6 @@
|
|||
<script src="scripts/display-ws-connection.js"></script>
|
||||
<script src="scripts/send-ws-messages.js"></script>
|
||||
<script src="scripts/copy-game-link.js"></script>
|
||||
<script src="scripts/handle-redirects.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
document.addEventListener('htmx:wsAfterMessage', function (e) {
|
||||
const message = JSON.parse(e.detail.message);
|
||||
if (message.type === 'redirect_to_game') {
|
||||
window.location.href = '/?gameId=' + message.gameId;
|
||||
}
|
||||
});
|
|
@ -27,5 +27,29 @@ document.addEventListener('htmx:wsConfigSend', function (e) {
|
|||
e.detail.parameters = {
|
||||
type: 'decline_takeback',
|
||||
};
|
||||
} else if (e.target.id == 'draw-button') {
|
||||
e.detail.parameters = {
|
||||
type: 'request_draw',
|
||||
};
|
||||
} else if (e.target.id == 'accept-draw-button') {
|
||||
e.detail.parameters = {
|
||||
type: 'accept_draw',
|
||||
};
|
||||
} else if (e.target.id == 'decline-draw-button') {
|
||||
e.detail.parameters = {
|
||||
type: 'decline_draw',
|
||||
};
|
||||
} else if (e.target.id == 'rematch-button') {
|
||||
e.detail.parameters = {
|
||||
type: 'request_rematch',
|
||||
};
|
||||
} else if (e.target.id == 'accept-rematch-button') {
|
||||
e.detail.parameters = {
|
||||
type: 'accept_rematch',
|
||||
};
|
||||
} else if (e.target.id == 'decline-rematch-button') {
|
||||
e.detail.parameters = {
|
||||
type: 'decline_rematch',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -100,6 +100,12 @@ export class GomokuGame {
|
|||
}
|
||||
}
|
||||
|
||||
public declareDraw() {
|
||||
this.status = 'finished';
|
||||
this.winnerColor = 'draw';
|
||||
this.currentPlayerColor = null;
|
||||
}
|
||||
|
||||
private checkWin(row: number, col: number, color: PlayerColor): boolean {
|
||||
const directions = [
|
||||
[1, 0], // vertical
|
||||
|
|
|
@ -4,7 +4,14 @@ export interface Message {
|
|||
| 'resign'
|
||||
| 'request_takeback'
|
||||
| 'accept_takeback'
|
||||
| 'decline_takeback';
|
||||
| 'decline_takeback'
|
||||
| 'request_draw'
|
||||
| 'accept_draw'
|
||||
| 'decline_draw'
|
||||
| 'request_rematch'
|
||||
| 'accept_rematch'
|
||||
| 'decline_rematch'
|
||||
| 'redirect_to_game';
|
||||
}
|
||||
|
||||
export interface MakeMoveMessage extends Message {
|
||||
|
@ -19,3 +26,19 @@ export interface RequestTakebackMessage extends Message {}
|
|||
export interface AcceptTakebackMessage extends Message {}
|
||||
|
||||
export interface DeclineTakebackMessage extends Message {}
|
||||
|
||||
export interface RequestDrawMessage extends Message {}
|
||||
|
||||
export interface AcceptDrawMessage extends Message {}
|
||||
|
||||
export interface DeclineDrawMessage extends Message {}
|
||||
|
||||
export interface RequestRematchMessage extends Message {}
|
||||
|
||||
export interface AcceptRematchMessage extends Message {}
|
||||
|
||||
export interface DeclineRematchMessage extends Message {}
|
||||
|
||||
export interface RedirectToGameMessage extends Message {
|
||||
gameId: string;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,13 @@ import {
|
|||
RequestTakebackMessage,
|
||||
AcceptTakebackMessage,
|
||||
DeclineTakebackMessage,
|
||||
RequestDrawMessage,
|
||||
AcceptDrawMessage,
|
||||
DeclineDrawMessage,
|
||||
RequestRematchMessage,
|
||||
AcceptRematchMessage,
|
||||
DeclineRematchMessage,
|
||||
RedirectToGameMessage,
|
||||
} from './messages';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
|
@ -37,8 +44,13 @@ class GameServer {
|
|||
blackPlayerId?: string;
|
||||
whitePlayerId?: string;
|
||||
takebackRequesterId: string | null = null;
|
||||
drawRequesterId: string | null = null;
|
||||
rematchRequesterId: string | null = null;
|
||||
|
||||
constructor(id: string) {
|
||||
constructor(
|
||||
id: string,
|
||||
private webSocketHandler: WebSocketHandler,
|
||||
) {
|
||||
this.id = id;
|
||||
this.gomoku = new GomokuGame();
|
||||
this.connections = new Map();
|
||||
|
@ -146,10 +158,29 @@ class GameServer {
|
|||
buttonsHtml = (
|
||||
<div>
|
||||
<button id="accept-takeback-button" ws-send="click">
|
||||
Accept
|
||||
Accept Takeback
|
||||
</button>
|
||||
<button id="decline-takeback-button" ws-send="click">
|
||||
Decline
|
||||
Decline Takeback
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else if (this.drawRequesterId) {
|
||||
if (this.drawRequesterId === conn.id) {
|
||||
buttonsHtml = (
|
||||
<button id="draw-button" disabled>
|
||||
Draw Requested
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
buttonsHtml = (
|
||||
<div>
|
||||
<button id="accept-draw-button" ws-send="click">
|
||||
Accept Draw
|
||||
</button>
|
||||
<button id="decline-draw-button" ws-send="click">
|
||||
Decline Draw
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -163,9 +194,39 @@ class GameServer {
|
|||
<button id="takeback-button" ws-send="click">
|
||||
Takeback
|
||||
</button>
|
||||
<button id="draw-button" ws-send="click">
|
||||
Draw
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else if (this.gomoku.status === 'finished') {
|
||||
if (this.rematchRequesterId) {
|
||||
if (this.rematchRequesterId === conn.id) {
|
||||
buttonsHtml = (
|
||||
<button id="rematch-button" disabled>
|
||||
Rematch Requested
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
buttonsHtml = (
|
||||
<div>
|
||||
<button id="accept-rematch-button" ws-send="click">
|
||||
Accept Rematch
|
||||
</button>
|
||||
<button id="decline-rematch-button" ws-send="click">
|
||||
Decline Rematch
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
buttonsHtml = (
|
||||
<button id="rematch-button" ws-send="click">
|
||||
Rematch
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
conn.ws.send(<div id="button-box">{buttonsHtml}</div>);
|
||||
console.log(`Sent buttons for game ${this.id} to player ${conn.id}`);
|
||||
|
@ -219,6 +280,30 @@ class GameServer {
|
|||
this.handleDeclineTakeback(conn, message as DeclineTakebackMessage);
|
||||
break;
|
||||
}
|
||||
case 'request_draw': {
|
||||
this.handleRequestDraw(conn, message as RequestDrawMessage);
|
||||
break;
|
||||
}
|
||||
case 'accept_draw': {
|
||||
this.handleAcceptDraw(conn, message as AcceptDrawMessage);
|
||||
break;
|
||||
}
|
||||
case 'decline_draw': {
|
||||
this.handleDeclineDraw(conn, message as DeclineDrawMessage);
|
||||
break;
|
||||
}
|
||||
case 'request_rematch': {
|
||||
this.handleRequestRematch(conn, message as RequestRematchMessage);
|
||||
break;
|
||||
}
|
||||
case 'accept_rematch': {
|
||||
this.handleAcceptRematch(conn, message as AcceptRematchMessage);
|
||||
break;
|
||||
}
|
||||
case 'decline_rematch': {
|
||||
this.handleDeclineRematch(conn, message as DeclineRematchMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,8 +340,9 @@ class GameServer {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.takebackRequesterId) {
|
||||
if (this.takebackRequesterId || this.drawRequesterId) {
|
||||
this.takebackRequesterId = null;
|
||||
this.drawRequesterId = null;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
|
||||
|
@ -321,6 +407,10 @@ class GameServer {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.drawRequesterId) {
|
||||
conn.sendMessage('error', 'A draw has already been requested.');
|
||||
return;
|
||||
}
|
||||
this.takebackRequesterId = conn.id;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
|
@ -369,6 +459,139 @@ class GameServer {
|
|||
this.takebackRequesterId = null;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
|
||||
private handleRequestDraw(
|
||||
conn: PlayerConnection,
|
||||
message: RequestDrawMessage,
|
||||
): void {
|
||||
if (this.gomoku.status !== 'playing') {
|
||||
conn.sendMessage(
|
||||
'error',
|
||||
'You can only request a draw in an active game.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.takebackRequesterId) {
|
||||
conn.sendMessage('error', 'A takeback has already been requested.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.drawRequesterId = conn.id;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
|
||||
private handleAcceptDraw(
|
||||
conn: PlayerConnection,
|
||||
message: AcceptDrawMessage,
|
||||
): void {
|
||||
if (this.gomoku.status !== 'playing') {
|
||||
conn.sendMessage(
|
||||
'error',
|
||||
'You can only accept a draw in an active game.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.drawRequesterId) {
|
||||
conn.sendMessage('error', 'No draw has been requested.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.gomoku.declareDraw();
|
||||
this.drawRequesterId = null;
|
||||
this.broadcastBoard();
|
||||
this.broadcastButtons();
|
||||
this.broadcastTitle();
|
||||
}
|
||||
|
||||
private handleDeclineDraw(
|
||||
conn: PlayerConnection,
|
||||
message: DeclineDrawMessage,
|
||||
): void {
|
||||
if (this.gomoku.status !== 'playing') {
|
||||
conn.sendMessage(
|
||||
'error',
|
||||
'You can only decline a draw in an active game.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.drawRequesterId) {
|
||||
conn.sendMessage('error', 'No draw has been requested.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.drawRequesterId = null;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
|
||||
private handleRequestRematch(
|
||||
conn: PlayerConnection,
|
||||
message: RequestRematchMessage,
|
||||
): void {
|
||||
if (this.gomoku.status !== 'finished') {
|
||||
conn.sendMessage(
|
||||
'error',
|
||||
'You can only request a rematch in a finished game.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.rematchRequesterId = conn.id;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
|
||||
private handleAcceptRematch(
|
||||
conn: PlayerConnection,
|
||||
message: AcceptRematchMessage,
|
||||
): void {
|
||||
if (this.gomoku.status !== 'finished') {
|
||||
conn.sendMessage(
|
||||
'error',
|
||||
'You can only accept a rematch in a finished game.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.rematchRequesterId) {
|
||||
conn.sendMessage('error', 'No rematch has been requested.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newGameId = this.webSocketHandler.createGame(
|
||||
undefined,
|
||||
this.whitePlayerId,
|
||||
this.blackPlayerId,
|
||||
);
|
||||
const redirectMessage: RedirectToGameMessage = {
|
||||
type: 'redirect_to_game',
|
||||
gameId: newGameId,
|
||||
};
|
||||
this.connections.forEach((c) => {
|
||||
c.ws.send(redirectMessage);
|
||||
});
|
||||
}
|
||||
|
||||
private handleDeclineRematch(
|
||||
conn: PlayerConnection,
|
||||
message: DeclineRematchMessage,
|
||||
): void {
|
||||
if (this.gomoku.status !== 'finished') {
|
||||
conn.sendMessage(
|
||||
'error',
|
||||
'You can only decline a rematch in a finished game.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.rematchRequesterId) {
|
||||
conn.sendMessage('error', 'No rematch has been requested.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.rematchRequesterId = null;
|
||||
this.broadcastButtons();
|
||||
}
|
||||
}
|
||||
|
||||
export class WebSocketHandler {
|
||||
|
@ -430,9 +653,16 @@ export class WebSocketHandler {
|
|||
gameServer.handleMessage(ws, message);
|
||||
}
|
||||
|
||||
public createGame(id?: string): string {
|
||||
public createGame(
|
||||
id?: string,
|
||||
blackPlayerId?: string,
|
||||
whitePlayerId?: string,
|
||||
): string {
|
||||
const realId = id ? id : uuidv4();
|
||||
this.games.set(realId, new GameServer(realId));
|
||||
this.games.set(realId, new GameServer(realId, this));
|
||||
const game = this.games.get(realId)!;
|
||||
game.blackPlayerId = blackPlayerId;
|
||||
game.whitePlayerId = whitePlayerId;
|
||||
return realId;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue