From be96e461662f6ef97f2d2825a0c1d1872558e35b Mon Sep 17 00:00:00 2001 From: Nathan Date: Sat, 2 Nov 2024 23:48:10 +0000 Subject: [PATCH] Changes to 'drawACard', serverdata, and UI changes Return data from server for each client (cardCounts) Return data for client that drew (cardData, listPos. etc.) UI changes on client to display the temporary 'cards' --- cardGen.js | 2 +- components.js | 1 + gameHelper.js | 53 +++++++++++++++ gameMod.js | 127 +++++++++++++++++++++++------------ public/index.html | 1 + public/js/canvas/draw.js | 108 ++++++++++++++++++++++++++++- public/js/game/components.js | 3 + public/js/game/dataUpdate.js | 14 +++- public/js/game/socket.js | 14 ++++ public/js/global.js | 2 + roomMod.js | 1 + server.js | 65 ++++++++++++++++++ 12 files changed, 345 insertions(+), 46 deletions(-) create mode 100644 gameHelper.js diff --git a/cardGen.js b/cardGen.js index 160cb1c..2eccb69 100644 --- a/cardGen.js +++ b/cardGen.js @@ -490,7 +490,7 @@ function requestDeck(itemData = null){ // Add the entity into 'inDeck' component // also add a listPosition based on current deckLength itemData.component.inDeck[itemCount] = true; // Not sure what to store here - itemData.component.listPosition[itemCount] = itemData.component.cardCount.deck[forPlayer]; + itemData.component.listPosition[itemCount] = itemData.component.cardCount.deck[forPlayer] + 1; // Associate the card with the deck // TODO: Change deckIn to something more sensical diff --git a/components.js b/components.js index 6d789fe..8b59d6a 100644 --- a/components.js +++ b/components.js @@ -10,6 +10,7 @@ const component = { roomId : null, turn : 0, playerTurn : 0, + player : {}, cardCount : { deck : {}, diff --git a/gameHelper.js b/gameHelper.js new file mode 100644 index 0000000..949a1ed --- /dev/null +++ b/gameHelper.js @@ -0,0 +1,53 @@ +// Moves card positions down/up for the old/new element for player in room +function setCardPosition(roomId, player, card, newPosition, newElement, oldPosition, oldElement){ + + // Move anything in the old boardElement after the old listPosition down by one + moveElementPositions(roomId, player, 0, oldElement, oldPosition); + + // Move anything in the new boardElement after (including) the new listPosition up by one + moveElementPositions(roomId, player, 1, newElement, newPosition); + + // Then fit the card into the new gap that's opened up + listPosition[card] = newPosition; + + // Remove from oldElement + delete oldElement[card] + // Add to newElement + newElement[card] = newPosition; + +} + +// direction 0 up, 1 down +function moveElementPositions(roomId, player, direction, element, position){ + + position = position; + listPosition = global.roomData[roomId].itemData.component.listPosition; + + for (const [key, value] of Object.entries(element)) { + + if(global.roomData[roomId].itemData.component.player[key] != player){ + continue; + } + + + // Move down from (not including) the position passed + if(direction == 0 && listPosition[key] > position){ + listPosition[key]--; + } + + // Move everything from (including) the new position up + if(direction == 1 && listPosition[key] >= position){ + console.log('hit'); + console.log(listPosition[key]); + console.log(position); + listPosition[key]++; + } + + } + +} + +module.exports = { + setCardPosition +}; + diff --git a/gameMod.js b/gameMod.js index 611f375..d7d3c8d 100644 --- a/gameMod.js +++ b/gameMod.js @@ -1,3 +1,5 @@ +const gameHelper = require('./gameHelper'); + // For anything related to the actual game itself (kinda) // this will be split into different bits, but should be what manages a rooms // game states, and alladat @@ -17,7 +19,7 @@ function passTurn(roomId, playerId){ //global.socketAlert(roomData[roomId].playerData[playerId].socketId, playerTurn, 'log'); if(playerTurn != playerId){ - global.socketAlert(roomData[roomId].playerData[playerId].socketId, 'Not your turn', 'alert'); + global.socketAlert(global.getPlayerSocketFromRoom(playerId, roomId), 'Not your turn', 'alert'); return false; }; @@ -32,30 +34,14 @@ function passTurn(roomId, playerId){ } // Send turn data to each player - // Probably via the sockets in room (as could be spectators in future) - let clients = global.io.sockets.adapter.rooms.get(roomId); - for (const clientId of clients) { - - const clientSocket = global.io.sockets.sockets.get(clientId); - console.log('>> '+clientSocket.playerId+' passed turn'); - - // Send the data back - let turnData = { - 'playerTurn': newPlayerTurn, - 'turn': global.roomData[roomId].itemData.component.turn, - }; - - global.io.to(clientSocket.id).emit('responsePassTurn', turnData); - - } + global.socketResponsePassTurn(roomId); // Let the player know it's their turn via alert too (in case tabbed out) + // TODO: This could probably be done front-end from the newPlayerTurn in socketResponsePassTurn global.socketAlert(roomData[roomId].playerData[newPlayerTurn].socketId, 'Your turn', 'alert'); - // Start of the new players turn, draw a card drawACard(roomId, newPlayerTurn); - } @@ -65,49 +51,104 @@ function drawACard(roomId, playerId){ global.socketAlert(roomData[roomId].playerData[playerId].socketId, 'Hand full; cannot draw card', 'alert'); return false; } + if(global.roomData[roomId].itemData.component.cardCount.deck[playerId] <= 0){ + global.socketAlert(roomData[roomId].playerData[playerId].socketId, 'Deck empty; cannot draw card', 'alert'); + return false; + } + + // TODO: Check no card event/trigger occured that prevents/change draw card + // Change position to last position available in hand - let fromPosition = global.roomData[roomId].itemData.component.cardCount.deck[playerId]; // 'Bottom' of deck - let toPosition = global.roomData[roomId].itemData.component.cardCount.hand[playerId]; // Rightmost hand pos + let fromPosition = global.roomData[roomId].itemData.component.cardCount.deck[playerId]; // 'top' of deck + let toPosition = global.roomData[roomId].itemData.component.cardCount.hand[playerId] + 1; // Rightmost hand pos (starting at 1) + + // ECSey att2, there's surely a better way of getting playerX top card within inDeck? + // Tried unions but it messes up the object data. Maybe need to have no data in each object + // and have it literally just be keys? + + // Get each card from the deck + for (const [key, value] of Object.entries(global.roomData[roomId].itemData.component.inDeck)) { + // Key is the entity here + + // If the card inDeck does not belongs to the player, skip over it + if(global.roomData[roomId].itemData.component.player[key] != playerId){ + continue; + } + + // If the card isn't the last (bottom) card of deck, skip over it + // TODO: -1 is jank, sort so listPositions all start from 1..x + if(global.roomData[roomId].itemData.component.listPosition[key] != fromPosition){ + continue; + } + + // The main man + // Move positions in hand/deck, and put the item from the deck into the hand + gameHelper.setCardPosition(roomId, playerId, key, toPosition, global.roomData[roomId].itemData.component.hand, fromPosition, global.roomData[roomId].itemData.component.inDeck); + + } - - // from inDeck to hand - // change the listPositions of both (only return listPosition of hand ids?) - // Reduce deckSize by 1 for the player that drew global.roomData[roomId].itemData.component.cardCount.deck[playerId]--; // And increase the hand size by 1 global.roomData[roomId].itemData.component.cardCount.hand[playerId]++; + + // Then emit the deckSize and hand size to all the player's sockets + global.socketResponseDrawCard(roomId, playerId); - // TODO: Move card from deck to hand for the player. Dupe what I've got in board for - // this, but tidy it up - //global.roomData[roomId].itemData.component.inDeck = newPlayerTurn; - //global.roomData[roomId].itemData.component.hand = newPlayerTurn; + // Emit the 'hand' and related cardData for cards in the players hand + // Could merge this with the top? + global.socketResponsePlayerDrewCard(roomId, playerId); +} - // Then emit the deckSize and hand size to all the player's sockets - let clients = global.io.sockets.adapter.rooms.get(roomId); - for (const clientId of clients) { - const clientSocket = global.io.sockets.sockets.get(clientId); - console.log('>> '+clientSocket.playerId+' drew card'); +// DATA RETURNER DUDES +// TODO: Where to put this? Kind of a helper, kind of functionality. Hmmmmm +// maybe do a dataHelper? then anything to return data can be included there? +// TODO: May get all the data from hand, board, grave, etc. in functions +// like this, then union all the data and return that in one swoomp +// Probably better to just get all the keys from the boardlemenets and do +// the loop once though... +function getPlayerHandData(roomId, playerId){ + + let handEntities = {}; + let handPositions = {}; + let handCardData = {}; + + for (const [key, value] of Object.entries(global.roomData[roomId].itemData.component.hand)) { + + // Key is entity ID here + + // If the entity in hand belongs to the player, then they are allowed its data + if(global.roomData[roomId].itemData.component.player[key] == playerId){ - // Send the data back - global.io.to(clientSocket.id).emit( - 'responseDrawCard' - ,global.roomData[roomId].itemData.component.cardCount - ); + // Get entity of items in the hand + handEntities[key] = global.roomData[roomId].itemData.component.hand[key]; + // Get listPosition of just items in the hand + handPositions[key] = global.roomData[roomId].itemData.component.listPosition[key]; + // Same for cardData + handCardData[key] = global.roomData[roomId].itemData.component.cardData[key]; // TODO: Nothing on client side? + + // Leaving other bits for now + + } } - - // Emit the 'hand' and related cardData for cards in the players hand - - + + return { + 'handEntities': handEntities, + 'handPositions': handPositions, + 'handCardData': handCardData + }; + } module.exports = { passTurn + ,getPlayerHandData + ,drawACard }; diff --git a/public/index.html b/public/index.html index dfa5155..ea0eb6f 100644 --- a/public/index.html +++ b/public/index.html @@ -19,6 +19,7 @@
+


diff --git a/public/js/canvas/draw.js b/public/js/canvas/draw.js index 33c0767..fa6bd7c 100644 --- a/public/js/canvas/draw.js +++ b/public/js/canvas/draw.js @@ -5,9 +5,14 @@ function drawGameBoard(){ drawPlayerNames(); calculateDeckPositions(); + calculateHandPositions(); drawEntities(); + // TEMP. Likely want to pass entities, but if you don't have cardData for them + // then don't draw the card face up? + drawFakeHand(); + } function drawPlayerNames(){ @@ -68,6 +73,35 @@ function calculateDeckPositions(){ } } +// TODO: Move from draw into somewhere else +function calculateHandPositions(){ + + for (const [key, value] of Object.entries(gameData.hand)) { + + // key is entity Id here + + switch(gameData.player[key]){ + + // Set position for player hand (all the time at current) + case gameData.playerId: + + let cardsInHand = gameData.cardCount.hand[gameData.playerId]; + let positionInHand = gameData.listPosition[key]; + + gameData.position[key] = [ + canvas.width/2 - (cardWidth * (cardsInHand - (positionInHand+1)) - (cardMargin * (positionInHand+1))) + ,canvas.height-cardWidth*1.5-20 + ]; + gameData.size[key] = [cardWidth, cardHeight]; + break; + + // Opponent, currently done in fakeHand + + } + } + +} + function drawEntities(){ @@ -79,10 +113,15 @@ function drawEntities(){ if(gameData.size[key] !== undefined){ // If the entity is a deck - if(gameData.deck[key]){ + if(key in gameData.deck){ drawDeck(key); } + // If card in (player) hand + if(key in gameData.hand){ + drawCardInHand(key); + } + } } @@ -123,4 +162,71 @@ function drawDeck(entity){ printText(deckCount, textX, textY, 'center', 'bottom', 'normal', 'bold', '10', 'Arial', '#000'); } +function drawCardInHand(entity){ + + console.log('Draw card in hand'); + console.log(gameData.position[entity]); + console.log(gameData.size[entity]); + + // Draw the deck shape + let shape = new Shape({ + x: gameData.position[entity][0], + y: gameData.position[entity][1], + width: gameData.size[entity][0], + height: gameData.size[entity][1], + fillStyle: '#EEE', + strokeStyle: '#AAA', + }); + shape.draw(); + +} + + +// TEMP, or maybe not for opponent. Not 100% how to draw opponent hand without passing the data +// avoiding passing the data (for now) to prevent cheating by checking their cardIds etc. in the +// console. An issue that comes with making this in JS... +function drawFakeHand(){ + + console.log('Draw FAKE hand'); + + // itemList length is the count (/highest listPosition) in the hand in this case + // i is the listPosition of the entity (which since this isn't using the entities at the mo...) + + // All jank temp kinda vars + // Needed as i substitute to track the fake hand drawn count + let opponentHandItem = 0; + let itemListLength = 0; // Believe value should just be the count of current cardCount.hand + let i = 0; // Believe value should just be the count of current cardCount.hand + + // Loop each 'hand' not actual hand, but count of cards in hand + for (const [key, value] of Object.entries(gameData.cardCount.hand)) { + + // key is the playerId here + switch(gameData.player[key]){ + + // Set position for opponents deck + case gameData.opponentId: + + // Then loop the size of the hand + itemListLength = value; // Believe value should just be the count of current cardCount.hand + i = opponentHandItem; // Believe value should just be the count of current cardCount.hand + + for(i; i < itemListLength; i++){ + let shape = new Shape({ + x: canvas.width/2 - (cardWidth * (itemListLength - (i+1)) - (cardMargin * (i+1))), + y: 20, + width: cardWidth, + height: cardHeight, + fillStyle: '#EEE', + strokeStyle: '#AAA', + }); + shape.draw(); + } + break; + + } + + } + +} diff --git a/public/js/game/components.js b/public/js/game/components.js index df92a49..f368fe7 100644 --- a/public/js/game/components.js +++ b/public/js/game/components.js @@ -19,8 +19,11 @@ let gameData = { // Real components from here? + listPosition : {}, + cardData : {}, player : {}, deck : {}, + hand : {}, // Local components, not done on serverside // calculated, etc. by client diff --git a/public/js/game/dataUpdate.js b/public/js/game/dataUpdate.js index 8aabd47..60508c1 100644 --- a/public/js/game/dataUpdate.js +++ b/public/js/game/dataUpdate.js @@ -11,7 +11,7 @@ function updateGameData(data){ updateTurn(data.component.turn, data.component.playerTurn); updateItems(data.component.item, data.component.itemCount); - updatePlayerComponent(data.player); + updatePlayerComponent(data.component.player); updateDecks(data.component.deck); updateCardCount(data.component.cardCount); @@ -52,3 +52,15 @@ function updateCardCount(cardCount = null){ gameData.cardCount = cardCount; } +// Cards in hand +function updatePlayerHand(data){ + console.log('Update player hand'); + // TODO: These will likely not be = as will overwrite. + // Likely to union all the data server-side and pass as one + // function such as updateEntities() or something + gameData.hand = data.handEntities; + gameData.listPosition = data.handPositions; + gameData.cardData = data.handCardData; + console.log(gameData); +} + diff --git a/public/js/game/socket.js b/public/js/game/socket.js index bda521b..04eab94 100644 --- a/public/js/game/socket.js +++ b/public/js/game/socket.js @@ -10,6 +10,10 @@ socket.on('responsePassTurn', function (data) { updateTurn(data.turn, data.playerTurn); drawGameBoard(); }); +function requestDrawACard(){ + console.log('>> drawACard'); + socket.emit('drawACard', gameData.roomId, gameData.playerId); +} // Functions like this would be elsewhere, do client-side // validation THEN request stuff from the server? @@ -19,9 +23,19 @@ function passTurn(){ } // Draw Card +// Both players get new hand + deck counts updated socket.on('responseDrawCard', function (data) { console.log('<< drawCard'); updateCardCount(data); drawGameBoard(); }); +// Player drew card +// Player that drew the card (atm) gets the cardData, listPosition +// TODO: related attack, cost, effects, etc. +socket.on('responsePlayerDrewCard', function (data) { + console.log('<< playerDrewCard'); + updatePlayerHand(data); + drawGameBoard(); +}); + diff --git a/public/js/global.js b/public/js/global.js index cbcbaa9..c70bd98 100644 --- a/public/js/global.js +++ b/public/js/global.js @@ -20,3 +20,5 @@ const maxHandSize = 4; const maxBoardSize = 3; const maxShield = 2; +const cardMargin = 10; + diff --git a/roomMod.js b/roomMod.js index be7b19f..384f2af 100644 --- a/roomMod.js +++ b/roomMod.js @@ -77,6 +77,7 @@ function roomGeneration(roomId){ // Add all the empty components to the room itemData itemData.component = components.component; + itemData.component.player = itemData.player; // TODO: do this in setPlayers // Generate the decks, and card within the deck cardLists [itemData] = await Promise.all([ cardGen.requestDeck(itemData) ]); diff --git a/server.js b/server.js index 255de91..beab350 100644 --- a/server.js +++ b/server.js @@ -55,6 +55,16 @@ function onConnection(socket){ gameMod.passTurn(roomId, playerId); }); + socket.on('drawACard', function(roomId, playerId) { + gameMod.drawACard(roomId, playerId); + }); + +} + +global.getPlayerSocketFromRoom = function(playerId, roomId){ + + return roomData[roomId].playerData[playerId].socketId; + } // Globals for easier clientside alerts/logs, etc. @@ -64,3 +74,58 @@ global.socketAlert = function(socket, message, type = 'alert'){ ); } +// passTurn response for all clients in room +// TODO: include spectators when added +global.socketResponsePassTurn = function(roomId){ + + let clients = global.io.sockets.adapter.rooms.get(roomId); + for (const clientId of clients) { + + const clientSocket = global.io.sockets.sockets.get(clientId); + console.log('>> '+clientSocket.playerId+' passed turn'); + + // Send the data back. Whose turn it is, and what turn + let turnData = { + 'playerTurn': global.roomData[roomId].itemData.component.playerTurn, + 'turn': global.roomData[roomId].itemData.component.turn, + }; + + global.io.to(clientSocket.id).emit('responsePassTurn', turnData); + + } + +} + +// Then emit the deckSize and hand size to all the player's sockets +// TODO: spectators in future, won't comment about this from now on +global.socketResponseDrawCard = function(roomId, playerId){ + + let clients = global.io.sockets.adapter.rooms.get(roomId); + for (const clientId of clients) { + + const clientSocket = global.io.sockets.sockets.get(clientId); + console.log('>> '+roomData[roomId].playerData[playerId].playerId+' drew card'); + + // Send the data back + global.io.to(clientSocket.id).emit( + 'responseDrawCard' + ,global.roomData[roomId].itemData.component.cardCount + ); + + } + +} + +global.socketResponsePlayerDrewCard = function(roomId, playerId){ + + // This function may be redundant fast, as hand, board, grave, void + // anywhere that's visible to one (or both) players will need all the data + // sending, so will likely be one function that returns it all? + // Could do each thing as their own, and union the data too? + global.io.to(global.getPlayerSocketFromRoom(playerId, roomId)).emit( + 'responsePlayerDrewCard' + ,gameMod.getPlayerHandData(roomId, playerId) + ); + +} +