diff --git a/events b/events index 22656ba..fc3f0e1 160000 --- a/events +++ b/events @@ -1 +1 @@ -Subproject commit 22656ba60812db5471b61acc0e657d2f81ae0645 +Subproject commit fc3f0e1677d2e503dc0b198659e820e092cc654d diff --git a/src/games/controllers/games.controller.ts b/src/games/controllers/games.controller.ts index 0cfb893..74049c7 100644 --- a/src/games/controllers/games.controller.ts +++ b/src/games/controllers/games.controller.ts @@ -139,6 +139,12 @@ export class GamesController { if (!player) { throw new HttpException('You are not in this game', HttpStatus.FORBIDDEN); } + if (player.hasGuessed) { + throw new HttpException( + 'You cannot send messages if you have already guessed the word', + HttpStatus.FORBIDDEN, + ); + } await this.gameService.postMessage(game, player, value); } diff --git a/src/games/entities/game.entity.ts b/src/games/entities/game.entity.ts index 1477fdc..cd8f49c 100644 --- a/src/games/entities/game.entity.ts +++ b/src/games/entities/game.entity.ts @@ -49,18 +49,17 @@ export class Game { turn: number = 0; @OneToMany(() => ConceptInTurn, (cit) => cit.game, { - onDelete: 'CASCADE', eager: true, }) currentConcepts: ConceptInTurn[]; @OneToMany(() => Message, (msg) => msg.game, { - onDelete: 'CASCADE', eager: true, }) _messages: Message[]; get messages() { - return this._messages.sort((a, b) => a.id - b.id); + if (!this._messages) return []; + return [...this._messages].sort((a, b) => a.id - b.id); } } diff --git a/src/games/entities/message.entity.ts b/src/games/entities/message.entity.ts index 99ba85b..d6f4be5 100644 --- a/src/games/entities/message.entity.ts +++ b/src/games/entities/message.entity.ts @@ -20,13 +20,13 @@ export class Message { value: string; @JoinColumn({ name: 'authorId' }) - @ManyToOne(() => Player, { eager: true }) - author: Player; + @ManyToOne(() => Player, { onDelete: 'CASCADE', eager: true, nullable: true }) + author?: Player; get authorId() { - return this.author.userId; + return this.author?.userId; } - @ManyToOne(() => Game) + @ManyToOne(() => Game, { onDelete: 'CASCADE' }) game: Promise; } diff --git a/src/games/entities/player.entity.ts b/src/games/entities/player.entity.ts index daa6180..eecee01 100644 --- a/src/games/entities/player.entity.ts +++ b/src/games/entities/player.entity.ts @@ -29,4 +29,7 @@ export class Player { @Column({ default: 0 }) score: number = 0; + + @Column({ default: false }) + hasGuessed: boolean = false; } diff --git a/src/games/services/games.service.ts b/src/games/services/games.service.ts index 4647666..f87e69f 100644 --- a/src/games/services/games.service.ts +++ b/src/games/services/games.service.ts @@ -14,12 +14,12 @@ import { User } from 'src/users/entities/user.entity'; import { WordService } from 'src/word/services/word.service'; import { + GameEndTurnEvent, GameMessageDeleteEvent, GameMessageEvent, GameStartEvent, } from 'events/game.events'; import random from 'random'; -import { platform } from 'os'; import { Message } from '../entities/message.entity'; @Injectable() @@ -27,6 +27,7 @@ export class GameService { constructor( @InjectRepository(Game) private gameRepository: Repository, @InjectRepository(Message) private messageResporitory: Repository, + @InjectRepository(Player) private playerRepository: Repository, private readonly wordService: WordService, private eventEmitter: EventEmitter2, ) {} @@ -35,8 +36,8 @@ export class GameService { const rng = random.clone(game.seed); game.started = true; game.currentPlayer = rng.choice(game.players); - const words = await this.wordService.listActive(); - game.currentWord = rng.choice(words); + const words = await this.getShuffledWordList(game); + game.currentWord = words[0]; await this.gameRepository.save(game); this.eventEmitter.emit( @@ -47,30 +48,87 @@ export class GameService { async postMessage(game: Game, author: Player, value: string) { const message = this.messageResporitory.create(); - message.author = author; message.game = Promise.resolve(game); - message.value = value; + + if ( + value.toLowerCase() == game.currentWord.value.toLowerCase() && + author.id != game.currentPlayer.id + ) { + message.value = author.userId; + author.hasGuessed = true; + author.score++; + game.currentPlayer.score++; + await this.playerRepository.save([author, game.currentPlayer]); + } else { + message.author = author; + message.value = value; + } + await this.messageResporitory.save(message); this.eventEmitter.emit( 'sse.game.' + game.id, new GameMessageEvent(message), ); - const oldMessage = game.messages[0]; - const oldMessageId = oldMessage.id; + if (game.messages.length > 3) { - await this.messageResporitory.remove(oldMessage); + await this.deleteMessage(game, game.messages[0]); } + + if ( + game.players + .filter((p) => p.id != game.currentPlayer.id) + .every((p) => p.hasGuessed) + ) { + await this.endTurn(game); + } + } + + private async deleteMessage(game: Game, message: Message) { + const oldMessageId = message.id; + await this.messageResporitory.remove(message); this.eventEmitter.emit( 'sse.game.' + game.id, new GameMessageDeleteEvent(oldMessageId), ); } + async endTurn(game: Game) { + const smartPlayers = game.players.filter((p) => p.hasGuessed); + smartPlayers.forEach((p) => (p.hasGuessed = false)); + + const wordList = await this.getShuffledWordList(game); + game.turn++; + game.currentWord = wordList[game.turn % wordList.length]; + const playerIndex = game.players.findIndex( + (p) => p.userId == game.currentPlayer.userId, + ); + game.currentPlayer = game.players[(playerIndex + 1) % game.players.length]; + + // await this.currentConceptReporitory.remove(game.currentConcept) + await this.gameRepository.save(game); + await this.playerRepository.save(smartPlayers); + + await this.messageResporitory.delete({ game }); + + this.eventEmitter.emit( + 'sse.game.' + game.id, + new GameEndTurnEvent(game.currentPlayer), + ); + } + + /** + * Shuffles the word list using a per-game seeded rng + */ + async getShuffledWordList(game: Game) { + const rng = random.clone(game.seed); + const words = await this.wordService.listActive(); + return words + .map((value) => ({ value, sort: rng.float() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); + } + async end(game: Game, winner: Player) { throw Error('Not Implemented'); } - - async guessWord(game: Game, user: User, word: string) { - throw Error('Not Implemented'); - } }