Initial commit
This commit is contained in:
parent
19cae8ae63
commit
830d733f1b
1 changed files with 165 additions and 165 deletions
|
|
@ -1,39 +1,39 @@
|
||||||
import "./ttt.css"
|
import "./ttt.css"
|
||||||
import { addRoute, type RouteHandlerReturn } from "@app/routing";
|
import {addRoute, type RouteHandlerReturn} from "@app/routing";
|
||||||
import tttPage from "./ttt.html?raw";
|
import tttPage from "./ttt.html?raw";
|
||||||
import { showError, showInfo, showSuccess, showWarn } from "@app/toast";
|
import {showError, showInfo, showSuccess, showWarn} from "@app/toast";
|
||||||
import { io } from "socket.io-client";
|
import {io} from "socket.io-client";
|
||||||
import type { GameUpdate, CSocket as Socket } from "./socket";
|
import type {CSocket as Socket, GameUpdate} from "./socket";
|
||||||
import { updateUser } from "@app/auth";
|
import {updateUser} from "@app/auth";
|
||||||
import client from "@app/api";
|
import client from "@app/api";
|
||||||
|
|
||||||
|
|
||||||
declare module 'ft_state' {
|
declare module 'ft_state' {
|
||||||
interface State {
|
interface State {
|
||||||
tttSock?: Socket;
|
tttSock?: Socket;
|
||||||
tttkeepAliveInterval?: ReturnType<typeof setInterval>;
|
tttkeepAliveInterval?: ReturnType<typeof setInterval>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum QueueState {
|
enum QueueState {
|
||||||
InQueue = "In Queue",
|
InQueue = "In Queue",
|
||||||
InGame = "In Game",
|
InGame = "In Game",
|
||||||
Idle = "Join Queue",
|
Idle = "Join Queue",
|
||||||
};
|
}
|
||||||
|
|
||||||
document.addEventListener("ft:pageChange", () => {
|
document.addEventListener("ft:pageChange", () => {
|
||||||
if (window.__state.tttSock !== undefined) window.__state.tttSock.close();
|
if (window.__state.tttSock !== undefined) window.__state.tttSock.close();
|
||||||
if (window.__state.tttkeepAliveInterval !== undefined) clearInterval(window.__state.tttkeepAliveInterval);
|
if (window.__state.tttkeepAliveInterval !== undefined) clearInterval(window.__state.tttkeepAliveInterval);
|
||||||
window.__state.tttSock = undefined;
|
window.__state.tttSock = undefined;
|
||||||
window.__state.tttkeepAliveInterval = undefined;
|
window.__state.tttkeepAliveInterval = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getSocket(): Socket {
|
export function getSocket(): Socket {
|
||||||
if (window.__state.tttSock === undefined)
|
if (window.__state.tttSock === undefined)
|
||||||
window.__state.tttSock = io(window.location.host, { path: "/api/ttt/socket.io/" }) as any as Socket;
|
window.__state.tttSock = io(window.location.host, {path: "/api/ttt/socket.io/"}) as any as Socket;
|
||||||
if (window.__state.tttkeepAliveInterval === undefined)
|
if (window.__state.tttkeepAliveInterval === undefined)
|
||||||
window.__state.tttkeepAliveInterval = setInterval(() => window.__state.tttSock?.emit('keepalive'), 100);
|
window.__state.tttkeepAliveInterval = setInterval(() => window.__state.tttSock?.emit('keepalive'), 100);
|
||||||
return window.__state.tttSock;
|
return window.__state.tttSock;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CurrentGameInfo = GameUpdate & { lastState: GameUpdate['gameState'] | null };
|
type CurrentGameInfo = GameUpdate & { lastState: GameUpdate['gameState'] | null };
|
||||||
|
|
@ -41,164 +41,164 @@ type CurrentGameInfo = GameUpdate & { lastState: GameUpdate['gameState'] | null
|
||||||
// Route handler for the Tic-Tac-Toe page.
|
// Route handler for the Tic-Tac-Toe page.
|
||||||
// Instantiates the game logic and binds UI events.
|
// Instantiates the game logic and binds UI events.
|
||||||
async function handleTTT(): Promise<RouteHandlerReturn> {
|
async function handleTTT(): Promise<RouteHandlerReturn> {
|
||||||
const socket: Socket = getSocket();
|
const socket: Socket = getSocket();
|
||||||
void socket;
|
void socket;
|
||||||
return {
|
return {
|
||||||
html: tttPage, postInsert: async (app) => {
|
html: tttPage, postInsert: async (app) => {
|
||||||
if (!app) {
|
if (!app) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let user = await updateUser();
|
let user = await updateUser();
|
||||||
if (user === null)
|
if (user === null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const userXString = document.getElementById("playerX-name");
|
const userXString = document.getElementById("playerX-name");
|
||||||
const userOString = document.getElementById("playerO-name");
|
const userOString = document.getElementById("playerO-name");
|
||||||
const currentPlayerIndicator = document.getElementById("currentPlayer");
|
const currentPlayerIndicator = document.getElementById("currentPlayer");
|
||||||
const joinQueueBtn = document.getElementById("JoinQueueBtn");
|
const joinQueueBtn = document.getElementById("JoinQueueBtn");
|
||||||
const currentPlayerTimer = document.getElementById("currentPlayerTimer")
|
const currentPlayerTimer = document.getElementById("currentPlayerTimer")
|
||||||
if (!userXString || !userOString || !currentPlayerIndicator || !joinQueueBtn || !currentPlayerTimer) {
|
if (!userXString || !userOString || !currentPlayerIndicator || !joinQueueBtn || !currentPlayerTimer) {
|
||||||
return showError('fatal error');
|
return showError('fatal error');
|
||||||
}
|
}
|
||||||
|
|
||||||
joinQueueBtn.addEventListener("click", () => {
|
joinQueueBtn.addEventListener("click", () => {
|
||||||
console.log('===== JOIN QUEUE BUTTON PRESSED =====');
|
console.log('===== JOIN QUEUE BUTTON PRESSED =====');
|
||||||
if (joinQueueBtn.innerText !== QueueState.Idle) {
|
if (joinQueueBtn.innerText !== QueueState.Idle) {
|
||||||
console.log("== Entering in first if ==");
|
console.log("== Entering in first if ==");
|
||||||
if (joinQueueBtn.innerText === QueueState.InQueue) {
|
if (joinQueueBtn.innerText === QueueState.InQueue) {
|
||||||
console.log("== Entering in second if ==");
|
console.log("== Entering in second if ==");
|
||||||
socket.emit("dequeue");
|
socket.emit("dequeue");
|
||||||
joinQueueBtn.innerText = QueueState.Idle;
|
joinQueueBtn.innerText = QueueState.Idle;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
joinQueueBtn.innerText = QueueState.InQueue;
|
joinQueueBtn.innerText = QueueState.InQueue;
|
||||||
socket.emit("enqueue");
|
socket.emit("enqueue");
|
||||||
});
|
});
|
||||||
|
|
||||||
let curGame: CurrentGameInfo | null = null;
|
let curGame: CurrentGameInfo | null = null;
|
||||||
let curGameX: {id: string, name: string} | null = null;
|
let curGameX: { id: string, name: string } | null = null;
|
||||||
let curGameO: {id: string, name: string} | null = null;
|
let curGameO: { id: string, name: string } | null = null;
|
||||||
|
|
||||||
socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`));
|
socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`));
|
||||||
socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`));
|
socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`));
|
||||||
socket.on('newGame', async (gameState) => {
|
socket.on('newGame', async (gameState) => {
|
||||||
showInfo(`newGame: ${gameState.gameId}`)
|
showInfo(`newGame: ${gameState.gameId}`)
|
||||||
|
|
||||||
currentPlayerTimer.innerText = "";
|
currentPlayerTimer.innerText = "";
|
||||||
joinQueueBtn.innerText = QueueState.InGame;
|
joinQueueBtn.innerText = QueueState.InGame;
|
||||||
curGame = { ...gameState, lastState: null };
|
curGame = {...gameState, lastState: null};
|
||||||
|
|
||||||
let resX = await client.getUser({user: curGame.playerX});
|
let resX = await client.getUser({user: curGame.playerX});
|
||||||
|
|
||||||
curGameX = {id: curGame.playerX, name: 'Player X'};
|
curGameX = {id: curGame.playerX, name: 'Player X'};
|
||||||
if (resX.kind === 'success')
|
if (resX.kind === 'success')
|
||||||
curGameX.name = resX.payload.name;
|
curGameX.name = resX.payload.name;
|
||||||
else
|
else
|
||||||
showError(`Unable to get player information: ${resX.msg}`);
|
showError(`Unable to get player information: ${resX.msg}`);
|
||||||
let resO = await client.getUser({user: curGame.playerO});
|
let resO = await client.getUser({user: curGame.playerO});
|
||||||
|
|
||||||
curGameO = {id: curGame.playerO, name: 'Player O'};
|
curGameO = {id: curGame.playerO, name: 'Player O'};
|
||||||
if (resO.kind === 'success')
|
if (resO.kind === 'success')
|
||||||
curGameO.name = resO.payload.name;
|
curGameO.name = resO.payload.name;
|
||||||
else
|
else
|
||||||
showError(`Unable to get player information: ${resO.msg}`);
|
showError(`Unable to get player information: ${resO.msg}`);
|
||||||
|
|
||||||
if (user.id === curGameO.id) {
|
if (user.id === curGameO.id) {
|
||||||
userOString.classList.add('text-red-800');
|
userOString.classList.add('text-red-800');
|
||||||
userOString.classList.remove('text-gray-800');
|
userOString.classList.remove('text-gray-800');
|
||||||
userXString.classList.remove('text-red-800');
|
userXString.classList.remove('text-red-800');
|
||||||
userXString.classList.add('text-gray-800');
|
userXString.classList.add('text-gray-800');
|
||||||
}
|
} else if (user.id === curGameX.id) {
|
||||||
else if (user.id === curGameX.id) {
|
userXString.classList.add('text-red-800');
|
||||||
userXString.classList.add('text-red-800');
|
userXString.classList.remove('text-gray-800');
|
||||||
userXString.classList.remove('text-gray-800');
|
userOString.classList.remove('text-red-800');
|
||||||
userOString.classList.remove('text-red-800');
|
userOString.classList.add('text-gray-800');
|
||||||
userOString.classList.add('text-gray-800');
|
}
|
||||||
}
|
userXString.innerText = curGameX.name;
|
||||||
userXString.innerText = curGameX.name;
|
userOString.innerText = curGameO.name;
|
||||||
userOString.innerText = curGameO.name;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const cells = app.querySelectorAll<HTMLDivElement>(".ttt-cell");
|
const cells = app.querySelectorAll<HTMLDivElement>(".ttt-cell");
|
||||||
|
|
||||||
const updateUI = (boardState: (string | null)[]) => {
|
const updateUI = (boardState: (string | null)[]) => {
|
||||||
boardState.forEach((state, idx) => {
|
boardState.forEach((state, idx) => {
|
||||||
cells[idx].innerText = state || " ";
|
cells[idx].innerText = state || " ";
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeEnd = (type: 'win' | 'conceded' | 'draw', player: 'X' | 'O') => {
|
const makeEnd = (type: 'win' | 'conceded' | 'draw', player: 'X' | 'O') => {
|
||||||
if (type === 'draw') {
|
// TODO: Enhance the draw notification
|
||||||
showWarn('It\'s a draw !')
|
if (type === 'draw') {
|
||||||
}
|
showWarn('It\'s a draw !')
|
||||||
|
}
|
||||||
|
|
||||||
if (type === 'win') {
|
if (type === 'win') {
|
||||||
let youWin: boolean;
|
let youWin: boolean;
|
||||||
switch (player) {
|
switch (player) {
|
||||||
case 'X':
|
case 'X':
|
||||||
youWin = (curGame?.playerX === user.id);
|
youWin = (curGame?.playerX === user.id);
|
||||||
break;
|
break;
|
||||||
case 'O':
|
case 'O':
|
||||||
youWin = (curGame?.playerO === user.id);
|
youWin = (curGame?.playerO === user.id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (youWin)
|
// TODO: Enhance the win/loss notification
|
||||||
showSuccess('You won the game !');
|
if (youWin)
|
||||||
else
|
showSuccess('You won the game !');
|
||||||
showError('You lost the game :(');
|
else
|
||||||
}
|
showError('You lost the game :(');
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
socket.on('gameEnd', () => {
|
socket.on('gameEnd', () => {
|
||||||
curGame = null;
|
curGame = null;
|
||||||
currentPlayerTimer.innerText = "Waiting for match...";
|
currentPlayerTimer.innerText = "Waiting for match...";
|
||||||
joinQueueBtn.innerText = QueueState.Idle;
|
joinQueueBtn.innerText = QueueState.Idle;
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('gameBoard', (u) => {
|
socket.on('gameBoard', (u) => {
|
||||||
if (curGame === null) {
|
if (curGame === null) {
|
||||||
return showError('Got game State, but no in a game ?');
|
return showError('Got game State, but no in a game ?');
|
||||||
}
|
}
|
||||||
|
|
||||||
curGame = {...u, lastState: curGame.lastState};
|
curGame = {...u, lastState: curGame.lastState};
|
||||||
|
|
||||||
if (curGame.currentPlayer === 'X')
|
if (curGame.currentPlayer === 'X') {
|
||||||
{
|
currentPlayerIndicator.innerText = "<";
|
||||||
currentPlayerIndicator.innerText = "<";
|
} else if (curGame.currentPlayer === 'O') {
|
||||||
} else if (curGame.currentPlayer === 'O') {
|
currentPlayerIndicator.innerText = ">";
|
||||||
currentPlayerIndicator.innerText = ">";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateUI(u.boardState);
|
updateUI(u.boardState);
|
||||||
|
|
||||||
if (u.gameState && u.gameState !== "ongoing") {
|
if (u.gameState && u.gameState !== "ongoing") {
|
||||||
if (u.gameState !== curGame.lastState) {
|
if (u.gameState !== curGame.lastState) {
|
||||||
curGame.lastState = u.gameState;
|
curGame.lastState = u.gameState;
|
||||||
switch (u.gameState) {
|
switch (u.gameState) {
|
||||||
case 'winX':
|
case 'winX':
|
||||||
case 'winO':
|
case 'winO':
|
||||||
makeEnd('win', u.gameState[3] as 'X' | 'O');
|
makeEnd('win', u.gameState[3] as 'X' | 'O');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
makeEnd('draw', 'X');
|
makeEnd('draw', 'X');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cells?.forEach(function(c, idx) {
|
cells?.forEach(function (c, idx) {
|
||||||
c.addEventListener("click", () => {
|
c.addEventListener("click", () => {
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.emit("gameMove", { index: idx });
|
socket.emit("gameMove", {index: idx});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoute("/ttt", handleTTT);
|
addRoute("/ttt", handleTTT);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue