From ac9116e1383d9e3c67ea8e6a28878635ef51fb3c Mon Sep 17 00:00:00 2001 From: Kirill Ivlev Date: Wed, 13 Nov 2024 02:08:39 +0400 Subject: [PATCH] Versus --- src/game/game.controller.spec.ts | 3 + src/game/game.controller.ts | 15 ++-- src/game/game.module.ts | 8 +- src/game/game.service.ts | 32 ++------ src/game/versus/versus.controller.spec.ts | 23 ++++++ src/game/versus/versus.controller.ts | 37 +++++++++ src/game/versus/versus.service.spec.ts | 31 +++++++ src/game/versus/versus.service.ts | 98 +++++++++++++++++++++++ src/game/versus/versus.types.d.ts | 6 ++ src/guests/guests.controller.ts | 7 +- src/mocks/versus-service.mock.ts | 3 + src/schemas/versus.schema.ts | 15 ++++ src/shared/events.consts.ts | 1 + src/shared/shared.service.ts | 6 ++ 14 files changed, 249 insertions(+), 36 deletions(-) create mode 100644 src/game/versus/versus.controller.spec.ts create mode 100644 src/game/versus/versus.controller.ts create mode 100644 src/game/versus/versus.service.spec.ts create mode 100644 src/game/versus/versus.service.ts create mode 100644 src/game/versus/versus.types.d.ts create mode 100644 src/mocks/versus-service.mock.ts create mode 100644 src/schemas/versus.schema.ts diff --git a/src/game/game.controller.spec.ts b/src/game/game.controller.spec.ts index e9b857e..cc45b54 100644 --- a/src/game/game.controller.spec.ts +++ b/src/game/game.controller.spec.ts @@ -2,6 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { GameController } from './game.controller'; import {GameService} from "./game.service"; import {GameServiceMock} from "../mocks/game-service.mock"; +import {VersusService} from "./versus/versus.service"; +import {VersusServiceMock} from "../mocks/versus-service.mock"; describe('GameController', () => { let controller: GameController; @@ -11,6 +13,7 @@ describe('GameController', () => { controllers: [GameController], providers: [ { provide: GameService, useValue: GameServiceMock }, + { provide: VersusService, useValue: VersusServiceMock }, ] }).compile(); diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index a451301..6c28a04 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -1,10 +1,11 @@ import { Controller, Get, Logger, Param, Post } from '@nestjs/common'; import { GameService } from './game.service'; +import {VersusService} from "./versus/versus.service"; @Controller('game') export class GameController { private readonly logger = new Logger(GameController.name); - constructor(private gameService: GameService) { + constructor(private gameService: GameService, private versusService: VersusService) { } @Post(':id/complete') @@ -32,14 +33,16 @@ export class GameController { return this.gameService.playExtraCards(); } - @Post('simulate-versus') - async SimulateVersus() { - this.logger.verbose('[SimulateVersus] enter'); - return this.gameService.simulateVersus(); - } + @Get('state-details') async getStateDetails() { return this.gameService.getStateDetails(); } + + @Post('clear-queue') + async clearQueue() { + this.logger.warn(`[clearQueue] enter`); + await this.gameService.clearGameQueue(); + } } diff --git a/src/game/game.module.ts b/src/game/game.module.ts index c49ae13..bbb4d25 100644 --- a/src/game/game.module.ts +++ b/src/game/game.module.ts @@ -17,6 +17,9 @@ import {ConfigService} from "@nestjs/config"; import {SelectTargetPlayerHandler} from "./comand-handlers/select-target-player.handler"; import {SendBetweenRoundsActionsHandler} from "../guests/command/send-between-rounds-actions.command"; import {GuestsModule} from "../guests/guests.module"; +import { VersusService } from './versus/versus.service'; +import { VersusController } from './versus/versus.controller'; +import {Versus, VersusSchema} from "../schemas/versus.schema"; const eventHandlers = [ @@ -42,11 +45,12 @@ const commandHandlers = [ CqrsModule, MongooseModule.forFeature([ { name: GameQueue.name, schema: GameQueueSchema }, + { name: Versus.name, schema: VersusSchema } ]), forwardRef(() => GuestsModule) ], - providers: [GameService, ConfigService, ...eventHandlers, ...commandHandlers], + providers: [GameService, ConfigService, ...eventHandlers, ...commandHandlers, VersusService], exports: [GameService], - controllers: [GameController], + controllers: [GameController, VersusController], }) export class GameModule {} diff --git a/src/game/game.service.ts b/src/game/game.service.ts index 759382b..5c85159 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -116,34 +116,12 @@ export class GameService implements OnApplicationBootstrap{ }) } - async simulateVersus() { - const guests = (await this.guestService.findAll()).slice(0,2).map((guest) => { - return { - id: guest.telegramId, - name: guest.name, - } - }); - if(guests.length < 2) { - throw new Error("Can't simulate, in db less than 2 players") - } - await this.beginVersus(guests[0].id, guests[1].id); - } - - async beginVersus(player1: number, player2: number) { - await this.sharedService.setConfig('current_action', JSON.stringify({ - action:'versus', - data: { - player1: player1, - player2: player2, - } - })); - this.sharedService.sendSocketNotificationToAllClients( - SocketEvents.BEGIN_VERSUS, - { player1, player2 } - ) - } - async getStateDetails() { return await this.sharedService.getConfig('current_action') || null; } + + async clearGameQueue() { + await this.gameQueueModel.deleteMany({}).exec(); + return { result: true }; + } } diff --git a/src/game/versus/versus.controller.spec.ts b/src/game/versus/versus.controller.spec.ts new file mode 100644 index 0000000..5fbe666 --- /dev/null +++ b/src/game/versus/versus.controller.spec.ts @@ -0,0 +1,23 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VersusController } from './versus.controller'; +import {VersusService} from "./versus.service"; +import {VersusServiceMock} from "../../mocks/versus-service.mock"; + +describe('VersusController', () => { + let controller: VersusController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VersusController], + providers: [ + { provide: VersusService, useValue: VersusServiceMock }, + ] + }).compile(); + + controller = module.get(VersusController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/game/versus/versus.controller.ts b/src/game/versus/versus.controller.ts new file mode 100644 index 0000000..7d6cb7c --- /dev/null +++ b/src/game/versus/versus.controller.ts @@ -0,0 +1,37 @@ +import {Body, Controller, Get, Logger, Post} from '@nestjs/common'; +import {VersusService} from "./versus.service"; +import {VersusDto} from "./versus.types"; + +@Controller('versus') +export class VersusController { + private logger = new Logger(VersusController.name); + constructor(private versusService: VersusService) { + + } + + @Post('simulate-versus') + async SimulateVersus() { + this.logger.verbose('[SimulateVersus] enter'); + return this.versusService.simulateVersus(); + } + + @Post('import') + async Import(@Body() data: VersusDto[]) { + return await this.versusService.importVersus(data); + } + + @Get() + async GetVersusTask() { + return await this.versusService.getVersusTask(); + } + + @Post('complete') + async Completed(@Body() payload: { winner: number }) { + return await this.versusService.complete(payload.winner); + } + + @Post('reset-all') + async markAllUncompleted() { + return await this.versusService.markAllAsUncompleted(); + } +} diff --git a/src/game/versus/versus.service.spec.ts b/src/game/versus/versus.service.spec.ts new file mode 100644 index 0000000..f5f06a8 --- /dev/null +++ b/src/game/versus/versus.service.spec.ts @@ -0,0 +1,31 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VersusService } from './versus.service'; +import {GuestsService} from "../../guests/guests.service"; +import {GuestsServiceMock} from "../../mocks/guests-service.mock"; +import {SharedService} from "../../shared/shared.service"; +import {getModelToken} from "@nestjs/mongoose"; +import {Versus} from "../../schemas/versus.schema"; +import {Model} from "mongoose"; +import {CommandBus} from "@nestjs/cqrs"; + +describe('VersusService', () => { + let service: VersusService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + VersusService, + { provide: GuestsService, useValue: GuestsServiceMock }, + { provide: SharedService, useValue: SharedService }, + { provide: getModelToken(Versus.name), useValue: Model }, + { provide: CommandBus, useValue: CommandBus }, + ], + }).compile(); + + service = module.get(VersusService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/game/versus/versus.service.ts b/src/game/versus/versus.service.ts new file mode 100644 index 0000000..a607acc --- /dev/null +++ b/src/game/versus/versus.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common'; +import {SocketEvents} from "../../shared/events.consts"; +import {GuestsService} from "../../guests/guests.service"; +import {SharedService} from "../../shared/shared.service"; +import {InjectModel} from "@nestjs/mongoose"; +import {Versus, VersusDocument} from "../../schemas/versus.schema"; +import {Model} from "mongoose"; +import {VersusDto} from "./versus.types"; +import {CommandBus} from "@nestjs/cqrs"; +import {IncreasePlayerScoreCommand} from "../../guests/command/increase-player-score.command"; +import {IncreasePlayerWinningRateCommand} from "../commands/increase-player-winning-rate.command"; + +@Injectable() +export class VersusService { + static configKeyCurrentAction = 'current_action'; + static configKeyActiveVersus = 'active_versus'; + constructor( + private guestService: GuestsService, + private sharedService: SharedService, + @InjectModel(Versus.name) private versusModel: Model, + private cmdBus: CommandBus, + ) { + } + async simulateVersus() { + const guests = (await this.guestService.findAll()).slice(0,2).map((guest) => { + return { + id: guest.telegramId, + name: guest.name, + } + }); + if(guests.length < 2) { + throw new Error("Can't simulate, in db less than 2 players") + } + await this.beginVersus(guests[0].id, guests[1].id); + } + + async beginVersus(player1: number, player2: number) { + const [p1data,p2data] = await Promise.all([this.guestService.findById(player1), this.guestService.findById(player2)]); + await this.sharedService.setConfig(VersusService.configKeyCurrentAction, JSON.stringify({ + action:'versus', + data: { + player1: player1, + player2: player2, + player1name: p1data.name, + player2name: p2data.name, + } + })); + this.sharedService.sendSocketNotificationToAllClients( + SocketEvents.BEGIN_VERSUS, + { player1, player2, player1name: p1data.name, player2name: p2data.name } + ) + } + + async importVersus(data: VersusDto[]) { + data.map(async (record) => { + const item = new this.versusModel({ + ...record + }); + await item.save(); + }); + return { result: true }; + } + + async getVersusTask() { + const rand = await this.versusModel + .aggregate([{ $match: { completed: false } }, { $sample: { size: 1 } }]) + .exec(); + // @TODO check who win with telegram + + const item = await this.versusModel.findOne({ _id: rand[0]._id }).exec(); + await this.sharedService.setConfig(VersusService.configKeyActiveVersus, item.id); + return item; + } + async markAllAsUncompleted() { + const versuses = await this.versusModel.find().exec(); + versuses.map(async (versus) => { + versus.completed = false; + await versus.save(); + }); + return { result: true }; + } + + async complete(winner: number) { + const activeVersus = await this.sharedService.getConfig(VersusService.configKeyActiveVersus); + const item = await this.versusModel.findOne({ _id: activeVersus.value }).exec(); + item.completed = true; + await item.save(); + await this.cmdBus.execute(new IncreasePlayerScoreCommand(winner, 2)); + await this.cmdBus.execute(new IncreasePlayerWinningRateCommand(winner, 30)); + await this.sharedService.setConfig(VersusService.configKeyCurrentAction, ''); + this.sharedService.sendSocketNotificationToAllClients( + SocketEvents.END_VERSUS, + { winner: winner } + ) + + return item; + } +} diff --git a/src/game/versus/versus.types.d.ts b/src/game/versus/versus.types.d.ts new file mode 100644 index 0000000..e81bec8 --- /dev/null +++ b/src/game/versus/versus.types.d.ts @@ -0,0 +1,6 @@ +export interface VersusDto { + id: string; + text: string; + completed: boolean; + description: string; +} \ No newline at end of file diff --git a/src/guests/guests.controller.ts b/src/guests/guests.controller.ts index 0fc9cea..31dd713 100644 --- a/src/guests/guests.controller.ts +++ b/src/guests/guests.controller.ts @@ -1,6 +1,6 @@ import { Controller, - Get, Param, Res + Get, Param, Post, Res } from "@nestjs/common"; import { GuestsService } from './guests.service'; import { Response } from 'express'; @@ -42,4 +42,9 @@ export class GuestsController { // this.echoService.enterQuiz(u.chatId); }); } + @Post('reset-score') + async resetAllPlayersScore() { + await this.guestService.resetPlayersScore(); + return { result: true }; + } } diff --git a/src/mocks/versus-service.mock.ts b/src/mocks/versus-service.mock.ts new file mode 100644 index 0000000..b3555ad --- /dev/null +++ b/src/mocks/versus-service.mock.ts @@ -0,0 +1,3 @@ +export class VersusServiceMock { + +} \ No newline at end of file diff --git a/src/schemas/versus.schema.ts b/src/schemas/versus.schema.ts new file mode 100644 index 0000000..49a8295 --- /dev/null +++ b/src/schemas/versus.schema.ts @@ -0,0 +1,15 @@ +import {Prop, Schema, SchemaFactory} from "@nestjs/mongoose"; +import {Document} from "mongoose"; + +@Schema() +export class Versus { + @Prop() + text: string; + @Prop({ default: false}) + completed: boolean; + @Prop() + description: string; +} + +export type VersusDocument = Versus & Document; +export const VersusSchema = SchemaFactory.createForClass(Versus); \ No newline at end of file diff --git a/src/shared/events.consts.ts b/src/shared/events.consts.ts index 1823fe5..37781fe 100644 --- a/src/shared/events.consts.ts +++ b/src/shared/events.consts.ts @@ -14,4 +14,5 @@ export enum SocketEvents { NOTIFICATION = 'notification', FEATURE_FLAG_CHANGED = 'feature_flag_changed', BEGIN_VERSUS = 'begin_versus', + END_VERSUS = 'end_versus', } diff --git a/src/shared/shared.service.ts b/src/shared/shared.service.ts index aec82bf..99fd64d 100644 --- a/src/shared/shared.service.ts +++ b/src/shared/shared.service.ts @@ -54,6 +54,12 @@ export class SharedService { } } + /** + * Notifies all connected socket about changes + * @deprecated Use specific handlers in this class + * @param event + * @param payload + */ sendSocketNotificationToAllClients(event: string, payload?: any) { this.logger.verbose(`Sending notification to client: ${event}, ${JSON.stringify(payload)}`); this.socketGateway.notifyAllClients(event, payload);