Feat : start games with sse

This commit is contained in:
2024-06-11 18:57:01 -04:00
parent 0d4c0e008c
commit 1d272b7761
16 changed files with 485 additions and 223 deletions
+20
View File
@@ -12,6 +12,7 @@
"@nestjs/common": "^10.3.2",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.3.2",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/mapped-types": "^2.0.5",
"@nestjs/mongoose": "^10.0.6",
"@nestjs/platform-express": "^10.3.2",
@@ -1828,6 +1829,19 @@
}
}
},
"node_modules/@nestjs/event-emitter": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz",
"integrity": "sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==",
"license": "MIT",
"dependencies": {
"eventemitter2": "6.4.9"
},
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0"
}
},
"node_modules/@nestjs/mapped-types": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz",
@@ -5018,6 +5032,12 @@
"node": ">= 0.6"
}
},
"node_modules/eventemitter2": {
"version": "6.4.9",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
"license": "MIT"
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+1
View File
@@ -22,6 +22,7 @@
"@nestjs/common": "^10.3.2",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.3.2",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/mapped-types": "^2.0.5",
"@nestjs/mongoose": "^10.0.6",
"@nestjs/platform-express": "^10.3.2",
+2
View File
@@ -7,9 +7,11 @@ import { GamesModule } from './games/games.module';
import { UsersModule } from './users/users.module';
import { CardsService } from './cards/cards.service';
import { CardsModule } from './cards/cards.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
@Module({
imports: [
EventEmitterModule.forRoot(),
ConfigModule.forRoot(),
MongooseModule.forRoot(process.env.MONGO_ENDPOINT, {
auth: {
+63 -121
View File
@@ -1,141 +1,83 @@
import {
Body,
Controller,
Get,
Post,
Param,
Delete,
UseGuards,
Session,
HttpException,
HttpStatus,
Patch,
Body,
Param,
Post,
Session,
Sse,
UseGuards,
} from '@nestjs/common';
import { GamesService } from '../services/games.lobby.service';
import { LobbyService } from '../services/games.lobby.service';
import { AuthGuard } from '../guards/auth.guard';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ReadGameDto } from '../dto/read-games.dto';
import { GameGuard } from '../guards/game.guard';
import { UpdateGameDto } from '../dto/update-game.dto';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { StartingDeckDto } from '../dto/update-game.dto';
import { Game, Player } from '../schemas/game.schema';
import { Document } from 'mongoose';
import { GamePipe } from '../pipe/game.pipe';
@ApiTags('lobby')
@Controller('games')
import { ReadUserDto } from '../dto/read-games.dto';
import { Observable, fromEvent, map } from 'rxjs';
import { EventEmitter2 } from '@nestjs/event-emitter';
function getPlayer(session: Record<string, any>, game: Game) {
const user = session.user as ReadUserDto;
if (!user) {
throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED);
}
for (const team of game.teams) {
for (const player of team.players) {
if (player.user._id.toHexString() == user._id) {
return player as Document<Player> & Player;
}
}
}
throw new HttpException(
'You are not part of this game',
HttpStatus.FORBIDDEN,
);
}
@ApiTags('game')
@Controller('games/:id')
export class GamesController {
constructor(private readonly gamesService: GamesService) {}
constructor(
private readonly lobbyService: LobbyService,
private eventEmitter: EventEmitter2,
) {}
@Post()
@ApiOperation({ summary: 'Create game' })
@ApiResponse({
status: 201,
description: 'Game successfully created.',
type: ReadGameDto,
})
@ApiResponse({
status: 400,
description: 'You can only create one game at a time',
})
@ApiResponse({ status: 403, description: 'Forbidden.' })
@Post('changeStartingDeck')
@ApiOperation({ summary: 'Pick a different starting deck' })
@UseGuards(AuthGuard)
async create(@Session() session: Record<string, any>) {
const games = await this.gamesService.findByOwner(session.user._id);
if (games) {
throw new HttpException(
'You can only create one game at a time',
HttpStatus.BAD_REQUEST,
);
}
return this.gamesService.create(session.user);
}
@Get()
@ApiOperation({ summary: 'List all games' })
findAll() {
const games = this.gamesService.findAll();
return games;
}
@Get(':id')
@ApiOperation({ summary: 'Get game information' })
@UseGuards(GameGuard)
async findOne(@Param('id') id: string) {
const game = await this.gamesService.findById(id);
return game;
}
@Patch(':id')
@ApiOperation({ summary: 'Edit game settings' })
@UseGuards(AuthGuard, GameGuard)
async update(
@Session() session: Record<string, any>,
async changeStartingDeck(
@Param('id') id: string,
@Body() updateGameDto: UpdateGameDto,
@Param('id', GamePipe) game: Document<Game> & Game,
@Body() startingDeckDto: StartingDeckDto,
@Session() session: Record<string, string>,
) {
const game = await this.gamesService.findById(id);
if (game.owner._id.toString() != session.user._id) {
throw new HttpException(
'You can only edit games you own',
HttpStatus.FORBIDDEN,
);
}
return this.gamesService.update(id, updateGameDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete game' })
@UseGuards(AuthGuard, GameGuard)
async remove(
@Session() session: Record<string, any>,
@Param('id') id: string,
) {
const game = await this.gamesService.findById(id);
if (game.owner._id.toString() != session.user._id) {
throw new HttpException(
'You can only delete games you own',
HttpStatus.FORBIDDEN,
);
}
const deleted = await this.gamesService.remove(id);
if (deleted) {
return;
}
}
@Post(':id/start')
@ApiOperation({ summary: 'Start game' })
@ApiResponse({
status: 201,
description: 'Game successfully started.',
})
@ApiResponse({
status: 400,
description: 'Bad Request',
})
@ApiResponse({
status: 403,
description: 'You can only start games you own',
})
@UseGuards(AuthGuard, GameGuard)
async start(
@Session() session: Record<string, any>,
@Param('id') id: string,
) {
const game = await this.gamesService.findById(id);
if (game.started) {
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
}
if (game.teams.length < 2 && !process.env.DEV_MODE) {
throw new HttpException(
'Not enough teams to start the game',
'You cannot do that after the game has started',
HttpStatus.BAD_REQUEST,
);
}
if (game.owner._id.toString() != session.user._id) {
throw new HttpException(
'You can only start games you own',
HttpStatus.FORBIDDEN,
const player = getPlayer(session, game);
await this.lobbyService.changeStartingDeck(startingDeckDto, player, game);
}
@Sse('/subscribe')
subscribe(
@Param('id') id: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@Param('id', GamePipe) _game: Document<Game> & Game,
): Observable<{ data: string }> {
return fromEvent(this.eventEmitter, 'sse.game.' + id).pipe(
map((payload) => {
return {
data: JSON.stringify(payload),
};
}),
);
}
this.gamesService.start(id);
return;
}
}
@@ -1,17 +1,17 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GamesController } from './games.controller';
import { GamesService } from '../services/games.lobby.service';
import { LobbyController } from './games.lobby.controller';
import { LobbyService } from '../services/games.lobby.service';
describe('GamesController', () => {
let controller: GamesController;
let controller: LobbyController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [GamesController],
providers: [GamesService],
controllers: [LobbyController],
providers: [LobbyService],
}).compile();
controller = module.get<GamesController>(GamesController);
controller = module.get<LobbyController>(LobbyController);
});
it('should be defined', () => {
@@ -0,0 +1,159 @@
import {
Controller,
Get,
Post,
Param,
Delete,
UseGuards,
Session,
HttpException,
HttpStatus,
Patch,
Body,
Sse,
} from '@nestjs/common';
import { LobbyService } from '../services/games.lobby.service';
import { AuthGuard } from '../guards/auth.guard';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ReadGameDto } from '../dto/read-games.dto';
import { UpdateGameDto } from '../dto/update-game.dto';
import { GamePipe } from '../pipe/game.pipe';
import { Game } from '../schemas/game.schema';
import { Document } from 'mongoose';
import { Observable, fromEvent, map } from 'rxjs';
import { EventEmitter2 } from '@nestjs/event-emitter';
@ApiTags('lobby')
@Controller('games')
export class LobbyController {
constructor(
private readonly gamesService: LobbyService,
private eventEmitter: EventEmitter2,
) {}
@Post()
@ApiOperation({ summary: 'Create game' })
@ApiResponse({
status: 201,
description: 'Game successfully created.',
type: ReadGameDto,
})
@ApiResponse({
status: 400,
description: 'You can only create one game at a time',
})
@ApiResponse({ status: 403, description: 'Forbidden.' })
@UseGuards(AuthGuard)
async create(@Session() session: Record<string, any>) {
const games = await this.gamesService.findByOwner(session.user._id);
if (games) {
throw new HttpException(
'You can only create one game at a time',
HttpStatus.BAD_REQUEST,
);
}
return this.gamesService.create(session.user);
}
@Get()
@ApiOperation({ summary: 'List all games' })
findAll() {
const games = this.gamesService.findAll();
return games;
}
@Sse('/subscribe')
subscribe(): Observable<{ data: string }> {
console.log('subscris');
return fromEvent(this.eventEmitter, 'sse.lobby').pipe(
map((payload) => {
return {
data: JSON.stringify(payload),
};
}),
);
}
@Get(':id')
@ApiOperation({ summary: 'Get game information' })
async findOne(@Param('id') id: string, @Param('id', GamePipe) game: Game) {
return new ReadGameDto(game);
}
@Patch(':id')
@ApiOperation({ summary: 'Edit game settings' })
@UseGuards(AuthGuard)
async update(
@Param('id') id: string,
@Session() session: Record<string, any>,
@Body() updateGameDto: UpdateGameDto,
@Param('id', GamePipe) game: Document<Game> & Game,
) {
if (game.owner._id.toString() != session.user._id) {
throw new HttpException(
'You can only edit games you own',
HttpStatus.FORBIDDEN,
);
}
return this.gamesService.update(game, updateGameDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete game' })
@UseGuards(AuthGuard)
async remove(
@Param('id') id: string,
@Session() session: Record<string, any>,
@Param('id', GamePipe) game: Document<Game> & Game,
) {
if (game.owner._id.toString() != session.user._id) {
throw new HttpException(
'You can only delete games you own',
HttpStatus.FORBIDDEN,
);
}
const deleted = await this.gamesService.remove(game);
if (deleted) {
return;
}
}
@Post(':id/start')
@ApiOperation({ summary: 'Start game' })
@ApiResponse({
status: 201,
description: 'Game successfully started.',
})
@ApiResponse({
status: 400,
description: 'Bad Request',
})
@ApiResponse({
status: 403,
description: 'You can only start games you own',
})
@UseGuards(AuthGuard)
async start(
@Param('id') id: string,
@Session() session: Record<string, any>,
@Param('id', GamePipe) game: Document<Game> & Game,
) {
if (game.started) {
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
}
if (game.teams.length < 2 && !process.env.DEV_MODE) {
throw new HttpException(
'Not enough teams to start the game',
HttpStatus.BAD_REQUEST,
);
}
if (game.owner._id.toString() != session.user._id) {
throw new HttpException(
'You can only start games you own',
HttpStatus.FORBIDDEN,
);
}
this.gamesService.start(game);
return;
}
}
+5
View File
@@ -13,6 +13,8 @@ export class ReadPlayerDto extends DatabaseObjectDto {
this.discard = new ReadContainerDto(data.discard);
this.hand = new ReadContainerDto(data.hand, true);
this.stack = new ReadContainerDto(data.stack, true);
this.race = data.race;
this.class = data.class;
}
user: ReadUserDto;
@@ -23,6 +25,9 @@ export class ReadPlayerDto extends DatabaseObjectDto {
stack: ReadContainerDto;
discard: ReadContainerDto;
class: string;
race: string;
}
export class ReadTeamDto extends DatabaseObjectDto {
+5
View File
@@ -1,3 +1,8 @@
export class UpdateGameDto {
packs: string[];
}
export class StartingDeckDto {
class?: string;
race?: string;
}
+6 -4
View File
@@ -1,10 +1,12 @@
import { Module } from '@nestjs/common';
import { GamesService } from './services/games.lobby.service';
import { GamesController } from './controllers/games.controller';
import { LobbyService } from './services/games.lobby.service';
import { LobbyController } from './controllers/games.lobby.controller';
import { Game, GameSchema } from './schemas/game.schema';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersModule } from 'src/users/users.module';
import { CardsModule } from 'src/cards/cards.module';
import { GamesController } from './controllers/games.controller';
import { GameService } from './services/games.service';
@Module({
imports: [
@@ -12,7 +14,7 @@ import { CardsModule } from 'src/cards/cards.module';
UsersModule,
MongooseModule.forFeature([{ name: Game.name, schema: GameSchema }]),
],
controllers: [GamesController],
providers: [GamesService],
controllers: [LobbyController, GamesController],
providers: [LobbyService, GameService],
})
export class GamesModule {}
-32
View File
@@ -1,32 +0,0 @@
import {
Injectable,
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request } from 'express';
import { Observable } from 'rxjs';
import { GamesService } from '../services/games.lobby.service';
@Injectable()
export class GameGuard implements CanActivate {
constructor(private readonly gamesService: GamesService) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const http = context.switchToHttp();
const request = http.getRequest<Request>();
return (async () => {
const game = await this.gamesService.findById(request.params.id);
if (!game) {
throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
}
if (!(request.session as Record<string, any>).user) {
throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED);
}
return true;
})();
}
}
+22
View File
@@ -0,0 +1,22 @@
import {
PipeTransform,
Injectable,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Game } from '../schemas/game.schema';
import { Model } from 'mongoose';
@Injectable()
export class GamePipe implements PipeTransform {
constructor(@InjectModel(Game.name) private gameModel: Model<Game>) {}
async transform(value: any) {
const game = await this.gameModel.findById(value);
if (!game) {
throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
}
return game;
}
}
+9 -6
View File
@@ -1,6 +1,7 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { HydratedDocument, Types } from 'mongoose';
import { Card } from 'src/cards/schemas/cards.schema';
import { CardRole } from 'src/cards/schemas/cards.types';
import { User } from 'src/users/schemas/user.entity';
export type GameDocument = HydratedDocument<Game>;
@@ -25,6 +26,12 @@ export class Player extends Types.ObjectId {
})
user: User;
@Prop({ type: String, default: CardRole.PERSONAL })
class?: string;
@Prop()
race?: string;
@Prop({ type: ContainerSchema, default: {}, autopopulate: true })
board!: Container;
@@ -53,7 +60,7 @@ export const TeamSchema = SchemaFactory.createForClass(Team);
@Schema()
export class Game extends Types.ObjectId {
@Prop({ type: [{ type: Team, autopopulate: true }] })
@Prop({ type: [{ type: TeamSchema, autopopulate: true }] })
teams: Team[];
@Prop({ type: ContainerSchema, default: {}, autopopulate: true })
@@ -65,11 +72,7 @@ export class Game extends Types.ObjectId {
@Prop({ type: ContainerSchema, default: {}, autopopulate: true })
fireGems: Container;
@Prop({
type: mongoose.Schema.Types.ObjectId,
ref: 'Team',
autopopulate: true,
})
@Prop({ type: Team, autopopulate: true })
currentTurn?: Team;
@Prop({ default: false })
@@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GamesService } from './games.lobby.service';
import { LobbyService } from './games.lobby.service';
describe('GamesService', () => {
let service: GamesService;
let service: LobbyService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [GamesService],
providers: [LobbyService],
}).compile();
service = module.get<GamesService>(GamesService);
service = module.get<LobbyService>(LobbyService);
});
it('should be defined', () => {
+117 -44
View File
@@ -1,19 +1,23 @@
import { Injectable } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DatabaseObjectDto } from '../dto/database-object.dto';
import { InjectModel } from '@nestjs/mongoose';
import { Game, Player, Team } from '../schemas/game.schema';
import { Model } from 'mongoose';
import { ReadGameDto } from '../dto/read-games.dto';
import { Document, Model } from 'mongoose';
import { ReadContainerDto, ReadGameDto } from '../dto/read-games.dto';
import { User } from 'src/users/schemas/user.entity';
import { Card } from 'src/cards/schemas/cards.schema';
import { CardRole } from 'src/cards/schemas/cards.types';
import { UpdateGameDto } from '../dto/update-game.dto';
import { StartingDeckDto, UpdateGameDto } from '../dto/update-game.dto';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { GameService } from './games.service';
@Injectable()
export class GamesService {
export class LobbyService {
constructor(
@InjectModel(Game.name) private gameModel: Model<Game>,
@InjectModel(Card.name) private cardModel: Model<Card>,
private readonly gamesService: GameService,
private eventEmitter: EventEmitter2,
) {}
async create(owner: User): Promise<DatabaseObjectDto> {
@@ -31,7 +35,10 @@ export class GamesService {
} as Team);
const gamedata = await createdGame.save();
return new ReadGameDto(gamedata);
const dto = new ReadGameDto(gamedata);
this.eventEmitter.emit('sse.lobby', { type: 'create', ...dto });
return dto;
}
async findAll(): Promise<ReadGameDto[]> {
@@ -43,14 +50,6 @@ export class GamesService {
);
}
async findById(id: string) {
const game = await this.gameModel.findById(id).exec();
if (!game) {
return null;
}
return new ReadGameDto(game);
}
async findByOwner(_id: string): Promise<ReadGameDto> {
const game = await this.gameModel.findOne({ owner: _id }).exec();
if (!game) {
@@ -59,24 +58,71 @@ export class GamesService {
return new ReadGameDto(game);
}
async update(id: string, updateGameDto: UpdateGameDto) {
const game = await this.gameModel.findById(id).exec();
if (!game) {
return null;
}
async update(game: Document<Game> & Game, updateGameDto: UpdateGameDto) {
game.packs = updateGameDto.packs;
const packs = await this.cardModel.distinct('pack');
if (updateGameDto.packs.every((e) => packs.includes(e))) {
await game.save();
this.eventEmitter.emit('sse.lobby', {
type: 'update',
_id: game._id,
...updateGameDto,
});
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'update',
...updateGameDto,
});
return;
}
async remove(id: string) {
return await this.gameModel.findByIdAndDelete(id);
throw new HttpException('Pack not found', HttpStatus.NOT_FOUND);
}
async start(id: string) {
const game = await this.gameModel.findById(id).exec();
if (!game) {
return null;
async remove(game: Document<Game> & Game) {
this.eventEmitter.emit('sse.lobby', {
_id: game._id,
});
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'delete',
});
return await game.deleteOne();
}
async start(game: Document<Game> & Game) {
const random = Math.floor(Math.random() * game.teams.length);
game.currentTurn = game.teams[random];
await game.save();
game.started = true;
this.eventEmitter.emit('sse.lobby', {
type: 'start',
_id: game._id,
});
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'start',
game: new ReadGameDto(game),
});
let playerIndex = 0;
await Promise.all(
game.teams.map((team) =>
Promise.all(
team.players.map(async (player) => {
player.stack.cards = await this.cardModel
.find({
pack: { $in: game.packs },
role: { $in: [player.class, player.race] },
playerIndex,
})
.exec();
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'populateContainer',
container: new ReadContainerDto(player.stack, true),
});
this.gamesService.shuffleContainer(player.stack, game);
playerIndex++;
}),
),
),
);
game.fireGems.cards = await this.cardModel.find({
pack: { $in: game.packs },
role: CardRole.FIRE_GEM,
@@ -87,24 +133,51 @@ export class GamesService {
role: CardRole.MARKET,
});
let playerIndex = 0;
await Promise.all(
game.teams.map((team) =>
Promise.all(
team.players.map(async (player) => {
player.stack.cards = await this.cardModel
.find({
pack: { $in: game.packs },
role: CardRole.PERSONAL,
playerIndex,
})
.exec();
playerIndex++;
}),
),
),
);
game.started = true;
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'populateContainer',
container: new ReadContainerDto(game.fireGems),
});
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'populateContainer',
container: new ReadContainerDto(game.marketStack, true),
});
await this.gamesService.shuffleContainer(game.marketStack, game);
await game.save();
}
async changeStartingDeck(
data: StartingDeckDto,
player: Document<Player> & Player,
game: Document<Game> & Game,
) {
const packs = await this.cardModel
.find({ pack: { $in: game.packs } })
.distinct('role');
if (
!Object.values<string>(CardRole).includes(data.class) ||
!Object.values<string>(CardRole).includes(data.race) ||
data.race == CardRole.FIRE_GEM ||
data.race == CardRole.MARKET ||
data.class == CardRole.FIRE_GEM ||
data.class == CardRole.MARKET
) {
throw new HttpException('Invalid race or class', HttpStatus.NOT_FOUND);
}
if (
packs.includes(data.class as CardRole) &&
packs.includes(data.race as CardRole)
) {
player.class = data.class;
player.race = data.race;
await game.save();
this.eventEmitter.emit('sse.game.changeStartingDeck', {
_id: player._id,
...data,
});
return;
}
throw new HttpException('Pack not found', HttpStatus.NOT_FOUND);
}
}
+61
View File
@@ -0,0 +1,61 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Container, Game } from '../schemas/game.schema';
import { Document, Model } from 'mongoose';
import { Card } from 'src/cards/schemas/cards.schema';
import { EventEmitter2 } from '@nestjs/event-emitter';
function shuffle(a) {
let j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
@Injectable()
export class GameService {
constructor(
@InjectModel(Game.name) private gameModel: Model<Game>,
@InjectModel(Card.name) private cardModel: Model<Card>,
private eventEmitter: EventEmitter2,
) {}
async shuffleContainer(container: Container, game: Document<Game> & Game) {
container.cards = shuffle(container.cards);
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'shuffleContainer',
container: container._id.toHexString(),
});
await game.save();
}
async distributeCards(
cards: Card[],
from: Container,
to: Container,
game: Document<Game> & Game,
) {
if (cards.some((c) => !from.cards.includes(c))) {
throw new HttpException(
'Card not found in container',
HttpStatus.NOT_FOUND,
);
}
for (const c of cards) {
const index = from.cards.indexOf(c);
from.cards.splice(index, 1);
to.cards.push(c);
}
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'distributeCards',
from: from._id.toHexString(),
to: to._id.toHexString(),
cards: cards,
});
await game.save();
}
}
-1
View File
@@ -110,7 +110,6 @@ export class UsersService {
}
}
const data2 = await req2.json();
console.log(data2);
const dto = new LoginDto(data2.id, data2.global_name, data2.avatar);
const user = await this.getOrCreateUser(dto.userId, dto);
session.user = user;