feat(pong/database): store match outcomes in database
This commit is contained in:
parent
d59e8f93c8
commit
03be784a51
5 changed files with 139 additions and 36 deletions
|
|
@ -5,12 +5,14 @@ import { Database as DbImpl } from './mixin/_base';
|
|||
import { IUserDb, UserImpl } from './mixin/user';
|
||||
import { IBlockedDb, BlockedImpl } from './mixin/blocked';
|
||||
import { ITicTacToeDb, TicTacToeImpl } from './mixin/tictactoe';
|
||||
import { IPongDb, PongImpl } from './mixin/pong';
|
||||
|
||||
Object.assign(DbImpl.prototype, UserImpl);
|
||||
Object.assign(DbImpl.prototype, BlockedImpl);
|
||||
Object.assign(DbImpl.prototype, TicTacToeImpl);
|
||||
Object.assign(DbImpl.prototype, PongImpl);
|
||||
|
||||
export interface Database extends DbImpl, IUserDb, IBlockedDb, ITicTacToeDb { }
|
||||
export interface Database extends DbImpl, IUserDb, IBlockedDb, ITicTacToeDb, IPongDb { }
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
declare module 'fastify' {
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
Project Transcendance {
|
||||
Note: '''
|
||||
# DBML for Transcendance
|
||||
DBML (database markup language) is a simple, readable DSL language designed to define database structures.
|
||||
|
||||
## Benefits
|
||||
|
||||
* It is simple, flexible and highly human-readable
|
||||
* It is database agnostic, focusing on the essential database structure definition without worrying about the detailed syntaxes of each database
|
||||
* Comes with a free, simple database visualiser at [dbdiagram.io](http://dbdiagram.io)
|
||||
|
||||
# how to use it
|
||||
|
||||
ask Maieul :)
|
||||
'''
|
||||
}
|
||||
|
||||
Table user {
|
||||
id text [PK, not null]
|
||||
login text [unique]
|
||||
name text [not null, unique]
|
||||
password text [null, Note: "If password is NULL, this means that the user is created through OAUTH2 or guest login"]
|
||||
otp text [null, Note: "If otp is NULL, then the user didn't configure 2FA"]
|
||||
guest integer [not null, default: 0]
|
||||
oauth2 text [null, default: `NULL` , Note: "format: <provider>:<unique_id>; null if not logged via provider"]
|
||||
Note: "Represent a user"
|
||||
}
|
||||
|
|
@ -10,18 +10,14 @@ CREATE TABLE IF NOT EXISTS user (
|
|||
allow_guest_message INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blocked (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
user TEXT NOT NULL,
|
||||
blocked TEXT NOT NULL,
|
||||
|
||||
FOREIGN KEY(user) REFERENCES user(id)
|
||||
FOREIGN KEY(blocked) REFERENCES user(id)
|
||||
FOREIGN KEY (user) REFERENCES user (id) FOREIGN KEY (blocked) REFERENCES user (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair
|
||||
ON blocked(user, blocked);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_blocked_user_pair ON blocked (user, blocked);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tictactoe (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
|
|
@ -31,3 +27,16 @@ CREATE TABLE IF NOT EXISTS tictactoe (
|
|||
FOREIGN KEY(player1) REFERENCES user(id),
|
||||
FOREIGN KEY(player2) REFERENCES user(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pong (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
time TEXT NOT NULL default (datetime('now')),
|
||||
playerL TEXT NOT NULL,
|
||||
playerR TEXT NOT NULL,
|
||||
scoreL INTEGER NOT NULL,
|
||||
scoreR INTEGER NOT NULL,
|
||||
outcome TEXT NOT NULL,
|
||||
local INTEGER NOT NULL,
|
||||
FOREIGN KEY (playerL) REFERENCES user (id),
|
||||
FOREIGN KEY (playerR) REFERENCES user (id)
|
||||
);
|
||||
|
|
|
|||
110
src/@shared/src/database/mixin/pong.ts
Normal file
110
src/@shared/src/database/mixin/pong.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import UUID from '@shared/utils/uuid';
|
||||
import type { Database } from './_base';
|
||||
import { UserId } from './user';
|
||||
import { isNullish } from '@shared/utils';
|
||||
|
||||
export type PongGameOutcome = 'winR' | 'winL' | 'other';
|
||||
|
||||
// describe every function in the object
|
||||
export interface IPongDb extends Database {
|
||||
setPongGameOutcome(
|
||||
this: IPongDb,
|
||||
id: PongGameId,
|
||||
left: { id: UserId, score: number },
|
||||
right: { id: UserId, score: number },
|
||||
outcome: PongGameOutcome,
|
||||
local: boolean,
|
||||
): void;
|
||||
getPongGameFromId(
|
||||
this: IPongDb,
|
||||
id: PongGameId,
|
||||
): PongGame | undefined,
|
||||
}
|
||||
|
||||
export const PongImpl: Omit<IPongDb, keyof Database> = {
|
||||
/**
|
||||
* @brief Write the outcome of the specified game to the database.
|
||||
*
|
||||
* @param gameId The game we want to write the outcome of.
|
||||
*
|
||||
*/
|
||||
setPongGameOutcome(
|
||||
this: IPongDb,
|
||||
id: PongGameId,
|
||||
left: { id: UserId, score: number },
|
||||
right: { id: UserId, score: number },
|
||||
outcome: PongGameOutcome,
|
||||
local: boolean,
|
||||
): void {
|
||||
// Find a way to retrieve the outcome of the game.
|
||||
this.prepare(
|
||||
'INSERT INTO pong (id, playerL, playerR, scoreL, scoreR, outcome, local) VALUES (@id, @playerL, @playerR, @scoreL, @scoreR, @outcome, @local)',
|
||||
).run({ id, playerL: left.id, scoreL: left.score, playerR: right.id, scoreR: right.score, outcome, local: local ? 1 : 0 });
|
||||
},
|
||||
|
||||
getPongGameFromId(
|
||||
this: IPongDb,
|
||||
id: PongGameId,
|
||||
): PongGame | undefined {
|
||||
const q = this.prepare('SELECT * FROM pong WHERE id = @id').get({ id }) as Partial<PongGameTable> | undefined;
|
||||
return pongGameFromRow(q);
|
||||
},
|
||||
};
|
||||
|
||||
export type PongGameId = UUID & { readonly __uuid: unique symbol };
|
||||
|
||||
export type PongGame = {
|
||||
readonly id: PongGameId;
|
||||
readonly left: { id: UserId, score: number };
|
||||
readonly right: { id: UserId, score: number };
|
||||
readonly outcome: PongGameOutcome;
|
||||
readonly time: Date;
|
||||
};
|
||||
|
||||
// this is an internal type, never to be seen outside
|
||||
type PongGameTable = {
|
||||
id: PongGameId,
|
||||
playerL: UserId,
|
||||
playerR: UserId,
|
||||
scoreL: number,
|
||||
scoreR: number,
|
||||
outcome: PongGameOutcome,
|
||||
time: string,
|
||||
};
|
||||
|
||||
function pongGameFromRow(r: Partial<PongGameTable> | undefined): PongGame | undefined {
|
||||
if (isNullish(r)) return undefined;
|
||||
if (isNullish(r.id)) return undefined;
|
||||
if (isNullish(r.playerL)) return undefined;
|
||||
if (isNullish(r.playerR)) return undefined;
|
||||
if (isNullish(r.scoreL)) return undefined;
|
||||
if (isNullish(r.scoreR)) return undefined;
|
||||
if (isNullish(r.outcome)) return undefined;
|
||||
if (isNullish(r.time)) return undefined;
|
||||
|
||||
if (r.outcome !== 'winR' && r.outcome !== 'winL' && r.outcome !== 'other') return undefined;
|
||||
const date = Date.parse(r.time);
|
||||
if (Number.isNaN(date)) return undefined;
|
||||
|
||||
|
||||
return {
|
||||
id: r.id,
|
||||
left: { id: r.playerL, score: r.scoreL },
|
||||
right: { id: r.playerR, score: r.scoreR },
|
||||
outcome: r.outcome,
|
||||
time: new Date(date),
|
||||
};
|
||||
}
|
||||
|
||||
// this function will be able to be called from everywhere
|
||||
// export async function freeFloatingExportedFunction(): Promise<boolean> {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// this function will never be able to be called outside of this module
|
||||
// async function privateFunction(): Promise<string | undefined> {
|
||||
// return undefined;
|
||||
// }
|
||||
|
||||
// silence warnings
|
||||
// void privateFunction;
|
||||
|
|
@ -4,6 +4,7 @@ import { FastifyInstance } from 'fastify';
|
|||
import { Pong } from './game';
|
||||
import { GameMove, GameUpdate, SSocket } from './socket';
|
||||
import { isNullish } from '@shared/utils';
|
||||
import { PongGameId, PongGameOutcome } from '@shared/database/mixin/pong';
|
||||
|
||||
type PUser = {
|
||||
id: UserId;
|
||||
|
|
@ -13,7 +14,7 @@ type PUser = {
|
|||
updateInterval: NodeJS.Timeout,
|
||||
};
|
||||
|
||||
type GameId = string & { readonly __brand: unique symbol };
|
||||
type GameId = PongGameId;
|
||||
|
||||
class StateI {
|
||||
public static readonly UPDATE_INTERVAL_FRAMES: number = 60;
|
||||
|
|
@ -72,7 +73,9 @@ class StateI {
|
|||
g.tick();
|
||||
this.gameUpdate(gameId, u1.socket);
|
||||
this.gameUpdate(gameId, u2.socket);
|
||||
if (g.checkWinner() !== null) { this.cleanupGame(gameId, g); }
|
||||
if (g.checkWinner() !== null) {
|
||||
this.cleanupGame(gameId, g);
|
||||
}
|
||||
}, 1000 / StateI.UPDATE_INTERVAL_FRAMES);
|
||||
}
|
||||
}
|
||||
|
|
@ -175,6 +178,12 @@ class StateI {
|
|||
player.currentGame = null;
|
||||
player.socket.emit('gameEnd');
|
||||
}
|
||||
const rOutcome = game.checkWinner();
|
||||
let outcome: PongGameOutcome = 'other';
|
||||
if (rOutcome === 'left') { outcome = 'winL'; }
|
||||
if (rOutcome === 'right') { outcome = 'winR'; }
|
||||
this.fastify.db.setPongGameOutcome(gameId, { id: game.userLeft, score: game.score[0] }, { id: game.userRight, score: game.score[1] }, outcome, game.local);
|
||||
this.fastify.log.info('SetGameOutcome !');
|
||||
// do something here with the game result before deleting the game at the end
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue