(tic-tac-toe): game outcomes are now written to tictatoe database. :)
This commit is contained in:
parent
7724e24e4c
commit
f9801dafe7
2 changed files with 158 additions and 151 deletions
|
|
@ -24,7 +24,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair
|
|||
ON blocked(user, blocked);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tictactoe (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
player1 TEXT NOT NULL,
|
||||
player2 TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL
|
||||
|
|
|
|||
|
|
@ -1,198 +1,205 @@
|
|||
import { UserId } from '@shared/database/mixin/user';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import { GameMove, SSocket } from './socket';
|
||||
import { isNullish } from '@shared/utils';
|
||||
import { newUUID } from '@shared/utils/uuid';
|
||||
import { GameId } from '@shared/database/mixin/tictactoe';
|
||||
import { TTC } from './game';
|
||||
import {UserId} from '@shared/database/mixin/user';
|
||||
import {FastifyInstance} from 'fastify';
|
||||
import {GameMove, SSocket} from './socket';
|
||||
import {isNullish} from '@shared/utils';
|
||||
import {newUUID} from '@shared/utils/uuid';
|
||||
import {GameId} from '@shared/database/mixin/tictactoe';
|
||||
import {TTC} from './game';
|
||||
|
||||
type TTTUser = {
|
||||
socket: SSocket,
|
||||
userId: UserId,
|
||||
windowId: string,
|
||||
updateInterval: NodeJS.Timeout,
|
||||
currentGame: GameId | null,
|
||||
socket: SSocket,
|
||||
userId: UserId,
|
||||
windowId: string,
|
||||
updateInterval: NodeJS.Timeout,
|
||||
currentGame: GameId | null,
|
||||
}
|
||||
|
||||
export class StateI {
|
||||
private users: Map<UserId, TTTUser> = new Map();
|
||||
private users: Map<UserId, TTTUser> = new Map();
|
||||
|
||||
private queue: Set<UserId> = new Set();
|
||||
private queue: Set<UserId> = new Set();
|
||||
|
||||
private queueInterval: NodeJS.Timeout;
|
||||
private queueInterval: NodeJS.Timeout;
|
||||
|
||||
private games: Map<GameId, TTC> = new Map();
|
||||
private games: Map<GameId, TTC> = new Map();
|
||||
|
||||
constructor(private fastify: FastifyInstance) {
|
||||
this.queueInterval = setInterval(() => this.queuerFunction());
|
||||
void this.queueInterval;
|
||||
}
|
||||
constructor(private fastify: FastifyInstance) {
|
||||
this.queueInterval = setInterval(() => this.queuerFunction());
|
||||
void this.queueInterval;
|
||||
}
|
||||
|
||||
private queuerFunction(): void {
|
||||
const values = Array.from(this.queue.values());
|
||||
while (values.length >= 2) {
|
||||
const id1 = values.pop();
|
||||
const id2 = values.pop();
|
||||
public registerUser(socket: SSocket): void {
|
||||
this.fastify.log.info('Registering new user');
|
||||
if (this.users.has(socket.authUser.id)) {
|
||||
socket.emit('forceDisconnect', 'Already Connected');
|
||||
socket.disconnect();
|
||||
return;
|
||||
}
|
||||
this.users.set(socket.authUser.id, {
|
||||
socket,
|
||||
userId: socket.authUser.id,
|
||||
windowId: socket.id,
|
||||
updateInterval: setInterval(() => this.updateClient(socket), 3000),
|
||||
currentGame: null,
|
||||
});
|
||||
this.fastify.log.info('Registered new user');
|
||||
|
||||
if (isNullish(id1) || isNullish(id2)) {
|
||||
continue;
|
||||
}
|
||||
socket.on('disconnect', () => this.cleanupUser(socket));
|
||||
socket.on('enqueue', () => this.enqueueUser(socket));
|
||||
socket.on('dequeue', () => this.dequeueUser(socket));
|
||||
socket.on('debugInfo', () => this.debugSocket(socket));
|
||||
|
||||
const u1 = this.users.get(id1);
|
||||
const u2 = this.users.get(id2);
|
||||
socket.on('gameMove', (e) => this.gameMove(socket, e));
|
||||
if (socket) {
|
||||
console.log('Socket:', socket.id);
|
||||
}
|
||||
|
||||
if (isNullish(u1) || isNullish(u2)) {
|
||||
continue;
|
||||
}
|
||||
this.queue.delete(id1);
|
||||
this.queue.delete(id2);
|
||||
}
|
||||
|
||||
const gameId = newUUID() as unknown as GameId;
|
||||
const g = new TTC(u1.userId, u2.userId);
|
||||
const iState = {
|
||||
boardState: g.board,
|
||||
currentPlayer: g.getCurrentState(),
|
||||
playerX: g.playerX,
|
||||
playerO: g.playerO,
|
||||
gameState: g.checkState(),
|
||||
gameId: gameId,
|
||||
};
|
||||
private queuerFunction(): void {
|
||||
const values = Array.from(this.queue.values());
|
||||
while (values.length >= 2) {
|
||||
const id1 = values.pop();
|
||||
const id2 = values.pop();
|
||||
|
||||
u1.socket.emit('newGame', iState);
|
||||
u2.socket.emit('newGame', iState);
|
||||
this.games.set(gameId, g);
|
||||
if (isNullish(id1) || isNullish(id2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u1.currentGame = gameId;
|
||||
u2.currentGame = gameId;
|
||||
const u1 = this.users.get(id1);
|
||||
const u2 = this.users.get(id2);
|
||||
|
||||
g.gameUpdate = setInterval(() => {
|
||||
this.gameUpdate(gameId, u1.socket);
|
||||
this.gameUpdate(gameId, u2.socket);
|
||||
if (g.checkState() !== 'ongoing') { this.cleanupGame(gameId, g); }
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
if (isNullish(u1) || isNullish(u2)) {
|
||||
continue;
|
||||
}
|
||||
this.queue.delete(id1);
|
||||
this.queue.delete(id2);
|
||||
|
||||
public registerUser(socket: SSocket): void {
|
||||
this.fastify.log.info('Registering new user');
|
||||
if (this.users.has(socket.authUser.id)) {
|
||||
socket.emit('forceDisconnect', 'Already Connected');
|
||||
socket.disconnect();
|
||||
return;
|
||||
}
|
||||
this.users.set(socket.authUser.id, {
|
||||
socket,
|
||||
userId: socket.authUser.id,
|
||||
windowId: socket.id,
|
||||
updateInterval: setInterval(() => this.updateClient(socket), 3000),
|
||||
currentGame: null,
|
||||
});
|
||||
this.fastify.log.info('Registered new user');
|
||||
const gameId = newUUID() as unknown as GameId;
|
||||
const g = new TTC(u1.userId, u2.userId);
|
||||
const iState = {
|
||||
boardState: g.board,
|
||||
currentPlayer: g.getCurrentState(),
|
||||
playerX: g.playerX,
|
||||
playerO: g.playerO,
|
||||
gameState: g.checkState(),
|
||||
gameId: gameId,
|
||||
};
|
||||
|
||||
socket.on('disconnect', () => this.cleanupUser(socket));
|
||||
socket.on('enqueue', () => this.enqueueUser(socket));
|
||||
socket.on('dequeue', () => this.dequeueUser(socket));
|
||||
socket.on('debugInfo', () => this.debugSocket(socket));
|
||||
u1.socket.emit('newGame', iState);
|
||||
u2.socket.emit('newGame', iState);
|
||||
this.games.set(gameId, g);
|
||||
|
||||
socket.on('gameMove', (e) => this.gameMove(socket, e));
|
||||
}
|
||||
u1.currentGame = gameId;
|
||||
u2.currentGame = gameId;
|
||||
|
||||
private updateClient(socket: SSocket): void {
|
||||
socket.emit('updateInformation', {
|
||||
inQueue: this.queue.size,
|
||||
totalUser: this.users.size,
|
||||
});
|
||||
}
|
||||
g.gameUpdate = setInterval(() => {
|
||||
this.gameUpdate(gameId, u1.socket);
|
||||
this.gameUpdate(gameId, u2.socket);
|
||||
if (g.checkState() !== 'ongoing') {
|
||||
this.cleanupGame(gameId, g);
|
||||
this.fastify.db.setGameOutcome(gameId, u1.userId, u2.userId, g.checkState());
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupUser(socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
private updateClient(socket: SSocket): void {
|
||||
socket.emit('updateInformation', {
|
||||
inQueue: this.queue.size,
|
||||
totalUser: this.users.size,
|
||||
});
|
||||
}
|
||||
|
||||
clearInterval(this.users.get(socket.authUser.id)?.updateInterval);
|
||||
this.users.delete(socket.authUser.id);
|
||||
this.queue.delete(socket.authUser.id);
|
||||
}
|
||||
private cleanupUser(socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
|
||||
private cleanupGame(gameId: GameId, game: TTC): void {
|
||||
clearInterval(game.gameUpdate ?? undefined);
|
||||
this.games.delete(gameId);
|
||||
let player: TTTUser | undefined = undefined;
|
||||
if ((player = this.users.get(game.playerO)) !== undefined) {
|
||||
player.currentGame = null;
|
||||
player.socket.emit('gameEnd');
|
||||
}
|
||||
if ((player = this.users.get(game.playerX)) !== undefined) {
|
||||
player.currentGame = null;
|
||||
player.socket.emit('gameEnd');
|
||||
}
|
||||
// do something here with the game result before deleting the game at the end
|
||||
}
|
||||
clearInterval(this.users.get(socket.authUser.id)?.updateInterval);
|
||||
this.users.delete(socket.authUser.id);
|
||||
this.queue.delete(socket.authUser.id);
|
||||
}
|
||||
|
||||
private enqueueUser(socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
private cleanupGame(gameId: GameId, game: TTC): void {
|
||||
clearInterval(game.gameUpdate ?? undefined);
|
||||
this.games.delete(gameId);
|
||||
let player: TTTUser | undefined;
|
||||
if ((player = this.users.get(game.playerO)) !== undefined) {
|
||||
player.currentGame = null;
|
||||
player.socket.emit('gameEnd');
|
||||
}
|
||||
if ((player = this.users.get(game.playerX)) !== undefined) {
|
||||
player.currentGame = null;
|
||||
player.socket.emit('gameEnd');
|
||||
}
|
||||
// do something here with the game result before deleting the game at the end
|
||||
}
|
||||
|
||||
if (this.queue.has(socket.authUser.id)) return;
|
||||
private enqueueUser(socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
|
||||
if (this.users.get(socket.authUser.id)?.currentGame !== null) return;
|
||||
if (this.queue.has(socket.authUser.id)) return;
|
||||
|
||||
this.queue.add(socket.authUser.id);
|
||||
socket.emit('queueEvent', 'registered');
|
||||
}
|
||||
if (this.users.get(socket.authUser.id)?.currentGame !== null) return;
|
||||
|
||||
private dequeueUser(socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
this.queue.add(socket.authUser.id);
|
||||
socket.emit('queueEvent', 'registered');
|
||||
}
|
||||
|
||||
if (!this.queue.has(socket.authUser.id)) return;
|
||||
private dequeueUser(socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
|
||||
this.queue.delete(socket.authUser.id);
|
||||
socket.emit('queueEvent', 'unregistered');
|
||||
}
|
||||
if (!this.queue.has(socket.authUser.id)) return;
|
||||
|
||||
private debugSocket(socket: SSocket): void {
|
||||
console.log(({
|
||||
message: `socket debug for ${socket.id}`,
|
||||
userid: socket.authUser.id,
|
||||
queue: this.queue,
|
||||
users: this.users,
|
||||
}));
|
||||
}
|
||||
this.queue.delete(socket.authUser.id);
|
||||
socket.emit('queueEvent', 'unregistered');
|
||||
}
|
||||
|
||||
private gameUpdate(gameId: GameId, socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
const user = this.users.get(socket.authUser.id)!;
|
||||
private debugSocket(socket: SSocket): void {
|
||||
console.log(({
|
||||
message: `socket debug for ${socket.id}`,
|
||||
userid: socket.authUser.id,
|
||||
queue: this.queue,
|
||||
users: this.users,
|
||||
}));
|
||||
}
|
||||
|
||||
if (user.currentGame !== gameId || !this.games.has(user.currentGame)) return;
|
||||
private gameUpdate(gameId: GameId, socket: SSocket): void {
|
||||
if (!this.users.has(socket.authUser.id)) return;
|
||||
const user = this.users.get(socket.authUser.id)!;
|
||||
|
||||
const games = this.games.get(gameId)!;
|
||||
if (user.currentGame !== gameId || !this.games.has(user.currentGame)) return;
|
||||
|
||||
socket.emit('gameBoard', {
|
||||
boardState: games.board,
|
||||
currentPlayer: games.getCurrentState(),
|
||||
playerX: games.playerX,
|
||||
playerO: games.playerO,
|
||||
gameState: games.checkState(),
|
||||
gameId: gameId,
|
||||
});
|
||||
}
|
||||
const games = this.games.get(gameId)!;
|
||||
|
||||
private gameMove(socket: SSocket, update: GameMove) {
|
||||
if (!this.users.has(socket.authUser.id)) return 'unknownError';
|
||||
const user = this.users.get(socket.authUser.id)!;
|
||||
socket.emit('gameBoard', {
|
||||
boardState: games.board,
|
||||
currentPlayer: games.getCurrentState(),
|
||||
playerX: games.playerX,
|
||||
playerO: games.playerO,
|
||||
gameState: games.checkState(),
|
||||
gameId: gameId,
|
||||
});
|
||||
}
|
||||
|
||||
if (user.currentGame !== null && !this.games.has(user.currentGame)) return 'unknownError';
|
||||
const game = this.games.get(user.currentGame!)!;
|
||||
private gameMove(socket: SSocket, update: GameMove) {
|
||||
if (!this.users.has(socket.authUser.id)) return 'unknownError';
|
||||
const user = this.users.get(socket.authUser.id)!;
|
||||
|
||||
game.makeMove(socket.authUser.id, update.index);
|
||||
}
|
||||
if (user.currentGame !== null && !this.games.has(user.currentGame)) return 'unknownError';
|
||||
const game = this.games.get(user.currentGame!)!;
|
||||
|
||||
game.makeMove(socket.authUser.id, update.index);
|
||||
}
|
||||
}
|
||||
|
||||
// this value will be overriten
|
||||
export let State: StateI = undefined as unknown as StateI;
|
||||
|
||||
export function createState(fastify: FastifyInstance) {
|
||||
if (State !== undefined) {
|
||||
throw new Error('State was already created !!!!');
|
||||
}
|
||||
State = new StateI(fastify);
|
||||
if (State !== undefined) {
|
||||
throw new Error('State was already created !!!!');
|
||||
}
|
||||
State = new StateI(fastify);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue