diff --git a/src/common/IHandler.ts b/src/common/IHandler.ts new file mode 100644 index 0000000..933fbd2 --- /dev/null +++ b/src/common/IHandler.ts @@ -0,0 +1,4 @@ +export interface IHandler { + canHandle(): boolean | void; + handle(): void; +} diff --git a/src/common/base.handler.ts b/src/common/base.handler.ts new file mode 100644 index 0000000..bc09763 --- /dev/null +++ b/src/common/base.handler.ts @@ -0,0 +1,15 @@ +import { IHandler } from './IHandler'; + +export abstract class BaseHandler implements IHandler { + constructor( + protected analyzer: T, + private handler?: () => void + ) {} + + abstract canHandle(): boolean | void; + handle() { + if (this.handler) { + this.handler(); + } + } +} diff --git a/src/common/handlerChain.ts b/src/common/handlerChain.ts new file mode 100644 index 0000000..fcf2df2 --- /dev/null +++ b/src/common/handlerChain.ts @@ -0,0 +1,17 @@ +import { IHandler } from './IHandler'; + +export class HandlerChain { + private handlers: IHandler[] = []; + addHandlers(...handlers: IHandler[]): void { + this.handlers.push(...handlers); + } + + process(): unknown | Promise { + for (const handler of this.handlers) { + if (handler.canHandle()) { + return handler.handle(); + } + } + console.log('No handler found for this data'); + } +} diff --git a/src/field/FieldAnalyzer.ts b/src/field/FieldAnalyzer.ts new file mode 100644 index 0000000..a9f8e09 --- /dev/null +++ b/src/field/FieldAnalyzer.ts @@ -0,0 +1,47 @@ +import { GamePayload } from 'src/game/game.repository'; +import { PlayerPayload } from 'src/player/player.repository'; +import { PlayerService } from 'src/player/player.service'; +import { FieldDocument } from 'src/schema/Field.schema'; + +export class FieldAnalyzer { + readonly currentPlayer: Partial; + + constructor( + readonly field: FieldDocument, + readonly game: Partial, + private playerService: PlayerService + ) { + this.currentPlayer = this.playerService.findPlayerWithTurn(game); + } + isOwnedByCurrentUser(): boolean { + return ( + this.field.ownedBy === this.currentPlayer.userId && this.field.price > 0 + ); + } + isOwnedByOtherAndNotPledged(): boolean { + return ( + this.field.ownedBy && + this.field.ownedBy !== this.currentPlayer.userId && + !this.field.isPledged + ); + } + isNotOwned(): boolean { + return !this.field.ownedBy && this.field.price > 0; + } + isAffordableForSomeone(): boolean { + return this.game.players.some( + (player) => + player.userId !== this.currentPlayer.userId && + player.money > this.field.price + ); + } + isSpecialField(): boolean { + return !this.field.price; + } + isSkipable(): boolean { + return ( + (this.field?.specialField && !this.field.secret && !this.field.toPay) || + this.field.isPledged + ); + } +} diff --git a/src/game/game.gateway.ts b/src/game/game.gateway.ts index fc32878..326f553 100644 --- a/src/game/game.gateway.ts +++ b/src/game/game.gateway.ts @@ -1,6 +1,5 @@ import { UseFilters, UseGuards, UsePipes } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { OnEvent } from '@nestjs/event-emitter'; import { JwtService } from '@nestjs/jwt'; import { ConnectedSocket, @@ -8,31 +7,19 @@ import { SubscribeMessage, WebSocketGateway, WebSocketServer, - WsException, } from '@nestjs/websockets'; import { parse } from 'cookie'; import { Server, Socket } from 'socket.io'; -import { HasLostGuard } from 'src/auth/guard'; -import { ActiveGameGuard } from 'src/auth/guard/activeGame.guard'; -import { WsGuard } from 'src/auth/guard/jwt.ws.guard'; -import { TurnGuard } from 'src/auth/guard/turn.guard'; +import { AuctionService } from 'src/auction/auction.service'; +import { TurnGuard, ValidPlayerGuard, WsGuard } from 'src/auth/guard'; import { JwtPayload } from 'src/auth/types/jwtPayloadType.type'; import { WsValidationPipe } from 'src/pipes/wsValidation.pipe'; -import { PlayerPayload } from 'src/player/player.repository'; -import { PlayerService } from 'src/player/player.service'; +import { TimerService } from 'src/timer/timers.service'; import { WebsocketExceptionsFilter } from 'src/utils/exceptions/websocket-exceptions.filter'; -import { WebSocketServerService } from 'src/webSocketServer/webSocketServer.service'; -import { ChatService } from './../chat/chat.service'; +import { WebSocketProvider } from 'src/webSocketProvider/webSocketProvider.service'; import { GetGameId } from './decorators/getGameCookieWs'; import { GamePayload } from './game.repository'; import { GameService } from './game.service'; -import { Auction } from './types/auction.type'; -import { Trade } from './types/trade.type'; -import { FieldDocument } from 'src/schema/Field.schema'; -import { AuctionService } from 'src/auction/auction.service'; -import { TimerService } from 'src/timer/timers.service'; -import { SecretService } from 'src/secret/secret.service'; -import { FieldValidator } from 'src/field/FieldValidator'; @WebSocketGateway({ cors: { @@ -50,32 +37,17 @@ import { FieldValidator } from 'src/field/FieldValidator'; export class GameGateway { constructor( private gameService: GameService, - private playerService: PlayerService, + private webSocketProvider: WebSocketProvider, private jwtService: JwtService, private configService: ConfigService, - private chatService: ChatService, - private webSocketServer: WebSocketServerService, private auctionService: AuctionService, - private timerService: TimerService, - private secretService: SecretService - ) { - this.rollDice = this.rollDice.bind(this); - this.putUpForAuction = this.putUpForAuction.bind(this); - this.passTurnToNext = this.passTurnToNext.bind(this); - this.winAuction = this.winAuction.bind(this); - this.payForField = this.payForField.bind(this); - this.payToBank = this.payToBank.bind(this); - this.payAll = this.payAll.bind(this); - this.resolveTwoUsers = this.resolveTwoUsers.bind(this); - } - + private timerService: TimerService + ) {} @WebSocketServer() - private server: Server; - + readonly server: Server; afterInit(server: Server) { - this.webSocketServer.setServer(server); + this.webSocketProvider.setServer(server); } - async handleConnection(socket: Socket & { jwtPayload: JwtPayload }) { try { const cookies = await this.extractCookies(socket); @@ -85,7 +57,7 @@ export class GameGateway { if (!userId) return; socket.join(userId); const game = await this.gameService.getGame(gameId); - if (!game || game.status !== 'ACTIVE') return; + if (game.status !== 'ACTIVE') return; const timers = this.timerService.timers; if (timers.has(gameId)) { this.rejoinGame(socket, gameId); @@ -95,21 +67,16 @@ export class GameGateway { this.rejoinGame(socket, gameId); const currentField = await this.gameService.findCurrentFieldFromGame(game); - let timerCallback: (args: unknown) => Promise; if (game.dices) { - timerCallback = currentField?.price - ? this.putUpForAuction - : this.passTurnToNext; + this.gameService.processRolledDices(game, currentField); } else { - timerCallback = this.rollDice; + this.timerService.set( + gameId, + updatedGame.timeOfTurn, + updatedGame, + this.gameService.rollDice + ); } - - this.timerService.set( - gameId, - updatedGame.timeOfTurn, - updatedGame, - timerCallback - ); } catch (err) { console.log(err); } @@ -146,61 +113,51 @@ export class GameGateway { } @SubscribeMessage('getVisibleGames') - async getVisibleGames() { + async onGetVisibleGames() { const games = await this.gameService.getVisibleGames(); return games; } @SubscribeMessage('getGameData') - async getGameData(@GetGameId() gameId: string) { - const game = await this.gameService.getGame(gameId); - const auction = this.auctionService.auctions.get(game.id); - const secretInfo = this.secretService.secrets.get(game.id); - const fields = await this.gameService.getGameFields(game.id); - this.server.emit('gameData', { game, fields, auction, secretInfo }); + async onGetGameData(@GetGameId() gameId: string) { + const { game, fields, auction, secretInfo } = + await this.gameService.getGameData(gameId); + this.server + .to(gameId) + .emit('gameData', { game, fields, auction, secretInfo }); + } + + @SubscribeMessage('createGame') + async createGame(socket: Socket & { jwtPayload: JwtPayload }) { + const createdGameWithPlayer = await this.gameService.createGame( + socket.jwtPayload.sub + ); + socket.join(createdGameWithPlayer.id); + return this.server.emit('newGameCreated', createdGameWithPlayer); } @SubscribeMessage('joinGame') async onJoinGame( socket: Socket & { jwtPayload: JwtPayload }, - data: { id: string } + dataArray: [{ id: string }, null] ) { - const { game, shouldStart } = await this.gameService.onJoinGame( + const data = dataArray[0]; + const game = await this.gameService.joinGame( data.id, socket.jwtPayload.sub ); - if (!game) { - this.server - .to(socket.id) - .emit('error', { message: 'Could not join game' }); - return; - } this.leaveAllRoomsExceptInitial(socket); socket.join(data.id); this.server.emit('onParticipateGame', game); - if (shouldStart) { - this.startGame(game); - } - } - - private startGame(game: Partial) { - // We can setTimeout here for some countdown on frontend - this.server.emit('clearStartedGame', { - gameId: game.id, - }); - this.server.to(game.id).emit('startGame', { - game, - chatId: game.chat.id, - }); - this.timerService.set(game.id, game.timeOfTurn, game, this.rollDice); } @SubscribeMessage('leaveGame') async onLeaveGame( socket: Socket & { jwtPayload: JwtPayload }, - data: { id: string } + dataArray: [{ id: string }, null] ) { - const game = await this.gameService.onLeaveGame( + const data = dataArray[0]; + const game = await this.gameService.leaveGame( data.id, socket.jwtPayload.sub ); @@ -213,683 +170,196 @@ export class GameGateway { } } - @UseGuards(ActiveGameGuard, TurnGuard, HasLostGuard) + @UseGuards(ValidPlayerGuard, TurnGuard) @SubscribeMessage('rollDice') async onRollDice( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - if (socket.game.dices) - throw new WsException('You have already rolled dices'); - this.timerService.clear(socket.game.id); - await this.rollDice(socket.game); - } - - async rollDice(game: Partial) { - const { updatedGame, playerNextField, hasOwner, currentPlayer, fields } = - await this.gameService.makeTurn(game); - this.server.to(game.id).emit('rolledDice', { - fields, - game: updatedGame, - }); - const fieldValidator = new FieldValidator( - playerNextField, - updatedGame, - this.playerService - ); - if (fieldValidator.isOwnedByCurrentUser()) { - this.timerService.set(game.id, 2500, updatedGame, this.passTurnToNext); - } - if (fieldValidator.isOwnedByOtherAndNotPledged()) { - this.steppedOnPrivateField(currentPlayer, playerNextField, updatedGame); - return; - } - if (fieldValidator.isNotOwned()) { - if (fieldValidator.isAffordableForSomeone()) { - this.timerService.set( - game.id, - game.timeOfTurn, - updatedGame, - this.putUpForAuction - ); - } else { - this.timerService.set(game.id, 2500, updatedGame, this.passTurnToNext); - } - } - - if (fieldValidator.isSpecialField()) { - this.processSpecialField(updatedGame, playerNextField); - } - if (fieldValidator.isSkipable()) { - this.timerService.set(game.id, 2500, updatedGame, this.passTurnToNext); - } + await this.gameService.rollDice(socket.game); } - async processSpecialField( - game: Partial, - playerNextField: FieldDocument + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('payToBankForSpecialField') + async onPayToBankForSpecialField( + @ConnectedSocket() + socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - if (playerNextField.toPay) { - this.timerService.set( - game.id, - game.timeOfTurn, - { game, userId: game.turnOfUserId, amount: playerNextField.toPay }, - this.payToBank - ); - } - if (playerNextField.secret) { - const secret = this.gameService.getRandomSecret(); - const secretInfo = await this.secretService.parseAndSaveSecret( - secret, - game - ); - const randomPlayer = game.players.find( - (player) => player.userId === secretInfo.users[1] - ); - if (secret.text.includes('$RANDOM_PLAYER$')) { - secret.text = secret.text.replace( - '$RANDOM_PLAYER$', - randomPlayer?.user.nickname - ); - } - const message = await this.chatService.onNewMessage(game.turnOfUserId, { - text: secret.text, - chatId: game.chat.id, - }); - this.server.to(game.id).emit('gameChatMessage', message); - this.server.to(game.id).emit('secret', secretInfo); - secret.text = secret.text.replace( - randomPlayer?.user.nickname, - '$RANDOM_PLAYER$' - ); - if (secretInfo.users.length === 1) { - if (secretInfo.amounts[0] < 0) { - this.timerService.set( - game.id, - game.timeOfTurn, - { game, userId: game.turnOfUserId, amount: secretInfo.amounts[0] }, - this.payToBank - ); - } else { - this.timerService.set( - game.id, - 3500, - { game, userId: game.turnOfUserId, amount: secretInfo.amounts[0] }, - this.payToBank - ); - } - } else if (secretInfo.users.length === 2) { - this.timerService.set( - game.id, - game.timeOfTurn, - game, - this.resolveTwoUsers - ); - } else if (secretInfo.users.length > 2) { - this.timerService.set(game.id, game.timeOfTurn, game, this.payAll); - } - } - } - - async payAll(game: Partial) { - const secretInfo = this.secretService.secrets.get(game.id); - let updatedPlayer = null; - for (const userId of secretInfo.users) { - const firstUser = secretInfo.users[0]; - if (userId && userId !== firstUser) { - if (secretInfo.amounts.length === 2) { - updatedPlayer = await this.payToUser({ - game, - userId, - userToPayId: firstUser, - amount: secretInfo.amounts[1], - }); - } - - if (secretInfo.amounts.length === 1) { - const { playerWhoPayed } = await this.gameService.payToBank( - game, - userId, - secretInfo.amounts[0] - ); - updatedPlayer = playerWhoPayed; - } - } - } - this.secretService.secrets.delete(game.id); - this.passTurnToNext(updatedPlayer?.game || game); - this.server.to(game.id).emit('updatePlayers', { - game: updatedPlayer?.game, - }); - } - - async resolveTwoUsers(game: Partial) { - let secretInfo = this.secretService.secrets.get(game.id); - const firstPay = secretInfo.amounts[0] < 1; - let updatedGameToReturn: null | Partial = null; - const fields = await this.gameService.getGameFields(game.id); - if (firstPay) { - const userId = secretInfo.users[0]; - if (userId) { - const player = game.players.find((player) => player.userId === userId); - if ( - this.playerService.estimateAssets(player, fields) < - secretInfo.amounts[0] - ) { - await this.playerService.loseGame(player.userId, game.id, fields); - return; - } - const { updatedGame } = await this.gameService.payToBank( - game, - userId, - secretInfo.amounts[0] - ); - updatedGameToReturn = updatedGame; - } - if (secretInfo.users[1]) { - const { updatedGame } = await this.gameService.payToBank( - game, - secretInfo.users[1], - secretInfo.amounts[1] - ); - updatedGameToReturn = updatedGame; - } - } else { - const userId = secretInfo.users[1]; - if (userId) { - const player = game.players.find((player) => player.userId === userId); - if ( - this.playerService.estimateAssets(player, fields) < - secretInfo.amounts[1] - ) { - await this.playerService.loseGame(player.userId, game.id, fields); - return; - } - const { updatedGame } = await this.gameService.payToBank( - game, - userId, - secretInfo.amounts[1] - ); - updatedGameToReturn = updatedGame; - } - if (secretInfo.users[0]) { - const { updatedGame } = await this.gameService.payToBank( - game, - secretInfo.users[0], - secretInfo.amounts[0] - ); - updatedGameToReturn = updatedGame; - } - } - secretInfo = null; - this.passTurnToNext(updatedGameToReturn); + await this.gameService.payToBankForSpecialField( + socket.jwtPayload.sub, + socket.game + ); } - @UseGuards(ActiveGameGuard, HasLostGuard) - @SubscribeMessage('payToUser') - async onPayToUser( + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('payToUserForSecret') + async onPayToUserForSecret( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { const game = socket.game; const userId = socket.jwtPayload.sub; - const secretInfo = this.secretService.secrets.get(game.id); - return this.payToUser({ - game, - userId, - userToPayId: secretInfo.users[0], - amount: secretInfo.amounts[1], - }); - } - - async payToUser({ - game, - userId, - userToPayId, - amount, - }: { - game: Partial; - userId: string; - userToPayId: string; - amount: number; - }) { - let secretInfo = this.secretService.secrets.get(game.id); - if (!secretInfo.users.includes(userId)) - throw new WsException('You cant pay for that secret'); - const indexOfUser = secretInfo.users.indexOf(userId); - if (amount > 0) - throw new WsException('You dont have to pay for this secret field'); - const player = game.players.find((player) => player.userId === userId); - const fields = await this.gameService.getGameFields(game.id); - let updatedPlayer = null; - if (player.money < amount) { - const userToPay = game.players.find( - (player) => player.userId === userToPayId - ); - updatedPlayer = await this.playerService.incrementMoneyWithUserAndGameId( - userToPayId, - game.id, - this.playerService.estimateAssets(userToPay, fields) - ); - await this.playerService.loseGame(player.userId, game.id, fields); - } else { - await this.playerService.incrementMoneyWithUserAndGameId( - userId, - game.id, - amount - ); - updatedPlayer = await this.playerService.incrementMoneyWithUserAndGameId( - userToPayId, - game.id, - -amount - ); - } - secretInfo.users.splice(indexOfUser, 1, ''); - if ( - secretInfo.users.every((userId, index) => { - if (secretInfo.amounts[index] > 0) return true; - return userId === ''; - }) - ) { - secretInfo = null; - } + const { game: updatedGame, secretInfo } = + await this.gameService.payToUserForSecret(game, userId); this.server.to(game.id).emit('updatePlayers', { - game: updatedPlayer.game, + game: updatedGame, secretInfo, }); - return updatedPlayer; } - @UseGuards(ActiveGameGuard, HasLostGuard) - @SubscribeMessage('payToBank') - async onPayToBank( + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('payToBankForSecret') + async onPayToBankForSecret( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - const currentField = await this.gameService.findCurrentFieldWithUserId( - socket.game - ); - const secretInfo = this.secretService.secrets.get(socket.game.id); - if (!socket.game.dices && !currentField.toPay && !secretInfo) - throw new WsException( - 'You cant pay for that field because smt is missing' - ); - let amountToPay = 0; - if (secretInfo) { - if (!secretInfo.users.includes(socket.jwtPayload.sub)) { - throw new WsException( - 'You cant pay to bank because no user in secretInfo' - ); - } - if (secretInfo.users.length === 1) { - if (secretInfo.amounts[0] > 0) { - throw new WsException( - 'You cant pay to bank because one user and he doesnt have to pay' - ); - } else { - amountToPay = secretInfo.amounts[0]; - } - } else if (secretInfo.users.length === 2) { - const index = secretInfo.users.findIndex( - (userId) => userId === socket.jwtPayload.sub - ); - if (secretInfo.amounts[index] > 0) { - throw new WsException( - 'You cant pay to bank two users and the one wants to pay dont have to' - ); - } else { - amountToPay = secretInfo.amounts[0]; - } - } else if ( - secretInfo.users.length > 2 && - socket.jwtPayload.sub !== secretInfo.users[0] - ) { - if (secretInfo.amounts.length === 2) { - if (secretInfo.amounts[1] > 0) { - throw new WsException( - 'You cant pay to bank more then 2 and this doesnt lya lya' - ); - } else { - amountToPay = secretInfo.amounts[0]; - } - } - - if (secretInfo.amounts.length === 1) { - if (secretInfo.amounts[0] > 0) { - throw new WsException( - 'You cant pay to bank more than 2 one amount and dont have t' - ); - } else { - amountToPay = secretInfo.amounts[0]; - } - } - if ( - secretInfo.users.every((userId, index) => { - if (secretInfo.amounts[index] > 0) return true; - return userId === ''; - }) - ) { - throw new WsException( - 'You cant pay to bank every user is empty string' - ); - } - } - } - if (currentField.toPay) { - this.payToBank({ - game: socket.game, - userId: socket.jwtPayload.sub, - amount: currentField.toPay, - }); - } - if (secretInfo) { - this.payToBank({ - game: socket.game, - userId: socket.jwtPayload.sub, - amount: amountToPay, - }); - const indexOfUser = secretInfo.users.findIndex( - (userId) => userId === socket.jwtPayload.sub - ); - secretInfo.users[indexOfUser] = ''; - } - } - - async payToBank(argsObj: { - game: Partial; - amount: number; - userId: string; - }) { - const { updatedGame } = await this.gameService.payToBank( - argsObj.game, - argsObj.userId, - argsObj.amount - ); - const secretInfo = this.secretService.secrets.get(updatedGame.id); - if (!secretInfo) { - await this.passTurnToNext(updatedGame); - } else { - this.server.to(updatedGame.id).emit('updatePlayers', { - game: updatedGame, - secretInfo, - }); - } - } - - async steppedOnPrivateField( - player: Partial, - field: FieldDocument, - game: Partial - ) { - const fields = await this.gameService.getGameFields(game.id); - if ( - this.playerService.estimateAssets(player, fields) >= - field.income[field.amountOfBranches] - ) { - this.timerService.set( - game.id, - game.timeOfTurn, - { game, field: field }, - this.payForField - ); - return; - } - const { updatedPlayer } = await this.playerService.loseGame( - player.userId, - game.id, - fields - ); - - await this.passTurnToNext(updatedPlayer.game); + const game = socket.game; + const userId = socket.jwtPayload.sub; + await this.gameService.payToBankForSecret(game, userId); } - @UseGuards(ActiveGameGuard, TurnGuard, HasLostGuard) - @SubscribeMessage('payForField') - async onPayForField( + @UseGuards(ValidPlayerGuard, TurnGuard) + @SubscribeMessage('payForPrivateField') + async onPayForPrivateField( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - const currentField = await this.gameService.findCurrentFieldWithUserId( - socket.game - ); - if (!socket.game.dices || !currentField.ownedBy) - throw new WsException('You cant pay for that field'); - this.timerService.clear(socket.game.id); - await this.payForField({ game: socket.game, field: currentField }); - } - - async payForField({ - game, - field, - }: { - game: Partial; - field: FieldDocument; - }) { - const { updatedGame, fields: updatedFields } = - await this.gameService.payForField(game, field); - this.server.to(game.id).emit('payedForField', { - game: updatedGame, - fields: updatedFields, - }); - this.passTurnToNext(updatedGame); + await this.gameService.payForPrivateField(socket.game); } - @UseGuards(ActiveGameGuard, TurnGuard, HasLostGuard) + @UseGuards(ValidPlayerGuard, TurnGuard) @SubscribeMessage('putUpForAuction') async onPutUpForAuction( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - await this.putUpForAuction(socket.game); + await this.gameService.putUpForAuction(socket.game); } - async putUpForAuction(game: Partial) { - const auction = await this.auctionService.putUpForAuction(game); - - const updatedGame = await this.gameService.updateGameWithNewTurn( - game, - 15000 - ); - this.server - .to(game.id) - .emit('hasPutUpForAuction', { game: updatedGame, auction }); - this.timerService.set(game.id, 15000, updatedGame, this.passTurnToNext); - } - - @SubscribeMessage('createGame') - async createGame(socket: Socket & { jwtPayload: JwtPayload }) { - const createdGameWithPlayer = await this.gameService.createGame( - socket.jwtPayload.sub - ); - - if (!createdGameWithPlayer) { - return socket.emit('error', { - message: - 'Ви вже знаходитесь в кімнаті, покиньте її щоб приєднатись до іншої', - }); - } else { - socket.join(createdGameWithPlayer.id); - } - - return this.server.emit('newGameCreated', createdGameWithPlayer); - } - - @UseGuards(ActiveGameGuard, HasLostGuard) + @UseGuards(ValidPlayerGuard) @SubscribeMessage('raisePrice') async raisePrice( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload }, @GetGameId() gameId: string, - @MessageBody('raiseBy') raiseBy: number, - @MessageBody('bidAmount') bidAmount: number + @MessageBody() dataArray: [{ raiseBy: number; bidAmount: number }, null] ) { + const data = dataArray[0]; const userId = socket.jwtPayload.sub; - const auction = await this.auctionService.raisePrice( + await this.auctionService.raisePrice( gameId, userId, - raiseBy, - bidAmount + data.raiseBy, + data.bidAmount ); - - if (auction) { - this.server.to(gameId).emit('raisedPrice', { auction }); - this.timerService.set( - gameId, - 15000, - { ...auction, gameId }, - this.winAuction - ); - } else { - this.server.to(userId).emit('error', { - message: 'Хтось одночасно поставив з вами і перебив вашу ставку', - }); - } } - @UseGuards(ActiveGameGuard, HasLostGuard) + @UseGuards(ValidPlayerGuard) @SubscribeMessage('refuseAuction') async refuseAuction( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload }, @GetGameId() gameId: string ) { const userId = socket.jwtPayload.sub; - const { auction, hasWinner, finished, game } = - await this.auctionService.refuseAuction(gameId, userId); - if (finished) { - if (hasWinner) { - this.winAuction({ ...auction, gameId }); - } else { - this.passTurnToNext(game); - } - } else { - this.server.to(gameId).emit('refusedFromAuction', { auction }); - } - } - - async winAuction(auction: Auction & { gameId: string }) { - const { updatedPlayer, fields } = - await this.auctionService.winAuction(auction); - this.server - .to(auction.gameId) - .emit('wonAuction', { auction, game: updatedPlayer.game, fields }); - const game = await this.gameService.getGame(auction.gameId); - this.passTurnToNext(game); + await this.auctionService.refuseAuction(gameId, userId); } - @UseGuards(ActiveGameGuard, TurnGuard, HasLostGuard) + @UseGuards(ValidPlayerGuard, TurnGuard) @SubscribeMessage('buyField') async onBuyField( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - await this.buyField(socket.game); - } - - async buyField(game: Partial) { - await this.gameService.buyField(game); - this.passTurnToNext(game); + await this.gameService.buyField(socket.game); } - @UseGuards(ActiveGameGuard, TurnGuard, HasLostGuard) + @UseGuards(ValidPlayerGuard, TurnGuard) @SubscribeMessage('passTurn') async onPassTurn( @ConnectedSocket() socket: Socket & { jwtPayload: JwtPayload; game: Partial } ) { - const fields = await this.gameService.getGameFields(socket.game.id); - const currentPlayer = this.playerService.findPlayerWithTurn(socket.game); - const currentField = this.gameService.findPlayerFieldByIndex( - fields, - currentPlayer.currentFieldIndex - ); - if (!currentField.large && currentField.ownedBy !== socket.jwtPayload.sub) { - throw new WsException('You cant pass turn with that field'); - } - await this.passTurnToNext(socket.game); + await this.gameService.passTurn(socket.game); } - async passTurnToNext(game: Partial) { - const { updatedGame } = await this.gameService.passTurnToNext(game); - const fields = await this.gameService.getGameFields(game.id); + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('buyBranch') + async onBuyBranch( + @ConnectedSocket() + socket: Socket & { game: Partial; jwtPayload: JwtPayload }, + @MessageBody() dataArray: [{ index: number }, null] + ) { + const data = dataArray[0]; + const index = data.index; + const game = socket.game; + const userId = socket.jwtPayload.sub; + const { updatedGame, fields } = await this.gameService.buyBranch( + game, + index, + userId + ); this.server .to(game.id) - .emit('passTurnToNext', { game: updatedGame, fields }); - this.timerService.set(game.id, game.timeOfTurn, updatedGame, this.rollDice); + .emit('updateGameData', { fields, game: updatedGame }); } - @OnEvent('passTurnToNext') - async handlePassTurnToNext(data: { game: Partial }) { - this.passTurnToNext(data.game); - } - @OnEvent('offerTrade') - async handleOfferTrade(data: { game: Partial; trade: Trade }) { - const { updatedGame } = await this.gameService.passTurnToUser({ - game: data.game, - toUserId: data.trade.toUserId, - }); - this.server.to(data.game.id).emit('updateGameData', { game: updatedGame }); - this.server - .to(data.trade.toUserId) - .emit('tradeOffered', { trade: data.trade }); - this.timerService.set( - updatedGame.id, - 10000, - updatedGame, - this.playerService.refuseFromTrade + + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('sellBranch') + async onSellBranch( + @ConnectedSocket() + socket: Socket & { game: Partial; jwtPayload: JwtPayload }, + @MessageBody() dataArray: [{ index: number }, null] + ) { + const data = dataArray[0]; + const index = data.index; + const game = socket.game; + const userId = socket.jwtPayload.sub; + const { updatedGame, fields } = await this.gameService.sellBranch( + game, + index, + userId ); + this.server + .to(game.id) + .emit('updateGameData', { fields, game: updatedGame }); } - @OnEvent('setRollDiceTimer') - async handleSetRollDiceTimer(game: Partial) { - this.timerService.set(game.id, game.timeOfTurn, game, this.rollDice); + + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('surrender') + async surrender( + @ConnectedSocket() + socket: Socket & { game: Partial; jwtPayload: JwtPayload } + ) { + const userId = socket.jwtPayload.sub; + const gameId = socket.game.id; + + const { updatedPlayer, updatedFields } = await this.gameService.loseGame( + userId, + gameId + ); + + this.server.to(gameId).emit('playerSurrendered', { + game: updatedPlayer.game, + fields: updatedFields, + }); } - @OnEvent('setAfterRolledDiceTimer') - async handleSetAfterRolledDiceTimer(updatedGame: Partial) { - const currentPlayer = this.playerService.findPlayerWithTurn(updatedGame); - const fields = await this.gameService.getGameFields(updatedGame.id); - const playerNextField = this.gameService.findPlayerFieldByIndex( - fields, - currentPlayer.currentFieldIndex + + @UseGuards(ValidPlayerGuard) + @SubscribeMessage('mortgageField') + async onMortgageField( + @ConnectedSocket() + socket: Socket & { game: Partial; jwtPayload: JwtPayload }, + @MessageBody() dataArray: [{ index: number }, null] + ) { + const data = dataArray[0]; + const index = data.index; + const game = socket.game; + const { player, fields } = await this.gameService.mortgageField( + game, + index, + socket.jwtPayload.sub ); - if ( - playerNextField.price && - playerNextField.ownedBy === updatedGame?.turnOfUserId - ) { - this.timerService.set( - updatedGame.id, - 2500, - updatedGame, - this.passTurnToNext - ); - } - if ( - playerNextField.ownedBy && - playerNextField.ownedBy !== currentPlayer.userId - ) { - this.steppedOnPrivateField(currentPlayer, playerNextField, updatedGame); - return; - } - if (playerNextField.price && !playerNextField.ownedBy) { - this.timerService.set( - updatedGame.id, - updatedGame.timeOfTurn, - updatedGame, - this.putUpForAuction - ); - } - if (!playerNextField.price) { - this.processSpecialField(updatedGame, playerNextField); - } - const currentField = - await this.gameService.findCurrentFieldWithUserId(updatedGame); - if ( - currentField?.specialField && - !currentField.secret && - !currentField.toPay - ) { - this.timerService.set( - updatedGame.id, - 12000, - updatedGame, - this.passTurnToNext - ); - } + this.server + .to(game.id) + .emit('updateGameData', { fields, game: player.game }); } } diff --git a/src/game/handlers/AllPlayersInvolved.handler.ts b/src/game/handlers/AllPlayersInvolved.handler.ts new file mode 100644 index 0000000..967601d --- /dev/null +++ b/src/game/handlers/AllPlayersInvolved.handler.ts @@ -0,0 +1,11 @@ +import { SecretAnalyzer } from 'src/secret/secretAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +import { WsException } from '@nestjs/websockets'; +export class AllPlayersInvolvedHandler extends BaseHandler { + throw() { + throw new WsException('You get money from all u dont have to pay'); + } + canHandle() { + return this.analyzer.isAllPlayersInvolved() || this.throw(); + } +} diff --git a/src/game/handlers/onePlayerInvolved.handler.ts b/src/game/handlers/onePlayerInvolved.handler.ts new file mode 100644 index 0000000..3127835 --- /dev/null +++ b/src/game/handlers/onePlayerInvolved.handler.ts @@ -0,0 +1,16 @@ +import { SecretAnalyzer } from 'src/secret/secretAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +import { WsException } from '@nestjs/websockets'; +export class OnePlayerInvolvedHandler extends BaseHandler { + canHandle() { + return this.analyzer.inOnePlayerInvolved(); + } + + handle() { + if (this.analyzer.secretInfo.amounts[0] > 0) { + throw new WsException( + 'You cant pay to bank because one user and he doesnt have to pay' + ); + } + } +} diff --git a/src/game/handlers/passTurn.handler.ts b/src/game/handlers/passTurn.handler.ts new file mode 100644 index 0000000..9949e7a --- /dev/null +++ b/src/game/handlers/passTurn.handler.ts @@ -0,0 +1,11 @@ +import { FieldAnalyzer } from 'src/field/FieldAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +export class PassTurnHandler extends BaseHandler { + canHandle() { + return ( + this.analyzer.isOwnedByCurrentUser() || + (this.analyzer.isNotOwned() && !this.analyzer.isAffordableForSomeone()) || + this.analyzer.isSkipable() + ); + } +} diff --git a/src/game/handlers/processSpecial.handler.ts b/src/game/handlers/processSpecial.handler.ts new file mode 100644 index 0000000..8fd218e --- /dev/null +++ b/src/game/handlers/processSpecial.handler.ts @@ -0,0 +1,7 @@ +import { FieldAnalyzer } from 'src/field/FieldAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +export class ProcessSpecialHandler extends BaseHandler { + canHandle() { + return this.analyzer.isSpecialField(); + } +} diff --git a/src/game/handlers/putUpForAuction.handler.ts b/src/game/handlers/putUpForAuction.handler.ts new file mode 100644 index 0000000..48dd086 --- /dev/null +++ b/src/game/handlers/putUpForAuction.handler.ts @@ -0,0 +1,7 @@ +import { FieldAnalyzer } from 'src/field/FieldAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +export class PutUpForAuctionHandler extends BaseHandler { + canHandle() { + return this.analyzer.isNotOwned() && this.analyzer.isAffordableForSomeone(); + } +} diff --git a/src/game/handlers/steppedOnPrivate.handler.ts b/src/game/handlers/steppedOnPrivate.handler.ts new file mode 100644 index 0000000..cf48e15 --- /dev/null +++ b/src/game/handlers/steppedOnPrivate.handler.ts @@ -0,0 +1,7 @@ +import { FieldAnalyzer } from 'src/field/FieldAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +export class SteppedOnPrivateHandler extends BaseHandler { + canHandle() { + return this.analyzer.isOwnedByOtherAndNotPledged(); + } +} diff --git a/src/game/handlers/twoPlayersInvolved.handler.ts b/src/game/handlers/twoPlayersInvolved.handler.ts new file mode 100644 index 0000000..92ebf91 --- /dev/null +++ b/src/game/handlers/twoPlayersInvolved.handler.ts @@ -0,0 +1,27 @@ +import { SecretAnalyzer } from 'src/secret/secretAnalyzer'; +import { BaseHandler } from '../../common/base.handler'; +import { WsException } from '@nestjs/websockets'; +import { SecretService } from 'src/secret/secret.service'; +export class TwoPlayersInvolvedHandler extends BaseHandler { + constructor( + analyzer: SecretAnalyzer, + private secretService: SecretService + ) { + super(analyzer); + } + canHandle() { + return this.analyzer.isTwoPlayersInvolved(); + } + + handle(): void { + const index = this.secretService.findIndexOfUserIdInSecretInfo( + this.analyzer.secretInfo, + this.analyzer.userId + ); + if (this.analyzer.secretInfo.amounts[index] > 0) { + throw new WsException( + 'You cant pay to bank two users and the one wants to pay dont have to' + ); + } + } +} diff --git a/src/secret/secretAnalyzer.ts b/src/secret/secretAnalyzer.ts new file mode 100644 index 0000000..7b77606 --- /dev/null +++ b/src/secret/secretAnalyzer.ts @@ -0,0 +1,29 @@ +import { SecretInfo } from 'src/game/types/secretInfo.type'; + +export class SecretAnalyzer { + constructor( + public readonly secretInfo: SecretInfo, + public readonly userId: string + ) {} + isOneUserHaveToPay() { + return this.secretInfo.users.length === 1 && this.secretInfo.amounts[0] < 0; + } + isOneUserHaveToReceive() { + return this.secretInfo.users.length === 1 && this.secretInfo.amounts[0] > 0; + } + inOnePlayerInvolved() { + return this.secretInfo.numOfPlayersInvolved === 'one'; + } + isTwoPlayersInvolved() { + return this.secretInfo.numOfPlayersInvolved === 'two'; + } + isAllPlayersInvolved() { + return this.secretInfo.numOfPlayersInvolved === 'all'; + } + isFirstAndShouldGet() { + return ( + this.secretInfo.users[0] === this.userId && + this.secretInfo.amounts[0] === null + ); + } +}