-
Notifications
You must be signed in to change notification settings - Fork 4
PLAN-156-late-user-joins-the-game #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import WebSocket from 'ws'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
| import { Player, PlayerStatus, PlayerRole } from '../domain'; | ||
| import { GameState } from '../domain/game'; | ||
| import { getPlayerAvatarColor } from '../helpers/player-avatar-color'; | ||
| import logger from '../logger'; | ||
| import { | ||
|
|
@@ -36,6 +37,33 @@ export const findPlayerById = ( | |
| return player; | ||
| }; | ||
|
|
||
| export const findPlayerByConnection = (ws: GameWebSocket): Player => { | ||
| const players = gameRoomService.getPlayers(ws.roomId); | ||
| const player = players.get(ws); | ||
| if (!player) { | ||
| throw new Error('player with this ws connection not found'); | ||
| } | ||
| return player; | ||
| }; | ||
|
|
||
| export const checkIfLastToVote = ( | ||
| roomId: string, | ||
| playerId: string, | ||
| ): boolean => { | ||
| const player = findPlayerById(roomId, playerId); | ||
| const voters = gameService.getVoters(roomId); | ||
| const votersWhoMadeMoveCount = gameService.getVoterWhoMadeActionCount(roomId); | ||
| const playerCount = gameRoomService.getPlayers(roomId).size; | ||
|
|
||
| const isOneVoteMissing = playerCount === votersWhoMadeMoveCount + 1; | ||
| const hasPlayerMoved = player[1].status !== PlayerStatus.ActionNotTaken; | ||
|
|
||
| if (isOneVoteMissing && voters.length > 1 && !hasPlayerMoved) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've got rid of |
||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
|
|
||
| const playerExists = (roomId: string, playerId: string): boolean => { | ||
| try { | ||
| findPlayerById(roomId, playerId); | ||
|
|
@@ -54,7 +82,18 @@ const publishFinalBoard = (ws: GameWebSocket): void => { | |
| }); | ||
| }; | ||
|
|
||
| const publishGameState = (ws: GameWebSocket): void => { | ||
| publish(ws.roomId, { | ||
| type: MessageType.UpdateGameState, | ||
| payload: gameRoomService.getGameState(ws.roomId), | ||
| }); | ||
| }; | ||
|
|
||
| export const figureMoved: Handler = (ws, payload: PlaceFigureMessage): void => { | ||
| if (gameRoomService.getGameState(ws.roomId) !== GameState.GAME_IN_PROGRESS) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems like some sort of workaround. Why is this needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its for a safety reasons. Without this check, even if game state is |
||
| return; | ||
| } | ||
|
|
||
| const players = getPlayers(ws.roomId); | ||
| logger.info(`Player ${players.get(ws)?.name} moved a figure.`); | ||
| players.set(ws, { | ||
|
|
@@ -67,6 +106,7 @@ export const figureMoved: Handler = (ws, payload: PlaceFigureMessage): void => { | |
| publish(ws.roomId, { type: MessageType.ActionMade, payload: newBoardState }); | ||
| publishAllPlayers(ws.roomId); | ||
| if (gameService.areAllPlayersDone(ws.roomId)) { | ||
| publishGameState(ws); | ||
| publishFinalBoard(ws); | ||
| } | ||
| }; | ||
|
|
@@ -80,6 +120,12 @@ const setDefaultStatusForPlayers = (ws: GameWebSocket): void => { | |
|
|
||
| export const resetGame = (ws: GameWebSocket): void => { | ||
| gameService.clearBoard(ws.roomId); | ||
| if (gameService.getVoters(ws.roomId).length < 2) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this condition needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because, without this condition, if one player joins the room and presses restart game button, the game state would become GameInProgress and he would be able to place figures on the board (at least in FE) |
||
| gameRoomService.setGameState(ws.roomId, GameState.GAME_NOT_STARTED); | ||
| } else { | ||
| gameRoomService.setGameState(ws.roomId, GameState.GAME_IN_PROGRESS); | ||
| } | ||
| publishGameState(ws); | ||
| setDefaultStatusForPlayers(ws); | ||
| publishBoard(ws.roomId); | ||
| publishAllPlayers(ws.roomId); | ||
|
|
@@ -90,6 +136,9 @@ export const moveSkipped: Handler = ( | |
| { playerId }: MoveSkippedMessage, | ||
| ): void => { | ||
| const players = getPlayers(ws.roomId); | ||
| if (gameRoomService.getGameState(ws.roomId) !== GameState.GAME_IN_PROGRESS) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const [playerConnection, player] = findPlayerById(ws.roomId, playerId); | ||
|
|
@@ -119,6 +168,7 @@ export const moveSkipped: Handler = ( | |
| payload: gameRoomService.getTurns(ws.roomId), | ||
| }); | ||
| if (gameService.areAllPlayersDone(ws.roomId)) { | ||
| publishGameState(ws); | ||
| publishFinalBoard(ws); | ||
| } | ||
| } catch (err) { | ||
|
|
@@ -146,6 +196,10 @@ const createNewPlayer = (params: { | |
| newPlayer.status = PlayerStatus.FigurePlaced; | ||
| } else if (gameService.playerHasSkipped(params.roomId, params.playerId)) { | ||
| newPlayer.status = PlayerStatus.MoveSkipped; | ||
| } else if ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. theres no
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is, its in the next line and wrapped with ( ). It probably got formatted this way, because the line got too large |
||
| gameRoomService.getGameState(params.roomId) === GameState.GAME_FINISHED | ||
| ) { | ||
| newPlayer.status = PlayerStatus.MoveSkipped; | ||
| } | ||
|
|
||
| return newPlayer; | ||
|
|
@@ -186,6 +240,19 @@ export const playerConnected: Handler = ( | |
| sendMessage(ws, MessageType.SetMyTurn, myTurn); | ||
| } | ||
|
|
||
| sendMessage( | ||
| ws, | ||
| MessageType.UpdateGameState, | ||
| gameRoomService.getGameState(ws.roomId), | ||
| ); | ||
|
|
||
| if (gameRoomService.getGameState(ws.roomId) === GameState.GAME_NOT_STARTED) { | ||
| if (gameService.getVoters(ws.roomId).length > 1) { | ||
| gameRoomService.setGameState(ws.roomId, GameState.GAME_IN_PROGRESS); | ||
| publishGameState(ws); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we move this call into a single place and it would be executed after every action taken by players?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why should we send so many messages? For example if user makes two turns(for example changes his mind), we would publish game that has not changed state twice. I think we should leave game state publishing, to when its really needed or game state changes |
||
| } | ||
| } | ||
|
|
||
| publishAllPlayers(ws.roomId); | ||
|
|
||
| if (gameService.areAllPlayersDone(ws.roomId)) { | ||
|
|
@@ -232,6 +299,42 @@ export const subscribe = (ws: GameWebSocket, newPlayer: Player): void => { | |
|
|
||
| export const unsubscribe = (ws: GameWebSocket): void => { | ||
| const players = getPlayers(ws.roomId); | ||
| try { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method got too large. Its hard to follow. |
||
| const playerId = findPlayerByConnection(ws).id; | ||
|
|
||
| if ( | ||
| gameService.getVoters(ws.roomId).length === 2 && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this condition needed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because, when there is only two people in the room and the game is not finished, when one of them logs out, in my opinion the game state should become game not started (we don't allow player to make votes, when he's the only one in the room). |
||
| gameRoomService.getGameState(ws.roomId) !== GameState.GAME_FINISHED | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we create a general algorithm that would be applied no matter how many players we have in the game or what the game state is. |
||
| ) { | ||
| gameRoomService.setGameState(ws.roomId, GameState.GAME_NOT_STARTED); | ||
| publishGameState(ws); | ||
| } else if (checkIfLastToVote(ws.roomId, playerId)) { | ||
| players.delete(ws); | ||
| setTimeout(() => { | ||
| try { | ||
| const player = findPlayerById(ws.roomId, playerId); | ||
| if (player) { | ||
| return; | ||
| } | ||
| } catch (err) { | ||
| logger.error(err?.message); | ||
| logger.info('Publishing: player disconnected the game.'); | ||
| gameRoomService.setGameState(ws.roomId, GameState.GAME_FINISHED); | ||
| const allPlayers = Array.from(players.values()); | ||
| publish(ws.roomId, { | ||
| type: MessageType.PlayerDisconnected, | ||
| payload: allPlayers, | ||
| }); | ||
| publishGameState(ws); | ||
| publishFinalBoard(ws); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is repeated all over the place. Can we find a better way to determine when final board should be pushed apart from placing it directly in the code? |
||
| } | ||
| }, 2000); | ||
| return; | ||
| } | ||
| } catch { | ||
| return; | ||
| } | ||
|
|
||
| logger.info(`Unsubscribing player ${players.get(ws)?.name}`); | ||
| players.delete(ws); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the logic behind puting methods into services?
Why is this method inside the players service, while
getPlayersandgetVoterWhoMadeActionCountare in gameService and gameRoomService?