From 1da155d20af225e877c1ad7b5e7bb3167b481d04 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Mon, 8 Apr 2024 19:31:40 +0300 Subject: [PATCH 01/24] fix max clients update bug --- apps/shelter-client/src/pages/Main.tsx | 2 +- apps/shelter-client/src/pages/Room.tsx | 10 +++++++--- apps/shelter-gateway/src/game/game.gateway.ts | 6 +++--- .../src/game/instance/instance.ts | 18 +++++++++++++----- apps/shelter-gateway/src/game/lobby/lobby.ts | 3 +-- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/apps/shelter-client/src/pages/Main.tsx b/apps/shelter-client/src/pages/Main.tsx index 532e423..c8723b1 100644 --- a/apps/shelter-client/src/pages/Main.tsx +++ b/apps/shelter-client/src/pages/Main.tsx @@ -41,7 +41,7 @@ const MainPage = () => { sm.emit({ event: ClientEvents.LobbyCreate, data: { - maxClients: 8, // used as default + maxClients: 4, // used as default organizatorId: user.userId, }, }); diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index 131a3cd..cf7a839 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -545,11 +545,15 @@ const RoomPage = () => { {

Change usename

Date: Tue, 9 Apr 2024 15:34:23 +0300 Subject: [PATCH 08/24] don't allow to vote if already voted --- .../src/activityLogs/activity-logs.service.ts | 18 +++-- apps/shelter-gateway/src/game/game.gateway.ts | 32 ++------- apps/shelter-gateway/src/game/game.module.ts | 5 ++ .../src/game/instance/instance.ts | 68 +++++++++++++------ .../src/game/lobby/lobby.manager.ts | 19 ++++-- apps/shelter-gateway/src/game/lobby/lobby.ts | 6 ++ 6 files changed, 92 insertions(+), 56 deletions(-) diff --git a/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts b/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts index d7b1776..59ee801 100644 --- a/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts +++ b/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts @@ -19,22 +19,32 @@ export class ActivityLogsService { data.payload.contestantId, ); data.payload['text'] = - `${user.displayName} used special card: ${data.payload.specialCard.text} on ${contestant.displayName}.`; + `Player ${user.displayName} used special card: ${data.payload.specialCard.text} on ${contestant.displayName}.`; } else { data.payload['text'] = - `${user.displayName} used special card: ${data.payload.specialCard.text}.`; + `Player ${user.displayName} used special card: ${data.payload.specialCard.text}.`; } } // vote kick if (data.action === constants.voteKick) { - data.payload['text'] = `${user.displayName} voted.`; + data.payload['text'] = `Player ${user.displayName} voted.`; } // reveal characteristic if (data.action === constants.revealChar) { data.payload['text'] = - `${user.displayName} revealed characteristic: ${data.payload.char.text}.`; + `Player ${user.displayName} revealed characteristic: ${data.payload.char.text}.`; + } + + // player kicked + if (data.action === constants.playerKicked) { + data.payload['text'] = `Player ${user.displayName} is kicked.`; + } + + // next stage started + if (data.action === constants.nextStageStarted) { + data.payload['text'] = `Stage ${data.payload.currentStage} is started.`; } return await this.databaseService.createActivityLog(data); diff --git a/apps/shelter-gateway/src/game/game.gateway.ts b/apps/shelter-gateway/src/game/game.gateway.ts index f7780e7..d2ecaa9 100644 --- a/apps/shelter-gateway/src/game/game.gateway.ts +++ b/apps/shelter-gateway/src/game/game.gateway.ts @@ -20,7 +20,7 @@ import { SocketExceptions } from './utils/SocketExceptions'; import { LobbyCreateDto } from './dto/LobbyCreate'; import { LobbyJoinDto } from './dto/LobbyJoin'; import { ChatMessage } from './dto/ChatMessage'; -import { DatabaseService, constants } from '@app/common'; +import { DatabaseService } from '@app/common'; import { ActivityLogsService } from '../activityLogs/activity-logs.service'; @UsePipes(new WsValidationPipe()) @@ -71,7 +71,11 @@ export class GameGateway ): Promise< WsResponse<{ message: string; color?: 'green' | 'red' | 'blue' | 'orange' }> > { - const lobby = this.lobbyManager.createLobby(data.maxClients); + const lobby = this.lobbyManager.createLobby( + data.maxClients, + this.databaseService, + this.activityLogsService, + ); // data.player.socketId = client.id // Cannot set properties of undefined (setting 'socketId') lobby.addClient(client); @@ -144,14 +148,6 @@ export class GameGateway } client.data.lobby.instance.voteKick(data, client); - - // create activity log - await this.activityLogsService.createActivityLog({ - userId: data.userId, - lobbyId: client.data.lobby.id, - action: constants.voteKick, - payload: data, - }); } @SubscribeMessage(ClientEvents.GameUseSpecialCard) @@ -167,14 +163,6 @@ export class GameGateway } client.data.lobby.instance.useSpecialCard(data, client); - - // create activity log - await this.activityLogsService.createActivityLog({ - userId: data.userId, - lobbyId: client.data.lobby.id, - action: constants.useSpecialCard, - payload: data, - }); } @SubscribeMessage(ClientEvents.GameRevealChar) @@ -187,13 +175,5 @@ export class GameGateway } client.data.lobby.instance.revealChar(data, client); - - // create activity log - await this.activityLogsService.createActivityLog({ - userId: data.userId, - lobbyId: client.data.lobby.id, - action: constants.revealChar, - payload: data, - }); } } diff --git a/apps/shelter-gateway/src/game/game.module.ts b/apps/shelter-gateway/src/game/game.module.ts index 0dcae96..a4e584b 100644 --- a/apps/shelter-gateway/src/game/game.module.ts +++ b/apps/shelter-gateway/src/game/game.module.ts @@ -1,10 +1,15 @@ import { Module } from '@nestjs/common'; // import { GameGateway } from './game.gateway'; import { LobbyManager } from './lobby/lobby.manager'; +import { DatabaseModule } from '@app/common'; +import { ActivityLogsModule } from '../activityLogs/activity-logs.module'; @Module({ + imports: [ActivityLogsModule], providers: [ // GameGateway, + DatabaseModule, + ActivityLogsModule, LobbyManager, ], }) diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index 4c61daf..73c35ea 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -10,6 +10,8 @@ import { Lobby } from '../lobby/lobby'; import { AuthenticatedSocket } from '../types'; import { ServerPayloads } from '../utils/ServerPayloads'; import { ServerEvents } from '../utils/ServerEvents'; +import { constants } from '@app/common'; + export class Instance { public hasStarted: boolean = false; @@ -29,7 +31,9 @@ export class Instance { private charsRevealedCount: number = 0; private readonly charOpenLimit: number = 2; // per 1 player on every stage - constructor(private readonly lobby: Lobby) { } + constructor( + private readonly lobby: Lobby, + ) { } public async triggerStart( data: { isPrivate: boolean; maxClients: number; organizatorId: string }, @@ -111,7 +115,7 @@ export class Instance { ); } - public revealChar(data: any, client: AuthenticatedSocket): void { + public async revealChar(data: any, client: AuthenticatedSocket): Promise { const { char, userId } = data; if (!this.hasStarted) { return; @@ -173,7 +177,7 @@ export class Instance { this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); if (allRevealsOnCurrentStage) { - this.transitNextStage() + this.transitNextStage(data, client) this.lobby.dispatchToLobby( ServerEvents.GameMessage, { @@ -183,6 +187,14 @@ export class Instance { ); } + // create activity log + await this.lobby.activityLogsService.createActivityLog({ + userId: data.userId, + lobbyId: client.data.lobby.id, + action: constants.revealChar, + payload: data, + }); + this.lobby.dispatchLobbyState(); this.lobby.dispatchToLobby( ServerEvents.GameMessage, @@ -214,6 +226,14 @@ export class Instance { this.voteKickList = [...this.voteKickList, { userId, contestantId }] + // create activity log + await this.lobby.activityLogsService.createActivityLog({ + userId: data.userId, + lobbyId: client.data.lobby.id, + action: constants.voteKick, + payload: data, + }); + // calc votes and kick player if (this.voteKickList.length >= this.players.filter(_ => _.isKicked !== true).length) { const contestantIds = this.voteKickList.map((_: { contestantId: any; }) => _.contestantId); @@ -232,6 +252,15 @@ export class Instance { this.kickedPlayers = [...this.kickedPlayers, keysWithHighestValue[0]] kickedPlayer = this.players.find(player => player.userId === keysWithHighestValue[0]) } + + // create activity log + await this.lobby.activityLogsService.createActivityLog({ + userId: data.userId, + lobbyId: client.data.lobby.id, + action: constants.playerKicked, + payload: { userId: kickedPlayer.userId }, // kicked player id + }); + this.lobby.dispatchToLobby( ServerEvents.GameMessage, { @@ -240,13 +269,6 @@ export class Instance { }, ); - // create activity log - // await this.activityLogsService.createActivityLog({ - // userId: data.userId, - // lobbyId: client.data.lobby.id, - // action: constants.playerKicked, - // payload: data, - // }); this.charsRevealedCount = 0 // clear round char counter this.voteKickList = []; // clear the list after kick @@ -258,7 +280,7 @@ export class Instance { return; } - this.transitNextStage(); + this.transitNextStage(data, client); this.lobby.dispatchLobbyState(); return; } @@ -275,7 +297,7 @@ export class Instance { ); } - public useSpecialCard(data: any, client: AuthenticatedSocket): void { + public async useSpecialCard(data: any, client: AuthenticatedSocket): Promise { const { specialCard, userId, contestantId = null } = data; if (!this.hasStarted || this.hasFinished) { return; @@ -296,6 +318,14 @@ export class Instance { this.applyChanges(specialCard.id, userId, contestantId) this.specialCards[userId].find(card => card.type === specialCard.type).isUsed = true; + // create activity log + await this.lobby.activityLogsService.createActivityLog({ + userId: data.userId, + lobbyId: client.data.lobby.id, + action: constants.useSpecialCard, + payload: data, + }); + const user = this.players.find(player => player.userId === userId); this.lobby.dispatchLobbyState(); this.lobby.dispatchToLobby( @@ -311,7 +341,7 @@ export class Instance { this.lobby.dispatchToLobby(ServerEvents.ChatMessage, data); } - private transitNextStage(): void { + private async transitNextStage(data: any, client: AuthenticatedSocket): Promise { this.currentStage = this.currentStage + 1; // deactivate stages @@ -328,12 +358,12 @@ export class Instance { } // create activity log - // await this.activityLogsService.createActivityLog({ - // userId: data.userId, - // lobbyId: client.data.lobby.id, - // action: constants.nextStageStarted, - // payload: data, - // }); + await this.lobby.activityLogsService.createActivityLog({ + userId: data.userId, + lobbyId: client.data.lobby.id, + action: constants.nextStageStarted, + payload: {...data, currentStage: this.currentStage}, // only currentStage needed + }); } diff --git a/apps/shelter-gateway/src/game/lobby/lobby.manager.ts b/apps/shelter-gateway/src/game/lobby/lobby.manager.ts index d177262..4a60d34 100644 --- a/apps/shelter-gateway/src/game/lobby/lobby.manager.ts +++ b/apps/shelter-gateway/src/game/lobby/lobby.manager.ts @@ -7,14 +7,10 @@ import { ServerException } from '../server.exception'; import { SocketExceptions } from '../utils/SocketExceptions'; import { ServerEvents } from '../utils/ServerEvents'; import { ServerPayloads } from '../utils/ServerPayloads'; -import { DatabaseService } from '@app/common'; import { LobbiesService } from '../../lobbies/lobbies.service'; export class LobbyManager { - constructor( - private readonly databaseService: DatabaseService, - private readonly lobbiesService: LobbiesService, - ) {} + constructor(private readonly lobbiesService: LobbiesService) {} public server: Server; private readonly lobbies: Map = new Map< Lobby['id'], @@ -27,8 +23,17 @@ export class LobbyManager { client.data.lobby?.removeClient(client); } - public createLobby(maxClients: number): Lobby { - const lobby = new Lobby(this.server, maxClients); + public createLobby( + maxClients: number, + databaseService, + activityLogsService, + ): Lobby { + const lobby = new Lobby( + this.server, + maxClients, + databaseService, + activityLogsService, + ); this.lobbies.set(lobby.id, lobby); return lobby; } diff --git a/apps/shelter-gateway/src/game/lobby/lobby.ts b/apps/shelter-gateway/src/game/lobby/lobby.ts index f5e8f00..0953e69 100644 --- a/apps/shelter-gateway/src/game/lobby/lobby.ts +++ b/apps/shelter-gateway/src/game/lobby/lobby.ts @@ -4,6 +4,8 @@ import { Instance } from '../instance/instance'; import { ServerEvents } from '../utils/ServerEvents'; import { ServerPayloads } from '../utils/ServerPayloads'; import { generateSixSymbolHash } from 'helpers'; +import { DatabaseService } from '@app/common'; +import { ActivityLogsService } from '../../activityLogs/activity-logs.service'; export class Lobby { public readonly id: string = generateSixSymbolHash(); @@ -14,10 +16,14 @@ export class Lobby { >(); public readonly instance: Instance = new Instance(this); public isPrivate: boolean = true; + public readonly databaseService = this._databaseService; + public readonly activityLogsService = this._activityLogsService; constructor( private readonly server: Server, public maxClients: number, + private readonly _databaseService: DatabaseService, + private readonly _activityLogsService: ActivityLogsService, ) {} public addClient(client: AuthenticatedSocket, playerData: any = {}): void { From 9d49544927eb45e94ad86f488c2b951b6b6ab8c3 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 15:45:22 +0300 Subject: [PATCH 09/24] small fix --- apps/shelter-gateway/src/game/instance/instance.ts | 5 +++-- helpers.ts | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index 73c35ea..6f8cff9 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -5,6 +5,7 @@ import { getRandomIndex, countOccurrences, getKeysWithHighestValue, + isset } from 'helpers'; import { Lobby } from '../lobby/lobby'; import { AuthenticatedSocket } from '../types'; @@ -129,7 +130,7 @@ export class Instance { return; } // open chars only on reveal stages - if (this.currentStage % 2 === 0) { + if (this.currentStage % 2 === 0 || !isset(this.currentStage)) { return; } @@ -215,7 +216,7 @@ export class Instance { } // vote only on kick stages - if (this.currentStage % 2 === 1) { + if (this.currentStage % 2 === 1 || !isset(this.currentStage)) { return; } diff --git a/helpers.ts b/helpers.ts index de58239..6149b4d 100644 --- a/helpers.ts +++ b/helpers.ts @@ -14,6 +14,10 @@ export const getRandomIndexInRange = (minIndex: number, maxIndex: number) => { return Math.floor(Math.random() * (maxIndex - minIndex) + minIndex); }; +export const isset = (val: any) => { + return val !== null && val !== undefined; +}; + export const generateFromCharacteristics = ( type: 'charList' | 'conditions' | 'specialCard', ): any => { From e7523834cf640692c240edf785fc674b4e900f85 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 15:59:30 +0300 Subject: [PATCH 10/24] don't allow to vote one characteristic twice in one round --- .../src/game/instance/instance.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index 6f8cff9..90e1ab7 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -118,7 +118,7 @@ export class Instance { public async revealChar(data: any, client: AuthenticatedSocket): Promise { const { char, userId } = data; - if (!this.hasStarted) { + if (!this.hasStarted || this.hasFinished) { return; } if (this.revealPlayerId !== userId) { @@ -136,11 +136,18 @@ export class Instance { const uCharList = this.characteristics[userId]; - // check if user not reveales more chars then limited - let uCharsRevealed: number = uCharList.filter( + + let uCharsRevealed = uCharList.filter( (char: { isRevealed: boolean }) => char.isRevealed === true, - ).length; - if (uCharsRevealed >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { + ); + + // check if user not reveales one char multiple times + if (uCharsRevealed.map(c => c.text).includes(char.text)) { + return; + } + + // check if user not reveales more chars then limited + if (uCharsRevealed.length >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { return; } @@ -209,6 +216,10 @@ export class Instance { public async voteKick(data: any, client: AuthenticatedSocket): Promise { const { userId, contestantId } = data; + if (!this.hasStarted || this.hasFinished) { + return; + } + // kicked player can not vote const isKicked = this.players.find(player => player.userId === userId).isKicked === true; if (isKicked) { From 2226260de8d443b74aabc2a31db4bd8b2e310d48 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 16:12:16 +0300 Subject: [PATCH 11/24] big characteristics should be translated to next line --- apps/shelter-client/src/styles/Room.scss | 4 +++- apps/shelter-gateway/src/game/instance/instance.ts | 11 +++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/shelter-client/src/styles/Room.scss b/apps/shelter-client/src/styles/Room.scss index 9346678..80d6781 100644 --- a/apps/shelter-client/src/styles/Room.scss +++ b/apps/shelter-client/src/styles/Room.scss @@ -217,7 +217,9 @@ $sunset-color: invert(69%) sepia(91%) saturate(296%) hue-rotate(342deg) brightne font-family: 'Roboto Condensed'; font-weight: 500; width: fit-content; - white-space: nowrap; + overflow-y: scroll; + height: 182px; + // white-space: nowrap; // border-bottom: #faaa63ca dashed 1px; text-transform: capitalize; } diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index 90e1ab7..f3ea5cc 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -137,9 +137,8 @@ export class Instance { const uCharList = this.characteristics[userId]; - let uCharsRevealed = uCharList.filter( - (char: { isRevealed: boolean }) => char.isRevealed === true, - ); + const uCharsRevealed = uCharList.filter((char: { isRevealed: boolean }) => char.isRevealed === true); + let uCharsRevealedLength = uCharsRevealed.length // check if user not reveales one char multiple times if (uCharsRevealed.map(c => c.text).includes(char.text)) { @@ -147,7 +146,7 @@ export class Instance { } // check if user not reveales more chars then limited - if (uCharsRevealed.length >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { + if (uCharsRevealedLength >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { return; } @@ -157,11 +156,11 @@ export class Instance { ).isRevealed = true; this.characteristics[userId] = uCharList; this.charsRevealedCount = this.charsRevealedCount + 1; - uCharsRevealed = uCharsRevealed + 1 + uCharsRevealedLength = uCharsRevealedLength + 1 /* check if user revealed all possible characteristics and choose next player that can reveal chars */ - if (uCharsRevealed === Math.ceil(this.currentStage / 2) * this.charOpenLimit) { + if (uCharsRevealedLength === Math.ceil(this.currentStage / 2) * this.charOpenLimit) { const chooseNextToReveal = (revealPlayerId, attempt = 0) => { const totalPlayers = this.players.length; if (attempt >= totalPlayers) return null; // Base case to prevent infinite recursion From 0f6922d93d7fe6540acacca6c9c40ff05aba0c64 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 16:28:01 +0300 Subject: [PATCH 12/24] use users actual avatar on opponent list for other --- apps/shelter-client/src/pages/Room.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index 560a117..7d3015d 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -350,7 +350,10 @@ const RoomPage = () => { camera block From e1d6fe72d1f9127e5e5e18f828175a11ac29ed86 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 17:22:01 +0300 Subject: [PATCH 13/24] scroll activity logs to the end --- apps/shelter-client/src/components/ActivityLogs.tsx | 9 +++++++-- apps/shelter-client/src/styles/ActivityLogs.scss | 1 + apps/shelter-gateway/src/game/lobby/lobby.manager.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/shelter-client/src/components/ActivityLogs.tsx b/apps/shelter-client/src/components/ActivityLogs.tsx index 5041bce..88fef46 100644 --- a/apps/shelter-client/src/components/ActivityLogs.tsx +++ b/apps/shelter-client/src/components/ActivityLogs.tsx @@ -1,5 +1,5 @@ import '../styles/ActivityLogs.scss'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from '../redux/store'; import { getActivityLogsByLobbyId } from '../api/requests'; @@ -14,6 +14,7 @@ const ActivityLogs = () => { const user = useSelector((state: RootState) => state.user); const app = useSelector((state: RootState) => state.app); const lobby = useSelector((state: RootState) => state.lobby); + const activityLogsRef = useRef(null); // LOCAL STATE const updateState = (newState: Partial): void => @@ -22,6 +23,10 @@ const ActivityLogs = () => { activityLogs: [], }); + if (activityLogsRef.current) { + activityLogsRef.current.scrollTop = activityLogsRef.current.scrollHeight; + } + useEffect(() => { handleGetActivityLogs(); }, []); @@ -39,7 +44,7 @@ const ActivityLogs = () => {

Activity Logs

-
+
{state.activityLogs.map((data: { payload: any; createdAt: string }) => { return (
diff --git a/apps/shelter-client/src/styles/ActivityLogs.scss b/apps/shelter-client/src/styles/ActivityLogs.scss index ea6a3cd..142b0e3 100644 --- a/apps/shelter-client/src/styles/ActivityLogs.scss +++ b/apps/shelter-client/src/styles/ActivityLogs.scss @@ -6,6 +6,7 @@ .activity-logs-block { display: flex; margin: 8px 0; + behavior: "smooth"; .log { margin: 0 4px; diff --git a/apps/shelter-gateway/src/game/lobby/lobby.manager.ts b/apps/shelter-gateway/src/game/lobby/lobby.manager.ts index 4a60d34..3ebf1df 100644 --- a/apps/shelter-gateway/src/game/lobby/lobby.manager.ts +++ b/apps/shelter-gateway/src/game/lobby/lobby.manager.ts @@ -55,6 +55,14 @@ export class LobbyManager { ); } + // TODO: Game already started check here + // if (lobby.instance.hasStarted) { + // throw new ServerException( + // SocketExceptions.LobbyError, + // 'Game already started', + // ); + // } + playerData.socketId = client.id; lobby.addClient(client, playerData); From bead1e0c1fdce2b6e446331413a186d3d81747bd Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 20:33:17 +0300 Subject: [PATCH 14/24] add end turn server side logic --- apps/shelter-client/src/websocket/types.ts | 1 + apps/shelter-gateway/src/game/game.gateway.ts | 5 ++ .../src/game/instance/instance.ts | 67 ++++++++++++++++--- .../src/game/utils/ClientEvents.ts | 1 + 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/apps/shelter-client/src/websocket/types.ts b/apps/shelter-client/src/websocket/types.ts index 74f38e1..4d89b16 100644 --- a/apps/shelter-client/src/websocket/types.ts +++ b/apps/shelter-client/src/websocket/types.ts @@ -49,6 +49,7 @@ export enum ClientEvents { LobbyLeave = 'client.lobby.leave', GameStart = 'client.game.start', GameRevealChar = 'client.game.reveal_char', // characteristic, i.e: gender, health etc.. + GameEndTurn = 'client.game.end_turn', GameVoteKick = 'client.game.vote_kick', GameUseSpecialCard = 'client.game.use_special_card', } diff --git a/apps/shelter-gateway/src/game/game.gateway.ts b/apps/shelter-gateway/src/game/game.gateway.ts index d2ecaa9..e74c326 100644 --- a/apps/shelter-gateway/src/game/game.gateway.ts +++ b/apps/shelter-gateway/src/game/game.gateway.ts @@ -176,4 +176,9 @@ export class GameGateway client.data.lobby.instance.revealChar(data, client); } + + @SubscribeMessage(ClientEvents.GameEndTurn) + onEndTurn(client: AuthenticatedSocket, data: any): void { + client.data.lobby.instance.endTurn(data, client); + } } diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index f3ea5cc..a12049a 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -135,8 +135,6 @@ export class Instance { } const uCharList = this.characteristics[userId]; - - const uCharsRevealed = uCharList.filter((char: { isRevealed: boolean }) => char.isRevealed === true); let uCharsRevealedLength = uCharsRevealed.length @@ -185,13 +183,6 @@ export class Instance { if (allRevealsOnCurrentStage) { this.transitNextStage(data, client) - this.lobby.dispatchToLobby( - ServerEvents.GameMessage, - { - color: 'blue', - message: `Stage ${this.currentStage} is started!`, - }, - ); } // create activity log @@ -212,6 +203,55 @@ export class Instance { ); } + /** + * Logic: By default player has endTurn=false. + * Player revealed all remaining characteristics: endTurn=true. + * If all players endTurn=true -> transit to the next stage + * and update endTurn=false for all. + * + * @param data + * @param client + * @returns Promise + */ + public async endTurn(data: any, client: AuthenticatedSocket): Promise { + const { userId } = data; + + if (!this.hasStarted || this.hasFinished) { + return; + } + // kicked player can not end turn + const isKicked = this.players.find(player => player.userId === userId).isKicked === true; + if (isKicked) { + return; + } + // end turn only on reveal stages + if (this.currentStage % 2 === 0 || !isset(this.currentStage)) { + return; + } + + // update endTurn + this.players.find(player => player.userId === userId).endTurn = true; + + this.lobby.dispatchLobbyState(); + this.lobby.dispatchToLobby( + ServerEvents.GameMessage, + { + color: 'blue', + message: 'Player has finished his turn!', + }, + ); + + /* check if all the players ended the turn. + Transit to the next stage, endTurn=false for all */ + const allEnded = this.players.filter(_ => _.endTurn).length === this.players.length; + if (allEnded) { + this.transitNextStage(data, client) + this.players.forEach(player => { + player.endTurn = false; + }); + } + } + public async voteKick(data: any, client: AuthenticatedSocket): Promise { const { userId, contestantId } = data; @@ -373,9 +413,16 @@ export class Instance { userId: data.userId, lobbyId: client.data.lobby.id, action: constants.nextStageStarted, - payload: {...data, currentStage: this.currentStage}, // only currentStage needed + payload: { currentStage: this.currentStage }, // only currentStage needed }); + this.lobby.dispatchToLobby( + ServerEvents.GameMessage, + { + color: 'blue', + message: `Stage ${this.currentStage} is started!`, + }, + ); } /* applies changes on special card use */ diff --git a/apps/shelter-gateway/src/game/utils/ClientEvents.ts b/apps/shelter-gateway/src/game/utils/ClientEvents.ts index 83b94ef..e2c5acd 100644 --- a/apps/shelter-gateway/src/game/utils/ClientEvents.ts +++ b/apps/shelter-gateway/src/game/utils/ClientEvents.ts @@ -6,6 +6,7 @@ export enum ClientEvents { LobbyLeave = 'client.lobby.leave', GameStart = 'client.game.start', GameRevealChar = 'client.game.reveal_char', // characteristic, i.e: gender, health etc.. + GameEndTurn = 'client.game.end_turn', GameVoteKick = 'client.game.vote_kick', GameUseSpecialCard = 'client.game.use_special_card', } From 8a846f714a686f80776ce8bbbf848c81313c5325 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 20:43:00 +0300 Subject: [PATCH 15/24] fix kicked player log bug --- .../src/activityLogs/activity-logs.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts b/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts index 59ee801..b89aad0 100644 --- a/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts +++ b/apps/shelter-gateway/src/activityLogs/activity-logs.service.ts @@ -39,7 +39,10 @@ export class ActivityLogsService { // player kicked if (data.action === constants.playerKicked) { - data.payload['text'] = `Player ${user.displayName} is kicked.`; + const kickedUser = await this.databaseService.getUserById( + data.payload.userId, + ); + data.payload['text'] = `Player ${kickedUser.displayName} is kicked.`; } // next stage started From b49a036c7223050bbe4a032d497786b4b5b7c691 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 21:43:04 +0300 Subject: [PATCH 16/24] add timer layout --- apps/shelter-client/src/pages/Room.tsx | 44 +++++++++++++++++-- apps/shelter-client/src/styles/Room.scss | 33 ++++++++++++++ apps/shelter-gateway/src/game/game.gateway.ts | 10 +++-- .../src/game/instance/instance.ts | 17 +++---- apps/shelter-gateway/src/game/lobby/lobby.ts | 4 +- .../src/game/utils/ServerPayloads.ts | 3 +- libs/common/src/database/database.service.ts | 6 ++- 7 files changed, 99 insertions(+), 18 deletions(-) diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index 7d3015d..56ff90d 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -37,6 +37,7 @@ interface IState { userCharList: charListType; userSpecialCards: specialCardsType; isPrivateLobby: boolean; + timer: number; voteKickList: any; maxClients: number; kickedPlayers: any[]; @@ -97,6 +98,7 @@ const RoomPage = () => { }, ], isPrivateLobby: true, + timer: 0, voteKickList: [], kickedPlayers: [], maxClients: 4, @@ -261,6 +263,7 @@ const RoomPage = () => { key?: string | null; isPrivate?: boolean; maxClients?: number; + timer?: number; } const handleSettingsUpdate = (data: settingsUpdate) => { sm.emit({ @@ -419,10 +422,7 @@ const RoomPage = () => { const handleGameStart = () => { sm.emit({ event: ClientEvents.GameStart, - data: { - maxClients: state.maxClients, - isPrivate: state.isPrivateLobby, - }, + data: {}, }); }; @@ -547,6 +547,42 @@ const RoomPage = () => { />
+
+
+

Turn on the timer

+

+ Players should end thair turns before the time limit to + avoid random :D +

+
+
+ +
+

Maximum players

diff --git a/apps/shelter-client/src/styles/Room.scss b/apps/shelter-client/src/styles/Room.scss index 80d6781..7232b6b 100644 --- a/apps/shelter-client/src/styles/Room.scss +++ b/apps/shelter-client/src/styles/Room.scss @@ -96,6 +96,39 @@ $sunset-color: invert(69%) sepia(91%) saturate(296%) hue-rotate(342deg) brightne } + .settings-timer { + display: flex; + padding: 0 0 5px 5px; + margin: 0 0 5px 0; + border-bottom: #000000 1px solid; + + .timer-text { + flex: 80%; + + h3 { + color: #000000; + font-size: 20px; + } + + p { + color: #000000; + font-size: 14px; + } + } + + .timer-selector { + margin: 0 10px; + flex: 20%; + display: flex; + align-items: center; + justify-content: center; + + select { + width: 90%; + } + } + } + .settings-max-players { display: flex; margin: 0 0 5px 5px; diff --git a/apps/shelter-gateway/src/game/game.gateway.ts b/apps/shelter-gateway/src/game/game.gateway.ts index e74c326..7f33569 100644 --- a/apps/shelter-gateway/src/game/game.gateway.ts +++ b/apps/shelter-gateway/src/game/game.gateway.ts @@ -84,7 +84,7 @@ export class GameGateway const context = { key: lobby.id, organizatorId: data.organizatorId, - settings: { maxClients: data.maxClients, isPrivate: true }, + settings: { maxClients: data.maxClients, isPrivate: true, timer: 0 }, }; await this.databaseService.createLobby(context); @@ -99,7 +99,7 @@ export class GameGateway @SubscribeMessage(ClientEvents.LobbyUpdate) async onLobbyUpdate(client: AuthenticatedSocket, data: any): Promise { - let isPrivate, maxClients; + let isPrivate, maxClients, timer; if (data.isPrivate !== null || data.isPrivate !== undefined) { client.data.lobby.isPrivate = data.isPrivate; isPrivate = data.isPrivate; @@ -108,10 +108,14 @@ export class GameGateway client.data.lobby.maxClients = data.maxClients; maxClients = data.maxClients; } + if (data.maxClients !== null || data.maxClients !== undefined) { + client.data.lobby.timer = data.timer; + timer = data.timer; + } // update lobby in database await this.databaseService.updateLobbyByKey(data.key, { - settings: { isPrivate, maxClients }, + settings: { isPrivate, maxClients, timer }, }); return { diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index a12049a..fb5ae02 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -36,10 +36,7 @@ export class Instance { private readonly lobby: Lobby, ) { } - public async triggerStart( - data: { isPrivate: boolean; maxClients: number; organizatorId: string }, - client: AuthenticatedSocket, - ): Promise { + public async triggerStart(data: any, client: AuthenticatedSocket): Promise { if (this.hasStarted) { return; } @@ -54,9 +51,13 @@ export class Instance { } // update lobby's settings - this.lobby.isPrivate = data.isPrivate; - // TODO: this.lobby.maxClients = data.maxClients; - // TODO: this.lobby.isTimerOn = data.isTimerOn; + const lobbydb = await this.lobby.databaseService.getLobbyByKeyOrNull(client.data.lobby.id); + this.lobby.isPrivate = lobbydb.settings.isPrivate; + this.lobby.maxClients = lobbydb.settings.maxClients; + this.lobby.timer = lobbydb.settings.timer; + + console.log('DEBUG', this.lobby.timer); + // set random characteristics this.hasStarted = true; @@ -248,7 +249,7 @@ export class Instance { this.transitNextStage(data, client) this.players.forEach(player => { player.endTurn = false; - }); + }); } } diff --git a/apps/shelter-gateway/src/game/lobby/lobby.ts b/apps/shelter-gateway/src/game/lobby/lobby.ts index 0953e69..d727c68 100644 --- a/apps/shelter-gateway/src/game/lobby/lobby.ts +++ b/apps/shelter-gateway/src/game/lobby/lobby.ts @@ -16,6 +16,7 @@ export class Lobby { >(); public readonly instance: Instance = new Instance(this); public isPrivate: boolean = true; + public timer: number = 0; public readonly databaseService = this._databaseService; public readonly activityLogsService = this._activityLogsService; @@ -73,7 +74,6 @@ export class Lobby { public dispatchLobbyState(): void { const payload: ServerPayloads[ServerEvents.LobbyState] = { lobbyId: this.id, - maxClients: this.maxClients, hasStarted: this.instance.hasStarted, hasFinished: this.instance.hasFinished, playersCount: this.clients.size, @@ -82,7 +82,9 @@ export class Lobby { characteristics: this.instance.characteristics, specialCards: this.instance.specialCards, conditions: this.instance.conditions, + maxClients: this.maxClients, isPrivate: this.isPrivate, + timer: this.timer, currentStage: this.instance.currentStage, stages: this.instance.stages, revealPlayerId: this.instance.revealPlayerId, diff --git a/apps/shelter-gateway/src/game/utils/ServerPayloads.ts b/apps/shelter-gateway/src/game/utils/ServerPayloads.ts index 6cbb773..2391087 100644 --- a/apps/shelter-gateway/src/game/utils/ServerPayloads.ts +++ b/apps/shelter-gateway/src/game/utils/ServerPayloads.ts @@ -3,7 +3,6 @@ import { ServerEvents } from './ServerEvents'; export type ServerPayloads = { [ServerEvents.LobbyState]: { lobbyId: string; - maxClients: number; hasStarted: boolean; hasFinished: boolean; playersCount: number; @@ -12,7 +11,9 @@ export type ServerPayloads = { characteristics: any; specialCards: any; conditions: any; + maxClients: number; isPrivate: boolean; + timer: number; currentStage: number; stages: any[]; revealPlayerId: string; diff --git a/libs/common/src/database/database.service.ts b/libs/common/src/database/database.service.ts index 893cac0..4274122 100644 --- a/libs/common/src/database/database.service.ts +++ b/libs/common/src/database/database.service.ts @@ -141,6 +141,10 @@ export class DatabaseService { data.settings.isPrivate !== undefined ? data.settings.isPrivate : lobby.settings.isPrivate, + timer: + data.settings.timer !== undefined + ? data.settings.timer + : lobby.settings.timer, }; // Update lobby settings with the merged object @@ -176,7 +180,7 @@ export class DatabaseService { return lobby; } - async getLobbyByKeyOrNull(key: string) { + async getLobbyByKeyOrNull(key: string): Promise { const lobby = await this.prisma.lobbies.findFirst({ where: { key: key }, }); From 3e49f1174f361a9269132aef5ff4c94d6e481f19 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 22:36:44 +0300 Subject: [PATCH 17/24] small refactor --- apps/shelter-client/src/components/CustomDropdown.tsx | 11 +++++------ apps/shelter-client/src/pages/Room.tsx | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/shelter-client/src/components/CustomDropdown.tsx b/apps/shelter-client/src/components/CustomDropdown.tsx index 7fb0008..328bdfa 100644 --- a/apps/shelter-client/src/components/CustomDropdown.tsx +++ b/apps/shelter-client/src/components/CustomDropdown.tsx @@ -44,22 +44,21 @@ const CustomDropdown: React.FC = ({ }; }, [isOpened, onClose, type]); - const isTypeNotifications = type === 'notifications'; return ( -
+
{children} {isOpened && (
{(type === 'account' || type === 'login') && ( -
{text}
+
{text}
)} {type === 'notifications' && ( -
{text}
+
{text}
)} {list.map((item, index) => ( -
+
- {state.isDetailsOpened ? ( + {state.isDetailsOpened && (
{charList.map((char: charType, index: any) => { @@ -410,7 +410,7 @@ const RoomPage = () => { })}
- ) : null} + )}
); })} From cc7fdb563f1210e6649920171fa58a19ddc1a918 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Tue, 9 Apr 2024 22:44:39 +0300 Subject: [PATCH 18/24] Healthy person should not see his lethality --- apps/shelter-client/src/pages/Room.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index 2f7afd3..aa12cd4 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -400,7 +400,9 @@ const RoomPage = () => { text={ char.isRevealed ? char.type === 'health' - ? `${char.text}(${char.stage})` + ? char.text !== 'Абсолютно здоровий' + ? `${char.text}(${char.stage})` + : char.text : char.text : 'Not revealed' } From eee63d6d0dc67176f6aac4bf2028ded71fd208f3 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Wed, 10 Apr 2024 13:04:12 +0300 Subject: [PATCH 19/24] add end turn logic --- .../src/components/Navigation.tsx | 1 - apps/shelter-client/src/pages/Room.tsx | 52 ++++++++++---- .../src/game/instance/instance.ts | 70 +++++++++---------- 3 files changed, 69 insertions(+), 54 deletions(-) diff --git a/apps/shelter-client/src/components/Navigation.tsx b/apps/shelter-client/src/components/Navigation.tsx index 3891202..31ca277 100644 --- a/apps/shelter-client/src/components/Navigation.tsx +++ b/apps/shelter-client/src/components/Navigation.tsx @@ -66,7 +66,6 @@ const Navigation = () => { stages: data.stages, }; dispatch(updateLobby(context)); - console.log('ss', context); // navigate to current room const route = ROUTES.ROOMS + '/' + data.lobbyId; diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index aa12cd4..abd1748 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -45,6 +45,7 @@ interface IState { modalProps: any; isOponentsListFocused: boolean; focusData: any; // sets on isOponentsListFocused + uRemainedChars: number; } type specialCardsType = { @@ -63,6 +64,17 @@ type charType = { isRevealed: boolean; }; +const getRemainedChars = (data: any, userId: string) => { + const alreadyRevealedCount = data.characteristics[userId].filter( + (_: { isRevealed: boolean }) => _.isRevealed === true, + ).length; + const remained = ( + Math.ceil(data.currentStage / 2) * 2 - + alreadyRevealedCount + ).toString(); + return remained; +}; + const RoomPage = () => { const user = useSelector((state: RootState) => state.user); const lobby = useSelector((state: RootState) => state.lobby); @@ -110,6 +122,7 @@ const RoomPage = () => { title: '', }, focusData: {}, + uRemainedChars: 2, }); useEffect(() => { @@ -157,16 +170,9 @@ const RoomPage = () => { // update reminded let tipStr: string = ' '; if (data.revealPlayerId === user.userId) { - // eslint-disable-next-line - const alreadyRevealedCount = data.characteristics[ - currentPlayer.userId - ].filter( - (_: { isRevealed: boolean }) => _.isRevealed === true, - ).length; - const remained = ( - Math.ceil(data.currentStage / 2) * 2 - - alreadyRevealedCount - ).toString(); + const remained = getRemainedChars(data, currentPlayer.userId); + updateState({ uRemainedChars: parseInt(remained) }); + console.log('uRemainedChars: ', remained); tipStr = `Open your characteristics, remained: ${remained}`; } else { const revealPlayer = data.players.find( @@ -203,8 +209,6 @@ const RoomPage = () => { }; }, [lobby.hasStarted, lobby.hasFinished, state.maxClients, dispatch]); - useEffect(() => {}, [state.isDescriptionOpened]); - // DATA SETS const kickBlockText = (player: { userId: string }) => { if (state.isOponentsListFocused) { @@ -427,13 +431,32 @@ const RoomPage = () => { data: {}, }); }; + const handleEndTurn = () => { + sm.emit({ + event: ClientEvents.GameEndTurn, + data: { + userId: user.userId, + }, + }); + updateState({ uRemainedChars: 2 }); + }; return (
{!state.kickedPlayers.includes(user.userId) || !lobby.hasFinished ? state.actionTip : 'You are kicked!'} - {!lobby.hasStarted || lobby.hasFinished ? ( + {state.uRemainedChars === 0 && lobby.currentStage! % 2 === 1 && ( +
+
+
+ +
+
+ )} + {(!lobby.hasStarted || lobby.hasFinished) && (
@@ -446,7 +469,7 @@ const RoomPage = () => { )}
- ) : null} + )}
); }; @@ -683,7 +706,6 @@ const RoomPage = () => {
{state.userCharList.map((char, index) => { - console.log(char); return (
{ @@ -137,7 +134,6 @@ export class Instance { const uCharList = this.characteristics[userId]; const uCharsRevealed = uCharList.filter((char: { isRevealed: boolean }) => char.isRevealed === true); - let uCharsRevealedLength = uCharsRevealed.length // check if user not reveales one char multiple times if (uCharsRevealed.map(c => c.text).includes(char.text)) { @@ -145,7 +141,7 @@ export class Instance { } // check if user not reveales more chars then limited - if (uCharsRevealedLength >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { + if (uCharsRevealed.length >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { return; } @@ -155,36 +151,6 @@ export class Instance { ).isRevealed = true; this.characteristics[userId] = uCharList; this.charsRevealedCount = this.charsRevealedCount + 1; - uCharsRevealedLength = uCharsRevealedLength + 1 - - /* check if user revealed all possible characteristics and - choose next player that can reveal chars */ - if (uCharsRevealedLength === Math.ceil(this.currentStage / 2) * this.charOpenLimit) { - const chooseNextToReveal = (revealPlayerId, attempt = 0) => { - const totalPlayers = this.players.length; - if (attempt >= totalPlayers) return null; // Base case to prevent infinite recursion - - const currentIndex = this.players.findIndex(p => p.userId === revealPlayerId); - const nextIndex = (currentIndex + 1) % totalPlayers; // When reaching the end of the player list, the search wraps around to the beginning - const revealPlayer = this.players[nextIndex]; - - if (revealPlayer.isKicked) { - // If the next player is kicked, recursively search for the next - return chooseNextToReveal(revealPlayer.userId, attempt + 1); - } - return revealPlayer.userId; - }; - - this.revealPlayerId = chooseNextToReveal(this.revealPlayerId); - } - - // transit to the next stage - const allRevealsOnCurrentStage = - this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); - - if (allRevealsOnCurrentStage) { - this.transitNextStage(data, client) - } // create activity log await this.lobby.activityLogsService.createActivityLog({ @@ -229,11 +195,37 @@ export class Instance { if (this.currentStage % 2 === 0 || !isset(this.currentStage)) { return; } + // only reveal player can end turn + if (this.revealPlayerId !== userId) { + return; + } // update endTurn this.players.find(player => player.userId === userId).endTurn = true; - this.lobby.dispatchLobbyState(); + /* check if user revealed all possible characteristics and + choose next player that can reveal chars */ + const uCharList = this.characteristics[userId]; + const uCharsRevealed = uCharList.filter((char: { isRevealed: boolean }) => char.isRevealed === true); + if (uCharsRevealed.length >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { + const chooseNextToReveal = (revealPlayerId, attempt = 0) => { + const totalPlayers = this.players.length; + if (attempt >= totalPlayers) return null; // Base case to prevent infinite recursion + + const currentIndex = this.players.findIndex(p => p.userId === revealPlayerId); + const nextIndex = (currentIndex + 1) % totalPlayers; // When reaching the end of the player list, the search wraps around to the beginning + const revealPlayer = this.players[nextIndex]; + + if (revealPlayer.isKicked) { + // If the next player is kicked, recursively search for the next + return chooseNextToReveal(revealPlayer.userId, attempt + 1); + } + return revealPlayer.userId; + }; + + this.revealPlayerId = chooseNextToReveal(this.revealPlayerId); + } + this.lobby.dispatchToLobby( ServerEvents.GameMessage, { @@ -242,15 +234,17 @@ export class Instance { }, ); - /* check if all the players ended the turn. + /* Check if all the players ended the turn and if all reveals on current stage. Transit to the next stage, endTurn=false for all */ const allEnded = this.players.filter(_ => _.endTurn).length === this.players.length; - if (allEnded) { + const allRevealsOnCurrentStage = this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); + if (allEnded && allRevealsOnCurrentStage) { this.transitNextStage(data, client) this.players.forEach(player => { player.endTurn = false; }); } + this.lobby.dispatchLobbyState(); } public async voteKick(data: any, client: AuthenticatedSocket): Promise { From 66a59824b54ff2843130f51b5a83f6c3ddbd7be0 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Wed, 10 Apr 2024 13:31:29 +0300 Subject: [PATCH 20/24] debug --- apps/shelter-client/src/components/Navigation.tsx | 1 + apps/shelter-gateway/src/game/instance/instance.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/shelter-client/src/components/Navigation.tsx b/apps/shelter-client/src/components/Navigation.tsx index 31ca277..2e6a284 100644 --- a/apps/shelter-client/src/components/Navigation.tsx +++ b/apps/shelter-client/src/components/Navigation.tsx @@ -66,6 +66,7 @@ const Navigation = () => { stages: data.stages, }; dispatch(updateLobby(context)); + console.log('ss: ', context); // navigate to current room const route = ROUTES.ROOMS + '/' + data.lobbyId; diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index cf076dd..b9aabc7 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -237,8 +237,8 @@ export class Instance { /* Check if all the players ended the turn and if all reveals on current stage. Transit to the next stage, endTurn=false for all */ const allEnded = this.players.filter(_ => _.endTurn).length === this.players.length; - const allRevealsOnCurrentStage = this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); - if (allEnded && allRevealsOnCurrentStage) { + // const allRevealsOnCurrentStage = this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); + if (allEnded) { this.transitNextStage(data, client) this.players.forEach(player => { player.endTurn = false; From 022eecd4d838314271d88eebc13a47dd76bd6a7b Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Wed, 10 Apr 2024 14:13:22 +0300 Subject: [PATCH 21/24] fix styles --- .../src/components/Navigation.tsx | 1 - apps/shelter-client/src/pages/Room.tsx | 2 ++ apps/shelter-client/src/styles/Room.scss | 21 ++++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/shelter-client/src/components/Navigation.tsx b/apps/shelter-client/src/components/Navigation.tsx index 2e6a284..31ca277 100644 --- a/apps/shelter-client/src/components/Navigation.tsx +++ b/apps/shelter-client/src/components/Navigation.tsx @@ -66,7 +66,6 @@ const Navigation = () => { stages: data.stages, }; dispatch(updateLobby(context)); - console.log('ss: ', context); // navigate to current room const route = ROUTES.ROOMS + '/' + data.lobbyId; diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index abd1748..91aa7a8 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -150,6 +150,8 @@ const RoomPage = () => { }), ); + console.log('DATA: ', data); + if (!lobby.hasStarted) { // update action tip and isOrganizator const tipStr = `Players: ${data.playersCount}/${state.maxClients}`; diff --git a/apps/shelter-client/src/styles/Room.scss b/apps/shelter-client/src/styles/Room.scss index 7232b6b..1e24d22 100644 --- a/apps/shelter-client/src/styles/Room.scss +++ b/apps/shelter-client/src/styles/Room.scss @@ -251,9 +251,8 @@ $sunset-color: invert(69%) sepia(91%) saturate(296%) hue-rotate(342deg) brightne font-weight: 500; width: fit-content; overflow-y: scroll; - height: 182px; - // white-space: nowrap; - // border-bottom: #faaa63ca dashed 1px; + scroll-behavior: smooth; + height: 186px; text-transform: capitalize; } @@ -520,4 +519,20 @@ $sunset-color: invert(69%) sepia(91%) saturate(296%) hue-rotate(342deg) brightne text-align: justify; -webkit-text-align: justify; font-size: 20px; +} + +.char-list-container::-webkit-scrollbar { + width: 12px; + background-color: transparent; +} + +.char-list-container::-webkit-scrollbar-thumb { + background-color: #ccc; + border-radius: 20px; + border: 3px solid transparent; + background-clip: content-box; +} + +.char-list-container::-webkit-scrollbar-button { + display: none; } \ No newline at end of file From 2a916e7d69252214d0374ecfb756ce102e4dd68e Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Wed, 10 Apr 2024 17:35:24 +0300 Subject: [PATCH 22/24] debug --- .../src/game/instance/instance.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index b9aabc7..abc4802 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -213,8 +213,8 @@ export class Instance { if (attempt >= totalPlayers) return null; // Base case to prevent infinite recursion const currentIndex = this.players.findIndex(p => p.userId === revealPlayerId); - const nextIndex = (currentIndex + 1) % totalPlayers; // When reaching the end of the player list, the search wraps around to the beginning - const revealPlayer = this.players[nextIndex]; + const revealPlayer = this.players[currentIndex + 1] || this.players[0]; + console.debug('[chooseNextToReveal] revealPlayer: ', revealPlayer); if (revealPlayer.isKicked) { // If the next player is kicked, recursively search for the next @@ -226,25 +226,27 @@ export class Instance { this.revealPlayerId = chooseNextToReveal(this.revealPlayerId); } - this.lobby.dispatchToLobby( - ServerEvents.GameMessage, - { - color: 'blue', - message: 'Player has finished his turn!', - }, - ); - /* Check if all the players ended the turn and if all reveals on current stage. Transit to the next stage, endTurn=false for all */ - const allEnded = this.players.filter(_ => _.endTurn).length === this.players.length; - // const allRevealsOnCurrentStage = this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); + const kicked = this.players.filter(player => player.isKicked); + const allEnded = this.players.filter(_ => _.endTurn).length === this.players.length - kicked.length; + const allRevealsOnCurrentStage = this.charsRevealedCount >= this.charOpenLimit * (this.players.filter(_ => _.isKicked !== true).length); + console.debug('[allRevealsOnCurrentStage]: ', allRevealsOnCurrentStage); if (allEnded) { this.transitNextStage(data, client) this.players.forEach(player => { player.endTurn = false; }); } + this.lobby.dispatchLobbyState(); + this.lobby.dispatchToLobby( + ServerEvents.GameMessage, + { + color: 'blue', + message: 'Player has finished his turn!', + }, + ); } public async voteKick(data: any, client: AuthenticatedSocket): Promise { From 68963bc7227b8dd1671e2352de5500b394ff1ed8 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Wed, 10 Apr 2024 17:50:35 +0300 Subject: [PATCH 23/24] fixed bugs --- .../src/game/instance/instance.ts | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/apps/shelter-gateway/src/game/instance/instance.ts b/apps/shelter-gateway/src/game/instance/instance.ts index abc4802..d0342d3 100644 --- a/apps/shelter-gateway/src/game/instance/instance.ts +++ b/apps/shelter-gateway/src/game/instance/instance.ts @@ -203,28 +203,8 @@ export class Instance { // update endTurn this.players.find(player => player.userId === userId).endTurn = true; - /* check if user revealed all possible characteristics and - choose next player that can reveal chars */ - const uCharList = this.characteristics[userId]; - const uCharsRevealed = uCharList.filter((char: { isRevealed: boolean }) => char.isRevealed === true); - if (uCharsRevealed.length >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { - const chooseNextToReveal = (revealPlayerId, attempt = 0) => { - const totalPlayers = this.players.length; - if (attempt >= totalPlayers) return null; // Base case to prevent infinite recursion - - const currentIndex = this.players.findIndex(p => p.userId === revealPlayerId); - const revealPlayer = this.players[currentIndex + 1] || this.players[0]; - console.debug('[chooseNextToReveal] revealPlayer: ', revealPlayer); - - if (revealPlayer.isKicked) { - // If the next player is kicked, recursively search for the next - return chooseNextToReveal(revealPlayer.userId, attempt + 1); - } - return revealPlayer.userId; - }; - - this.revealPlayerId = chooseNextToReveal(this.revealPlayerId); - } + // choose next player + this.chooseNextToReveal(data, client) /* Check if all the players ended the turn and if all reveals on current stage. Transit to the next stage, endTurn=false for all */ @@ -301,6 +281,11 @@ export class Instance { kickedPlayer = this.players.find(player => player.userId === keysWithHighestValue[0]) } + // choose next player if reveal one is kicked + if (this.revealPlayerId === kickedPlayer.userId) { + this.chooseNextToReveal(data, client) + } + // create activity log await this.lobby.activityLogsService.createActivityLog({ userId: data.userId, @@ -389,6 +374,31 @@ export class Instance { this.lobby.dispatchToLobby(ServerEvents.ChatMessage, data); } + /* check if user revealed all possible characteristics and + choose next player that can reveal chars */ + private chooseNextToReveal(data: any, client: AuthenticatedSocket): void { + const uCharList = this.characteristics[data.userId]; + const uCharsRevealed = uCharList.filter((char: { isRevealed: boolean }) => char.isRevealed === true); + if (uCharsRevealed.length >= Math.ceil(this.currentStage / 2) * this.charOpenLimit) { + const chooseNextToReveal = (revealPlayerId, attempt = 0) => { + const totalPlayers = this.players.length; + if (attempt >= totalPlayers) return null; // Base case to prevent infinite recursion + + const currentIndex = this.players.findIndex(p => p.userId === revealPlayerId); + const revealPlayer = this.players[currentIndex + 1] || this.players[0]; + console.debug('[chooseNextToReveal] revealPlayer: ', revealPlayer); + + if (revealPlayer.isKicked) { + // If the next player is kicked, recursively search for the next + return chooseNextToReveal(revealPlayer.userId, attempt + 1); + } + return revealPlayer.userId; + }; + + this.revealPlayerId = chooseNextToReveal(this.revealPlayerId); + } + } + private async transitNextStage(data: any, client: AuthenticatedSocket): Promise { this.currentStage = this.currentStage + 1; From 5c4cb89c3db268399a24471f95036a8ae508eab0 Mon Sep 17 00:00:00 2001 From: Denys Mitiachkin Date: Wed, 10 Apr 2024 18:10:05 +0300 Subject: [PATCH 24/24] fix end turn bug --- apps/shelter-client/src/pages/Room.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/shelter-client/src/pages/Room.tsx b/apps/shelter-client/src/pages/Room.tsx index 91aa7a8..22dba83 100644 --- a/apps/shelter-client/src/pages/Room.tsx +++ b/apps/shelter-client/src/pages/Room.tsx @@ -448,16 +448,19 @@ const RoomPage = () => { {!state.kickedPlayers.includes(user.userId) || !lobby.hasFinished ? state.actionTip : 'You are kicked!'} - {state.uRemainedChars === 0 && lobby.currentStage! % 2 === 1 && ( -
-
+ {!state.kickedPlayers.includes(user.userId) && + state.uRemainedChars === 0 && + lobby.currentStage! % 2 === 1 && + !lobby.hasFinished && (
- +
+
+ +
-
- )} + )} {(!lobby.hasStarted || lobby.hasFinished) && (