From a3dc01171a02afa6b1d27ba08703769f8903e36a Mon Sep 17 00:00:00 2001 From: legonzaur Date: Fri, 27 Dec 2024 22:54:31 +0100 Subject: [PATCH] Initial commit --- .vscode/settings.json | 2 +- src/app.module.ts | 10 +- src/games/controllers/games.controller.ts | 104 ++-- .../games.lobby.controller.spec.ts | 2 +- .../controllers/games.lobby.controller.ts | 100 +--- src/games/controllers/turn.controller.ts | 461 ---------------- src/games/games.module.ts | 19 +- src/games/pipe/game.pipe.ts | 12 +- src/games/schemas/concept.schema.ts | 21 + src/games/schemas/concept_in_turn.schema.ts | 33 ++ src/games/schemas/game.schema.ts | 36 +- src/games/schemas/player.schema.ts | 28 +- src/games/services/effect.functions.ts | 506 ------------------ .../services/games.lobby.service.spec.ts | 2 +- src/games/services/games.lobby.service.ts | 243 --------- src/games/services/games.service.ts | 360 +------------ src/games/services/lobby.service.ts | 71 +++ src/games/services/turn.service.ts | 470 ---------------- src/games/utils.ts | 117 ---- 19 files changed, 272 insertions(+), 2325 deletions(-) delete mode 100644 src/games/controllers/turn.controller.ts create mode 100644 src/games/schemas/concept.schema.ts create mode 100644 src/games/schemas/concept_in_turn.schema.ts delete mode 100644 src/games/services/effect.functions.ts delete mode 100644 src/games/services/games.lobby.service.ts create mode 100644 src/games/services/lobby.service.ts delete mode 100644 src/games/services/turn.service.ts delete mode 100644 src/games/utils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b55450f..387ad48 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "dbaeumer.vscode-eslint" } } diff --git a/src/app.module.ts b/src/app.module.ts index 57f216c..9f0e7d0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,17 +6,23 @@ import { GamesModule } from './games/games.module'; import { UsersModule } from './users/users.module'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from './users/schemas/user.entity'; +import { Game } from './games/schemas/game.schema'; +import { Topic } from './words/schemas/topic.schema'; +import { Word } from './words/schemas/word.schema'; +import { Player } from './games/schemas/player.schema'; @Module({ imports: [ EventEmitterModule.forRoot(), ConfigModule.forRoot(), TypeOrmModule.forRoot({ - type:"postgres", + type: 'postgres', url: process.env.PG_ENDPOINT, password: process.env.PG_PASSWORD, username: process.env.PG_USER, - synchronize: process.env.DEV_MODE=="true" + synchronize: process.env.DEV_MODE == 'true', + entities: [User, Player, Game, Topic, Word], }), GamesModule, UsersModule, diff --git a/src/games/controllers/games.controller.ts b/src/games/controllers/games.controller.ts index e111aaa..0d7dbec 100644 --- a/src/games/controllers/games.controller.ts +++ b/src/games/controllers/games.controller.ts @@ -9,20 +9,15 @@ import { Sse, UseGuards, } from '@nestjs/common'; -import { LobbyService } from '../services/games.lobby.service'; +import { LobbyService } from '../services/lobby.service'; import { AuthGuard } from '../guards/auth.guard'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { StartingDeckDto } from '../dto/update-game.dto'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Game } from '../schemas/game.schema'; -import mongoose, { Document } from 'mongoose'; import { GamePipe, GameStartedPipe } from '../pipe/game.pipe'; import { Observable, fromEvent, map } from 'rxjs'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { GameService } from '../services/games.service'; -import { getCurrentTeam, getPlayer, isInTeam, WithTransaction } from '../utils'; -import { User } from 'src/users/schemas/user.entity'; -import { ReadTeamDto } from '../dto/read-games.dto'; @ApiTags('game') @Controller('games/:id') @@ -33,76 +28,57 @@ export class GamesController { private eventEmitter: EventEmitter2, ) {} - @Post('changeStartingDeck') - @ApiOperation({ summary: 'Pick a different starting deck' }) + @Post('/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 changeStartingDeck( - @Param('id') id: string, - @Param('id', GamePipe) game: Document & Game, - @Body() startingDeckDto: StartingDeckDto, - @Session() session: Record, - ) { - if (game.started) { - throw new HttpException( - 'You cannot do that after the game has started', - HttpStatus.BAD_REQUEST, - ); - } - const player = getPlayer(session, game); - await this.lobbyService.changeStartingDeck(startingDeckDto, player, game); - } - - @Post('endTurn') - @UseGuards(AuthGuard) - async endTurn( - @Param('id') id: string, - @Param('id', GameStartedPipe) game: Document & Game, - @Session() session: Record, - ) { - const currentTeam = getCurrentTeam(game); - const currentPlayer = getPlayer(session, game); - - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) { - throw new HttpException( - 'You must consume all your discard markers before ending your turn', - HttpStatus.FORBIDDEN, - ); - } - await this.gameService.endTurn(game); - } - - @Post('leave') - @ApiOperation({ summary: "Leave a game that hasn't already started" }) - @UseGuards(AuthGuard) - async leave( + async start( @Param('id') id: string, @Session() session: Record, - @Param('id', GamePipe) game: Document & Game, + @Param('id', GamePipe) game: Game, ) { if (game.started) { throw new HttpException('Game already started', HttpStatus.BAD_REQUEST); } - return await this.gameService.leaveGame(game, session.user); + if (game.players.length < 2 && !process.env.DEV_MODE) { + throw new HttpException( + 'Not enough teams to start the game', + HttpStatus.BAD_REQUEST, + ); + } + if (game.owner.userId != session.user.userId) { + throw new HttpException( + 'You can only start games you own', + HttpStatus.FORBIDDEN, + ); + } + await this.gameService.start(game); + return; } @Post('surrender') - @ApiOperation({ summary: "Leave a game that hasn't already started" }) + @ApiOperation({ summary: 'Leave a game that has already started' }) @UseGuards(AuthGuard) async surrender( @Param('id') id: string, @Session() session: Record, - @Param('id', GamePipe) game: Document & Game, + @Param('id', GamePipe) game: Game, ) { if (!game.started) { throw new HttpException('Game has not started', HttpStatus.BAD_REQUEST); } - return await this.gameService.leaveGame(game, session.user); + return await this.lobbyService.leaveGame(game, session.user); } @Post('kick/:playerId') @@ -111,7 +87,7 @@ export class GamesController { async kick( @Param('id') id: string, @Session() session: Record, - @Param('id', GamePipe) game: Document & Game, + @Param('id', GamePipe) game: Game, @Param('playerId') playerId: string, ) { if (session.user.userId != game.owner.userId) { @@ -120,15 +96,11 @@ export class GamesController { HttpStatus.FORBIDDEN, ); } - return await this.gameService.kickPlayer(game, session.user, playerId); + return await this.lobbyService.kickPlayer(game, session.user, playerId); } @Sse('/subscribe') - subscribe( - @Param('id') id: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - @Param('id', GamePipe) _game: Document & Game, - ): Observable<{ data: string }> { + subscribe(@Param('id') id: string): Observable<{ data: string }> { return fromEvent(this.eventEmitter, 'sse.game.' + id).pipe( map((payload: any) => { return { diff --git a/src/games/controllers/games.lobby.controller.spec.ts b/src/games/controllers/games.lobby.controller.spec.ts index fa04ddd..a9a1ad6 100644 --- a/src/games/controllers/games.lobby.controller.spec.ts +++ b/src/games/controllers/games.lobby.controller.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LobbyController } from './games.lobby.controller'; -import { LobbyService } from '../services/games.lobby.service'; +import { LobbyService } from '../services/lobby.service'; describe('GamesController', () => { let controller: LobbyController; diff --git a/src/games/controllers/games.lobby.controller.ts b/src/games/controllers/games.lobby.controller.ts index d36f44e..6794116 100644 --- a/src/games/controllers/games.lobby.controller.ts +++ b/src/games/controllers/games.lobby.controller.ts @@ -12,14 +12,11 @@ import { Body, Sse, } from '@nestjs/common'; -import { LobbyService } from '../services/games.lobby.service'; +import { LobbyService } from '../services/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'; @@ -36,7 +33,7 @@ export class LobbyController { @ApiResponse({ status: 201, description: 'Game successfully created.', - type: ReadGameDto, + // type: ReadGameDto, }) @ApiResponse({ status: 400, @@ -45,7 +42,7 @@ export class LobbyController { @ApiResponse({ status: 403, description: 'Forbidden.' }) @UseGuards(AuthGuard) async create(@Session() session: Record) { - const games = await this.lobbyService.findByOwner(session.user._id); + const games = await this.lobbyService.findByOwner(session.user); if (games) { throw new HttpException( 'You can only create one game at a time', @@ -76,25 +73,8 @@ export class LobbyController { @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, - @Body() updateGameDto: UpdateGameDto, - @Param('id', GamePipe) game: Document & Game, - ) { - if (game.owner._id.toString() != session.user._id) { - throw new HttpException( - 'You can only edit games you own', - HttpStatus.FORBIDDEN, - ); - } - return this.lobbyService.update(game, updateGameDto); + return game; + // return new ReadGameDto(game); } @Delete(':id') @@ -103,57 +83,15 @@ export class LobbyController { async remove( @Param('id') id: string, @Session() session: Record, - @Param('id', GamePipe) game: Document & Game, + @Param('id', GamePipe) game: Game, ) { - if (game.owner._id.toString() != session.user._id) { + if (game.owner.userId == session.user.userId) { throw new HttpException( 'You can only delete games you own', HttpStatus.FORBIDDEN, ); } - const deleted = await this.lobbyService.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, - @Param('id', GamePipe) game: Document & 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, - ); - } - await this.lobbyService.start(game); - return; + await this.lobbyService.remove(game); } @Post(':id/join') @@ -162,17 +100,13 @@ export class LobbyController { async join( @Param('id') id: string, @Session() session: Record, - @Param('id', GamePipe) game: Document & Game, + @Param('id', GamePipe) game: Game, ) { if (game.started) { throw new HttpException('Game already started', HttpStatus.BAD_REQUEST); } - if ( - game.teams - .flatMap((t) => t.players) - .some((p) => p.user.userId == session.user.userId) - ) { + if (game.players.some((p) => p.user.userId == session.user.userId)) { throw new HttpException( 'You are already in this game', HttpStatus.BAD_REQUEST, @@ -180,4 +114,18 @@ export class LobbyController { } return await this.lobbyService.joinGame(game, session.user); } + + @Post(':id/leave') + @ApiOperation({ summary: "Leave a game that hasn't already started" }) + @UseGuards(AuthGuard) + async leave( + @Param('id') id: string, + @Session() session: Record, + @Param('id', GamePipe) game: Game, + ) { + if (game.started) { + throw new HttpException('Game already started', HttpStatus.BAD_REQUEST); + } + return await this.lobbyService.leaveGame(game, session.user); + } } diff --git a/src/games/controllers/turn.controller.ts b/src/games/controllers/turn.controller.ts deleted file mode 100644 index f6705cf..0000000 --- a/src/games/controllers/turn.controller.ts +++ /dev/null @@ -1,461 +0,0 @@ -import { - Body, - Controller, - HttpException, - HttpStatus, - Param, - Post, - Session, - UseGuards, -} from '@nestjs/common'; -import { AuthGuard } from '../guards/auth.guard'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { Game, Player } from '../schemas/game.schema'; -import { Document, Model } from 'mongoose'; -import { GameStartedPipe } from '../pipe/game.pipe'; - -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { TurnService } from '../services/turn.service'; -import { - getPlayer, - getCurrentTeam, - isInTeam, - findUser, - findPlayer, -} from '../utils'; -import { CardPipe } from '../pipe/card.pipe'; -import { Card } from 'src/cards/schemas/cards.schema'; -import { EffectPipe } from '../pipe/effect.pipe'; -import { Effect } from 'src/cards/schemas/effect.schema'; -import { GameService } from '../services/games.service'; -import { CardEffects, CardType } from 'src/cards/schemas/cards.types'; -import { UseTokenDTO } from '../dto/use-token-dto'; -import { DamagePlayerDTO } from '../dto/damage-player-dto'; -import { DamageChampionDTO } from '../dto/damage-champion-dto'; -import { equal } from 'assert'; - -@ApiTags('turn') -@Controller('games/:gameId/turn') -export class TurnController { - constructor( - private readonly turnService: TurnService, - private readonly gameService: GameService, - private eventEmitter: EventEmitter2, - ) {} - - @Post('lockCard/:cardId') - @ApiOperation({ summary: 'Lock a card' }) - @UseGuards(AuthGuard) - async lockCard( - @Param('gameId') _gameId: string, - @Param('cardId') _cardId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Param('cardId', CardPipe) card: Document & Card, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) { - throw new HttpException( - 'You cannot lock a card while having discard markers', - HttpStatus.BAD_REQUEST, - ); - } - if (game.currentTurn.cardsLocked.some((c) => c._id.equals(card._id))) { - throw new HttpException('Card already locked', HttpStatus.BAD_REQUEST); - } - if (!currentPlayer.board.cards.some((c) => c._id.equals(card._id))) { - throw new HttpException( - 'Card not found in player board', - HttpStatus.NOT_FOUND, - ); - } - - await this.turnService.lockCard(game, currentPlayer, card); - } - - @Post('buyCard/:cardId') - @ApiOperation({ summary: 'Buys a card from the market' }) - @UseGuards(AuthGuard) - async buyCard( - @Param('gameId') _gameId: string, - @Param('cardId') _cardId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Param('cardId', CardPipe) card: Document & Card, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) { - throw new HttpException( - 'You cannot buy a card while having discard markers', - HttpStatus.BAD_REQUEST, - ); - } - if (!game.market.cards.some((c) => c._id.equals(card._id))) { - throw new HttpException( - 'Card not in current game market', - HttpStatus.NOT_FOUND, - ); - } - if (game.currentTurn.gold < card.cost) { - throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); - } - await this.turnService.buyGeneric( - game, - card, - game.market, - currentPlayer.discard, - ); - await this.gameService.fillMarket(game); - } - - @Post('buyGem') - @ApiOperation({ summary: 'Buys a gem from the market' }) - @UseGuards(AuthGuard) - async buyGem( - @Param('gameId') _gameId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) { - throw new HttpException( - 'You cannot buy a card while having discard markers', - HttpStatus.BAD_REQUEST, - ); - } - if (game.fireGems.cards.length < 1) { - throw new HttpException('No Gems left', HttpStatus.NOT_FOUND); - } - const targetCard = game.fireGems.cards[0]; - await this.turnService.buyGeneric( - game, - targetCard, - game.fireGems, - currentPlayer.discard, - ); - } - - @Post('lockEffect/:effectId') - @ApiOperation({ summary: 'Lock an effect' }) - @UseGuards(AuthGuard) - async lockEffect( - @Param('gameId') _gameId: string, - @Param('effectId') _effectId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Param('effectId', EffectPipe) effect: Document & Effect, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) { - throw new HttpException( - 'You cannot lock an effect while having discard markers', - HttpStatus.BAD_REQUEST, - ); - } - if (game.currentTurn.lockedEffects.some((e) => e._id.equals(effect._id))) { - throw new HttpException('Effect already locked', HttpStatus.BAD_REQUEST); - } - const card = (await effect.populate('card')).card; - if (!currentPlayer.board.cards.some((c) => c._id.equals(card._id))) { - throw new HttpException( - 'Card not found in player board', - HttpStatus.NOT_FOUND, - ); - } - if (!game.currentTurn.cardsLocked.some((c) => c._id.equals(card._id))) { - throw new HttpException('Card not locked', HttpStatus.BAD_REQUEST); - } - - await this.turnService.lockEffect(game, currentPlayer, effect, card); - } - - @Post('makeDiscard/:playerId') - @ApiOperation({ summary: 'Use a token to make discard a player' }) - @UseGuards(AuthGuard) - async makeDiscard( - @Param('gameId') _gameId: string, - @Param('playerId') _playerId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - - const targetPlayer = findPlayer(_playerId, game); - if (isInTeam(currentTeam, targetPlayer)) { - throw new HttpException( - 'You cannot target players in the same team as yours', - HttpStatus.FORBIDDEN, - ); - } - if ( - targetPlayer.fuckUMarkers + targetPlayer.discardMarkers >= - targetPlayer.hand.cards.length - ) { - throw new HttpException( - 'You cannot add more discard markers to this player', - HttpStatus.BAD_REQUEST, - ); - } - const targetToken = game.currentTurn.tokens.find( - (t) => t.effect == CardEffects.MAKE_DISCARD, - ); - if (!targetToken) { - throw new HttpException('Token not found', HttpStatus.NOT_FOUND); - } - await this.turnService.addFuckUMarker(game, targetToken, targetPlayer); - const index = game.currentTurn.tokens.findIndex((t) => - t._id.equals(targetToken._id), - ); - game.currentTurn.tokens.splice(index, 1); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.useToken', - tokenId: targetToken._id.toHexString(), - }); - } - - @Post('useToken/:tokenId') - @ApiOperation({ summary: 'Use a token' }) - @UseGuards(AuthGuard) - async useToken( - @Param('gameId') _gameId: string, - @Param('tokenId') _tokenId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Param('tokenId', EffectPipe) token: Document & Effect, - @Body() useTokenDto: UseTokenDTO, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - - if (!game.currentTurn.tokens.some((t) => t._id.equals(token._id))) { - throw new HttpException('Token not found', HttpStatus.NOT_FOUND); - } - - const enemyTargetTokens = [ - CardEffects.STUN, - CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE, - ]; - - const marketTokens = [ - CardEffects.STACK_NEXT_ACTION_BOUGHT, - CardEffects.STACK_NEXT_CARD_BOUGHT, - CardEffects.PLAY_NEXT_CARD_BOUGHT, - ]; - - const discardTokens = [ - CardEffects.RESTACK_DISCARDED_ACTION, - CardEffects.RESTACK_DISCARDED_CARD, - CardEffects.RESTACK_DISCARDED_CHAMPION, - CardEffects.SACRIFICE, - ]; - - let targetCard: Card; - let targetPlayer = currentPlayer; - if (enemyTargetTokens.includes(token.effect)) { - targetPlayer = findPlayer(useTokenDto.playerId, game); - targetCard = targetPlayer.board.cards.find((c) => - c._id.equals(useTokenDto.cardId), - ); - } else if (marketTokens.includes(token.effect)) { - targetCard = - game.market.cards.find((c) => c._id.equals(useTokenDto.cardId)) ?? - game.fireGems.cards.find((c) => c._id.equals(useTokenDto.cardId)); - } else if (discardTokens.includes(token.effect)) { - targetCard = targetPlayer.discard.cards.find((c) => - c._id.equals(useTokenDto.cardId), - ); - } else { - targetCard = currentPlayer.board.cards.find((c) => - c._id.equals(useTokenDto.cardId), - ); - } - - if (!targetCard && token.effect == CardEffects.SACRIFICE) { - targetCard = currentPlayer.board.cards.find((c) => - c._id.equals(useTokenDto.cardId), - ); - } - - if (token.effect != CardEffects.DRAW_AND_DISCARD) { - if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) { - throw new HttpException( - 'You cannot use a token while having discard markers', - HttpStatus.BAD_REQUEST, - ); - } - if (!targetCard) { - throw new HttpException('Card not found', HttpStatus.NOT_FOUND); - } - } - - await this.turnService.useToken(game, targetPlayer, token, targetCard); - } - - @Post('damagePlayer/:playerId') - @ApiOperation({ summary: 'Consume damage points to damage a player' }) - @UseGuards(AuthGuard) - async damagePlayer( - @Param('gameId') _gameId: string, - @Param('playerId') playerId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Body() damagePlayerDTO: DamagePlayerDTO, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (damagePlayerDTO.amount > game.currentTurn.damage) { - throw new HttpException( - 'You cannot deal more damage than what your current damage points', - HttpStatus.BAD_REQUEST, - ); - } - - const targetPlayer = findPlayer(playerId, game); - if (targetPlayer.board.cards.some((c) => c.guard)) { - throw new HttpException( - 'You cannot target this player while there is a guard', - HttpStatus.BAD_REQUEST, - ); - } - await this.turnService.damagePlayer( - game, - damagePlayerDTO.amount, - currentPlayer, - targetPlayer, - ); - } - - @Post('damageChampion/:cardId') - @ApiOperation({ summary: 'Consume damage points to stun a champion' }) - @UseGuards(AuthGuard) - async damageChampion( - @Param('gameId') _gameId: string, - @Param('cardId') _cardId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Param('cardId', CardPipe) card: Document & Card, - @Body() damageChampion: DamageChampionDTO, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - if (card.defense > game.currentTurn.damage) { - throw new HttpException( - 'You cannot deal more damage than what your current damage points', - HttpStatus.BAD_REQUEST, - ); - } - - const targetPlayer = findPlayer(damageChampion.playerId, game); - if (!targetPlayer) { - throw new HttpException('Player not found', HttpStatus.NOT_FOUND); - } - if (card.cardType != CardType.CHAMPION) { - throw new HttpException('Card is not a champion', HttpStatus.BAD_REQUEST); - } - - if (!targetPlayer.board.cards.some((c) => c._id.equals(card._id))) { - throw new HttpException( - 'Champion not found in player board', - HttpStatus.NOT_FOUND, - ); - } - - if (!card.guard) { - if (targetPlayer.board.cards.some((c) => c.guard)) { - throw new HttpException( - 'You cannot target this champion while there is a guard', - HttpStatus.BAD_REQUEST, - ); - } - } - await this.turnService.damageChampion(game, targetPlayer, card); - } - - @Post('discardCard/:cardId') - @ApiOperation({ - summary: 'Discard a card if you have fuckU or discard markers', - }) - @UseGuards(AuthGuard) - async discardCard( - @Param('gameId') _gameId: string, - @Param('cardId') _cardId: string, - @Param('gameId', GameStartedPipe) game: Document & Game, - @Param('cardId', CardPipe) card: Document & Card, - @Session() session: Record, - ) { - const currentPlayer = getPlayer(session, game); - const currentTeam = getCurrentTeam(game); - if (!isInTeam(currentTeam, currentPlayer)) { - throw new HttpException( - 'It is not your turn to play', - HttpStatus.FORBIDDEN, - ); - } - - if (currentPlayer.discardMarkers == 0 && currentPlayer.fuckUMarkers == 0) { - throw new HttpException( - 'You do not have any discard or fuckU markers', - HttpStatus.BAD_REQUEST, - ); - } - - if (!currentPlayer.board.cards.some((c) => c._id.equals(_cardId))) { - throw new HttpException('Card not found in board', HttpStatus.NOT_FOUND); - } - - await this.turnService.discardCard(game, currentPlayer, card); - } -} diff --git a/src/games/games.module.ts b/src/games/games.module.ts index 5de0071..5ac3fd5 100644 --- a/src/games/games.module.ts +++ b/src/games/games.module.ts @@ -1,22 +1,15 @@ import { Module } from '@nestjs/common'; -import { LobbyService } from './services/games.lobby.service'; +import { LobbyService } from './services/lobby.service'; import { LobbyController } from './controllers/games.lobby.controller'; -import { Game, GameSchema } from './schemas/game.schema'; -import { MongooseModule } from '@nestjs/mongoose'; +import { Game } from './schemas/game.schema'; 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'; -import { TurnService } from './services/turn.service'; -import { TurnController } from './controllers/turn.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - imports: [ - CardsModule, - UsersModule, - MongooseModule.forFeature([{ name: Game.name, schema: GameSchema }]), - ], - controllers: [LobbyController, GamesController, TurnController], - providers: [LobbyService, GameService, TurnService], + imports: [UsersModule, TypeOrmModule.forFeature([Game])], + controllers: [LobbyController, GamesController], + providers: [LobbyService, GameService], }) export class GamesModule {} diff --git a/src/games/pipe/game.pipe.ts b/src/games/pipe/game.pipe.ts index 630aafe..f2fe6d8 100644 --- a/src/games/pipe/game.pipe.ts +++ b/src/games/pipe/game.pipe.ts @@ -11,10 +11,12 @@ import { Repository } from 'typeorm'; @Injectable() export class GamePipe implements PipeTransform { - constructor(@InjectRepository(Game) private gameRepository: Repository) {} + constructor( + @InjectRepository(Game) private gameRepository: Repository, + ) {} async transform(value: any) { - const game = await this.gameRepository.findBy({id: value}); + const game = await this.gameRepository.findBy({ id: value }); if (!game) { throw new HttpException('Game not found', HttpStatus.NOT_FOUND); } @@ -24,10 +26,12 @@ export class GamePipe implements PipeTransform { @Injectable() export class GameStartedPipe implements PipeTransform { - constructor(@InjectRepository(Game) private gameRepository: Repository) {} + constructor( + @InjectRepository(Game) private gameRepository: Repository, + ) {} async transform(value: any) { - const game = await this.gameRepository.findBy({id: value}); + const game = await this.gameRepository.findOne(value); if (!game) { throw new HttpException('Game not found', HttpStatus.NOT_FOUND); } diff --git a/src/games/schemas/concept.schema.ts b/src/games/schemas/concept.schema.ts new file mode 100644 index 0000000..da307c5 --- /dev/null +++ b/src/games/schemas/concept.schema.ts @@ -0,0 +1,21 @@ +import { + Column, + Entity, + JoinTable, + ManyToOne, + OneToMany, + OneToOne, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; +import { Player } from './player.schema'; + +@Entity() +@Unique(['value']) +export class Concept { + @PrimaryGeneratedColumn() + id: number; + + @Column() + value: string; +} diff --git a/src/games/schemas/concept_in_turn.schema.ts b/src/games/schemas/concept_in_turn.schema.ts new file mode 100644 index 0000000..5d7a9e0 --- /dev/null +++ b/src/games/schemas/concept_in_turn.schema.ts @@ -0,0 +1,33 @@ +import { + Column, + Entity, + JoinTable, + ManyToOne, + OneToMany, + OneToOne, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; +import { Concept } from './concept.schema'; +import { Game } from './game.schema'; + +@Entity() +export class ConceptInTurn { + @PrimaryGeneratedColumn() + id: number; + + @ManyToOne(() => Concept) + concept: Concept; + + @Column({ default: 0 }) + order: number; + + @Column({ default: 0 }) + subconcept: number; + + @Column({ default: 1 }) + markers: number; + + @ManyToOne(() => Game) + game: Promise; +} diff --git a/src/games/schemas/game.schema.ts b/src/games/schemas/game.schema.ts index 54c2167..a23a5b9 100644 --- a/src/games/schemas/game.schema.ts +++ b/src/games/schemas/game.schema.ts @@ -1,5 +1,15 @@ -import { Column, Entity, JoinTable, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { + Column, + Entity, + JoinTable, + ManyToOne, + OneToMany, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; import { Player } from './player.schema'; +import { ConceptInTurn } from './concept_in_turn.schema'; +import { User } from 'src/users/schemas/user.entity'; @Entity() export class Game { @@ -7,12 +17,24 @@ export class Game { id: number; @Column() - seed: string + seed: string; - @OneToOne(type=>Player, {onDelete:"CASCADE"}) - owner: Player + @ManyToOne(() => User) + owner: User; - @OneToMany(type=>Player, player => player.game, {onDelete:"CASCADE"}) + @OneToMany(() => Player, (player) => player.game, { onDelete: 'CASCADE' }) @JoinTable() - players: Player[] -} \ No newline at end of file + players: Player[]; + + @OneToOne(() => Player) + currentPlayer: Player; + + @Column() + currentWord: string; + + @Column() + started: boolean; + + @OneToMany(() => ConceptInTurn, (cit) => cit.game) + currentConcepts: ConceptInTurn[]; +} diff --git a/src/games/schemas/player.schema.ts b/src/games/schemas/player.schema.ts index 432736d..3fff418 100644 --- a/src/games/schemas/player.schema.ts +++ b/src/games/schemas/player.schema.ts @@ -1,16 +1,28 @@ import { User } from 'src/users/schemas/user.entity'; -import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { + Column, + Entity, + JoinTable, + ManyToMany, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; import { Game } from './game.schema'; @Entity() -@Unique(["user, game"]) -export class Player{ +@Unique(['user, game']) +export class Player { @PrimaryGeneratedColumn() - id: number + id: number; - @ManyToOne(type=>User) - user: User + @ManyToOne((type) => User) + user: User; - @ManyToOne(type=>Game) - game: Game + @ManyToOne((type) => Game) + game: Promise; + + @Column() + score: number; } diff --git a/src/games/services/effect.functions.ts b/src/games/services/effect.functions.ts deleted file mode 100644 index 284372e..0000000 --- a/src/games/services/effect.functions.ts +++ /dev/null @@ -1,506 +0,0 @@ -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { Game, Player } from '../schemas/game.schema'; -import mongoose, { Document } from 'mongoose'; -import { Card } from 'src/cards/schemas/cards.schema'; -import { - CardEffects, - CardRole, - CardType, - EffectType, - Per, -} from 'src/cards/schemas/cards.types'; -import { Effect, EffectCondition } from 'src/cards/schemas/effect.schema'; -import { TurnService } from './turn.service'; -import { GameService } from './games.service'; -import { ReadEffectDto } from 'src/cards/dto/effect.dto'; -import { HttpException, HttpStatus } from '@nestjs/common'; - -export function isAutoActivable(effect: Effect) { - if ( - !effect.mutex && - (!effect.condition || - ( effect.condition.effectId == EffectType.CHAMPION_ACTION && !effect.per)) - ) { - return true; - } - return false; -} - -async function addToken( - game: Document & Game, - effect: Effect, - _player: Player, - _card: Card, - _gameService: GameService, - eventEmitter: EventEmitter2, - _session: mongoose.mongo.ClientSession, -) { - game.currentTurn.tokens.push(effect); - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.addToken', - token: new ReadEffectDto(effect), - }); -} - -export async function removeToken( - game: Document & Game, - effect: Effect, - eventEmitter: EventEmitter2, -) { - game.currentTurn.tokens.splice( - game.currentTurn.tokens.findIndex((t) => t._id.equals(effect._id)), - 1, - ); - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.removeToken', - token: effect._id, - }); -} - -export const effectMappings = { - [CardEffects.GOLD]: async ( - game: Document & Game, - effect: Effect, - _player: Player, - _card: Card, - _gameService: GameService, - eventEmitter: EventEmitter2, - _session: mongoose.mongo.ClientSession, - ) => { - game.currentTurn.gold += effect.amount; - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'consumeEffect', - effect: effect._id.toHexString(), - }); - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.updateGold', - gold: game.currentTurn.gold, - operation: effect.amount, - }); - }, - - [CardEffects.DAMAGE]: async ( - game: Document & Game, - effect: Effect, - _player: Player, - _card: Card, - _gameService: GameService, - eventEmitter: EventEmitter2, - _session: mongoose.mongo.ClientSession, - ) => { - game.currentTurn.damage += effect.amount; - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'consumeEffect', - effect: effect._id.toHexString(), - }); - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.updateDamage', - damage: game.currentTurn.damage, - operation: effect.amount, - }); - }, - - [CardEffects.HEAL]: async ( - game: Document & Game, - effect: Effect, - player: Player, - _card: Card, - _gameService: GameService, - eventEmitter: EventEmitter2, - _session: mongoose.mongo.ClientSession, - ) => { - const team = game.teams.find((t) => - t.players.some((p) => p._id.equals(player._id)), - ); - team.health += effect.amount; - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'consumeEffect', - effect: effect._id.toHexString(), - }); - eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'team.updateHealth', - team: team._id, - health: team.health, - operation: effect.amount, - }); - }, - - [CardEffects.DRAW]: async ( - game: Document & Game, - effect: Effect, - player: Player, - _card: Card, - gameService: GameService, - _eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - await gameService.drawToBoard(effect.amount, player, game, session); - }, - [CardEffects.PREPARE]: addToken, - [CardEffects.STUN]: addToken, - [CardEffects.SACRIFICE]: addToken, - [CardEffects.DRAW_AND_DISCARD]: addToken, - [CardEffects.MAKE_DISCARD]: addToken, - [CardEffects.RESTACK_DISCARDED_CHAMPION]: addToken, - [CardEffects.RESTACK_DISCARDED_CARD]: addToken, - [CardEffects.STACK_NEXT_ACTION_BOUGHT]: addToken, - [CardEffects.STACK_NEXT_CARD_BOUGHT]: addToken, - [CardEffects.PLAY_NEXT_CARD_BOUGHT]: addToken, - - // [CardEffects.BUY_FOR_FREE]: addToken, - // [CardEffects.DISCARD_X_AND_DRAW_X]: addToken, - // [CardEffects.PREPARE_ANOTHER_CHAMPION]: addToken, - // [CardEffects.CHEAPER_CHAMPION]: addToken, - // [CardEffects.CHEAPER_CHAMPIONS_PASSIVE]: addToken, - // [CardEffects.CHEAPER_ACTION]: addToken, - // [CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE]: addToken, - - // [CardEffects.RESTACK_DISCARDED_ACTION]: addToken, - - // [CardEffects.KEEP_IN_HAND]: addToken, - // [CardEffects.BUY_GEM_FOR_FREE]: addToken, - // [CardEffects.CHEAPER_SKILLS_PASSIVE]: addToken, - // [CardEffects.CHEAPER_CARD_IF_HIGHER_PRICE]: addToken, - // [CardEffects.PICK_FACTION]: addToken, -} as Record< - CardEffects, - ( - game: Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => Promise ->; - -export const tokenMappings = { - [CardEffects.PREPARE]: async ( - game: Document & Game, - _effect: Effect, - player: Player, - card: Card, - _gameService: GameService, - turnService: TurnService, - _eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - if (card.cardType !== CardType.CHAMPION) { - throw new HttpException( - 'Cannot Prepare : Card is not a champion', - HttpStatus.BAD_REQUEST, - ); - } - const targetEffects = card.effects.filter( - (e) => e.condition.effectId == EffectType.CHAMPION_ACTION, - ); - - if ( - !targetEffects.every((e) => - game.currentTurn.lockedEffects.some((ee) => ee._id.equals(e._id)), - ) - ) { - throw new HttpException( - 'Cannot Prepare : champion effect not locked', - HttpStatus.BAD_REQUEST, - ); - } - - for (const e of targetEffects) { - await turnService.unlockEffect(game, player, e, card, session); - } - }, - [CardEffects.STUN]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - if (card.cardType !== CardType.CHAMPION) { - throw new HttpException( - 'Cannot Stun : Card is not a champion', - HttpStatus.BAD_REQUEST, - ); - } - await gameService.distributeCards( - [card], - player.board, - player.discard, - game, - session, - ); - }, - [CardEffects.SACRIFICE]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - if (game.currentTurn.cardsLocked.some((c) => c._id.equals(card._id))) { - throw new HttpException( - 'Cannot destroy : Card is locked', - HttpStatus.BAD_REQUEST, - ); - } - if (player.discard.cards.some((c) => c._id.equals(card._id))) { - await gameService.destroyCard(card, player.discard, game, session); - } else if (player.board.cards.some((c) => c._id.equals(card._id))) { - await gameService.destroyCard(card, player.board, game, session); - } else { - throw new HttpException( - 'Card not found in Discard or Board', - HttpStatus.NOT_FOUND, - ); - } - }, - [CardEffects.RESTACK_DISCARDED_CHAMPION]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - if (card.cardType != CardType.CHAMPION) { - throw new HttpException( - 'Cannot Restack : Card is not a champion', - HttpStatus.BAD_REQUEST, - ); - } - await gameService.distributeCards( - [card], - player.discard, - player.stack, - game, - session, - ); - }, - [CardEffects.RESTACK_DISCARDED_CARD]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - await gameService.distributeCards( - [card], - player.discard, - player.stack, - game, - session, - ); - }, - [CardEffects.STACK_NEXT_ACTION_BOUGHT]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - if (card.cardType != CardType.ACTION) { - throw new HttpException( - 'Cannot stack : Card is not an action', - HttpStatus.BAD_REQUEST, - ); - } - await turnService.buyGeneric( - game, - card, - game.market, - player.stack, - session, - ); - await gameService.fillMarket(game, session); - }, - [CardEffects.STACK_NEXT_CARD_BOUGHT]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - let fromContainer = game.market; - if (game.fireGems.cards.some((c) => c._id.equals(card._id))) { - fromContainer = game.fireGems; - } - - await turnService.buyGeneric( - game, - card, - fromContainer, - player.stack, - session, - ); - await gameService.fillMarket(game, session); - }, - [CardEffects.PLAY_NEXT_CARD_BOUGHT]: async ( - game: Document & Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - let fromContainer = game.market; - if (game.fireGems.cards.some((c) => c._id.equals(card._id))) { - fromContainer = game.fireGems; - } - - await turnService.buyGeneric( - game, - card, - fromContainer, - player.board, - session, - ); - await gameService.fillMarket(game, session); - }, - [CardEffects.DRAW_AND_DISCARD]: async ( - game: Document & Game, - effect: Effect, - player: Player, - _card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => { - if ( - game.currentTurn.drawAndDiscardCurrentEffect && - !game.currentTurn.drawAndDiscardCurrentEffect._id.equals(effect._id) - ) { - throw new HttpException( - 'You cannot use a token while having discard markers', - HttpStatus.BAD_REQUEST, - ); - } - await gameService.drawToBoard(1, player, game, session); - await turnService.addDiscardMarker(game, player, session); - game.currentTurn.drawAndDiscardCurrentAmount++; - game.currentTurn.drawAndDiscardCurrentEffect = effect; - }, -} as Record< - CardEffects, - ( - game: Game, - effect: Effect, - player: Player, - card: Card, - gameService: GameService, - turnService: TurnService, - eventEmitter: EventEmitter2, - session: mongoose.mongo.ClientSession, - ) => Promise ->; - -export const conditionsMappings = { - [EffectType.CHAMPION_ACTION]: async () => true, - [EffectType.FACTION_COMBO]: async ( - game: Document & Game, - player: Player, - card: Card, - condition: EffectCondition, - ) => { - return game.currentTurn.cardsLocked.some( - (c) => c.faction == condition.faction && !c._id.equals(card._id), - ); - }, - [EffectType.SUICIDE]: async ( - game: Document & Game, - player: Player, - card: Card, - condition: EffectCondition, - gameService: GameService, - session: mongoose.mongo.ClientSession, - ) => { - await gameService.destroyCard(card, player.board, game, session); - return true; - }, - // [EffectTypeSpecial.ACTION_AMOUNT]: {}, - // [EffectTypeSpecial.CARD_AMOUNT]: {}, - // [EffectTypeSpecial.CHAMPION_AMOUNT]: {}, - // [EffectTypeSpecial.TOTAL_DAMAGE]: {}, -} as Record< - string, - ( - game: Document & Game, - player: Player, - card: Card, - condition: EffectCondition, - gameService: GameService, - session: mongoose.mongo.ClientSession, - ) => Promise ->; - -export const perMapping = { - [Per.CARD_OF_SAME_FACTION]: async ( - game: Document & Game, - card: Card, - ) => { - return game.currentTurn.cardsLocked.filter((c) => c.faction == card.faction) - .length; - }, - [Per.OTHER_CARD_OF_SAME_FACTION]: async ( - game: Document & Game, - card: Card, - ) => { - return game.currentTurn.cardsLocked.filter( - (c) => c.faction == card.faction && !c._id.equals(card._id), - ).length; - }, - [Per.CHAMPION]: async (game: Document & Game) => { - return game.currentTurn.cardsLocked.filter((c) => c.defense).length; - }, - [Per.CHAMPION_OF_SAME_FACTION]: async ( - game: Document & Game, - card: Card, - ) => { - return game.currentTurn.cardsLocked.filter( - (c) => c.faction == card.faction && c.defense, - ).length; - }, - [Per.OTHER_CHAMPION]: async (game: Document & Game, card: Card) => { - return game.currentTurn.cardsLocked.filter( - (c) => !c._id.equals(card._id) && c.defense, - ).length; - }, - [Per.OTHER_GUARD]: async (game: Document & Game, card: Card) => { - return game.currentTurn.cardsLocked.filter( - (c) => !c._id.equals(card._id) && c.guard, - ).length; - }, - [Per.STUNNED_CHAMPION]: async () => { - console.error('per_stunned_champion not implemented'); - return 0; - }, - [Per.OTHER_KNIFE_PLAYED]: async (game: Document & Game, card: Card) => { - return game.currentTurn.cardsLocked.filter( - (c) => !c._id.equals(card._id) && c.cardId == 80, - ).length; - }, -} as Record< - string, - (game: Document & Game, card: Card) => Promise ->; diff --git a/src/games/services/games.lobby.service.spec.ts b/src/games/services/games.lobby.service.spec.ts index 6eaae5f..69beeb5 100644 --- a/src/games/services/games.lobby.service.spec.ts +++ b/src/games/services/games.lobby.service.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { LobbyService } from './games.lobby.service'; +import { LobbyService } from './lobby.service'; describe('GamesService', () => { let service: LobbyService; diff --git a/src/games/services/games.lobby.service.ts b/src/games/services/games.lobby.service.ts deleted file mode 100644 index 93ef7d8..0000000 --- a/src/games/services/games.lobby.service.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { DatabaseObjectDto } from '../dto/database-object.dto'; -import { InjectConnection, InjectModel } from '@nestjs/mongoose'; -import { Game, Player, Team } from '../schemas/game.schema'; -import mongoose, { Document, Model } from 'mongoose'; -import { - ReadContainerDto, - ReadGameDto, - ReadPlayerDto, - ReadTeamDto, -} 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 { StartingDeckDto, UpdateGameDto } from '../dto/update-game.dto'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { GameService } from './games.service'; -import { WithTransaction } from '../utils'; -import { platform } from 'os'; - -@Injectable() -export class LobbyService { - constructor( - @InjectModel(Game.name) private gameModel: Model, - @InjectModel(Card.name) private cardModel: Model, - @InjectConnection() private readonly connection: mongoose.Connection, - private readonly gamesService: GameService, - private eventEmitter: EventEmitter2, - ) {} - - @WithTransaction - async create(owner: User, session?: mongoose.mongo.ClientSession) { - const createdGame = new this.gameModel(); - createdGame.owner = owner; - createdGame.packs = [ - 'pack1_base_deck', - 'pack1_base_necros', - 'pack1_base_wild', - 'pack1_base_imperial', - 'pack1_base_guild', - ]; - await this.joinGame(createdGame, owner, session); - const dto = new ReadGameDto(createdGame); - this.eventEmitter.emit('sse.lobby', { type: 'create', ...dto }); - return dto; - } - - @WithTransaction - async joinGame( - game: Document & Game, - user: User, - session?: mongoose.mongo.ClientSession, - ) { - game.teams.push({ - players: [{ user } as Player], - } as Team); - - const team = game.teams.at(-1); - // this.eventEmitter.emit('sse.lobby', { - // type: 'joinGame', - // team: new ReadTeamDto(team), - // }); - this.eventEmitter.emit('sse.lobby', { - type: 'joinGame', - team: new ReadTeamDto(team), - game: game._id.toHexString(), - }); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'joinGame', - team: new ReadTeamDto(team), - }); - await game.save({ session }); - - return 'true!'; - } - - async findAll(): Promise { - const games = await this.gameModel.find().exec(); - return await Promise.all( - games.map(async (e) => { - return await new ReadGameDto(e); - }), - ); - } - - async findByOwner(_id: string): Promise { - const game = await this.gameModel.findOne({ owner: _id }).exec(); - if (!game) { - return null; - } - return new ReadGameDto(game); - } - - async update(game: Document & 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; - } - throw new HttpException('Pack not found', HttpStatus.NOT_FOUND); - } - - async remove(game: Document & Game) { - this.eventEmitter.emit('sse.lobby', { - type: 'delete', - _id: game._id, - }); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'delete', - }); - return await game.deleteOne(); - } - - @WithTransaction - async start( - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - const random = Math.floor(Math.random() * game.teams.length); - game.currentTurn.currentTeam = game.teams[random]._id.toHexString(); - await game.save({ session }); - game.started = true; - this.eventEmitter.emit('sse.lobby', { - type: 'start', - game: game._id, - }); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'start', - game: new ReadGameDto(game), - }); - - let playerIndex = 0; - for (const team of game.teams) { - for (const player of team.players) { - 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), - }); - await this.gamesService.shuffleContainer(player.stack, game, session); - playerIndex++; - } - } - - game.fireGems.cards = await this.cardModel.find({ - pack: { $in: game.packs }, - role: CardRole.FIRE_GEM, - }); - - game.marketStack.cards = await this.cardModel.find({ - pack: { $in: game.packs }, - role: CardRole.MARKET, - }); - - 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, session); - await this.gamesService.fillMarket(game, session); - for (const team of game.teams) { - for (const player of team.players) { - if (team._id.equals(game.currentTurn.currentTeam)) { - await this.gamesService.drawToHand(3, player, game, session); - } else { - await this.gamesService.drawToHand(5, player, game, session); - } - } - } - - const currentTeam = game.teams.find((t) => - t._id.equals(game.currentTurn.currentTeam), - ); - - for (const player of currentTeam.players) { - await this.gamesService.distributeCards( - player.hand.cards, - player.hand, - player.board, - game, - session, - ); - } - - await game.save({ session }); - } - - async changeStartingDeck( - data: StartingDeckDto, - player: Document & Player, - game: Document & Game, - ) { - const packs = await this.cardModel - .find({ pack: { $in: game.packs } }) - .distinct('role'); - - if ( - !Object.values(CardRole).includes(data.class) || - !Object.values(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); - } -} diff --git a/src/games/services/games.service.ts b/src/games/services/games.service.ts index 350660c..284939f 100644 --- a/src/games/services/games.service.ts +++ b/src/games/services/games.service.ts @@ -5,367 +5,29 @@ import { Inject, Injectable, } from '@nestjs/common'; -import { InjectModel, InjectConnection } from '@nestjs/mongoose'; -import { Container, Game, Player, Team } from '../schemas/game.schema'; -import mongoose, { Document, Model } from 'mongoose'; -import { Card } from 'src/cards/schemas/cards.schema'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Game } from '../schemas/game.schema'; +import { Repository } from 'typeorm'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { WithTransaction, getCurrentTeam, shuffle } from '../utils'; -import { TurnService } from './turn.service'; -import { CardRole } from 'src/cards/schemas/cards.types'; +import { Player } from '../schemas/player.schema'; import { User } from 'src/users/schemas/user.entity'; -import { ReadTeamDto } from '../dto/read-games.dto'; @Injectable() export class GameService { constructor( - @Inject(forwardRef(() => TurnService)) - private readonly turnService: TurnService, - @InjectModel(Game.name) private gameModel: Model, - @InjectModel(Card.name) private cardModel: Model, - @InjectConnection() private readonly connection: mongoose.Connection, + @InjectRepository(Game) private gameRepository: Repository, private eventEmitter: EventEmitter2, ) {} - @WithTransaction - async endTurn( - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - let currentTeam = getCurrentTeam(game); - - for (const player of currentTeam.players) { - if (player.discardMarkers > 0) { - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'player.removeDiscardMarker', - operation: -player.discardMarkers, - discardMarkers: 0, - playerId: player._id.toHexString(), - }); - player.discardMarkers = 0; - } - - if (player.fuckUMarkers > 0) { - player.fuckUMarkers--; - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'player.removeFuckUMarker', - operation: -player.fuckUMarkers, - fuckUMarkers: 0, - playerId: player._id.toHexString(), - }); - player.fuckUMarkers = 0; - } - - await this.distributeCards( - player.board.cards.filter((c) => !c.defense), - player.board, - player.discard, - game, - session, - ); - await this.drawToHand(5, player, game, session); - } - - const index = game.teams.indexOf(currentTeam); - - game.currentTurn.currentTeam = - game.teams[(index + 1) % game.teams.length]._id.toHexString(); - - game.currentTurn.cardsLocked = []; - game.currentTurn.lockedEffects = []; - game.currentTurn.tokens = []; - game.currentTurn.damage = 0; - game.currentTurn.gold = 0; - - await game.save({ session }); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'endTurn', - currentTurn: game.currentTurn, - }); - - currentTeam = getCurrentTeam(game); - - for (const player of currentTeam.players) { - for (const card of player.board.cards) { - if (card.defense) { - await this.turnService.lockCard(game, player, card, session); - } - } - } - - for (const player of currentTeam.players) { - await this.distributeCards( - player.hand.cards, - player.hand, - player.board, - game, - session, - ); - } - await game.save({ session }); + async start(game: Game) { + throw Error('Not Implemented'); } - @WithTransaction - async drawToBoard( - amount, - player: Player, - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - await this.drawCards( - amount, - player.stack, - player.board, - player.discard, - game, - session, - ); + async end(game: Game, winner: Player) { + throw Error('Not Implemented'); } - @WithTransaction - async drawToHand( - amount, - player: Player, - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - await this.drawCards( - amount, - player.stack, - player.hand, - player.discard, - game, - session, - ); - } - - @WithTransaction - private async drawCards( - amount: number, - from: Container, - to: Container, - discard: Container, - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - const shallow = [...from.cards]; - shallow.reverse(); - if (amount <= from.cards.length) { - await this.distributeCards( - shallow.slice(0, amount), - from, - to, - game, - session, - ); - await game.save({ session }); - return; - } else { - const toDraw = from.cards.length; - await this.distributeCards( - shallow.slice(0, toDraw), - from, - to, - game, - session, - ); - if (discard.cards.length == 0) { - await game.save({ session }); - return; - } - // restack all discarded - await this.distributeCards(discard.cards, discard, from, game, session); - await this.shuffleContainer(from, game, session); - await this.drawCards(amount - toDraw, from, to, discard, game, session); - } - } - - @WithTransaction - async fillMarket( - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - if (game.market.cards.length < 5) { - const amount = Math.min( - game.marketStack.cards.length, - 5 - game.market.cards.length, - ); - - await this.distributeCards( - game.marketStack.cards.slice(0, amount), - game.marketStack, - game.market, - game, - session, - ); - } - const missingIndexes = game.market.cards.reduce(function (a, e, i) { - if (e === null) a.push(i); - return a; - }, []); - if (missingIndexes.length > 0) { - console.error('not implemented'); - } - } - - @WithTransaction - async shuffleContainer( - container: Container, - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - container.cards = shuffle(container.cards); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'shuffleContainer', - container: container._id.toHexString(), - }); - await game.save({ session }); - } - - @WithTransaction - async distributeCards( - cards: Card[], - from: Container, - to: Container, - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - if (cards.some((c) => !from.cards.some((cc) => cc._id.equals(c._id)))) { - throw new HttpException( - 'Card not found in container', - HttpStatus.NOT_FOUND, - ); - } - const shallow = [...cards]; - for (const c of shallow) { - const index = from.cards.findIndex((cc) => cc?._id.equals(c._id)); - from.cards.splice(index, 1); - to.cards.push(c); - } - await game.save({ session }); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'distributeCards', - from: from._id.toHexString(), - to: to._id.toHexString(), - cards: shallow, - }); - } - - @WithTransaction - async destroyCard( - card: Card, - from: Container, - game: Document & Game, - session?: mongoose.mongo.ClientSession, - ) { - if (card.role == CardRole.FIRE_GEM) { - await this.distributeCards([card], from, game.fireGems, game, session); - return; - } - const index = from.cards.findIndex((cc) => cc?._id.equals(card._id)); - from.cards.splice(index, 1); - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'destroyCard', - from: from._id.toHexString(), - card: card._id, - }); - - await game.save({ session }); - } - - @WithTransaction - async killTeam( - game: Document & Game, - team: Team, - session?: mongoose.mongo.ClientSession, - ) { - const index = game.teams.findIndex((t) => t._id.equals(team._id)); - game.teams.splice(index, 1); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'killTeam', - teamId: team._id, - }); - await game.save({ session }); - if (game.teams.length <= 1) { - await this.endGame(game, game.teams[0], session); - return false; - } - return true; - } - - @WithTransaction - async leaveGame( - game: Document & Game, - user: User, - session?: mongoose.mongo.ClientSession, - ) { - const team = game.teams.find((t) => - t.players.find((p) => p.user._id.equals(user._id)), - ); - if (team == undefined) { - throw new HttpException('User not in game', HttpStatus.FORBIDDEN); - } - if (game.owner._id.equals(user._id)) { - throw new HttpException( - 'You cannot leave a game you own', - HttpStatus.FORBIDDEN, - ); - } - const player = team.players.find((p) => p.user._id.equals(user._id)); - await this.kickPlayer(game, game.owner, player._id.toHexString(), session); - } - - @WithTransaction - async kickPlayer( - game: Document & Game, - user: User, - kickedPlayerId: string, - session?: mongoose.mongo.ClientSession, - ) { - const team = game.teams.find((t) => - t.players.find((p) => p._id.equals(kickedPlayerId)), - ); - if (team == undefined) { - throw new HttpException('User not in game', HttpStatus.FORBIDDEN); - } - const index = team.players.findIndex((p) => p.user._id.equals(user._id)); - team.players.splice(index, 1); - this.eventEmitter.emit('sse.lobby', { - type: 'leaveTeam', - playerId: kickedPlayerId, - team: new ReadTeamDto(team), - game: game._id.toHexString(), - }); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'leaveTeam', - team: new ReadTeamDto(team), - playerId: kickedPlayerId, - }); - let gameExists = true; - if (team.players.length == 0) { - if (!game.started) { - const teamIndex = game.teams.indexOf(team); - game.teams.splice(teamIndex, 1); - } else { - gameExists = await this.killTeam(game, team, session); - } - } - if (gameExists) { - await game.save({ session }); - } - } - - @WithTransaction - async endGame( - game: Document & Game, - winner: Team, - session?: mongoose.mongo.ClientSession, - ) { - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'endGame', - winner: winner._id.toHexString(), - }); - await game.deleteOne({ session }); + async guessWord(game: Game, user: User, word: string) { + throw Error('Not Implemented'); } } diff --git a/src/games/services/lobby.service.ts b/src/games/services/lobby.service.ts new file mode 100644 index 0000000..48dc53a --- /dev/null +++ b/src/games/services/lobby.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { GameService } from './games.service'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Game } from '../schemas/game.schema'; +import { User } from 'src/users/schemas/user.entity'; +import { Player } from '../schemas/player.schema'; + +@Injectable() +export class LobbyService { + constructor( + @InjectRepository(Game) private gameRepository: Repository, + @InjectRepository(Player) private playerRepository: Repository, + private readonly gamesService: GameService, + private eventEmitter: EventEmitter2, + ) {} + + async create(owner: User) { + const createdGame = this.gameRepository.create(); + createdGame.owner = owner; + await this.joinGame(createdGame, owner); + this.eventEmitter.emit('sse.lobby', { type: 'create', ...createdGame }); + return createdGame; + } + + async joinGame(game: Game, user: User) { + const player = this.playerRepository.create(); + player.user = user; + game.players.push(); + + this.eventEmitter.emit('sse.lobby', { + type: 'joinGame', + game: game.id, + player: player, + }); + this.eventEmitter.emit('sse.game.' + game.id, { + type: 'joinGame', + player: player, + }); + this.playerRepository.save(player); + this.gameRepository.save(game); + } + + findByOwner(user: User) { + return this.gameRepository.findBy({ owner: user }); + } + + async leaveGame(game: Game, user: User) { + throw Error('not implemented'); + } + + async kickPlayer(game: Game, user: User, kickedPlayerId: string) { + throw Error('not implemented'); + } + + async findAll(): Promise { + return this.gameRepository.find(); + } + + async remove(game: Game) { + this.eventEmitter.emit('sse.lobby', { + type: 'delete', + _id: game.id, + }); + this.eventEmitter.emit('sse.game.' + game.id, { + type: 'delete', + }); + this.gameRepository.remove(game); + } +} diff --git a/src/games/services/turn.service.ts b/src/games/services/turn.service.ts deleted file mode 100644 index 21eddc9..0000000 --- a/src/games/services/turn.service.ts +++ /dev/null @@ -1,470 +0,0 @@ -import { - forwardRef, - HttpException, - HttpStatus, - Inject, - Injectable, -} from '@nestjs/common'; -import { Container, Game, Player } from '../schemas/game.schema'; -import mongoose, { Document, Model } from 'mongoose'; -import { Card } from 'src/cards/schemas/cards.schema'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { Effect } from 'src/cards/schemas/effect.schema'; -import { CardEffects } from 'src/cards/schemas/cards.types'; -import { getPlayerTeam, WithTransaction } from '../utils'; -import { - conditionsMappings, - effectMappings, - isAutoActivable, - perMapping, - tokenMappings, -} from './effect.functions'; -import { InjectConnection, InjectModel } from '@nestjs/mongoose'; -import { GameService } from './games.service'; - -@Injectable() -export class TurnService { - constructor( - private eventEmitter: EventEmitter2, - @Inject(forwardRef(() => GameService)) - private readonly gamesService: GameService, - @InjectModel(Game.name) private gameModel: Model, - @InjectConnection() private readonly connection: mongoose.Connection, - ) {} - - @WithTransaction - async lockCard( - game: Document & Game, - player: Player, - card: Card, - session?: mongoose.mongo.ClientSession, - ) { - game.currentTurn.cardsLocked.push(card); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'lockCard', - card: card._id.toHexString(), - }); - for (const effect of card.effects) { - if (isAutoActivable(effect)) { - await this.lockEffect(game, player, effect, card, session); - } - } - await game.save({ session }); - } - - @WithTransaction - async unlockEffect( - game: Document & Game, - player: Player, - effect: Effect, - card: Card, - session: mongoose.mongo.ClientSession, - ) { - const index = game.currentTurn.lockedEffects.findIndex((ee) => - ee._id.equals(effect._id), - ); - game.currentTurn.lockedEffects.splice(index, 1); - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'unlockEffect', - effect: effect._id.toHexString(), - }); - - for (const sub of effect.subEffects) { - await this.unlockEffect(game, player, sub, card, session); - } - - for (const e of [effect, ...effect.subEffects]) { - if (isAutoActivable(e)) { - await this.lockEffect(game, player, e, card, session); - } - } - await game.save({ session }); - } - - @WithTransaction - async lockEffect( - game: Document & Game, - player: Player, - effect: Effect, - card: Card, - session?: mongoose.mongo.ClientSession, - ) { - const effectRunner = effectMappings[effect.effect]; - if (!effectRunner) { - throw new HttpException( - `Effect ${effect.effect} not implemented`, - HttpStatus.NOT_IMPLEMENTED, - ); - } - // Process effect conditions - if (effect.condition) { - const effectCondition = conditionsMappings[effect.condition.effectId]; - if (!effectCondition) { - throw new HttpException( - `Condition ${effect.condition.effectId} not implemented`, - HttpStatus.NOT_IMPLEMENTED, - ); - } - const conditionSuccess = await effectCondition( - game, - player, - card, - effect.condition, - this.gamesService, - session, - ); - - if (!conditionSuccess) { - throw new HttpException( - `Condition ${effect.condition.effectId} not met`, - HttpStatus.BAD_REQUEST, - ); - } - } - const effectUUID = effect._id.toHexString(); - if (game.currentTurn.lockedEffects.some((e) => e._id.equals(effectUUID))) { - console.error(`Effect ${effectUUID} already used this turn`); - throw new HttpException( - `Effect ${effectUUID} already used this turn`, - HttpStatus.CONFLICT, - ); - } - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'lockEffect', - effect: effectUUID, - }); - - // Process effect mutexes - - if (effect.mutex) { - for (const e of card.effects) { - if (e.mutex == effect.mutex) { - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'lockEffect', - effect: e._id.toHexString(), - }); - game.currentTurn.lockedEffects.push(e); - } - } - } - - // Process effect Per - game.currentTurn.lockedEffects.push(effect); - let times = 1; - if (effect.per) { - times = await perMapping[effect.per](game, card); - } - for (let t = 0; t < times; t++) { - await effectRunner( - game, - effect, - player, - card, - this.gamesService, - this.eventEmitter, - session, - ); - } - - // process subeffects - if (effect.subEffects) { - for (const subeffect of effect.subEffects) { - await this.lockEffect(game, player, subeffect, effect.card, session); - } - } - - await game.save({ session }); - } - - @WithTransaction - async buyGeneric( - game: Document & Game, - card: Card, - fromContainer: Container, - toContainer: Container, - session?: mongoose.mongo.ClientSession, - ) { - if (!card.cost) { - throw new TypeError('Card cost is null or undefined'); - } - // const targetChangingEffects = [CardEffects.STACK_NEXT_CARD_BOUGHT]; - // if (card.cardType == CardType.ACTION) { - // targetChangingEffects.push(CardEffects.STACK_NEXT_ACTION_BOUGHT); - // } - // const targetChangingToken = game.currentTurn.tokens.find((e) => - // targetChangingEffects.includes(e.effect), - // ); - - // const forFreeEffects = [CardEffects.BUY_FOR_FREE]; - // if (card.role == CardRole.FIRE_GEM) { - // forFreeEffects.push(CardEffects.BUY_GEM_FOR_FREE); - // } - - // const forFreeToken = game.currentTurn.tokens.find((e) => - // forFreeEffects.includes(e.effect), - // ); - - // const reducedPriceEffects = []; - - // if (card.cardType == CardType.ACTION) { - // reducedPriceEffects.push(CardEffects.CHEAPER_ACTION); - // } - // if (card.cardType == CardType.CHAMPION) { - // reducedPriceEffects.push(CardEffects.CHEAPER_CHAMPION); - // reducedPriceEffects.push(CardEffects.CHEAPER_CHAMPIONS_PASSIVE); - // } - // // if(card.cost) - // CardEffects.CHEAPER_CARD_IF_HIGHER_PRICE - - // const reducedPriceToken = game.currentTurn.tokens.find((e) => - // forFreeEffects.includes(e.effect), - // ); - - const cost = card.cost; - // if (forFreeToken) { - // cost = 0; - // removeToken(game, targetChangingToken, this.eventEmitter); - // } - - if (game.currentTurn.gold < cost) { - throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); - } - // if (targetChangingToken) { - // targetContainer = player.stack; - // removeToken(game, targetChangingToken, this.eventEmitter); - // } - - await this.gamesService.distributeCards( - [card], - fromContainer, - toContainer, - game, - session, - ); - - if (cost != 0) { - game.currentTurn.gold -= cost; - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.updateGold', - gold: game.currentTurn.gold, - operation: -cost, - }); - } - - await game.save({ session }); - } - - @WithTransaction - async addFuckUMarker( - game: Document & Game, - token: Effect, - target: Player, - session?: mongoose.mongo.ClientSession, - ) { - if ( - target.hand.cards.length <= - target.fuckUMarkers + target.discardMarkers - ) { - throw new HttpException( - "This player doesn't have enough cards in the hand", - HttpStatus.BAD_REQUEST, - ); - } - target.fuckUMarkers++; - const index = game.currentTurn.tokens.findIndex((t) => - t._id.equals(token._id), - ); - if (index == -1) { - throw new Error('Token not found'); - } - game.currentTurn.tokens.splice(index, 1); - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'player.addFuckUMarker', - operation: 1, - fuckUMarkers: target.fuckUMarkers, - playerId: target._id.toHexString(), - }); - - await game.save({ session }); - } - - @WithTransaction - async addDiscardMarker( - game: Document & Game, - target: Player, - session?: mongoose.mongo.ClientSession, - ) { - const unlockedCards = target.board.cards.filter((c) => !c.isLocked(game)); - if ( - target.hand.cards.length + unlockedCards.length <= - target.fuckUMarkers + target.discardMarkers - ) { - throw new HttpException( - "This player doesn't have enough cards in the hand", - HttpStatus.BAD_REQUEST, - ); - } - target.discardMarkers++; - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'player.addDiscardMarker', - operation: 1, - discardMarkers: target.discardMarkers, - playerId: target._id.toHexString(), - }); - - await game.save({ session }); - } - - @WithTransaction - async useToken( - game: Document & Game, - player: Player, - token: Effect, - target: Card, - session?: mongoose.mongo.ClientSession, - ) { - const handler = tokenMappings[token.effect]; - if (!handler) { - throw new Error('Effect not implemented'); - } - await handler( - game, - token, - player, - target, - this.gamesService, - this, - this.eventEmitter, - session, - ); - if ( - token.effect != CardEffects.DRAW_AND_DISCARD || - (token.effect == CardEffects.DRAW_AND_DISCARD && - token.times <= game.currentTurn.drawAndDiscardCurrentAmount) - ) { - const index = game.currentTurn.tokens.findIndex((t) => - t._id.equals(token._id), - ); - game.currentTurn.tokens.splice(index, 1); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.useToken', - tokenId: token._id.toHexString(), - }); - } - - // process subeffects - if (token.subEffects) { - for (const subeffect of token.subEffects) { - await this.lockEffect(game, player, subeffect, token.card, session); - } - } - - await game.save({ session }); - } - - @WithTransaction - async damagePlayer( - game: Document & Game, - amount: number, - player: Player, - targetPlayer: Player, - session?: mongoose.mongo.ClientSession, - ) { - const team = getPlayerTeam(game, targetPlayer); - team.health -= amount; - game.currentTurn.damage -= amount; - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.updateDamage', - damage: game.currentTurn.damage, - operation: -amount, - }); - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'team.updateHealth', - team: team._id, - health: team.health, - operation: -amount, - }); - await game.save({ session }); - if (team.health <= 0) { - await this.gamesService.killTeam(game, team, session); - } - } - - @WithTransaction - async damageChampion( - game: Document & Game, - targetPlayer: Player, - targetCard: Card, - session?: mongoose.mongo.ClientSession, - ) { - game.currentTurn.damage -= targetCard.defense; - - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.updateDamage', - damage: game.currentTurn.damage, - operation: -targetCard.defense, - }); - - await this.gamesService.distributeCards( - [targetCard], - targetPlayer.board, - targetPlayer.discard, - game, - session, - ); - - await game.save({ session }); - } - - @WithTransaction - async discardCard( - game: Document & Game, - player: Player, - card: Card, - session?: mongoose.mongo.ClientSession, - ) { - await this.gamesService.distributeCards( - [card], - player.board, - player.discard, - game, - ); - if (player.discardMarkers > 0) { - player.discardMarkers--; - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'player.removeDiscardMarker', - operation: -1, - discardMarkers: player.discardMarkers, - playerId: player._id.toHexString(), - }); - } else if (player.fuckUMarkers > 0) { - player.fuckUMarkers--; - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'player.removeFuckUMarker', - operation: -1, - fuckUMarkers: player.fuckUMarkers, - playerId: player._id.toHexString(), - }); - } - - game.currentTurn.drawAndDiscardCurrentAmount = 0; - if (game.currentTurn.drawAndDiscardCurrentEffect) { - const tokenIndex = game.currentTurn.tokens.findIndex((t) => - t._id.equals(game.currentTurn.drawAndDiscardCurrentEffect._id), - ); - game.currentTurn.tokens.splice(tokenIndex, 1); - this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { - type: 'currentTurn.useToken', - tokenId: game.currentTurn.drawAndDiscardCurrentEffect._id.toHexString(), - }); - game.currentTurn.drawAndDiscardCurrentEffect = null; - } - - await game.save({ session }); - } -} diff --git a/src/games/utils.ts b/src/games/utils.ts deleted file mode 100644 index 0a08deb..0000000 --- a/src/games/utils.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { HttpStatus } from '@nestjs/common/enums'; -import { HttpException } from '@nestjs/common/exceptions'; -import mongoose, { Document } from 'mongoose'; -import { Game, Player, Team } from './schemas/game.schema'; -import { ReadUserDto } from './dto/read-games.dto'; - -export 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; -} - -export function getCurrentTeam(game: Document & Game) { - return game.teams.find((t) => t._id.equals(game.currentTurn.currentTeam)); -} - -export function getPlayer(session: Record, game: Game) { - const user = session.user as ReadUserDto; - if (!user) { - throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED); - } - - return findUser(user._id, game); -} - -export function findUser(playerId: string, game: Game) { - for (const team of game.teams) { - for (const player of team.players) { - if (player.user._id.equals(playerId)) { - return player as Document & Player; - } - } - } - throw new HttpException('User not found in game', HttpStatus.FORBIDDEN); -} - -export function findPlayer(playerId: string, game: Game) { - for (const team of game.teams) { - for (const player of team.players) { - if (player._id.equals(playerId)) { - return player as Document & Player; - } - } - } - throw new HttpException('Player not found in game', HttpStatus.FORBIDDEN); -} - -export function isInTeam(team: Team, player: Player) { - if (team.players.some((p) => p._id.equals(player._id))) { - return true; - } - return false; -} - -export function getPlayerTeam(game: Game, player: Player) { - return game.teams.find((t) => - t.players.some((p) => p._id.equals(player._id)), - ); -} -export function WithTransaction( - target: any, - propertyKey: string, - descriptor: PropertyDescriptor, -) { - const originalFunc = descriptor.value; - - descriptor.value = function (...args: any[]) { - return new Promise(async (resolve, reject) => { - try { - if (args.at(-1) instanceof mongoose.mongo.ClientSession) { - const data = await originalFunc.apply(this, args).catch((e) => { - if (e instanceof HttpException) { - throw e; - } - console.log(e); - reject( - new HttpException( - 'Another request is processing', - HttpStatus.TOO_MANY_REQUESTS, - ), - ); - return; - }); - resolve(data); - return data; - } else { - await this.connection - .transaction(async (session) => { - const data = await originalFunc.apply(this, [...args, session]); - resolve(data); - return data; - }) - .catch((e) => { - if (e instanceof HttpException) { - reject(e); - } - reject( - new HttpException( - 'Another request is processing', - HttpStatus.TOO_MANY_REQUESTS, - ), - ); - }); - } - } catch (e) { - reject(e); - } - }); - }; - - return descriptor; -}