diff --git a/cardGen.js b/cardGen.js index 51eab33..da51b14 100644 --- a/cardGen.js +++ b/cardGen.js @@ -577,6 +577,7 @@ function requestDeck(itemData = null){ // Set the handSize to 0, will need moving somewhere else //itemData.component.deck[deckItem].handSize = 0; itemData.component.cardCount.hand[forPlayer] = 0; + itemData.component.cardCount.board[forPlayer] = 0; itemCount++; // Increment item to not overwrite } diff --git a/components.js b/components.js index 3ae34e6..2d6dead 100644 --- a/components.js +++ b/components.js @@ -15,6 +15,7 @@ const component = { cardCount : { deck : {}, hand : {}, + board : {}, }, // Card Stuff diff --git a/gameMod.js b/gameMod.js index 1753ebc..5afeea6 100644 --- a/gameMod.js +++ b/gameMod.js @@ -128,7 +128,10 @@ function playFromHand(roomId, playerId, position){ return false; } - playACardFromHand(roomId, playerId, cardId); + // Attempt to play the card from hand + if(!playACardFromHand(roomId, playerId, cardId)){ + return false; + } } @@ -140,18 +143,11 @@ function playACardFromHand(roomId, playerId, cardId){ return false; } - // If the card can be played/had been - // Reduce handSize by 1 for the player that played the card - global.roomData[roomId].itemData.component.cardCount.hand[playerId]--; - - - // TODO: Send a socket response for 'played a card from hand' with cardData - // of said card. This is sent so that it can be 'chained' and added to the stack - // On play, it is put onto field/activated but the effect(s) don't occur until both/all - // players have a chance to 'chain', once the 'chain' is completed, the effect(s) trigger global.socketResponsePlayFromHand(roomId, playerId, cardId); + // TODO: Above can probably be the same/similar to what is added to the roomData on + // client-end (when it's fully done) - + return true; } @@ -180,12 +176,50 @@ function playACard(roomId, playerId, cardId, playedFrom){ } +function hasSpaceOnBoard(roomId, playerId){ + // TODO: + return true; +} +function removeFromHand(roomId, playerId, cardId){ + console.log('remove from hand'); + if(cardId in global.roomData[roomId].itemData.component.hand){ + delete(global.roomData[roomId].itemData.component.hand[cardId]); + } + global.roomData[roomId].itemData.component.cardCount.hand[playerId]--; +} + function playAUnit(roomId, playerId, cardId, playedFrom){ console.log('playAUnit'); + // TODO: Make work, AND allow to play to opponent board (in future) + if(hasSpaceOnBoard(roomId, playerId) !== true){ + return false + } + // TODO: Costs + if(playedFrom == 'hand'){ + + // Remove from hand + removeFromHand(roomId, playerId, cardId); + + // Add unit to board + global.roomData[roomId].itemData.component.board[cardId] = cardId; + global.roomData[roomId].itemData.component.cardCount.board[playerId]++; + + // Change list positions of hand and board + // Next position on board (using cardCount to determine here) + // From current position in hand (the listPosition of the entity at this current point) + gameHelper.setCardPosition(roomId, playerId, cardId, global.roomData[roomId].itemData.component.cardCount.board[playerId], global.roomData[roomId].itemData.component.board, global.roomData[roomId].itemData.component.listPosition[cardId], global.roomData[roomId].itemData.component.hand); + + // TODO: unit onPlay effects to the stack + + } + + // Add to board + return true; + } function playASpell(roomId, playerId, cardId, playedFrom){ @@ -196,14 +230,31 @@ function playASpell(roomId, playerId, cardId, playedFrom){ // TODO: If spell has different effects, select which one/ensure // correct one is used based on criteria + if(playedFrom == 'hand'){ + // Remove from hand + console.log(cardId); + console.log(global.roomData[roomId].itemData.component.hand); + + // Remove from ahnd + removeFromHand(roomId, playerId, cardId); + // if Spell when it's final effect finished (or removed) from stack, it should get + // added to grave then + + } + + // If card was played, add its effect(s) to the stack + // If it should be added to stack (spell, or onPlay effect) do so // Add to stack (each part of the spells effect) // TODO: Use actual effects, for now just adding a 'drawCard' for testing - addToStack(roomId, playerId, cardId, null); + // Spell can/'has' been played + return true; + } function playAToken(roomId, playerId, cardId, playedFrom){ - + return false; + return true; } // Not 100% sure how to implement the stack @@ -233,14 +284,52 @@ function addToStack(roomId, playerId, cardId, effectId){ console.log(global.roomData[roomId].itemData.component.stack); + // Send addToStack response to trigger any animations, etc. + global.socketResponseAddToStack(roomId); + // TODO: TEMP, this will need to wait for a 'resolve' accept from both players before the stack // would trigger. + //resolveStack(roomId); + + // TODO: Improve this, potentially drop out of function, and have a while stack > 0 + // do the stack stuff. If it's <= 0 then other functionality is as normal + // Need to write a game loop for this rather than a + // nested function calling the getStackResponse. + getStackResponse(roomId); + +} + +function getStackResponse(roomId){ + + // If there's something in the stack both/all players must accept to resolve + // and/or have a chance to play a counter/chain card/effect atop of the current + // top of the stack + + // TODO: Opponent gets chance to chain first, then player + // if opponent chained, player gets to chain that chain first before stopping + // opponent double chaining + + global.socketGetStackResponse(roomId); + +} + +function acceptResolveStack(roomId, playerId){ + + // TODO: Make so each player needs to accept + // with whoever is to counter getting to ability to chain first + + // Once the player has resolved, the next player gets the option to chain/resolve + + // If all players have resolved, then resolve the top of the stack resolveStack(roomId); } function resolveStack(roomId){ + // Resolve the stack if all players have requested + // to resolve the stack, and not chain anything atop of it + // Does the next effect in the stack, if something // is to chain onto the stack that would instead trigger // 'addToStack' after paying any costs @@ -249,14 +338,25 @@ function resolveStack(roomId){ let stackLength = Object.keys(global.roomData[roomId].itemData.component.stack).length; if(stackLength > 0){ + // Send the 'resolve' response to room to trigger any animations, etc. + global.socketResponseResolveStack(roomId); + // Trigger the last (most recently added to) the stack effect // THIS WILL NOW ACTUALLY CAST THE EFFECT STEP WITHOUT INTERRUPT // While the stack is being resolved their are no counters/chains until // the next stack action which players will get option to chain or not again - console.log( - global.roomData[roomId].itemData.component.stack[stackLength] - ); + + let stackTrigger = global.roomData[roomId].itemData.component.stack[stackLength]; + + // TODO: actually trigger the correct effect, etc. + // check if targets, check validity, etc. then trigger + + // TODO: Remove drawACard and use actual triggers/effects + drawACard(roomId, stackTrigger.targetPlayer); + + // Once the effect atop the stack has triggered, remove it from the stack + delete(global.roomData[roomId].itemData.component.stack[stackLength]); } @@ -335,6 +435,15 @@ function getPlayerHandData(roomId, playerId){ } } + + + // TODO: This is here to prevent overwriting with less content when a draw happens. + // Will want reverting at some point (or other functions for returning only certain bits + // everywhere else should be written) + + //handEntities = global.roomData[roomId].itemData.component.hand; + handPositions = global.roomData[roomId].itemData.component.listPosition; + handCardData = global.roomData[roomId].itemData.component.cardData; return { 'handEntities': handEntities, @@ -351,5 +460,6 @@ module.exports = { ,drawACard ,shuffleDeck ,playFromHand + ,acceptResolveStack }; diff --git a/public/index.html b/public/index.html index c637292..5caa311 100644 --- a/public/index.html +++ b/public/index.html @@ -21,6 +21,7 @@ +


diff --git a/public/js/canvas/draw.js b/public/js/canvas/draw.js index 12133ff..99cffd2 100644 --- a/public/js/canvas/draw.js +++ b/public/js/canvas/draw.js @@ -6,6 +6,7 @@ function drawGameBoard(){ drawPlayerNames(); calculateDeckPositions(); calculateHandPositions(); + calculateBoardPositions(); drawEntities(); @@ -100,6 +101,50 @@ function calculateHandPositions(){ } } +} +// TODO: Move from draw into somewhere else +function calculateBoardPositions(){ + + for (const [key, value] of Object.entries(gameData.board)) { + + // key is entity Id here + let cardsOnBoard = 0; + let position = 0; + + switch(gameData.player[key]){ + + // Set position for player hand (all the time at current) + case gameData.playerId: + + cardsOnBoard = gameData.cardCount.board[gameData.playerId]; + position = gameData.listPosition[key]; + + gameData.position[key] = [ + canvas.width/2 - (cardWidth * (cardsOnBoard - (position+1)) - (cardMargin * (position+1))) + ,canvas.height-((cardHeight*2) * handScale)-30 + ]; + + gameData.size[key] = [cardWidth * handScale, cardHeight * handScale]; + + break; + + // Opponent + case gameData.opponentId: + + cardsOnBoard = gameData.cardCount.board[gameData.opponentId]; + position = gameData.listPosition[key]; + + gameData.position[key] = [ + canvas.width/2 - (cardWidth * (cardsOnBoard - (position+1)) - (cardMargin * (position+1))) + ,((cardHeight*2) * handScale)+30 + ]; + gameData.size[key] = [cardWidth * handScale, cardHeight * handScale]; + + break; + + } + } + } // TODO: Move this function elsewhere, not really a draw function function calculateCardSpacing(positionInt, size, standardSize){ @@ -129,6 +174,11 @@ function drawEntities(){ drawCardInHand(key); } + // If card on board + if(key in gameData.board){ + drawCardOnBoard(key); + } + if(key in gameData.inInteractionMenu){ // Add the menu with 'play', 'activate effect', 'inspect', etc. drawInteractionMenu(key); @@ -168,10 +218,27 @@ function drawDeck(entity){ } function drawCardInHand(entity){ - // TODO: Tapped, Attacking, Targettable, Activatable borders - // TODO: Change card colour based on its colours - // TODO: Not for hand, but cards on board need flight, etc. + + // Draw the card 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(); + + drawCardDetails(entity); + +} + +function drawCardOnBoard(entity){ + + // TODO: Tapped, Attacking, Targettable, Activatable borders + // TODO: Passives, flight, etc. effects // Draw the card shape let shape = new Shape({ @@ -261,29 +328,36 @@ function drawCardText(entity){ // so TODO gotta make it scaled % wise with the 80/16 I wrote above // NAME + if(gameData.cardData[entity] !== undefined && gameData.cardData[entity]['name'] !== undefined){ printText(gameData.cardData[entity]['name'] , positionX + 5 + 10 // + 5 (spacing for cost) + 10 for more spacing , positionY + 10 + 5 // + 10pt + 5 as it's the spacing I actually want , 'left', 'alphabetic', 'normal', 'normal', fontSize, 'Arial', fillStyle, strokeStyle, strokeSize, false ); + } // COST + if(gameData.cardData[entity] !== undefined && gameData.cardData[entity]['cost'] !== undefined){ printText(gameData.cardData[entity]['cost'] , positionX + 5 , positionY + 10 + 5 // + 10pt + 5 as it's the spacing I actually want , 'left', 'alphabetic', 'normal', 'normal', fontSize, 'Arial', fillStyle, strokeStyle, strokeSize, false ); + } // TYPE + if(gameData.cardData[entity] !== undefined && gameData.cardData[entity]['type'] !== undefined){ printText( CLASS[gameData.cardData[entity]['type']] , positionX + 5 , positionY + width // To be slightly over bottom of image (which uses width to position) , 'left', 'alphabetic', 'normal', 'normal', fontSize, 'Arial', fillStyle, strokeStyle, strokeSize, false ); + } // CLASS(ES) + if(gameData.cardData[entity] !== undefined && gameData.cardData[entity]['class'] !== undefined){ let classes = ''; // Loop the classes, and add each of them to the card for(let i = 0; i < gameData.cardData[entity]['class'].length; i++){ @@ -299,8 +373,10 @@ function drawCardText(entity){ , positionY + width // To be slightly over bottom of image (which uses width to position) , 'left', 'alphabetic', 'normal', 'normal', fontSize, 'Arial', fillStyle, strokeStyle, strokeSize, false ); + } // EFFECT(S) + if(gameData.cardData[entity] !== undefined && gameData.cardData[entity]['effect'] !== undefined){ // TODO: Split the text onto lines based on character count (but not splitting words) so they fit into the // bounds of the card element let effects = ''; @@ -317,13 +393,16 @@ function drawCardText(entity){ , positionY + width + 10 // To be beneath image, and type + classes , 'left', 'alphabetic', 'normal', 'normal', fontSize, 'Arial', fillStyle, strokeStyle, strokeSize, false ); + } // ATTACK + if(gameData.cardData[entity] !== undefined && gameData.cardData[entity]['attack'] !== undefined){ printText(gameData.cardData[entity]['attack'] , positionX + width/2 - ctx.measureText(gameData.cardData[entity]['attack']).width/2 // Should be centred , positionY + 10 + (height - 10) // + 10pt + 5 as it's the spacing I actually want , 'left', 'alphabetic', 'normal', 'normal', fontSize, 'Arial', fillStyle, strokeStyle, strokeSize, false ); + } // Reset font for other draws // TODO: Probably just use a save/restore for the font draws in here @@ -477,3 +556,11 @@ function drawInteractionMenu(entity){ } +// TODO: Put the stack into the UI +function drawStackResolve(){ + +} +// TODO: Draw stack, all cards in the stack in order (use the cardId for display) +// make inspectable, and display which part of the effect will be triggered in it's +// stack position + diff --git a/public/js/canvas/interaction.js b/public/js/canvas/interaction.js index d70b504..208c82e 100644 --- a/public/js/canvas/interaction.js +++ b/public/js/canvas/interaction.js @@ -69,6 +69,8 @@ canvas.addEventListener('click', function(event) { console.log(key); + // After an interaction, clear the menu to prevent redraw + clearInteractionMenu(); // Then return true to prevent another interaction return true; } diff --git a/public/js/game/components.js b/public/js/game/components.js index bfd019b..3dd9e62 100644 --- a/public/js/game/components.js +++ b/public/js/game/components.js @@ -15,6 +15,7 @@ let gameData = { cardCount : { deck : {}, hand : {}, + board : {}, }, inInteractionMenu : {}, @@ -27,6 +28,7 @@ let gameData = { player : {}, deck : {}, hand : {}, + board : {}, // 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 60508c1..106906b 100644 --- a/public/js/game/dataUpdate.js +++ b/public/js/game/dataUpdate.js @@ -59,8 +59,36 @@ function updatePlayerHand(data){ // 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; + gameData.listPosition = data.handPositions; // Change to all listPositions + gameData.cardData = data.handCardData; // Changed to all cardData + // Changes made here, as when drawing a card updateBoard data was wiped and caused + // 'undefined' listPositions and other data... +} + +function updateBoard(data){ + // TODO: ONLY RETURN NEEDED DATA + // AND ONLY UPDATE NEEDED DATA + // TODO: Currently everything is being passed + // which is 1 slow, and 2 bad because it gives players + // too much information, they only need what's to be + // drawn and interacted with + console.log('TODO: updateBoard correctly'); + console.log(data); + gameData.board = data.board; + gameData.passive = data.passive; + gameData.listPosition = data.listPosition + gameData.cardData = data.cardData; + gameData.cardCost = data.cardCost; + gameData.cardColours = data.cardColours; + //console.log(data); +} + +// To prevent typical functionality (draw, etc.) +// if there's a stack in play. +// TODO: Can only chain the stack or resolve it +function updateStack(data){ + alert('You must resolve the stack, or chain an effect'); + console.log(data); console.log(gameData); } diff --git a/public/js/game/socket.js b/public/js/game/socket.js index 3f1fac1..5b6a016 100644 --- a/public/js/game/socket.js +++ b/public/js/game/socket.js @@ -18,13 +18,18 @@ function requestDrawACard(){ console.log('>> drawACard'); socket.emit('drawACard', gameData.roomId, gameData.playerId); } -// Draw Card // Both players get new hand + deck counts updated socket.on('responseDrawCard', function (data) { console.log('<< drawCard'); updateCardCount(data); drawGameBoard(); }); +socket.on('responseCardCounts', function (data) { + console.log('<< responseCardCounts'); + updateCardCount(data); + drawGameBoard(); +}); + // Player drew card // Player that drew the card (atm) gets the cardData, listPosition @@ -36,6 +41,12 @@ socket.on('responsePlayerDrewCard', function (data) { }); +socket.on('responseUpdateBoard', function (data) { + console.log('<< updateBoard'); + updateBoard(data); + drawGameBoard(); +}); + // SHUFFLE DECK function requestShuffleDeck(){ console.log('>> shuffleDeck'); @@ -58,9 +69,38 @@ function requestPlayFromHand(listPosition){ socket.emit('playFromHand', gameData.roomId, gameData.playerId, listPosition); } socket.on('responsePlayFromHand', function (data) { + // Return boardData, update hand console.log('<< playFromHand'); + console.log(data); }); +// Stack +socket.on('responseAddToStack', function (data) { + console.log('<< addToStack'); + console.log(data); +}); +socket.on('responseResolveStack', function (data) { + console.log('<< resolveStack _x_'); + console.log(data); + drawGameBoard(); +}); +socket.on('responseRemoveFromStack', function (data) { + console.log('<< removeFromStack ?'); + console.log(data); +}); +// All players need to accept the stack 'resolve' before it'll occur +socket.on('responseGetStackResponse', function (data) { + // TODO: Return all valid effect triggers/responses to the effect trigger + // on the top of the stack, and a 'resolve' option to not trigger anything + console.log('<< getStackResponse'); + updateStack(data); +}); +function requestResolveStack(){ + // This is just for 'resolve' press. Not playing atop the stack. + console.log('>> requestResolveStack'); + socket.emit('requestResolveStack', gameData.roomId, gameData.playerId); +} + // Functions like this would be elsewhere, do client-side // validation THEN request stuff from the server? diff --git a/server.js b/server.js index d543604..7efc4d8 100644 --- a/server.js +++ b/server.js @@ -66,6 +66,11 @@ function onConnection(socket){ socket.on('playFromHand', function(roomId, playerId, listPosition) { gameMod.playFromHand(roomId, playerId, listPosition); }); + + socket.on('requestResolveStack', function(roomId, playerId) { + gameMod.acceptResolveStack(roomId, playerId); + console.log('resolve stack yep yep yep'); + }); } @@ -116,20 +121,26 @@ global.socketResponseDrawCard = function(roomId, playerId){ console.log('>> '+roomData[roomId].playerData[playerId].playerId+' drew card'); - global.socketReturnCardCounts(roomId); + global.socketReturnCardCounts(roomId, 'responseDrawCard'); } -global.socketReturnCardCounts = function(roomId){ +global.socketReturnCardCounts = function(roomId, responseName = 'responseCardCounts'){ let data = global.roomData[roomId].itemData.component.cardCount; - global.sendToEachSocket(roomId, 'responseDrawCard', data); + global.sendToEachSocket(roomId, responseName, data); } global.socketResponsePlayerDrewCard = function(roomId, playerId){ + global.socketResponsePlayerHandData(roomId, playerId); + +} + +global.socketResponsePlayerHandData = 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? @@ -150,6 +161,17 @@ global.socketResponseShuffleDeck = function(roomId, playerId, hasShuffled){ } +global.socketResponseUpdateBoard = function(roomId){ + + // TODO: Only return the data that's needed, no everything + let data = global.roomData[roomId].itemData.component; + // Return the data on the board (to both players) + // TODO: If card flips/face-downs are to be a thing, only return the cardData, etc. + // to the player who's side it's on + global.sendToEachSocket(roomId, 'responseUpdateBoard', data); + +} + global.socketResponsePlayFromHand = function(roomId, playerId, cardId){ console.log('>> '+roomData[roomId].playerData[playerId].playerId+' played from hand'); @@ -157,8 +179,28 @@ global.socketResponsePlayFromHand = function(roomId, playerId, cardId){ // Return the new cardCounts for hand/deck/grave, etc. global.socketReturnCardCounts(roomId); - // Return the card being played TODO: Return all related card data, not just the ID + // Update hand of player that card's hand was played from (using existing logic) + global.socketResponsePlayerHandData(roomId, playerId); + + // Update the board (for both players) + global.socketResponseUpdateBoard(roomId); + + // So the animations can be played with the new cardData (in future) global.sendToEachSocket(roomId, 'responsePlayFromHand', cardId); +} + +// Stack +global.socketResponseAddToStack = function(roomId){ + global.sendToEachSocket(roomId, 'responseAddToStack', 'testData'); +} +global.socketResponseResolveStack = function(roomId){ + global.sendToEachSocket(roomId, 'responseResolveStack', 'testData'); +} +// TODO: Each player one at a time to prevent just self-chaining 24/7 +global.socketGetStackResponse = function(roomId){ + // TODO: Return all valid effect triggers/responses to the effect trigger + // on the top of the stack, and a 'resolve' option to not trigger anything + global.sendToEachSocket(roomId, 'responseGetStackResponse', 'testData'); }