This commit is contained in:
Kirill Ivlev 2024-11-13 02:08:39 +04:00
parent 456cdcb4aa
commit ac9116e138
14 changed files with 249 additions and 36 deletions

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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 {}

View file

@ -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 };
}
}

View file

@ -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>(VersusController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View file

@ -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();
}
}

View file

@ -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>(VersusService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View file

@ -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<VersusDocument>,
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;
}
}

6
src/game/versus/versus.types.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
export interface VersusDto {
id: string;
text: string;
completed: boolean;
description: string;
}

View file

@ -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 };
}
}

View file

@ -0,0 +1,3 @@
export class VersusServiceMock {
}

View file

@ -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);

View file

@ -14,4 +14,5 @@ export enum SocketEvents {
NOTIFICATION = 'notification',
FEATURE_FLAG_CHANGED = 'feature_flag_changed',
BEGIN_VERSUS = 'begin_versus',
END_VERSUS = 'end_versus',
}

View file

@ -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);