From c65bbac33ff1106b252b36052fc25bdb2ac3276f Mon Sep 17 00:00:00 2001 From: James Quigley Date: Tue, 15 Mar 2022 20:50:41 -0400 Subject: [PATCH] Track moves in the database, valid and invalid --- server/src/GameManager.ts | 14 +++++++----- server/src/datalayer/memory.ts | 13 +++++++++++ server/src/datalayer/mongo/index.ts | 32 ++++++++++++++++++++++++++++ server/src/datalayer/mongo/models.ts | 22 +++++++++++++++++++ server/src/routes/game.ts | 22 +++++++++++++++++-- server/src/types.ts | 5 +++++ 6 files changed, 101 insertions(+), 7 deletions(-) diff --git a/server/src/GameManager.ts b/server/src/GameManager.ts index f708ba06..63b25abf 100644 --- a/server/src/GameManager.ts +++ b/server/src/GameManager.ts @@ -100,7 +100,10 @@ export default class GameManager { return this.game; } - makeMove(userId: string, move: HexCoord[]): Game { + makeMove( + userId: string, + move: HexCoord[] + ): { game: Game | null; word: string } { if (!this.game) { throw new Error("Game Manager has no game!"); } @@ -132,9 +135,10 @@ export default class GameManager { } } console.log("move received word", word); - if (!isValidWord(word, WordsObject)) { + const valid = isValidWord(word, WordsObject); + if (!valid) { console.log("word invalid", word); - throw new Error("Not a valid word"); + return { game: null, word }; } const gridCopy: { [coord: string]: Cell } = {}; @@ -251,7 +255,7 @@ export default class GameManager { this.game.moves.push(gameMove); this.game.gameOver = true; this.game.winner = this.game.turn; - return this.game; + return { game: this.game, word }; } for (const cell of Object.values(this.game.grid)) { @@ -292,7 +296,7 @@ export default class GameManager { ? this.game.turn : (Number(!this.game.turn) as 0 | 1); this.game.turn = nextTurn; - return this.game; + return { game: this.game, word }; } createGame(userId: string): Game { diff --git a/server/src/datalayer/memory.ts b/server/src/datalayer/memory.ts index b3bf18fd..f9ef9b3b 100644 --- a/server/src/datalayer/memory.ts +++ b/server/src/datalayer/memory.ts @@ -11,10 +11,23 @@ export default class Memory implements DataLayer { authTokens: { [key: string]: AuthToken; } = {}; + moves: { + [key: string]: { + count: number; + valid: boolean; + }; + } = {}; constructor() { this.games = {}; this.users = {}; } + async saveMove(word: string, valid: boolean): Promise { + this.moves[word] = { + valid, + count: (this.moves[word]?.count ?? 0) + 1, + }; + return true; + } async createUser( id: string, diff --git a/server/src/datalayer/mongo/index.ts b/server/src/datalayer/mongo/index.ts index 4ad9d1af..44be1fbf 100644 --- a/server/src/datalayer/mongo/index.ts +++ b/server/src/datalayer/mongo/index.ts @@ -46,6 +46,38 @@ export default class Mongo implements DataLayer { } } + async saveMove( + word: string, + valid: boolean, + options?: Options + ): Promise { + if (!this.connected) { + throw new Error("Db not connected"); + } + try { + const res = await Models.Move.findOneAndUpdate( + { + word, + }, + { + word, + valid, + $inc: { + count: 1, + }, + }, + { + upsert: true, + session: options?.session, + } + ); + return true; + } catch (e) { + console.log(e); + throw e; + } + } + async createUser(id: string, options?: Options): Promise { if (!this.connected) { throw new Error("Db not connected"); diff --git a/server/src/datalayer/mongo/models.ts b/server/src/datalayer/mongo/models.ts index acc4143c..8e986ebc 100644 --- a/server/src/datalayer/mongo/models.ts +++ b/server/src/datalayer/mongo/models.ts @@ -160,9 +160,31 @@ const migrationSchema = new Schema<{ version: number }>( const MigrationModel = model<{ version: number }>("Migration", migrationSchema); +const moveSchema = new Schema<{ word: string; count: number; valid: boolean }>({ + word: { + type: String, + required: true, + }, + count: { + type: Number, + required: true, + default: 0, + }, + valid: { + type: Boolean, + required: true, + }, +}); + +const MoveModel = model<{ word: string; count: number; valid: boolean }>( + "Move", + moveSchema +); + export default { Game: GameModel, User: UserModel, AuthToken: AuthTokenModel, Migration: MigrationModel, + Move: MoveModel, }; diff --git a/server/src/routes/game.ts b/server/src/routes/game.ts index abb1fd67..b333fd6d 100644 --- a/server/src/routes/game.ts +++ b/server/src/routes/game.ts @@ -258,7 +258,13 @@ export default (io: Server) => { return; } console.log("Bot move", botMove); - game = gm.makeMove("AI", botMove); + const response = gm.makeMove("AI", botMove); + const valid = Boolean(response.game); + await dl.saveMove(response.word, valid, { session }); + if (!response.game) { + return; + } + game = response.game; await dl.saveGame(gameId, game, { session, }); @@ -336,7 +342,19 @@ export default (io: Server) => { let newGame: Game; try { - newGame = gm.makeMove(user, parsedMove); + const { game, word } = gm.makeMove(user, parsedMove); + const valid = Boolean(game); + await dl.saveMove(word, valid, { session }); + if (!valid) { + res.status(400); + res.send("Invalid word"); + return; + } + if (!game) { + res.sendStatus(500); + return; + } + newGame = game; } catch (e: unknown) { res.status(400); if (e instanceof Error) { diff --git a/server/src/types.ts b/server/src/types.ts index 8e3bcd30..f6579676 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -15,6 +15,11 @@ export interface AuthToken { } export interface DataLayer { + saveMove( + word: string, + valid: boolean, + options?: Record + ): Promise; createUser(id: string, options?: Record): Promise; deleteUser(id: string, options?: Record): Promise; createAuthToken(