From cbb825424198b9bb070e25100dfe211340cdf880 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Mon, 3 Jul 2017 22:26:43 -0700 Subject: [PATCH 01/26] Set editor to default to read-only mode --- public/local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/local.js b/public/local.js index 7a302dc..3a42bf0 100644 --- a/public/local.js +++ b/public/local.js @@ -81,7 +81,7 @@ var editor = ace.edit('editor'); var Range = ace.require('ace/range').Range; editor.setTheme("ace/theme/monokai"); editor.getSession().setMode('ace/mode/javascript'); - +editor.setReadOnly(true); /* ------------------------------------------------------------ EVENT LISTENERS / SEND DATA TO SERVER From 4da64d1ef0a6b08748e1363ba333846e760ce683 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Mon, 3 Jul 2017 23:04:59 -0700 Subject: [PATCH 02/26] Refactor global vars to gameState, rename stuff - Change `nextTurnChangeTimestamp` to `timeRemaining` - Rename `currentPlayerIndex` to `turnIndex` - Rename `playerList` to `players` --- public/local.js | 4 +- server.js | 187 +++++++++++++++++++++++++++--------------------- 2 files changed, 109 insertions(+), 82 deletions(-) diff --git a/public/local.js b/public/local.js index 3a42bf0..eab8e6e 100644 --- a/public/local.js +++ b/public/local.js @@ -330,7 +330,7 @@ function handleTurnChange (turnData) { // Update UI togglePlayerHighlight(true); - updateTimeLeftView(turnData.millisRemaining); + updateTimeLeftView(turnData.timeRemaining); updateCurrentTurnView(turnData.current.name); updateNextTurnView(turnData.next.name); toggleMyTurnHighlight(); @@ -359,7 +359,7 @@ function handleUpdateState (turnData) { togglePlayerHighlight(true); // Update UI - updateTimeLeftView(turnData.millisRemaining); + updateTimeLeftView(turnData.timeRemaining); updateCurrentTurnView(turnData.current.name); updateNextTurnView(turnData.next.name); toggleMyTurnHighlight(); diff --git a/server.js b/server.js index 353650e..0e57e84 100644 --- a/server.js +++ b/server.js @@ -65,66 +65,103 @@ server.listen(port, function() { }); /* ------------------------------------------------------------ - EVENT NAMES: SERVER FUNCTIONS: - - userLogin io.emit: playerListChange - socket.emit: editorTextChange, editorScrollChange, editorCursorChange, turnChange - - disconnect Broadcast: playerListChange - - editorTextChange Broadcast: editorTextChange - - editorCursorChange Broadcast: editorCursorChange - - editorScrollChange Broadcast: editorScrollChange - - updateState Broadcast: updateState - - turnChange Broadcast: turnChange - - createNewGist Broadcast: createNewGist - - newGistLink Broadcast: newGistLink --------------------------------------------------------------- */ + GAME STATE: + +{ + timeRemaining, + turnIndex, + currentGist: {id, url, owner}, + playerList: + [ + {id, login,avatar_url}, { ... }, { ... }, ... + ], + editor: + { + content, + cursorAndSelection: { cursor: {column, row}, range: { end: {column, row}, start: {column, row} }, + scroll: {scrollLeft, scrollTop} + } +} -// playerData: -// { 'socket-id-here': {login: 'username-here', avatar_url: 'user-avatar-url-here'} } +-------------------------------------------------------------- */ -var playerData = {}; -var playerList = []; +let gameState = { + timeRemaining: null, + turnIndex: 0, + currentGist: null, + players: [], + editor: + { + content: '// Type JavaScript here!', + cursorAndSelection: null, + scroll: null + } +}; -var editorContent = '// Type JavaScript here!'; -var editorCursorAndSelection; -var editorScroll; +/* ------------------------------------------------------------ + EVENT NAMES: -var currentGist; + // Will redo this part! -var timerId; -var nextTurnChangeTimestamp; -var currentPlayerIndex; -const turnDuration = 60000; // 3 min: 180000 +-------------------------------------------------------------- */ +const turnDuration = 60000; +let timerId = null; + +/* ---------------------------------------------------------------------------------------------------------------------------------------------- + EVENTS: + +Event Name Sent By Sent To Data Description +------------------ ---------- ------------------ -------------------------- ----------------------------------------------------------------- +playerJoined Client Server {login, avatar_url} When new player completes login process +playerJoined Server All other clients {id, login, avatar_url} Update other clients with new player data +gameState Server One client See game state model! Initialize game state for new player that just logged in, + and trigger new gist creation if game is just starting! +playerLeft Server All other clients id Update other clients to remove disconnected player +turnChange Server All clients null ??? Trigger clients to change the turn +newGist Client Server {id, url, owner} Broadcast new Gist data +editorTextChange Client Server "just a string!" Broadcast changes to code editor content +editorScrollChange Client Server {scrollLeft, scrollTop} Broadcast changes to code editor content +editorCursorChange Client Server { Broadcast cursor moves or selection changes + cursor: {column, row}, + range: { + end: {column, row}, + start: {column, row} + } + } +disconnect Client Server ... When clients disconnect from server (SocketIO function) +connection Client Server ... When clients connect to server (SocketIO function) +------------------------------------------------------------------------------------------------------------------------------------------------- */ // When a user connects over websocket, io.on('connection', function (socket) { console.log('\nA user connected! (But not yet logged in.)\n'); - //console.log('\t\t playerList.length: ' + playerList.length); + //console.log('\t\t playerList.length: ' + gameState.players.length); // When a user logs in, socket.on('userLogin', function (userData) { console.log('\n* * * * # # # # User logged in! # # # # * * * * *'); console.log('\t\t > > > ' + userData.login + ' < < <\n'); - //console.log('\t\t playerList.length: ' + playerList.length); + //console.log('\t\t gameState.players.length: ' + gameState.players.length); // Add user ID/name to playerList playerData[socket.id] = {}; playerData[socket.id].login = userData.login; playerData[socket.id].avatar_url = userData.avatar_url; - playerList.push(socket.id); + gameState.players.push(socket.id); // Send current state of the text editor to the new client, to initialize! - socket.emit('editorTextChange', editorContent); - if (editorScroll != null) { - socket.emit('editorScrollChange', editorScroll); + socket.emit('editorTextChange', gameState.editor.content); + if (gameState.editor.cursorAndSelection != null) { + socket.emit('editorScrollChange', gameState.editor.cursorAndSelection); } - if (editorCursorAndSelection != null) { - socket.emit('editorCursorChange', editorCursorAndSelection); + if (gameState.editor.cursorAndSelection != null) { + socket.emit('editorCursorChange', gameState.editor.cursorAndSelection); } // If there is 1 player logged in (the first player to join, who just triggered the "userLogin" event), // START THE GAME!!! - if (playerList.length === 1) { + if (gameState.players.length === 1) { timerId = startTurnTimer(timerId, turnDuration, socket.id); // Notify the first user to create a new gist now! @@ -139,38 +176,33 @@ io.on('connection', function (socket) { console.log('\non("userLogin") -- turnData broadcasted!\n'); //console.log( getTurnData() ); - - //console.log(' ! ! ! ! ! ! player data and list ! ! ! ! ! !'); - //console.log(playerData); - //console.log(playerList); - //console.log('\t\t playerList.length: ' + playerList.length); - }); + }); // When a user disconnects, socket.on('disconnect', function() { console.log('\nA user disconnected!\n'); - //console.log('currentPlayerIndex: ' + currentPlayerIndex); + //console.log('gameState.turnIndex: ' + gameState.turnIndex); // If disconnected user was logged in, - if (playerList.indexOf(socket.id) !== -1) { + if (gameState.players.indexOf(socket.id) !== -1) { console.log('\n\t User removed from list of logged-in players. ID: ' + socket.id); // Temporarily save ID of current player (before removing from playerList, for a later check!) - var currentPlayerId = playerList[currentPlayerIndex]; + var currentPlayerId = gameState.players[gameState.turnIndex]; // Remove disconnected user from playerList delete playerData[socket.id]; - playerList.splice( playerList.indexOf(socket.id), 1); + gameState.players.splice( gameState.players.indexOf(socket.id), 1); // If no logged-in players are left, reset the game! - if (playerList.length === 0) { + if (gameState.players.length === 0) { console.log('\nNo players left. Turning off the turn timer!\n'); - currentPlayerIndex = null; + gameState.turnIndex = null; // Turn off the timer clearInterval(timerId); - nextTurnChangeTimestamp = null; + gameState.timeRemaining = null; // Otherwise, if there are players left, } else { @@ -180,7 +212,7 @@ io.on('connection', function (socket) { // Turn off the timer clearInterval(timerId); - nextTurnChangeTimestamp = null; + gameState.timeRemaining = null; // Re-initialize the turn (and timer), passing control to the next user timerId = startTurnTimer(timerId, turnDuration, socket.id); @@ -191,16 +223,11 @@ io.on('connection', function (socket) { // Broadcast updated playerList to update all other clients socket.broadcast.emit('playerListChange', playerData); - } - - //console.log('playerData: '); - //console.log(playerData); - //console.log('playerList: '); - //console.log(playerList); + } } else { console.log('\n\t User was not yet logged in, so no action taken.\n'); - } + } }); // When "editorTextChange" event received, update editor state and broadcast it back out @@ -210,14 +237,14 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to type (in case of client-side tampering with the JS!) - if (socket.id === playerList[currentPlayerIndex]) { + if (socket.id === gameState.players[gameState.turnIndex]) { // Update saved state of the shared text editor - editorContent = data; + gameState.editor.content = data; // Broadcast updated editor content to other clients - socket.broadcast.emit('editorTextChange', editorContent); + socket.broadcast.emit('editorTextChange', gameState.editor.content); - //console.log('Broadcasting editorContent to other clients!'); + //console.log('Broadcasting gameState.editor.content to other clients!'); } }); @@ -229,12 +256,12 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === playerList[currentPlayerIndex]) { + if (socket.id === gameState.players[gameState.turnIndex]) { // Update saved state of the shared text editor - editorCursorAndSelection = data; + gameState.editor.cursorAndSelection = data; // Broadcast data to other clients - socket.broadcast.emit('editorCursorChange', editorCursorAndSelection); + socket.broadcast.emit('editorCursorChange', gameState.editor.cursorAndSelection); //console.log('Broadcasting editorCursorChange to other clients!'); } @@ -248,12 +275,12 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === playerList[currentPlayerIndex]) { + if (socket.id === gameState.players[gameState.turnIndex]) { // Update saved state of the shared text editor - editorScroll = data; + gameState.editor.scroll = data; // Broadcast data to other clients - socket.broadcast.emit('editorScrollChange', editorScroll); + socket.broadcast.emit('editorScrollChange', gameState.editor.cursorAndSelection); //console.log('Broadcasting editorScrollChange to other clients!'); } @@ -266,7 +293,7 @@ io.on('connection', function (socket) { console.log('\nnewGistLink event received!\n'); //console.log(data); - currentGist = data; + gameState.currentGist = data; // Broadcast data to other clients socket.broadcast.emit('newGistLink', data); @@ -281,30 +308,30 @@ io.on('connection', function (socket) { ---------------------------------------------------- */ function changeTurn(socketId) { // If current client is first player, initialize! - if (currentPlayerIndex == null) { + if (gameState.turnIndex == null) { console.log('\nINITIALIZING FIRST PLAYER\n'); - currentPlayerIndex = playerList.indexOf(socketId); - //console.log('currentPlayerIndex: ' + currentPlayerIndex); + gameState.turnIndex = gameState.players.indexOf(socketId); + //console.log('gameState.turnIndex: ' + gameState.turnIndex); // Otherwise, increment the current player } else { - //console.log('\nIncrementing currentPlayerIndex\n'); - currentPlayerIndex = (currentPlayerIndex + 1) % playerList.length; - //console.log('NEW currentPlayerIndex: ' + currentPlayerIndex); + //console.log('\nIncrementing gameState.turnIndex\n'); + gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; + //console.log('NEW gameState.turnIndex: ' + gameState.turnIndex); } } // Returns turnChange object for the current turn function getTurnData() { //console.log('getTurnData called'); - var currentPlayerId = playerList[currentPlayerIndex]; + var currentPlayerId = gameState.players[gameState.turnIndex]; var currentPlayerName = playerData[currentPlayerId].login; - var nextPlayerIndex = (currentPlayerIndex + 1) % playerList.length; + var nextPlayerIndex = (gameState.turnIndex + 1) % gameState.players.length; - var nextPlayerId = playerList[nextPlayerIndex]; + var nextPlayerId = gameState.players[nextPlayerIndex]; var nextPlayerName = playerData[nextPlayerId].login; - return {millisRemaining: nextTurnChangeTimestamp - Date.now(), current: {id: currentPlayerId, name: currentPlayerName}, next: {id: nextPlayerId, name: nextPlayerName}, gist: currentGist}; + return {timeRemaining: gameState.timeRemaining - Date.now(), current: {id: currentPlayerId, name: currentPlayerName}, next: {id: nextPlayerId, name: nextPlayerName}, gist: gameState.currentGist}; } // Initializes the turn and turn timer, returns timerId @@ -312,19 +339,19 @@ function startTurnTimer(timerId, turnDuration, socketId) { console.log('\nInitializing turn timer!'); // Initialize time of next turn change (will use this to sync the clients) - nextTurnChangeTimestamp = Date.now() + turnDuration; + gameState.timeRemaining = Date.now() + turnDuration; - console.log( 'Next turn at: ' + new Date(nextTurnChangeTimestamp).toString().substring(16,25) ); + console.log( 'Next turn at: ' + new Date(gameState.timeRemaining).toString().substring(16,25) ); // Initialize the turn data using given user ID changeTurn(socketId); // Every time the timer goes off, timerId = setInterval(() => { - console.log('\n >>>>>>>>>>> ' + new Date().toString().substring(16,25) + ' - Time to change turns! <<<<<<<<<<\n'); + console.log('\n >>>>>>>>>>> ' + new Date().toString().substring(16,25) + ' - Time to change turns! <<<<<<<<<<\n'); - // Update time of next turn change - nextTurnChangeTimestamp = Date.now() + turnDuration; + // Update time of next turn change + gameState.timeRemaining = Date.now() + turnDuration; changeTurn(socketId); From ffb8b78f4571c2e8ef9628b422a0a8755691fcc3 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Mon, 3 Jul 2017 23:29:21 -0700 Subject: [PATCH 03/26] Refactor player list, add helper functions --- server.js | 77 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/server.js b/server.js index 0e57e84..e199c47 100644 --- a/server.js +++ b/server.js @@ -135,20 +135,15 @@ connection Client Server ... When clients connect to server (Sock // When a user connects over websocket, io.on('connection', function (socket) { - console.log('\nA user connected! (But not yet logged in.)\n'); - //console.log('\t\t playerList.length: ' + gameState.players.length); + console.log('\nA user connected! (But not yet logged in.)\n'); // When a user logs in, socket.on('userLogin', function (userData) { console.log('\n* * * * # # # # User logged in! # # # # * * * * *'); - console.log('\t\t > > > ' + userData.login + ' < < <\n'); - //console.log('\t\t gameState.players.length: ' + gameState.players.length); + console.log('\t\t > > > ' + userData.login + ' < < <\n'); - // Add user ID/name to playerList - playerData[socket.id] = {}; - playerData[socket.id].login = userData.login; - playerData[socket.id].avatar_url = userData.avatar_url; - gameState.players.push(socket.id); + // Add new user + gameState.players.push({id: socket.id, login: userData.login, avatar_url: userData.avatar_url}); // Send current state of the text editor to the new client, to initialize! socket.emit('editorTextChange', gameState.editor.content); @@ -174,8 +169,7 @@ io.on('connection', function (socket) { // Broadcast updated playerList to ALL clients io.emit('playerListChange', playerData); - console.log('\non("userLogin") -- turnData broadcasted!\n'); - //console.log( getTurnData() ); + console.log('\non("userLogin") -- turnData broadcasted!\n'); }); // When a user disconnects, @@ -184,16 +178,15 @@ io.on('connection', function (socket) { console.log('\nA user disconnected!\n'); //console.log('gameState.turnIndex: ' + gameState.turnIndex); - // If disconnected user was logged in, - if (gameState.players.indexOf(socket.id) !== -1) { + // If disconnected user was logged in, + if (getPlayerById(socket.id, gameState.players) !== -1) { console.log('\n\t User removed from list of logged-in players. ID: ' + socket.id); // Temporarily save ID of current player (before removing from playerList, for a later check!) var currentPlayerId = gameState.players[gameState.turnIndex]; - // Remove disconnected user from playerList - delete playerData[socket.id]; - gameState.players.splice( gameState.players.indexOf(socket.id), 1); + // Remove disconnected user from player list + removePlayer(socket.id, gameState.players); // If no logged-in players are left, reset the game! if (gameState.players.length === 0) { @@ -237,7 +230,7 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to type (in case of client-side tampering with the JS!) - if (socket.id === gameState.players[gameState.turnIndex]) { + if ( socket.id === getCurrentPlayerId() ) { // Update saved state of the shared text editor gameState.editor.content = data; @@ -256,7 +249,7 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === gameState.players[gameState.turnIndex]) { + if (socket.id === getCurrentPlayerId() ) { // Update saved state of the shared text editor gameState.editor.cursorAndSelection = data; @@ -275,7 +268,7 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === gameState.players[gameState.turnIndex]) { + if (socket.id === getCurrentPlayerId() ) { // Update saved state of the shared text editor gameState.editor.scroll = data; @@ -307,29 +300,27 @@ io.on('connection', function (socket) { FUNCTIONS ---------------------------------------------------- */ function changeTurn(socketId) { - // If current client is first player, initialize! + // If current client is first player, initialize! if (gameState.turnIndex == null) { console.log('\nINITIALIZING FIRST PLAYER\n'); - gameState.turnIndex = gameState.players.indexOf(socketId); - //console.log('gameState.turnIndex: ' + gameState.turnIndex); + // *** REWRITE THIS PART! *** + // Otherwise, increment the current player - } else { - //console.log('\nIncrementing gameState.turnIndex\n'); + } else { gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; - //console.log('NEW gameState.turnIndex: ' + gameState.turnIndex); } } // Returns turnChange object for the current turn function getTurnData() { //console.log('getTurnData called'); - var currentPlayerId = gameState.players[gameState.turnIndex]; - var currentPlayerName = playerData[currentPlayerId].login; + var currentPlayerId = getCurrentPlayerId(); + var currentPlayerName = gameState.players[gameState.turnIndex].login; var nextPlayerIndex = (gameState.turnIndex + 1) % gameState.players.length; - var nextPlayerId = gameState.players[nextPlayerIndex]; - var nextPlayerName = playerData[nextPlayerId].login; + var nextPlayerId = gameState.players[nextPlayerIndex].id; + var nextPlayerName = gameState.players[nextPlayerIndex].login; return {timeRemaining: gameState.timeRemaining - Date.now(), current: {id: currentPlayerId, name: currentPlayerName}, next: {id: nextPlayerId, name: nextPlayerName}, gist: gameState.currentGist}; } @@ -362,4 +353,32 @@ function startTurnTimer(timerId, turnDuration, socketId) { }, turnDuration); // TO DO: enable user-specified turn length return timerId; +} + +// Helper functions, just in case: +function getCurrentPlayerId() { + return gameState.players[gameState.turnIndex].id; +} +function getPlayerById(id, playerList){ + for (var i = 0; i < playerList.length; i++) { + if (playerList[i].id === id) { + return playerList[i]; + } + } + return -1; +} +function getPlayerIndexById(id, playerList) { + for (var i = 0; i < playerList.length; i++) { + if (playerList[i].id === id) { + return i; + } + } + return -1; +} +function removePlayer(id, playerList) { + for (var i = 0; i < playerList.length; i++) { + if (playerList[i].id === id) { + playerList.splice(i, 1); + } + } } \ No newline at end of file From 1f579c248c45cde7ef284e4d799cc20ac4a386d0 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 10:20:09 -0700 Subject: [PATCH 04/26] Add new events list, rename some events - Rename `newGistLink` to `newGist` - Rename `playerListChange` to `playerJoined` - Rename `handleUserLogin` to `loginUser` --- public/local.js | 88 +++++++++++++++++++++++++++---------------------- server.js | 24 +++++--------- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/public/local.js b/public/local.js index eab8e6e..c01f43b 100644 --- a/public/local.js +++ b/public/local.js @@ -53,7 +53,7 @@ if (getAllUrlParams().access_token) { currentAccessToken = getAllUrlParams().access_token; getJSON('https://api.github.com/user?access_token=' + currentAccessToken) - .then(handleUserLogin).catch(handleError); + .then(loginUser).catch(handleError); // Otherwise, if user has not yet started the login process, } else { @@ -83,31 +83,52 @@ editor.setTheme("ace/theme/monokai"); editor.getSession().setMode('ace/mode/javascript'); editor.setReadOnly(true); +/* --------------------------------------------------------------------------------------------------------------------------------------------------- + EVENTS: + +Event Name Sent By Sent To Data Client Functions: Description +------------------ ---------- ------------------ -------------------------- ----------------------------- --------------------------------------------- +playerJoined Client Server {login, avatar_url} loginUser When new player completes login process +playerJoined Server All other clients {id, login, avatar_url} handlePlayerJoined Update other clients with new player data +gameState Server One client See game state model! handleGameState Initialize game state for new player that just logged in, + and trigger new gist creation if game is just starting! +playerLeft Server All other clients id Update other clients to remove disconnected player +turnChange Server All clients null ??? handleTurnChange Triggerclients to change the turn +newGist Client Server {id, url, owner} handleNewGist Broadcast new Gist data +editorTextChange Client Server "just a string!" handleLocalEditorTextChange Broadcast changes to code editor content +editorScrollChange Client Server {scrollLeft, scrollTop} handleLocalEditorScrollChange Broadcast changes to code editor content +editorCursorChange Client Server { handleLocalEditorCursorChange Broadcast cursor moves or selection changes + cursor: {column, row}, + range: { + end: {column, row}, + start: {column, row} + } + } +disconnect Client Server ... ... When clients disconnect from server (SocketIO function) +connection Client Server ... ... When clients connect to server (SocketIO function) + +---------------------------------------------------------------------------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------ - EVENT LISTENERS / SEND DATA TO SERVER - - EVENT NAMES: CLIENT FUNCTIONS: - - connection Send: SocketIO built-in event - - disconnect Send: SocketIO built-in event - - userLogin Send: handleUserLogin - - editorTextChange Send: handleLocalEditorTextChange - Receive: handleServerEditorTextChange - - playerListChange Receive: handlePlayerListChange - - updateState Receive: handleUpdateState - - turnChange Receive: handleTurnChange - - editorCursorChange Send: handleLocalEditorCursorChange - Receive: handleServerEditorCursorChange - - editorScrollChange Send: handleLocalEditorScrollChange - Receive: handleServerEditorScrollChange - - createNewGist Receive: handleCreateNewGist - - newGistLink Receive: handleNewGistLink - Send: (sent after creating or forking) --------------------------------------------------------------- */ + LOCAL EVENT LISTENERS +-------------------------------------------------------------- */ editor.getSession().on('change', handleLocalEditorTextChange); editor.getSession().selection.on('changeCursor', handleLocalEditorCursorChange); editor.getSession().on('changeScrollLeft', handleLocalEditorScrollChange); editor.getSession().on('changeScrollTop', handleLocalEditorScrollChange); +/* ------------------------------------------------- + SERVER EVENT LISTENERS +---------------------------------------------------- */ +socket.on('editorTextChange', handleServerEditorTextChange); +socket.on('editorCursorChange', handleServerEditorCursorChange); +socket.on('editorScrollChange', handleServerEditorScrollChange); +socket.on('playerJoined', handlePlayerJoined); +socket.on('updateState', handleUpdateState); +socket.on('turnChange', handleTurnChange); +socket.on('createNewGist', handleCreateNewGist); +socket.on('newGist', handleNewGist); + // When client connects to server, socket.on('connect', function(){ // Generate default name to match socket.id @@ -125,7 +146,7 @@ socket.on('disconnect', function(){ }); // Log in with authenticated user's GitHub data -function handleUserLogin (userData) { +function loginUser (userData) { console.log('**************** Logged in! GitHub User Data: *********************'); console.log(userData); @@ -136,7 +157,7 @@ function handleUserLogin (userData) { updateLoggedInView(userData.login, userData.avatar_url); // Notify server that user logged in - socket.emit('userLogin', {login: userData.login, avatar_url: userData.avatar_url}); + socket.emit('playerJoined', {login: userData.login, avatar_url: userData.avatar_url}); } // Send editorInputView data to server @@ -203,17 +224,6 @@ function handleLocalEditorScrollChange (event) { // TODO: Test 'input' event some more in different browsers! // maybe add support for IE < 9 later? -/* ------------------------------------------------- - EVENT LISTENERS / RECEIVE DATA FROM SERVER ----------------------------------------------------- */ -socket.on('editorTextChange', handleServerEditorTextChange); -socket.on('editorCursorChange', handleServerEditorCursorChange); -socket.on('editorScrollChange', handleServerEditorScrollChange); -socket.on('playerListChange', handlePlayerListChange); -socket.on('updateState', handleUpdateState); -socket.on('turnChange', handleTurnChange); -socket.on('createNewGist', handleCreateNewGist); -socket.on('newGistLink', handleNewGistLink); // When receiving new editorInputView data from server function handleServerEditorTextChange (data) { @@ -244,8 +254,8 @@ function handleServerEditorScrollChange (data) { } // When receiving new player list data from server -function handlePlayerListChange (playerData) { - //console.log('%c playerListChange event received!', 'color: blue; font-weight: bold;'); +function handlePlayerJoined (playerData) { + //console.log('%c playerJoined event received!', 'color: blue; font-weight: bold;'); //console.dir(playerData); // Transform the data!! @@ -367,8 +377,8 @@ function handleUpdateState (turnData) { } -// When receiving "newGistLink" event from server, -function handleNewGistLink (gistData) { +// When receiving "newGist" event from server, +function handleNewGist (gistData) { // Update local state console.log("called handleNewGist at " + new Date().toString().substring(16,25), 'color: green; font-weight: bold;'); console.log(gistData); @@ -557,7 +567,7 @@ function handleCreateNewGist() { currentGist = {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}; // Send new gist data to server - socket.emit('newGistLink', {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); + socket.emit('newGist', {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); updateCurrentGistView({id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); @@ -606,7 +616,7 @@ function forkAndEditGist(gistId, codeEditorContent) { console.dir(gistObject); // Send new gist data to server - socket.emit('newGistLink', {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); + socket.emit('newGist', {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); // Then edit the new gist: editGist(gistObject.id, codeEditorContent); diff --git a/server.js b/server.js index e199c47..5a7bb03 100644 --- a/server.js +++ b/server.js @@ -98,12 +98,6 @@ let gameState = { } }; -/* ------------------------------------------------------------ - EVENT NAMES: - - // Will redo this part! - --------------------------------------------------------------- */ const turnDuration = 60000; let timerId = null; @@ -138,7 +132,7 @@ io.on('connection', function (socket) { console.log('\nA user connected! (But not yet logged in.)\n'); // When a user logs in, - socket.on('userLogin', function (userData) { + socket.on('playerJoined', function (userData) { console.log('\n* * * * # # # # User logged in! # # # # * * * * *'); console.log('\t\t > > > ' + userData.login + ' < < <\n'); @@ -154,7 +148,7 @@ io.on('connection', function (socket) { socket.emit('editorCursorChange', gameState.editor.cursorAndSelection); } - // If there is 1 player logged in (the first player to join, who just triggered the "userLogin" event), + // If there is 1 player logged in (the first player to join, who just triggered the "playerJoined" event), // START THE GAME!!! if (gameState.players.length === 1) { timerId = startTurnTimer(timerId, turnDuration, socket.id); @@ -167,9 +161,9 @@ io.on('connection', function (socket) { io.emit( 'updateState', getTurnData() ); // Broadcast updated playerList to ALL clients - io.emit('playerListChange', playerData); + io.emit('playerJoined', playerData); - console.log('\non("userLogin") -- turnData broadcasted!\n'); + console.log('\non("playerJoined") -- turnData broadcasted!\n'); }); // When a user disconnects, @@ -215,7 +209,7 @@ io.on('connection', function (socket) { socket.broadcast.emit( 'updateState', getTurnData() ); // Broadcast updated playerList to update all other clients - socket.broadcast.emit('playerListChange', playerData); + socket.broadcast.emit('playerJoined', playerData); } } else { @@ -280,16 +274,16 @@ io.on('connection', function (socket) { }); - // When "newGistLink" event received, update state and broadcast it back out - socket.on('newGistLink', function (data) { + // When "newGist" event received, update state and broadcast it back out + socket.on('newGist', function (data) { - console.log('\nnewGistLink event received!\n'); + console.log('\nnewGist event received!\n'); //console.log(data); gameState.currentGist = data; // Broadcast data to other clients - socket.broadcast.emit('newGistLink', data); + socket.broadcast.emit('newGist', data); }); From bd96b1fcfcb9d510f1515944983556fabe2b43a2 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 11:45:05 -0700 Subject: [PATCH 05/26] Refactor game start on server --- server.js | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/server.js b/server.js index 5a7bb03..c2e6307 100644 --- a/server.js +++ b/server.js @@ -131,39 +131,25 @@ io.on('connection', function (socket) { console.log('\nA user connected! (But not yet logged in.)\n'); - // When a user logs in, + // When a player logs in, socket.on('playerJoined', function (userData) { console.log('\n* * * * # # # # User logged in! # # # # * * * * *'); console.log('\t\t > > > ' + userData.login + ' < < <\n'); - // Add new user + // Add new player gameState.players.push({id: socket.id, login: userData.login, avatar_url: userData.avatar_url}); - - // Send current state of the text editor to the new client, to initialize! - socket.emit('editorTextChange', gameState.editor.content); - if (gameState.editor.cursorAndSelection != null) { - socket.emit('editorScrollChange', gameState.editor.cursorAndSelection); - } - if (gameState.editor.cursorAndSelection != null) { - socket.emit('editorCursorChange', gameState.editor.cursorAndSelection); - } - // If there is 1 player logged in (the first player to join, who just triggered the "playerJoined" event), - // START THE GAME!!! + // If there is 1 player logged in, START THE GAME!!! if (gameState.players.length === 1) { timerId = startTurnTimer(timerId, turnDuration, socket.id); - - // Notify the first user to create a new gist now! - socket.emit('createNewGist', null); } - - // Broadcast current turn data to all clients (for the case where nextPlayerId changes when a second user joins) - io.emit( 'updateState', getTurnData() ); - // Broadcast updated playerList to ALL clients - io.emit('playerJoined', playerData); + // Initialize new player + socket.emit('gameState', gameState); + + // Broadcast new player data to all OTHER clients + socket.broadcast.emit('playerJoined', playerData); - console.log('\non("playerJoined") -- turnData broadcasted!\n'); }); // When a user disconnects, From 87a16e562003b38b56a39ab1ee550d4155377376 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 11:51:36 -0700 Subject: [PATCH 06/26] Refactor turn timer on server --- server.js | 55 ++++++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/server.js b/server.js index c2e6307..ae29796 100644 --- a/server.js +++ b/server.js @@ -141,7 +141,7 @@ io.on('connection', function (socket) { // If there is 1 player logged in, START THE GAME!!! if (gameState.players.length === 1) { - timerId = startTurnTimer(timerId, turnDuration, socket.id); + timerId = startTurnTimer(timerId, turnDuration); } // Initialize new player @@ -188,11 +188,11 @@ io.on('connection', function (socket) { gameState.timeRemaining = null; // Re-initialize the turn (and timer), passing control to the next user - timerId = startTurnTimer(timerId, turnDuration, socket.id); + timerId = startTurnTimer(timerId, turnDuration); } // Broadcast current turn data to update all other clients - socket.broadcast.emit( 'updateState', getTurnData() ); + socket.broadcast.emit( 'updateState', null ); // Broadcast updated playerList to update all other clients socket.broadcast.emit('playerJoined', playerData); @@ -279,57 +279,30 @@ io.on('connection', function (socket) { /* ------------------------------------------------- FUNCTIONS ---------------------------------------------------- */ -function changeTurn(socketId) { - // If current client is first player, initialize! - if (gameState.turnIndex == null) { - console.log('\nINITIALIZING FIRST PLAYER\n'); - // *** REWRITE THIS PART! *** - - // Otherwise, increment the current player - } else { - gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; - } -} - -// Returns turnChange object for the current turn -function getTurnData() { - //console.log('getTurnData called'); - var currentPlayerId = getCurrentPlayerId(); - var currentPlayerName = gameState.players[gameState.turnIndex].login; - - var nextPlayerIndex = (gameState.turnIndex + 1) % gameState.players.length; - - var nextPlayerId = gameState.players[nextPlayerIndex].id; - var nextPlayerName = gameState.players[nextPlayerIndex].login; - - return {timeRemaining: gameState.timeRemaining - Date.now(), current: {id: currentPlayerId, name: currentPlayerName}, next: {id: nextPlayerId, name: nextPlayerName}, gist: gameState.currentGist}; +function changeTurn() { + gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; + // Broadcast turnChange to ALL clients + io.emit('turnChange', null); } // Initializes the turn and turn timer, returns timerId -function startTurnTimer(timerId, turnDuration, socketId) { +function startTurnTimer(timerId, turnDuration) { console.log('\nInitializing turn timer!'); - // Initialize time of next turn change (will use this to sync the clients) - gameState.timeRemaining = Date.now() + turnDuration; + // Initialize or reset time remaining + gameState.timeRemaining = turnDuration; - console.log( 'Next turn at: ' + new Date(gameState.timeRemaining).toString().substring(16,25) ); - - // Initialize the turn data using given user ID - changeTurn(socketId); + console.log( 'Next turn at: ' + new Date(Date.now() + gameState.timeRemaining).toString().substring(16,25) ); // Every time the timer goes off, - timerId = setInterval(() => { + timerId = setInterval(() => { console.log('\n >>>>>>>>>>> ' + new Date().toString().substring(16,25) + ' - Time to change turns! <<<<<<<<<<\n'); // Update time of next turn change - gameState.timeRemaining = Date.now() + turnDuration; - - changeTurn(socketId); + gameState.timeRemaining = turnDuration; - // Broadcast turnChange with data to ALL clients - io.emit( 'turnChange', getTurnData() ); + changeTurn(); - //console.log( getTurnData() ); }, turnDuration); // TO DO: enable user-specified turn length return timerId; From dbf7d64462f6ff14798baccb5eda07da4afda657 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 13:12:34 -0700 Subject: [PATCH 07/26] Add handleGameState to client, more refactoring --- public/local.js | 177 ++++++++++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 72 deletions(-) diff --git a/public/local.js b/public/local.js index c01f43b..e164d61 100644 --- a/public/local.js +++ b/public/local.js @@ -1,15 +1,38 @@ // Start a WebSocket connection with the server using SocketIO var socket = io(); - // Note that the SocketIO client-side library and this file (local.js) - // were both imported in index.html right before the tag +/* ------------------------------------------------------------ + GAME STATE: + +{ + timeRemaining, + turnIndex, + currentGist: {id, url, owner}, + playerList: + [ + {id, login,avatar_url}, { ... }, { ... }, ... + ], + editor: + { + content, + cursorAndSelection: { cursor: {column, row}, range: { end: {column, row}, start: {column, row} }, + scroll: {scrollLeft, scrollTop} + } +} + +-------------------------------------------------------------- */ + +let gameState = { + timeRemaining: null, + turnIndex: 0, + currentGist: null, + players: [] +}; // SAVING LOCAL STATE -- GLOBAL VARS (ugh) var currentPlayerId; -var userName; var nextPlayerId; var animationId; -var currentGist; // gistData: {id, url, owner} // Meant to be temporary: var currentAccessToken; @@ -124,9 +147,7 @@ socket.on('editorTextChange', handleServerEditorTextChange); socket.on('editorCursorChange', handleServerEditorCursorChange); socket.on('editorScrollChange', handleServerEditorScrollChange); socket.on('playerJoined', handlePlayerJoined); -socket.on('updateState', handleUpdateState); socket.on('turnChange', handleTurnChange); -socket.on('createNewGist', handleCreateNewGist); socket.on('newGist', handleNewGist); // When client connects to server, @@ -148,10 +169,7 @@ socket.on('disconnect', function(){ // Log in with authenticated user's GitHub data function loginUser (userData) { console.log('**************** Logged in! GitHub User Data: *********************'); - console.log(userData); - - // Save user's GitHub name to local game state - userName = userData.login; + console.log(userData); // Update views with user's GitHub name and avatar updateLoggedInView(userData.login, userData.avatar_url); @@ -162,14 +180,8 @@ function loginUser (userData) { // Send editorInputView data to server function handleLocalEditorTextChange (event) { - //console.log('handleLocalEditorTextChange event! value: '); - //console.log(event); - - //console.log('%c ' + editor.getValue(), 'color: green; font-weight: bold;'); - // If user is the current player, they can broadcast - if (socket.id === currentPlayerId) { - //console.log('Sending data to server!') + if (socket.id === getCurrentPlayerId() ) { // Send data to server socket.emit( 'editorTextChange', editor.getValue() ); } @@ -187,7 +199,7 @@ function handleUserNameChange (event) { //console.log('%c ' + myNameView.textContent, 'color: green; font-weight: bold;'); // Update UI if user is the current or next player - if (currentPlayerId === socket.id) { + if (getCurrentPlayerId() === socket.id) { updateCurrentTurnView(myNameView.textContent); } else if (nextPlayerId === socket.id) { updateNextTurnView(myNameView.textContent); @@ -199,14 +211,10 @@ function handleUserNameChange (event) { // Send cursor and selection data to server function handleLocalEditorCursorChange (event) { - //console.log('editorCursorChange fired!'); - //console.log('%c ' + event, 'color: green; font-weight: bold;'); - // Cursor object: - // {column, row} - + // {column, row} // Selection Range object: - // { end: {column, row}, start: {column, row} } + // { end: {column, row}, start: {column, row} } // Send to server: socket.emit( 'editorCursorChange', { cursor: editor.getSession().selection.getCursor(), range: editor.getSession().selection.getRange() } ); @@ -214,9 +222,6 @@ function handleLocalEditorCursorChange (event) { // Send scroll data to server function handleLocalEditorScrollChange (event) { - //console.log('editorScrollChange (left or top) fired!'); - //console.log('%c scrollLeft: ' + editor.getSession().getScrollLeft() + ', scrollTop: ' + editor.getSession().getScrollTop(), 'color: green; font-weight: bold;'); - // Send to server: socket.emit('editorScrollChange', { scrollLeft: editor.getSession().getScrollLeft(), scrollTop: editor.getSession().getScrollTop() }); } @@ -224,20 +229,13 @@ function handleLocalEditorScrollChange (event) { // TODO: Test 'input' event some more in different browsers! // maybe add support for IE < 9 later? - // When receiving new editorInputView data from server function handleServerEditorTextChange (data) { - //console.log('editorTextChange event received!'); - //console.log('%c ' + data, 'color: blue; font-weight: bold;'); - updateEditorView(data); } // When receiving new cursor/selection data from server function handleServerEditorCursorChange (data) { - //console.log('%c cursorChange event received!', 'color: blue; font-weight: bold;'); - //console.dir(data); - // Set Ace editor's cursor and selection range to match var updatedRange = new Range(data.range.start.row, data.range.start.column, data.range.end.row, data.range.end.column); editor.getSession().selection.setSelectionRange( updatedRange ); @@ -245,19 +243,50 @@ function handleServerEditorCursorChange (data) { // When receiving new scroll data from server function handleServerEditorScrollChange (data) { - //console.log('%c editorScrollChange event received!', 'color: blue; font-weight: bold;'); - //console.dir(data); - // Set Ace editor's scroll position to match editor.getSession().setScrollLeft(data.scrollLeft); editor.getSession().setScrollTop(data.scrollTop); } +// Initialize client after logging in, using game state data from server +function handleGameState (serverGameState) { + gameState.timeRemaining = serverGameState.timeRemaining; + gameState.turnIndex = serverGameState.turnIndex; + gameState.currentGist = serverGameState.currentGist; + gameState.players = serverGameState.players; + + console.log("handleGameState called"); + console.dir(gameState); + + // Update editor content + updateEditorView(serverGameState.editor.content); + + // Update editor cursor and selection range + if (serverGameState.editor.cursorAndSelection !== null) { + var updatedRange = new Range(serverGameState.editor.cursorAndSelection.range.start.row, serverGameState.editor.cursorAndSelection.range.start.column, serverGameState.editor.cursorAndSelection.range.end.row, serverGameState.editor.cursorAndSelection.range.end.column); + editor.getSession().selection.setSelectionRange( updatedRange ); + } + + // Update editor scroll position + if (serverGameState.editor.scroll !== null) { + editor.getSession().setScrollLeft(serverGameState.editor.scroll.scrollLeft); + editor.getSession().setScrollTop(serverGameState.editor.scroll.scrollTop); + } + + // If no Gist exists, create it! + if (gameState.currentGist !== null) { + handleCreateNewGist(); + } + + // Update UI + updateTimeLeftView(gameState.timeRemaining); + updateCurrentTurnView(getCurrentPlayer().login); + updateNextTurnView(getCurrentPlayer().login); + toggleMyTurnHighlight(); +} + // When receiving new player list data from server function handlePlayerJoined (playerData) { - //console.log('%c playerJoined event received!', 'color: blue; font-weight: bold;'); - //console.dir(playerData); - // Transform the data!! // Transform into an array to more easily reorder it @@ -299,7 +328,7 @@ function handleTurnChange (turnData) { togglePlayerHighlight(false); // Temporarily save the previous player ID for later comparison - var previousPlayerId = currentPlayerId; + var previousPlayerId = getCurrentPlayerId(); // Update local state currentPlayerId = turnData.current.id; @@ -310,7 +339,7 @@ function handleTurnChange (turnData) { console.log("User's turn is about to end."); // If the current player does NOT own the current Gist, - if (userName !== turnData.gist.owner) { + if (getPlayerById(socket.id, gameState.players).login !== turnData.gist.owner) { //console.log("handleTurnChange: now forking and editing gist " + turnData.gist.id); @@ -347,36 +376,6 @@ function handleTurnChange (turnData) { if (turnData.gist != null) updateCurrentGistView(turnData.gist); } -// When receiving updateState event from server, -// when new users join the game! -function handleUpdateState (turnData) { - //console.log('%c handleUpdateState event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;'); - //console.dir(turnData); - - // Remove highlight from previous current player's name in playerListView - togglePlayerHighlight(false); - - // Temporarily save the previous player ID for later comparison - var previousPlayerId = currentPlayerId; - - // Update local state - currentPlayerId = turnData.current.id; - nextPlayerId = turnData.next.id; - - //console.log('Updated local state. Current ID: ' + currentPlayerId + ', next ID: ' + nextPlayerId); - - // Add highlight to the new current player's name in playerListView - togglePlayerHighlight(true); - - // Update UI - updateTimeLeftView(turnData.timeRemaining); - updateCurrentTurnView(turnData.current.name); - updateNextTurnView(turnData.next.name); - toggleMyTurnHighlight(); - if (turnData.gist != null) updateCurrentGistView(turnData.gist); -} - - // When receiving "newGist" event from server, function handleNewGist (gistData) { // Update local state @@ -748,4 +747,38 @@ function getAllUrlParams(url) { } return obj; +} + +function getCurrentPlayer() { + return gameState.players[gameState.turnIndex]; +} + +function getCurrentPlayerId() { + return gameState.players[gameState.turnIndex].id; +} + +function getPlayerById(id, playerList){ + for (var i = 0; i < playerList.length; i++) { + if (playerList[i].id === id) { + return playerList[i]; + } + } + return -1; +} + +function getPlayerIndexById(id, playerList) { + for (var i = 0; i < playerList.length; i++) { + if (playerList[i].id === id) { + return i; + } + } + return -1; +} + +function removePlayer(id, playerList) { + for (var i = 0; i < playerList.length; i++) { + if (playerList[i].id === id) { + playerList.splice(i, 1); + } + } } \ No newline at end of file From 3a9a59ca1f929c2005bfbd4566e95ae61fe2a8b5 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 13:23:44 -0700 Subject: [PATCH 08/26] Close #18, only current player can type --- public/local.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/local.js b/public/local.js index e164d61..ff775a7 100644 --- a/public/local.js +++ b/public/local.js @@ -283,6 +283,11 @@ function handleGameState (serverGameState) { updateCurrentTurnView(getCurrentPlayer().login); updateNextTurnView(getCurrentPlayer().login); toggleMyTurnHighlight(); + + // If user is the current player, let them type! + if ( socket.id === getCurrentPlayerId() ) { + editor.setReadOnly(false); + } } // When receiving new player list data from server From 27303e6ba0936c4d81fa5040d2d6860b2e879bc7 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 13:35:08 -0700 Subject: [PATCH 09/26] Refactor handleTurnChange on client, more state edits --- public/local.js | 56 +++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/public/local.js b/public/local.js index ff775a7..da9ec04 100644 --- a/public/local.js +++ b/public/local.js @@ -30,8 +30,6 @@ let gameState = { }; // SAVING LOCAL STATE -- GLOBAL VARS (ugh) -var currentPlayerId; -var nextPlayerId; var animationId; // Meant to be temporary: var currentAccessToken; @@ -281,7 +279,7 @@ function handleGameState (serverGameState) { // Update UI updateTimeLeftView(gameState.timeRemaining); updateCurrentTurnView(getCurrentPlayer().login); - updateNextTurnView(getCurrentPlayer().login); + updateNextTurnView(getNextPlayer().login); toggleMyTurnHighlight(); // If user is the current player, let them type! @@ -313,7 +311,7 @@ function handlePlayerJoined (playerData) { //console.log(playerArray); // Get names of current and next players based on saved local IDs - var currentPlayerName = playerData[currentPlayerId].login; + var currentPlayerName = playerData[getCurrentPlayerId()].login; var nextPlayerName = playerData[nextPlayerId].login; //console.log('Updating UI with currentPlayerName: ' + currentPlayerName + ', nextPlayerName: ' + nextPlayerName); @@ -325,43 +323,40 @@ function handlePlayerJoined (playerData) { } // When receiving turnChange event from server -function handleTurnChange (turnData) { +function handleTurnChange () { console.log('%c turnChange event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;'); - //console.dir(turnData); - // Remove highlight from previous current player's name in playerListView + // Remove highlight from previous player's name in playerListView togglePlayerHighlight(false); // Temporarily save the previous player ID for later comparison var previousPlayerId = getCurrentPlayerId(); - - // Update local state - currentPlayerId = turnData.current.id; - nextPlayerId = turnData.next.id; + + changeTurn(); // If user's turn is ending and a Gist exists, fork and/or edit the gist before passing control to next player! - if (socket.id === previousPlayerId && turnData.gist != null) { + if (socket.id === previousPlayerId && gameState.currentGist != null) { console.log("User's turn is about to end."); // If the current player does NOT own the current Gist, - if (getPlayerById(socket.id, gameState.players).login !== turnData.gist.owner) { + if (getCurrentPlayer().login !== gameState.currentGist.owner) { - //console.log("handleTurnChange: now forking and editing gist " + turnData.gist.id); + console.log("handleTurnChange: now forking and editing gist " + gameState.currentGist.id); // Fork/edit current Gist on behalf of player whose turn is about to end, and send new ID to server - forkAndEditGist(turnData.gist.id, editor.getValue()); + forkAndEditGist(gameState.currentGist.id, editor.getValue()); // Otherwise, JUST EDIT the current Gist and send new ID to server } else { - //console.log("handleTurnChange: now editing gist " + turnData.gist.id); - editGist(turnData.gist.id, editor.getValue()); + console.log("handleTurnChange: now editing gist " + gameState.currentGist.id); + editGist(gameState.currentGist.id, editor.getValue()); } } // If user is no longer the current player, prevent them from typing/broadcasting! - if (socket.id !== currentPlayerId) { + if ( socket.id !== getCurrentPlayerId() ) { console.log("User's turn is over."); editor.setReadOnly(true); // Otherwise if user's turn is now starting, @@ -369,16 +364,14 @@ function handleTurnChange (turnData) { console.log("User's turn is starting!"); // let the user type/broadcast again editor.setReadOnly(false); - } // Update UI togglePlayerHighlight(true); - updateTimeLeftView(turnData.timeRemaining); - updateCurrentTurnView(turnData.current.name); - updateNextTurnView(turnData.next.name); + updateTimeLeftView(gameState.timeRemaining); + updateCurrentTurnView(getCurrentPlayer().login); + updateNextTurnView(getNextPlayer().login); toggleMyTurnHighlight(); - if (turnData.gist != null) updateCurrentGistView(turnData.gist); } // When receiving "newGist" event from server, @@ -417,7 +410,7 @@ function updateLoggedInView (userName, userAvatar) { function toggleMyTurnHighlight () { // If user is the next player, highlight text box - if (socket.id === currentPlayerId) { + if ( socket.id === getCurrentPlayerId() ) { document.body.classList.add('myturn'); } else { document.body.classList.remove('myturn'); @@ -428,13 +421,13 @@ function toggleMyTurnHighlight () { // Highlight name of current player in playerListView function togglePlayerHighlight (toggleOn) { // First check if element exists, for case where user is the only player - if (document.getElementById(currentPlayerId)) { + if ( document.getElementById(getCurrentPlayerId()) ) { // Add highlight if (toggleOn) { - document.getElementById(currentPlayerId).classList.add('highlight'); + document.getElementById( getCurrentPlayerId() ).classList.add('highlight'); // Remove highlight } else { - document.getElementById(currentPlayerId).classList.remove('highlight'); + document.getElementById( getCurrentPlayerId() ).classList.remove('highlight'); } } } @@ -754,6 +747,10 @@ function getAllUrlParams(url) { return obj; } +function changeTurn() { + gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; +} + function getCurrentPlayer() { return gameState.players[gameState.turnIndex]; } @@ -762,6 +759,11 @@ function getCurrentPlayerId() { return gameState.players[gameState.turnIndex].id; } +function getNextPlayer() { + var nextPlayerIndex = (gameState.turnIndex + 1) % gameState.players.length; + return gameState.players[nextPlayerIndex]; +} + function getPlayerById(id, playerList){ for (var i = 0; i < playerList.length; i++) { if (playerList[i].id === id) { From bdc0252cdec878d7f78dd328e6e6575d691f78ae Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 13:37:34 -0700 Subject: [PATCH 10/26] Refactor getCurrentPlayer(), not ...ById() --- public/local.js | 24 ++++++++++-------------- server.js | 10 +++++----- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/public/local.js b/public/local.js index da9ec04..30857aa 100644 --- a/public/local.js +++ b/public/local.js @@ -179,7 +179,7 @@ function loginUser (userData) { // Send editorInputView data to server function handleLocalEditorTextChange (event) { // If user is the current player, they can broadcast - if (socket.id === getCurrentPlayerId() ) { + if (socket.id === getCurrentPlayer().id ) { // Send data to server socket.emit( 'editorTextChange', editor.getValue() ); } @@ -197,7 +197,7 @@ function handleUserNameChange (event) { //console.log('%c ' + myNameView.textContent, 'color: green; font-weight: bold;'); // Update UI if user is the current or next player - if (getCurrentPlayerId() === socket.id) { + if (getCurrentPlayer().id === socket.id) { updateCurrentTurnView(myNameView.textContent); } else if (nextPlayerId === socket.id) { updateNextTurnView(myNameView.textContent); @@ -283,7 +283,7 @@ function handleGameState (serverGameState) { toggleMyTurnHighlight(); // If user is the current player, let them type! - if ( socket.id === getCurrentPlayerId() ) { + if ( socket.id === getCurrentPlayer().id ) { editor.setReadOnly(false); } } @@ -311,7 +311,7 @@ function handlePlayerJoined (playerData) { //console.log(playerArray); // Get names of current and next players based on saved local IDs - var currentPlayerName = playerData[getCurrentPlayerId()].login; + var currentPlayerName = playerData[getCurrentPlayer().id].login; var nextPlayerName = playerData[nextPlayerId].login; //console.log('Updating UI with currentPlayerName: ' + currentPlayerName + ', nextPlayerName: ' + nextPlayerName); @@ -330,7 +330,7 @@ function handleTurnChange () { togglePlayerHighlight(false); // Temporarily save the previous player ID for later comparison - var previousPlayerId = getCurrentPlayerId(); + var previousPlayerId = getCurrentPlayer().id; changeTurn(); @@ -356,7 +356,7 @@ function handleTurnChange () { } // If user is no longer the current player, prevent them from typing/broadcasting! - if ( socket.id !== getCurrentPlayerId() ) { + if ( socket.id !== getCurrentPlayer().id ) { console.log("User's turn is over."); editor.setReadOnly(true); // Otherwise if user's turn is now starting, @@ -410,7 +410,7 @@ function updateLoggedInView (userName, userAvatar) { function toggleMyTurnHighlight () { // If user is the next player, highlight text box - if ( socket.id === getCurrentPlayerId() ) { + if ( socket.id === getCurrentPlayer().id ) { document.body.classList.add('myturn'); } else { document.body.classList.remove('myturn'); @@ -421,13 +421,13 @@ function toggleMyTurnHighlight () { // Highlight name of current player in playerListView function togglePlayerHighlight (toggleOn) { // First check if element exists, for case where user is the only player - if ( document.getElementById(getCurrentPlayerId()) ) { + if ( document.getElementById(getCurrentPlayer().id) ) { // Add highlight if (toggleOn) { - document.getElementById( getCurrentPlayerId() ).classList.add('highlight'); + document.getElementById( getCurrentPlayer().id ).classList.add('highlight'); // Remove highlight } else { - document.getElementById( getCurrentPlayerId() ).classList.remove('highlight'); + document.getElementById( getCurrentPlayer().id ).classList.remove('highlight'); } } } @@ -755,10 +755,6 @@ function getCurrentPlayer() { return gameState.players[gameState.turnIndex]; } -function getCurrentPlayerId() { - return gameState.players[gameState.turnIndex].id; -} - function getNextPlayer() { var nextPlayerIndex = (gameState.turnIndex + 1) % gameState.players.length; return gameState.players[nextPlayerIndex]; diff --git a/server.js b/server.js index ae29796..f5463d3 100644 --- a/server.js +++ b/server.js @@ -210,7 +210,7 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to type (in case of client-side tampering with the JS!) - if ( socket.id === getCurrentPlayerId() ) { + if ( socket.id === getCurrentPlayer().id ) { // Update saved state of the shared text editor gameState.editor.content = data; @@ -229,7 +229,7 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === getCurrentPlayerId() ) { + if (socket.id === getCurrentPlayer().id ) { // Update saved state of the shared text editor gameState.editor.cursorAndSelection = data; @@ -248,7 +248,7 @@ io.on('connection', function (socket) { //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === getCurrentPlayerId() ) { + if (socket.id === getCurrentPlayer().id ) { // Update saved state of the shared text editor gameState.editor.scroll = data; @@ -309,8 +309,8 @@ function startTurnTimer(timerId, turnDuration) { } // Helper functions, just in case: -function getCurrentPlayerId() { - return gameState.players[gameState.turnIndex].id; +function getCurrentPlayer() { + return gameState.players[gameState.turnIndex]; } function getPlayerById(id, playerList){ for (var i = 0; i < playerList.length; i++) { From bfdaa7f0d53fb5b6665b5f255c1c1530dfd57879 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 13:51:43 -0700 Subject: [PATCH 11/26] Refactor handlePlayerJoined, other odds and ends --- public/local.js | 41 ++++++++++++----------------------------- server.js | 8 ++++---- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/public/local.js b/public/local.js index 30857aa..e8091a2 100644 --- a/public/local.js +++ b/public/local.js @@ -290,36 +290,19 @@ function handleGameState (serverGameState) { // When receiving new player list data from server function handlePlayerJoined (playerData) { - // Transform the data!! + // Add new player + gameState.players.push(playerData); - // Transform into an array to more easily reorder it - var playerIdArray = Object.keys(playerData); + // Merge the two arrays, reording so current user is at the top (but removed from this list), without modifying the turn order + var clientIndex = getPlayerIndexById(socket.id, gameState,players); + var playersTopSegment = gameState.players.slice(clientIndex); + var playersBottomSegment = gameState.players.slice(0, clientIndex); + var reorderedPlayers = playersTopSegment.concat(playersBottomSegment); - var userIndex = playerIdArray.indexOf(socket.id); - var playerListTopSegment = playerIdArray.slice(userIndex+1); - var playerListBottomSegment = playerIdArray.slice(0, userIndex); - - // Merge the two arrays, reording so current user is at the top - // (but removed from this list), without modifying the turn order - playerIdArray = playerListTopSegment.concat(playerListBottomSegment); - - // Generate an array of ids, user logins, and avatar_urls for updating the UI - var playerArray = playerIdArray.map(function(id){ - return {id: id, login: playerData[id].login, avatar_url: playerData[id].avatar_url}; - }); - //console.log('playerArray:'); - //console.log(playerArray); - - // Get names of current and next players based on saved local IDs - var currentPlayerName = playerData[getCurrentPlayer().id].login; - var nextPlayerName = playerData[nextPlayerId].login; - - //console.log('Updating UI with currentPlayerName: ' + currentPlayerName + ', nextPlayerName: ' + nextPlayerName); - // Update the UI - updatePlayerListView(playerArray); - updateCurrentTurnView(currentPlayerName); - updateNextTurnView(nextPlayerName); + updatePlayerListView(reorderedPlayers); + updateCurrentTurnView(getCurrentPlayer().login); + updateNextTurnView(getNextPlayer().login); } // When receiving turnChange event from server @@ -432,7 +415,7 @@ function togglePlayerHighlight (toggleOn) { } } -// Using data from server, update list of players +// Update list of players function updatePlayerListView (playerArray) { // Delete the contents of playerListView each time while (playerListView.firstChild) { @@ -454,7 +437,7 @@ function updatePlayerListView (playerArray) { userAvatarElem.classList.add('avatar'); // If this player is the current player, highlight their name - if (player.id === currentPlayerId) { + if (player.id === getCurrentPlayer().id) { playerElement.classList.add('highlight'); } diff --git a/server.js b/server.js index f5463d3..934a3cc 100644 --- a/server.js +++ b/server.js @@ -132,12 +132,12 @@ io.on('connection', function (socket) { console.log('\nA user connected! (But not yet logged in.)\n'); // When a player logs in, - socket.on('playerJoined', function (userData) { + socket.on('playerJoined', function (playerData) { console.log('\n* * * * # # # # User logged in! # # # # * * * * *'); - console.log('\t\t > > > ' + userData.login + ' < < <\n'); + console.log('\t\t > > > ' + playerData.login + ' < < <\n'); // Add new player - gameState.players.push({id: socket.id, login: userData.login, avatar_url: userData.avatar_url}); + gameState.players.push({id: socket.id, login: playerData.login, avatar_url: playerData.avatar_url}); // If there is 1 player logged in, START THE GAME!!! if (gameState.players.length === 1) { @@ -148,7 +148,7 @@ io.on('connection', function (socket) { socket.emit('gameState', gameState); // Broadcast new player data to all OTHER clients - socket.broadcast.emit('playerJoined', playerData); + socket.broadcast.emit('playerJoined', {id: socket.id, login: playerData.login, avatar_url: playerData.avatar_url}); }); From 00d125d810d1135ceb07fce9d6cf172eadd8d389 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 13:55:45 -0700 Subject: [PATCH 12/26] Remove handleUserNameChange(), preventUserTyping() --- public/local.js | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/public/local.js b/public/local.js index e8091a2..f0179ae 100644 --- a/public/local.js +++ b/public/local.js @@ -185,28 +185,6 @@ function handleLocalEditorTextChange (event) { } } -// Function that prevents user from typing when it's not their turn -function preventUserTyping (event) { - event.preventDefault(); - //return false; -}; - -// Send user's new name to server and update UI -function handleUserNameChange (event) { - //console.log('handleUserNameChange event! value: '); - //console.log('%c ' + myNameView.textContent, 'color: green; font-weight: bold;'); - - // Update UI if user is the current or next player - if (getCurrentPlayer().id === socket.id) { - updateCurrentTurnView(myNameView.textContent); - } else if (nextPlayerId === socket.id) { - updateNextTurnView(myNameView.textContent); - } - - // Send user's new name to server - socket.emit('userNameChange', myNameView.textContent); -} - // Send cursor and selection data to server function handleLocalEditorCursorChange (event) { // Cursor object: @@ -489,7 +467,7 @@ function updateCurrentTurnView (playerName) { currentTurnView.textContent = playerName; // If user is the current player, highlight their name - if (socket.id === currentPlayerId) { + if (socket.id === getCurrentPlayer().id) { currentTurnView.classList.add('highlightme'); currentTurnView.textContent = "It's your turn!"; } else { @@ -503,7 +481,7 @@ function updateNextTurnView (playerName) { nextTurnView.textContent = playerName; // If user is the next player, highlight their name - if (socket.id === nextPlayerId) { + if (socket.id === getNextPlayer().id) { nextTurnView.classList.add('highlightme'); nextTurnView.textContent = "You're up next!"; } else { From 45165b6da46d96799056280e0c7e34f267007b63 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 14:12:49 -0700 Subject: [PATCH 13/26] Fix small typo in handleUserJoined --- public/local.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/local.js b/public/local.js index f0179ae..5008eed 100644 --- a/public/local.js +++ b/public/local.js @@ -272,7 +272,7 @@ function handlePlayerJoined (playerData) { gameState.players.push(playerData); // Merge the two arrays, reording so current user is at the top (but removed from this list), without modifying the turn order - var clientIndex = getPlayerIndexById(socket.id, gameState,players); + var clientIndex = getPlayerIndexById(socket.id, gameState.players); var playersTopSegment = gameState.players.slice(clientIndex); var playersBottomSegment = gameState.players.slice(0, clientIndex); var reorderedPlayers = playersTopSegment.concat(playersBottomSegment); From 11821ef1a6774c072645674fa16f0526f6ba3e86 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 14:13:55 -0700 Subject: [PATCH 14/26] Close #14, verify newGist event on server --- public/local.js | 16 ++++++++-------- server.js | 46 ++++++++++++++++++---------------------------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/public/local.js b/public/local.js index 5008eed..4454e03 100644 --- a/public/local.js +++ b/public/local.js @@ -290,24 +290,24 @@ function handleTurnChange () { // Remove highlight from previous player's name in playerListView togglePlayerHighlight(false); - // Temporarily save the previous player ID for later comparison - var previousPlayerId = getCurrentPlayer().id; + // Temporarily save the previous player for later comparison + var previousPlayer = getCurrentPlayer(); changeTurn(); - // If user's turn is ending and a Gist exists, fork and/or edit the gist before passing control to next player! - if (socket.id === previousPlayerId && gameState.currentGist != null) { + // If this client's turn is ending and a Gist exists, fork and/or edit the gist before passing control to next player! + if (socket.id === previousPlayer.id && gameState.currentGist != null) { console.log("User's turn is about to end."); - // If the current player does NOT own the current Gist, - if (getCurrentPlayer().login !== gameState.currentGist.owner) { + // If this client (the previous player) does NOT own the current Gist, + if (previousPlayer.login !== gameState.currentGist.owner) { console.log("handleTurnChange: now forking and editing gist " + gameState.currentGist.id); - // Fork/edit current Gist on behalf of player whose turn is about to end, and send new ID to server + // Fork/edit current Gist on behalf of this client (the previous player, whose turn is ending), and send new ID to server forkAndEditGist(gameState.currentGist.id, editor.getValue()); - // Otherwise, JUST EDIT the current Gist and send new ID to server + // Otherwise, just edit the current Gist } else { console.log("handleTurnChange: now editing gist " + gameState.currentGist.id); diff --git a/server.js b/server.js index 934a3cc..03e746e 100644 --- a/server.js +++ b/server.js @@ -205,72 +205,56 @@ io.on('connection', function (socket) { // When "editorTextChange" event received, update editor state and broadcast it back out socket.on('editorTextChange', function (data) { - - //console.log('editorTextChange event received!'); - //console.log(data); // Double check that this user is allowed to type (in case of client-side tampering with the JS!) - if ( socket.id === getCurrentPlayer().id ) { + if ( socket.id === getCurrentPlayer().id ) { // Update saved state of the shared text editor gameState.editor.content = data; // Broadcast updated editor content to other clients socket.broadcast.emit('editorTextChange', gameState.editor.content); - - //console.log('Broadcasting gameState.editor.content to other clients!'); } - }); // When "editorCursorChange" event received, update editor state and broadcast it back out socket.on('editorCursorChange', function (data) { - - //console.log('editorCursorChange event received!'); - //console.log(data); // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === getCurrentPlayer().id ) { + if (socket.id === getCurrentPlayer().id ) { // Update saved state of the shared text editor gameState.editor.cursorAndSelection = data; // Broadcast data to other clients socket.broadcast.emit('editorCursorChange', gameState.editor.cursorAndSelection); - - //console.log('Broadcasting editorCursorChange to other clients!'); } - }); // When "editorScrollChange" event received, update editor state and broadcast it back out socket.on('editorScrollChange', function (data) { - //console.log('editorScrollChange event received!'); - //console.log(data); - // Double check that this user is allowed to broadcast (in case of client-side tampering with the JS!) - if (socket.id === getCurrentPlayer().id ) { + if (socket.id === getCurrentPlayer().id ) { // Update saved state of the shared text editor gameState.editor.scroll = data; // Broadcast data to other clients socket.broadcast.emit('editorScrollChange', gameState.editor.cursorAndSelection); - - //console.log('Broadcasting editorScrollChange to other clients!'); - } - + } }); // When "newGist" event received, update state and broadcast it back out socket.on('newGist', function (data) { - console.log('\nnewGist event received!\n'); - //console.log(data); + // Double check that this user is allowed to broadcast a new gist! + if (socket.id === getPreviousPlayer().id ) { + console.log('\nnewGist event received!\n'); + //console.log(data); - gameState.currentGist = data; + gameState.currentGist = data; - // Broadcast data to other clients - socket.broadcast.emit('newGist', data); - + // Broadcast data to other clients + socket.broadcast.emit('newGist', data); + } }); }); // End of SocketIO part of the code @@ -312,6 +296,12 @@ function startTurnTimer(timerId, turnDuration) { function getCurrentPlayer() { return gameState.players[gameState.turnIndex]; } + +function getPreviousPlayer() { + var previousPlayerIndex = (gameState.turnIndex - 1) % gameState.players.length; + return gameState.players[previousPlayerIndex]; +} + function getPlayerById(id, playerList){ for (var i = 0; i < playerList.length; i++) { if (playerList[i].id === id) { From 0e04f6a012cdbe83cc4d17fad95465b65dea055e Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 14:30:47 -0700 Subject: [PATCH 15/26] Add playerLeft event to server and refactor --- server.js | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/server.js b/server.js index 03e746e..b8acb8e 100644 --- a/server.js +++ b/server.js @@ -152,50 +152,44 @@ io.on('connection', function (socket) { }); - // When a user disconnects, + // When a player disconnects, socket.on('disconnect', function() { - console.log('\nA user disconnected!\n'); - //console.log('gameState.turnIndex: ' + gameState.turnIndex); + console.log('\nA user disconnected!\n'); - // If disconnected user was logged in, + // If disconnected player was logged in, if (getPlayerById(socket.id, gameState.players) !== -1) { + console.log('\n\t User removed from list of logged-in players. ID: ' + socket.id); - // Temporarily save ID of current player (before removing from playerList, for a later check!) - var currentPlayerId = gameState.players[gameState.turnIndex]; + // Broadcast the disconnected player's ID to update all other clients + socket.broadcast.emit( 'playerLeft', socket.id ); + + // Temporarily save ID of current player (before removing from player list, for a later check!) + var currentPlayerId = getCurrentPlayer().id; - // Remove disconnected user from player list + // Remove disconnected player from player list removePlayer(socket.id, gameState.players); // If no logged-in players are left, reset the game! if (gameState.players.length === 0) { console.log('\nNo players left. Turning off the turn timer!\n'); - gameState.turnIndex = null; // Turn off the timer - clearInterval(timerId); - gameState.timeRemaining = null; + clearInterval(timerId); - // Otherwise, if there are players left, - } else { - // If the disconnected user was the current player, restart timer and change the turn! - if (socket.id === currentPlayerId) { + // Otherwise, if there are players left, and the disconnected player was the current player, restart timer and change the turn! + } else if (socket.id === currentPlayerId) { console.log('\nCURRENT PLAYER disconnected! Restarting turn/timer.\n'); // Turn off the timer - clearInterval(timerId); - gameState.timeRemaining = null; - - // Re-initialize the turn (and timer), passing control to the next user - timerId = startTurnTimer(timerId, turnDuration); - } + clearInterval(timerId); - // Broadcast current turn data to update all other clients - socket.broadcast.emit( 'updateState', null ); + // Restart the timer + timerId = startTurnTimer(timerId, turnDuration); - // Broadcast updated playerList to update all other clients - socket.broadcast.emit('playerJoined', playerData); + // Change the turn, passing control to the next player and broadcasting to all players + changeTurn(); } } else { From 29279518b4616d9ed186e1a3ff310b9bec355dfa Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 14:45:45 -0700 Subject: [PATCH 16/26] Add handlePlayerLeft on client, refactor more --- public/local.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/public/local.js b/public/local.js index 4454e03..e303633 100644 --- a/public/local.js +++ b/public/local.js @@ -255,6 +255,7 @@ function handleGameState (serverGameState) { } // Update UI + updatePlayerListView(gameState.players); updateTimeLeftView(gameState.timeRemaining); updateCurrentTurnView(getCurrentPlayer().login); updateNextTurnView(getNextPlayer().login); @@ -266,23 +267,26 @@ function handleGameState (serverGameState) { } } -// When receiving new player list data from server -function handlePlayerJoined (playerData) { +// When a new player joins, update using data from server +function handlePlayerJoined (newPlayerData) { // Add new player - gameState.players.push(playerData); - - // Merge the two arrays, reording so current user is at the top (but removed from this list), without modifying the turn order - var clientIndex = getPlayerIndexById(socket.id, gameState.players); - var playersTopSegment = gameState.players.slice(clientIndex); - var playersBottomSegment = gameState.players.slice(0, clientIndex); - var reorderedPlayers = playersTopSegment.concat(playersBottomSegment); + gameState.players.push(newPlayerData); // Update the UI - updatePlayerListView(reorderedPlayers); + updatePlayerListView(gameState.players); updateCurrentTurnView(getCurrentPlayer().login); updateNextTurnView(getNextPlayer().login); } +// When a player disconnects, update using data from server +function handlePlayerLeft (playerId) { + // Remove disconnected player from player list + removePlayer(playerId, gameState.players); + + // Remove view for disconnected player from the player list view + playerListView.removeChild( document.getElementById(playerId) ); +} + // When receiving turnChange event from server function handleTurnChange () { console.log('%c turnChange event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;'); @@ -394,7 +398,14 @@ function togglePlayerHighlight (toggleOn) { } // Update list of players -function updatePlayerListView (playerArray) { +function updatePlayerListView (playerArray) { + + // First reorder the player array so current user is at the top (but removed from this list), without modifying the turn order + var clientIndex = getPlayerIndexById(socket.id, playerArray); + var playersTopSegment = playerArray.slice(clientIndex); + var playersBottomSegment = playerArray.slice(0, clientIndex); + var reorderedPlayers = playersTopSegment.concat(playersBottomSegment); + // Delete the contents of playerListView each time while (playerListView.firstChild) { playerListView.removeChild(playerListView.firstChild); @@ -404,7 +415,7 @@ function updatePlayerListView (playerArray) { playerListView.appendChild(myNameListItemView); // Append player names to playerListView - playerArray.forEach(function(player){ + reorderedPlayers.forEach(function(player){ // Create an
  • node with player's name and avatar var playerElement = document.createElement('li'); playerElement.id = player.id; From 79e7aa4c633746e10096dd30ce932c806bdd984c Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Tue, 4 Jul 2017 14:51:52 -0700 Subject: [PATCH 17/26] Add new SocketIO event listeners on client --- public/local.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/local.js b/public/local.js index e303633..72d81e9 100644 --- a/public/local.js +++ b/public/local.js @@ -144,7 +144,9 @@ editor.getSession().on('changeScrollTop', handleLocalEditorScrollChange); socket.on('editorTextChange', handleServerEditorTextChange); socket.on('editorCursorChange', handleServerEditorCursorChange); socket.on('editorScrollChange', handleServerEditorScrollChange); +socket.on('gameState', handleGameState); socket.on('playerJoined', handlePlayerJoined); +socket.on('playerLeft', handlePlayerLeft); socket.on('turnChange', handleTurnChange); socket.on('newGist', handleNewGist); From af532835064ba12cb6de885254431b79a61faffa Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Wed, 5 Jul 2017 09:32:08 -0700 Subject: [PATCH 18/26] Refactor updatePlayerListView and similar --- public/local.js | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/public/local.js b/public/local.js index 72d81e9..71603cd 100644 --- a/public/local.js +++ b/public/local.js @@ -53,8 +53,6 @@ var timeLeftView = document.getElementById('timeleft'); var currentTurnView = document.getElementById('currentturn'); var nextTurnView = document.getElementById('nextturn'); var playerListView = document.getElementById('playerlist'); -var myNameView = document.getElementById('myname'); -var myNameListItemView = document.getElementById('me'); var currentGistView = document.getElementById('currentgist'); /* ------------------------------------------------- GITHUB AUTHENTICATION @@ -150,15 +148,6 @@ socket.on('playerLeft', handlePlayerLeft); socket.on('turnChange', handleTurnChange); socket.on('newGist', handleNewGist); -// When client connects to server, -socket.on('connect', function(){ - // Generate default name to match socket.id - //myNameView.textContent = 'Anonymous-' + socket.id.slice(0,4); - - // Update ID of first
  • in playerListView for player name highlighting with togglePlayerHighlight() - myNameListItemView.id = socket.id; -}); - // When client disconnects, stop the timer! socket.on('disconnect', function(){ console.log('* * * * * * * * DISCONNECTED FROM SERVER * * * * * * * *'); @@ -362,15 +351,6 @@ function updateLoggedInView (userName, userAvatar) { window.setTimeout(function(){ loginModalView.style.display = 'none'; }, 900); - - // Set myNameView to use GitHub username - myNameView.textContent = userName; - - // Display user's GitHub avatar image - var userAvatarElem = document.createElement('img'); - userAvatarElem.src = userAvatar; - userAvatarElem.classList.add('avatar'); - myNameListItemView.insertBefore(userAvatarElem, myNameView); } // UI highlights to notify user when it's their turn @@ -402,7 +382,7 @@ function togglePlayerHighlight (toggleOn) { // Update list of players function updatePlayerListView (playerArray) { - // First reorder the player array so current user is at the top (but removed from this list), without modifying the turn order + // First reorder the player array so current user is at the top, without modifying the turn order var clientIndex = getPlayerIndexById(socket.id, playerArray); var playersTopSegment = playerArray.slice(clientIndex); var playersBottomSegment = playerArray.slice(0, clientIndex); @@ -413,9 +393,6 @@ function updatePlayerListView (playerArray) { playerListView.removeChild(playerListView.firstChild); } - // Put li#me back into playerListView! (using previously saved reference) - playerListView.appendChild(myNameListItemView); - // Append player names to playerListView reorderedPlayers.forEach(function(player){ // Create an
  • node with player's name and avatar From b1cbac53d61ffd6a7a7ef4b3b8fb45daf7c91d3b Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Thu, 6 Jul 2017 13:51:52 -0700 Subject: [PATCH 19/26] Unrefactor turn timer, use timestamp on both! --- public/local.js | 32 ++++++++++++++------------------ server.js | 12 ++++++------ 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/public/local.js b/public/local.js index 71603cd..07026c8 100644 --- a/public/local.js +++ b/public/local.js @@ -5,7 +5,7 @@ var socket = io(); GAME STATE: { - timeRemaining, + nextTurnTimestamp, turnIndex, currentGist: {id, url, owner}, playerList: @@ -23,7 +23,7 @@ var socket = io(); -------------------------------------------------------------- */ let gameState = { - timeRemaining: null, + nextTurnTimestamp: null, turnIndex: 0, currentGist: null, players: [] @@ -217,7 +217,7 @@ function handleServerEditorScrollChange (data) { // Initialize client after logging in, using game state data from server function handleGameState (serverGameState) { - gameState.timeRemaining = serverGameState.timeRemaining; + gameState.nextTurnTimestamp = serverGameState.nextTurnTimestamp; gameState.turnIndex = serverGameState.turnIndex; gameState.currentGist = serverGameState.currentGist; gameState.players = serverGameState.players; @@ -247,7 +247,7 @@ function handleGameState (serverGameState) { // Update UI updatePlayerListView(gameState.players); - updateTimeLeftView(gameState.timeRemaining); + updateTimeLeftView(gameState.nextTurnTimestamp); updateCurrentTurnView(getCurrentPlayer().login); updateNextTurnView(getNextPlayer().login); toggleMyTurnHighlight(); @@ -282,6 +282,9 @@ function handlePlayerLeft (playerId) { function handleTurnChange () { console.log('%c turnChange event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;'); + // Update the timestamp of the next turn, reset the clock! + gameState.nextTurnTimestamp = Date.now() + turnDuration; + // Remove highlight from previous player's name in playerListView togglePlayerHighlight(false); @@ -292,7 +295,7 @@ function handleTurnChange () { // If this client's turn is ending and a Gist exists, fork and/or edit the gist before passing control to next player! if (socket.id === previousPlayer.id && gameState.currentGist != null) { - console.log("User's turn is about to end."); + console.log("This user's turn is about to end."); // If this client (the previous player) does NOT own the current Gist, if (previousPlayer.login !== gameState.currentGist.owner) { @@ -312,19 +315,18 @@ function handleTurnChange () { } // If user is no longer the current player, prevent them from typing/broadcasting! - if ( socket.id !== getCurrentPlayer().id ) { - console.log("User's turn is over."); + if ( socket.id !== getCurrentPlayer().id ) { editor.setReadOnly(true); // Otherwise if user's turn is now starting, } else { - console.log("User's turn is starting!"); + console.log("User's turn is starting. Allow typing!"); // let the user type/broadcast again editor.setReadOnly(false); } // Update UI togglePlayerHighlight(true); - updateTimeLeftView(gameState.timeRemaining); + updateTimeLeftView(gameState.nextTurnTimestamp); updateCurrentTurnView(getCurrentPlayer().login); updateNextTurnView(getNextPlayer().login); toggleMyTurnHighlight(); @@ -424,18 +426,12 @@ function updateEditorView (editorData) { editor.selection.clearSelection(); } -// Update timeLeftView with the time remaining -function updateTimeLeftView (timerDurationMillis) { - - //console.log('updateTimeLeftView CALLED with: ' + timerDurationMillis); - - var turnEndTimestamp = Date.now() + timerDurationMillis; +// Update timeLeftView to display the time remaining in mm:ss format +function updateTimeLeftView (nextTurnTimestamp) { // Animate countdown timer function step(timestamp) { - var millisRemaining = turnEndTimestamp - Date.now(); - - //console.log('millisRemaining: ' + millisRemaining); + var millisRemaining = nextTurnTimestamp - Date.now(); var secondsRemaining = Math.floor(millisRemaining / 1000); var minutes = Math.floor(secondsRemaining / 60); diff --git a/server.js b/server.js index b8acb8e..ba415a5 100644 --- a/server.js +++ b/server.js @@ -68,7 +68,7 @@ server.listen(port, function() { GAME STATE: { - timeRemaining, + nextTurnTimestamp, turnIndex, currentGist: {id, url, owner}, playerList: @@ -86,7 +86,7 @@ server.listen(port, function() { -------------------------------------------------------------- */ let gameState = { - timeRemaining: null, + nextTurnTimestamp: null, turnIndex: 0, currentGist: null, players: [], @@ -268,16 +268,16 @@ function startTurnTimer(timerId, turnDuration) { console.log('\nInitializing turn timer!'); // Initialize or reset time remaining - gameState.timeRemaining = turnDuration; + gameState.nextTurnTimestamp = Date.now() + turnDuration; - console.log( 'Next turn at: ' + new Date(Date.now() + gameState.timeRemaining).toString().substring(16,25) ); + console.log( 'Next turn at: ' + new Date(gameState.nextTurnTimestamp).toString().substring(16,25) ); - // Every time the timer goes off, + // Every time the timer goes off, update timestamp and change turn! timerId = setInterval(() => { console.log('\n >>>>>>>>>>> ' + new Date().toString().substring(16,25) + ' - Time to change turns! <<<<<<<<<<\n'); // Update time of next turn change - gameState.timeRemaining = turnDuration; + gameState.nextTurnTimestamp = Date.now() + turnDuration; changeTurn(); From b4497002f1819b422882a98cdbeb3d15149005c6 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Fri, 7 Jul 2017 11:34:38 -0700 Subject: [PATCH 20/26] Refactor togglePlayerHighlight - Use parameters instead of global variables! - Use only current player ID, no longer relies on having previous player ID - Check if elements exist before modifying them! --- public/local.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/public/local.js b/public/local.js index 07026c8..10dbb94 100644 --- a/public/local.js +++ b/public/local.js @@ -325,7 +325,7 @@ function handleTurnChange () { } // Update UI - togglePlayerHighlight(true); + togglePlayerHighlight(getCurrentPlayer().id); updateTimeLeftView(gameState.nextTurnTimestamp); updateCurrentTurnView(getCurrentPlayer().login); updateNextTurnView(getNextPlayer().login); @@ -367,17 +367,19 @@ function toggleMyTurnHighlight () { } -// Highlight name of current player in playerListView -function togglePlayerHighlight (toggleOn) { - // First check if element exists, for case where user is the only player - if ( document.getElementById(getCurrentPlayer().id) ) { - // Add highlight - if (toggleOn) { - document.getElementById( getCurrentPlayer().id ).classList.add('highlight'); - // Remove highlight - } else { - document.getElementById( getCurrentPlayer().id ).classList.remove('highlight'); - } +// Highlight name of specified player in playerListView +function togglePlayerHighlight (playerId) { + var highlightedPlayerElement = document.querySelector('.highlight'); + var nextPlayerElement = document.getElementById(playerId); + + // Remove highlight from the currently-highlighted element if it exists: + if (highlightedPlayerElement) { + highlightedPlayerElement.classList.remove('highlight'); + } + + // Add highlight to specified player element (if element exists) + if (nextPlayerElement) { + nextPlayerElement.classList.add('highlight'); } } From bc46d774c20b80b5d31f4723019c613079951e0e Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Fri, 7 Jul 2017 12:43:18 -0700 Subject: [PATCH 21/26] Send disconnected player ID with turnChange --- public/local.js | 53 +++++++++++++++++++++++++------------------------ server.js | 11 +++++----- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/public/local.js b/public/local.js index 10dbb94..db2b3ff 100644 --- a/public/local.js +++ b/public/local.js @@ -279,41 +279,42 @@ function handlePlayerLeft (playerId) { } // When receiving turnChange event from server -function handleTurnChange () { +function handleTurnChange (disconnectedPlayerId) { console.log('%c turnChange event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;'); // Update the timestamp of the next turn, reset the clock! gameState.nextTurnTimestamp = Date.now() + turnDuration; - // Remove highlight from previous player's name in playerListView - togglePlayerHighlight(false); - - // Temporarily save the previous player for later comparison - var previousPlayer = getCurrentPlayer(); - - changeTurn(); - - // If this client's turn is ending and a Gist exists, fork and/or edit the gist before passing control to next player! - if (socket.id === previousPlayer.id && gameState.currentGist != null) { - console.log("This user's turn is about to end."); - - // If this client (the previous player) does NOT own the current Gist, - if (previousPlayer.login !== gameState.currentGist.owner) { - - console.log("handleTurnChange: now forking and editing gist " + gameState.currentGist.id); - - // Fork/edit current Gist on behalf of this client (the previous player, whose turn is ending), and send new ID to server - forkAndEditGist(gameState.currentGist.id, editor.getValue()); + // If turn change was NOT triggered by current player disconnecting, and a Gist exists, + if (!disconnectedPlayerId && gameState.currentGist != null) { - // Otherwise, just edit the current Gist - } else { - - console.log("handleTurnChange: now editing gist " + gameState.currentGist.id); - editGist(gameState.currentGist.id, editor.getValue()); - + // Temporarily save previous player info before changing turn + var previousPlayer = getCurrentPlayer(); + console.log("previousPlayer login: " + previousPlayer.login); + + // And if this client is the one whose turn is ending, then fork and/or edit the Gist before passing control to next player! + if (socket.id === previousPlayer.id) { + console.log("This user's turn is about to end."); + + // If this client (the previous player) does NOT own the current Gist, + if (previousPlayer.login !== gameState.currentGist.owner) { + // Fork/edit current Gist on behalf of this client (the previous player, whose turn is ending), and send new ID to server + forkAndEditGist(gameState.currentGist.id, editor.getValue()); + console.log("handleTurnChange: now forking and editing gist " + gameState.currentGist.id); + + // Otherwise, just edit the current Gist + } else { + editGist(gameState.currentGist.id, editor.getValue()); + console.log("handleTurnChange: now editing gist " + gameState.currentGist.id); + } } } + + changeTurn(); + console.log("turn changed!"); + console.log("turnIndex: " + gameState.turnIndex + ", # players: " + gameState.players.length, ", current player: " + getCurrentPlayer().id + " - " + getCurrentPlayer().login); + // If user is no longer the current player, prevent them from typing/broadcasting! if ( socket.id !== getCurrentPlayer().id ) { editor.setReadOnly(true); diff --git a/server.js b/server.js index ba415a5..d5f98ea 100644 --- a/server.js +++ b/server.js @@ -188,8 +188,8 @@ io.on('connection', function (socket) { // Restart the timer timerId = startTurnTimer(timerId, turnDuration); - // Change the turn, passing control to the next player and broadcasting to all players - changeTurn(); + // Change the turn, including ID of disconnected player to send to clients + changeTurn(socket.id); } } else { @@ -257,10 +257,11 @@ io.on('connection', function (socket) { /* ------------------------------------------------- FUNCTIONS ---------------------------------------------------- */ -function changeTurn() { +function changeTurn(disconnectedPlayerId) { gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; - // Broadcast turnChange to ALL clients - io.emit('turnChange', null); + + // Broadcast turnChange to ALL clients, with disconnected player ID (which exists only if current player disconnected): + io.emit('turnChange', disconnectedPlayerId); } // Initializes the turn and turn timer, returns timerId From 8e82430e2932b1359d6c8dd181fe10a189a00ab5 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Fri, 7 Jul 2017 13:08:49 -0700 Subject: [PATCH 22/26] Fix Gist creation so it actually triggers! --- public/local.js | 14 ++++++-------- server.js | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/public/local.js b/public/local.js index db2b3ff..3414a48 100644 --- a/public/local.js +++ b/public/local.js @@ -241,8 +241,8 @@ function handleGameState (serverGameState) { } // If no Gist exists, create it! - if (gameState.currentGist !== null) { - handleCreateNewGist(); + if (gameState.currentGist == null) { + createNewGist(); } // Update UI @@ -252,7 +252,7 @@ function handleGameState (serverGameState) { updateNextTurnView(getNextPlayer().login); toggleMyTurnHighlight(); - // If user is the current player, let them type! + // If this client is the current player, let them type! if ( socket.id === getCurrentPlayer().id ) { editor.setReadOnly(false); } @@ -487,8 +487,8 @@ function updateCurrentGistView (gistData) { GITHUB API FUNCTIONS ---------------------------------------------------- */ // Make a POST request via AJAX to create a Gist for the current user -function handleCreateNewGist() { - console.log('called handleCreateNewGist at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;'); +function createNewGist() { + console.log('called createNewGist at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;'); // use currentAccessToken // use https://developer.github.com/v3/gists/#create-a-gist @@ -504,12 +504,10 @@ function handleCreateNewGist() { postWithGitHubToken('https://api.github.com/gists', gistObject).then(function(responseText){ //console.log(responseText); - console.log('handleCreateNewGist: response received at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;'); + console.log('createNewGist: response received at ' + new Date().toString().substring(16,25), 'color: red; font-weight: bold;'); var gistObject = JSON.parse(responseText); - console.dir(gistObject); - // Save new gist ID and URL locally currentGist = {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}; diff --git a/server.js b/server.js index d5f98ea..06d1e54 100644 --- a/server.js +++ b/server.js @@ -240,7 +240,7 @@ io.on('connection', function (socket) { socket.on('newGist', function (data) { // Double check that this user is allowed to broadcast a new gist! - if (socket.id === getPreviousPlayer().id ) { + if (socket.id === getPreviousPlayer().id) { console.log('\nnewGist event received!\n'); //console.log(data); From 7f8314239a5bd2d4c675de36f7aa8f28aec19c4e Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Fri, 7 Jul 2017 13:09:21 -0700 Subject: [PATCH 23/26] Fix getPreviousPlayer() math --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 06d1e54..597fc69 100644 --- a/server.js +++ b/server.js @@ -293,7 +293,7 @@ function getCurrentPlayer() { } function getPreviousPlayer() { - var previousPlayerIndex = (gameState.turnIndex - 1) % gameState.players.length; + var previousPlayerIndex = (gameState.turnIndex + gameState.players.length - 1) % gameState.players.length; return gameState.players[previousPlayerIndex]; } From 7bdb8dcdcf8b2398b1a6fe86e95c7e96f1e34989 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Fri, 7 Jul 2017 13:43:52 -0700 Subject: [PATCH 24/26] Fix Gist state updates --- public/local.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/public/local.js b/public/local.js index 3414a48..2d67b82 100644 --- a/public/local.js +++ b/public/local.js @@ -243,6 +243,8 @@ function handleGameState (serverGameState) { // If no Gist exists, create it! if (gameState.currentGist == null) { createNewGist(); + } else { // Otherwise, if a Gist does exist, display it! + updateCurrentGistView(gameState.currentGist); } // Update UI @@ -306,14 +308,13 @@ function handleTurnChange (disconnectedPlayerId) { } else { editGist(gameState.currentGist.id, editor.getValue()); console.log("handleTurnChange: now editing gist " + gameState.currentGist.id); - } + } } } changeTurn(); - - console.log("turn changed!"); - console.log("turnIndex: " + gameState.turnIndex + ", # players: " + gameState.players.length, ", current player: " + getCurrentPlayer().id + " - " + getCurrentPlayer().login); + + console.log("TURN CHANGED! turnIndex: " + gameState.turnIndex + ", # players: " + gameState.players.length, ", current player: " + getCurrentPlayer().id + " - " + getCurrentPlayer().login); // If user is no longer the current player, prevent them from typing/broadcasting! if ( socket.id !== getCurrentPlayer().id ) { @@ -335,9 +336,12 @@ function handleTurnChange (disconnectedPlayerId) { // When receiving "newGist" event from server, function handleNewGist (gistData) { - // Update local state console.log("called handleNewGist at " + new Date().toString().substring(16,25), 'color: green; font-weight: bold;'); console.log(gistData); + + // Update local state + gameState.currentGist = gistData; + // Update views updateCurrentGistView(gistData); } @@ -508,13 +512,11 @@ function createNewGist() { var gistObject = JSON.parse(responseText); - // Save new gist ID and URL locally - currentGist = {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}; + // Save new Gist data locally and update UI + handleNewGist({id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); - // Send new gist data to server - socket.emit('newGist', {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); - - updateCurrentGistView({id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); + // Send gist data to server + socket.emit('newGist', gameState.currentGist); }, handleError); From 37b3dcfc7718915a9fdd4b3368ef5b4d80e1c787 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Mon, 10 Jul 2017 11:16:04 -0700 Subject: [PATCH 25/26] Update turnIndex on disconnect event if needed --- public/local.js | 36 ++++++++++++++++++++---------------- server.js | 8 ++++++-- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/public/local.js b/public/local.js index 2d67b82..ac2ca45 100644 --- a/public/local.js +++ b/public/local.js @@ -8,16 +8,10 @@ var socket = io(); nextTurnTimestamp, turnIndex, currentGist: {id, url, owner}, - playerList: + players: [ {id, login,avatar_url}, { ... }, { ... }, ... - ], - editor: - { - content, - cursorAndSelection: { cursor: {column, row}, range: { end: {column, row}, start: {column, row} }, - scroll: {scrollLeft, scrollTop} - } + ] } -------------------------------------------------------------- */ @@ -31,6 +25,8 @@ let gameState = { // SAVING LOCAL STATE -- GLOBAL VARS (ugh) var animationId; +// Later this shouldn't be hard-coded: +const turnDuration = 60000; // Meant to be temporary: var currentAccessToken; @@ -272,7 +268,13 @@ function handlePlayerJoined (newPlayerData) { } // When a player disconnects, update using data from server -function handlePlayerLeft (playerId) { +function handlePlayerLeft (playerId) { + + // Update turnIndex only if disconnected player comes BEFORE current player in the players array + if ( getPlayerIndexById(playerId, gameState.players) < gameState.turnIndex ) { + gameState.turnIndex--; + } + // Remove disconnected player from player list removePlayer(playerId, gameState.players); @@ -302,12 +304,12 @@ function handleTurnChange (disconnectedPlayerId) { if (previousPlayer.login !== gameState.currentGist.owner) { // Fork/edit current Gist on behalf of this client (the previous player, whose turn is ending), and send new ID to server forkAndEditGist(gameState.currentGist.id, editor.getValue()); - console.log("handleTurnChange: now forking and editing gist " + gameState.currentGist.id); + console.log("handleTurnChange: now forking and editing gist " + gameState.currentGist.id + " owned by " + gameState.currentGist.owner + "(on behalf of player " + previousPlayer.login + ")"); // Otherwise, just edit the current Gist - } else { + } else { editGist(gameState.currentGist.id, editor.getValue()); - console.log("handleTurnChange: now editing gist " + gameState.currentGist.id); + console.log("handleTurnChange: now editing gist " + gameState.currentGist.id + " owned by " + gameState.currentGist.owner + "(on behalf of player " + previousPlayer.login + ")"); } } } @@ -562,13 +564,15 @@ function forkAndEditGist(gistId, codeEditorContent) { var gistObject = JSON.parse(responseText); console.dir(gistObject); - // Send new gist data to server - socket.emit('newGist', {id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); - // Then edit the new gist: editGist(gistObject.id, codeEditorContent); - updateCurrentGistView({id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); + // Save new Gist data locally and update UI + handleNewGist({id: gistObject.id, url: gistObject.html_url, owner: gistObject.owner.login}); + + // Send new gist data to server + socket.emit('newGist', gameState.currentGist); + }, handleError); diff --git a/server.js b/server.js index 597fc69..7ef1a29 100644 --- a/server.js +++ b/server.js @@ -66,12 +66,11 @@ server.listen(port, function() { /* ------------------------------------------------------------ GAME STATE: - { nextTurnTimestamp, turnIndex, currentGist: {id, url, owner}, - playerList: + players: [ {id, login,avatar_url}, { ... }, { ... }, ... ], @@ -168,6 +167,11 @@ io.on('connection', function (socket) { // Temporarily save ID of current player (before removing from player list, for a later check!) var currentPlayerId = getCurrentPlayer().id; + // Update turnIndex only if disconnected player comes BEFORE current player in the players array, and there are still players in the game: + if ( getPlayerIndexById(socket.id, gameState.players) < gameState.turnIndex && gameState.players.length > 1) { + gameState.turnIndex--; + } + // Remove disconnected player from player list removePlayer(socket.id, gameState.players); From 3562b08322e6ed012063a4ecac2fe4db0d0d8734 Mon Sep 17 00:00:00 2001 From: LearningNerd Date: Mon, 10 Jul 2017 11:36:06 -0700 Subject: [PATCH 26/26] Refactor turnChange event to only send a Boolean --- public/local.js | 6 +++--- server.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/local.js b/public/local.js index ac2ca45..2a2b267 100644 --- a/public/local.js +++ b/public/local.js @@ -108,7 +108,7 @@ playerJoined Server All other clients {id, login, avatar_url} handlePlayer gameState Server One client See game state model! handleGameState Initialize game state for new player that just logged in, and trigger new gist creation if game is just starting! playerLeft Server All other clients id Update other clients to remove disconnected player -turnChange Server All clients null ??? handleTurnChange Triggerclients to change the turn +turnChange Server All clients onDisconnect (Boolean) handleTurnChange Trigger clients to change the turn newGist Client Server {id, url, owner} handleNewGist Broadcast new Gist data editorTextChange Client Server "just a string!" handleLocalEditorTextChange Broadcast changes to code editor content editorScrollChange Client Server {scrollLeft, scrollTop} handleLocalEditorScrollChange Broadcast changes to code editor content @@ -283,14 +283,14 @@ function handlePlayerLeft (playerId) { } // When receiving turnChange event from server -function handleTurnChange (disconnectedPlayerId) { +function handleTurnChange (onDisconnect) { console.log('%c turnChange event received! TIME: ' + new Date().toString().substring(16,25), 'color: blue; font-weight: bold;'); // Update the timestamp of the next turn, reset the clock! gameState.nextTurnTimestamp = Date.now() + turnDuration; // If turn change was NOT triggered by current player disconnecting, and a Gist exists, - if (!disconnectedPlayerId && gameState.currentGist != null) { + if (!onDisconnect && gameState.currentGist != null) { // Temporarily save previous player info before changing turn var previousPlayer = getCurrentPlayer(); diff --git a/server.js b/server.js index 7ef1a29..516aa32 100644 --- a/server.js +++ b/server.js @@ -110,7 +110,7 @@ playerJoined Server All other clients {id, login, avatar_url} Update other gameState Server One client See game state model! Initialize game state for new player that just logged in, and trigger new gist creation if game is just starting! playerLeft Server All other clients id Update other clients to remove disconnected player -turnChange Server All clients null ??? Trigger clients to change the turn +turnChange Server All clients onDisconnect (Boolean) Trigger clients to change the turn newGist Client Server {id, url, owner} Broadcast new Gist data editorTextChange Client Server "just a string!" Broadcast changes to code editor content editorScrollChange Client Server {scrollLeft, scrollTop} Broadcast changes to code editor content @@ -192,8 +192,8 @@ io.on('connection', function (socket) { // Restart the timer timerId = startTurnTimer(timerId, turnDuration); - // Change the turn, including ID of disconnected player to send to clients - changeTurn(socket.id); + // Change the turn, including the onDisconnect=true flag to signal clients NOT to fork/edit the Gist on this turn change + changeTurn(true); } } else { @@ -261,11 +261,11 @@ io.on('connection', function (socket) { /* ------------------------------------------------- FUNCTIONS ---------------------------------------------------- */ -function changeTurn(disconnectedPlayerId) { +function changeTurn(onDisconnect) { gameState.turnIndex = (gameState.turnIndex + 1) % gameState.players.length; // Broadcast turnChange to ALL clients, with disconnected player ID (which exists only if current player disconnected): - io.emit('turnChange', disconnectedPlayerId); + io.emit('turnChange', onDisconnect); } // Initializes the turn and turn timer, returns timerId