const ctx = canvas.getContext('2d'); const canvasLeft = canvas.offsetLeft + canvas.clientLeft; const canvasTop = canvas.offsetTop + canvas.clientTop; const cardWidth = 80; const cardHeight = 120; const cards = new Image(); const back = new Image(); // Counters to keep track of players, and boardElements, may be changed in future // But once game starts, will be const anyway, so shouldn't need passing let players = 2; // Player, Opponent for now, but will be up to 6 players for 5v1 boss raids? let elements = ['realDeck', 'deck','board','hand','mana','shield', 'grave']; let elementsSizes = {}; // TODO: May need to have the base XY WH of board, hand, etc. stored for loop draw // Array of items, the 'Entity Manager' as such let item = []; let itemCount = 0; // Component 1: Where on the board, hand, deck, mana, shield, etc. // send back item, itemCount and individual comp. from server? let boardElement = {}; let cardData = {}; let position = {}; let size = {}; //let shape = {}; let cardStatus = {}; // tapped, attacking, inspected, untargettable (TODO:maybe used this instead of inEvent later) let player = {}; let listPosition = {}; let inEvent = null; // TODO: Do something else ECSey, think most logic for this is about let playerDeck = []; let opponentDeck = []; // TODO: Re-implement, then remove existing code let playerMana = []; let playerHand = []; // To disable drawing each time something changes let drawEachEvent = true; // For disabling draw each time and only occuring where I want to test let yourPlayerId = 0; // To compare click events of your/opponents cards let viewingPlayerId = 0; // To show the board from your/opponent/teammates perspective, etc. without play permission let deckCount = 35; let deckCountOpponent = 35; const maxHandSize = 4; const maxBoardSize = 3; const maxShield = 4; // Gonna need lots of refactoring, and sorting class Board{ constructor(){ console.log('initBoard'); ctx.font = "12px Arial"; canvas.style.backgroundColor = 'rgb(143 153 150)'; cards.src = 'images/deck.svg'; back.src = 'images/uno.svg'; ctx.fillStyle = '#000'; } drawBoard(force = false){ if(drawEachEvent == false && force == false){ return 0; } // Reset board ctx.clearRect(0, 0, canvas.width, canvas.height); // Room Name ctx.fillText(name, 0, 10); this.drawPlayerNames('Nathan', 'Evil Nathan'); this.drawCardsECS(); // Atop most everything atm for testing if(this.checkGameWin() == true){ this.drawWin(); } } checkGameWin(){ // If opponent shield is 0 then you win TODO: Otherwincons, check w/l for each player if(this.remainingShieldCount(1) <= 0){return true;} return false; } drawWin(){ var winBoard = new Shape({ name: name, x: 0, y: canvas.height/2 - (canvas.height/4)/2, width: canvas.width, height: canvas.height/4, fillStyle: '#CCC', strokeStyle: '#00EEAA' }); winBoard.draw(); ctx.fillStyle = '#000'; ctx.font = "bold 50pt Arial"; ctx.fillText('WINNER', 200, 300); ctx.font = "10pt Arial"; } drawPlayerNames(playerName, opponentName = false){ // Player Name ctx.fillText(playerName, 50, canvas.height - 50); // Opponent's Name if(opponentName){ ctx.fillText(opponentName, canvas.width - (ctx.measureText(opponentName).width + 50), 50); } } drawCardsECS(){ // ALL NON DECK CARDS DO BE DRAWN IN SAME LOOP (ideally) // Loop all items for(let itemKey = 0; itemKey < item.length; itemKey++){ // Loop each element, and player for(let elementCount in elements){ // Don't draw deck TODO:/gy/void // TODO: Unless inspecting let element = elements[elementCount]; if(element == 'deck'){ continue; } // Draw Elements // Loop each item left, and draw if element is currently looped. board,mana,etc. if(itemKey in boardElement && boardElement[itemKey] == element){ if(boardElement[itemKey] == 'board' && player[itemKey] == 0){ console.log('PLAYER BOARD'); } // Get the player the item belongs to let itemPlayer = player[itemKey]; //console.log('Element: '+element+', Player: '+itemPlayer); calculateItemSizePosition(itemKey); this.printCardToCanvas(itemKey); } } } } printCardToCanvas(itemKey){ // If the itemKey has cardData, position, size, and boardElement we can draw it // TODO: Add a check for error handling // Check status, and change border colour for display (for tapped atm) let border = null; if(cardStatus[itemKey] == 'tapped'){border = '#E0BC00';} if(cardStatus[itemKey] == 'attacking'){border = '#C92D22';} // Set the card 'cardboard' colour based on the card colour type let fill = null; if(boardElement[itemKey] != 'realDeck'){ // TODO: Change these to isset checks instead... let colourId = cardData[itemKey].colour; if(colourId == 0){ fill = '#EEE'; } else if(colourId == 1){ fill = '#0033EE'; } else if(colourId == 2){ fill = '#ED344A'; } }else{ fill = '#B0B0B0'; } let name = itemKey; // Not needed really anymore, but keeping for now let positionX = position[itemKey][0]; let positionY = position[itemKey][1]; let width = size[itemKey][0]; let height = size[itemKey][1]; // Draw the card shape itself let shape = new Shape({ name: name, x: positionX, y: positionY, width: width, height: height, fillStyle: fill, strokeStyle: border }); shape.draw(); if(boardElement[itemKey] != 'realDeck'){ // TODO: isset, or differ between types this.printCardImage(itemKey); this.printCardDetails(itemKey); }else{ // TODO: For realDeck only atm, also janked in. Seperate this... let counterx= positionX; let countery= positionY; let counterwidth= cardWidth*.375; let counterheight= cardHeight*.375; // TODO: Center in the circle let deckLength = getCurrentPositionAndLength('deck', player[itemKey])[1]; let textx=positionX - (ctx.measureText(deckLength).width/2); let texty=positionY + (ctx.measureText(deckLength).width/2); // Draw circle for deck count to sit in let deckCounterSprite = new Shape({ shape: 'circle', name: 'deckCountererer', x: counterx, y: countery, width: counterwidth, height: counterheight, fillStyle: '#DCDCDC' }); deckCounterSprite.draw(); // Draw deck count text ctx.fillStyle = '#000'; ctx.fillText(deckLength, textx, texty); } } printCardImage(itemKey){ let name = itemKey; // Not needed really anymore, but keeping for now let positionX = position[itemKey][0]; let positionY = position[itemKey][1]; let width = size[itemKey][0]; let height = size[itemKey][1]; let fill = '#BBB'; let shape = 'semi'; // Will be decided based on cardData.something? (for unit, spell, etc) // Add 'image' shape, will need to blitz sprite here in the future (based on cardData.id) let cardImageContainer = new Shape({ shape: 'semi', name: 'cardImageContainer_'+name, x: positionX+height/3, y: positionY+width/2, width: width*.9, height: height*.9, fillStyle: fill }); cardImageContainer.draw(); // Draw the actual image too } printCardDetails(itemKey){ let name = itemKey; // Not needed really anymore, but keeping for now let positionX = position[itemKey][0]; let positionY = position[itemKey][1]; let width = size[itemKey][0]; let height = size[itemKey][1]; // Add card name let fontSize = width/cardWidth*10; // 10 = baseFontSize of 10pt ctx.font = "bold "+fontSize+"pt Arial"; ctx.fillStyle = '#000'; ctx.fillText( cardData[itemKey]['name'] , positionX + (ctx.measureText(cardData[itemKey]['name']/2).width) - width/4 , positionY+height*.25 ); // Add card type ctx.fillText( cardData[itemKey]['type'] , positionX + (ctx.measureText(cardData[itemKey]['type']/2).width) - width/4 , positionY+height*.7 ); // Add text/effect area if(cardData[itemKey]['effect'] !== null){ ctx.fillText( cardData[itemKey]['effect'] , positionX + (ctx.measureText(cardData[itemKey]['effect']/2).width) - width/4 , positionY+height*.8 ); } // Attack ctx.fillText( cardData[itemKey]['atk'] , positionX + (ctx.measureText(cardData[itemKey]['atk']).width) , positionY+height*.95 ); // Add cost ctx.fillText( cardData[itemKey]['cost'] , positionX + (ctx.measureText(cardData[itemKey]['cost']).width) , positionY+height*.1 ); // Unbold font for other draws ctx.font = "10pt Arial"; } // Draw Invidual Cards, called by other deck stuff // Might be put into a card class, makes sense, eh. drawCard(array, arrayKey, name, positionX, positionY, width, height, fill, border){ // Card Colour //console.log('drawCard card: '+JSON.stringify(array[arrayKey])); let colourId = array[arrayKey].colour; if(colourId == 0){ fill = '#EEE' } else if(colourId == 1){ fill = '#0033EE' } if(array[arrayKey].tapped){ border = '#E0BC00'; console.log('drawCard tapped'); } var cardClickable = new Shape({ name: name, x: positionX, y: positionY, width: width, height: height, fillStyle: fill, strokeStyle: border }); array[arrayKey]['clickable'] = cardClickable; array[arrayKey]['clickable'].draw(); // Add image // TODO:half circle for unit Set start angle to 0 and end angle to Math.PI. // TODO:Ellipse for token (near full size) // TODO:Octagon for spell let cardImageContainer = new Shape({ shape: 'semi', name: 'cardImageContainer_'+name, x: positionX+height/3, y: positionY+width/2, width: width*.9, height: height*.9, fillStyle: "#BBB" }); cardImageContainer.draw(); // Add card name let fontSize = width/cardWidth*10; // 10 = baseFontSize of 10pt ctx.font = "bold "+fontSize+"pt Arial"; ctx.fillStyle = '#000'; ctx.fillText( array[arrayKey]['name'] , positionX + (ctx.measureText(array[arrayKey]['name']/2).width) - width/4 , positionY+height*.25 ); // Add card type ctx.fillText( array[arrayKey]['type'] , positionX + (ctx.measureText(array[arrayKey]['type']/2).width) - width/4 , positionY+height*.7 ); // Add text/effect area if(array[arrayKey]['effect'] !== null){ ctx.fillText( array[arrayKey]['effect'] , positionX + (ctx.measureText(array[arrayKey]['effect']/2).width) - width/4 , positionY+height*.8 ); } // Attack ctx.fillText( array[arrayKey]['atk'] , positionX + (ctx.measureText(array[arrayKey]['atk']).width) , positionY+height*.95 ); // Add cost ctx.fillText( array[arrayKey]['cost'] , positionX + (ctx.measureText(array[arrayKey]['cost']).width) , positionY+height*.1 ); // Unbold font for other draws ctx.font = "10pt Arial"; } // boardElement, cardData?, position?, size?, cardStatus, player, listPosition getItems(boardElementId = null, playerId = null, cardStatusId = null, listPositionId = null){ // So, the intent here is to normalise my nested loop I keep duping (parts of) // This will recieve each piece of content that can be stored in each ECS element // Then will loop each individually, setting a new array to be returned // This new array will then be used for tapping, untapping, moving positions, etc. // This is the 'core' of the loop, as it'll keep changing what's in it, removing // elements at each step. I.e. Will check boardElement for 'mana', removing all other boardElements // Then with just the 'mana' elements will check the player. OR can return all mana, OR all player items let newItems = []; let tempArray = []; // Set to all items in itemArray to start newItems = item; //let elements = ['deck','board','hand','mana','shield', 'grave']; // Stuff to be looped, and compared //let boardElement = {}; //let cardData = {}; //let position = {}; //let size = {}; //let cardStatus = {}; // tapped, attacking, inspected, untargettable (TODO:maybe used this instead of inEvent later) //let player = {}; //let listPosition = {}; // These have been re-ordered to try to minise looping // Example, player in theory halfs the cards instantly, whereas boardElement deck, may do little // boardElement often will be better though, but??? // PLAYER if(playerId !== null){ for(let newItem = 0; newItem < newItems.length; newItem++){ let itemKey = newItems[newItem]; // If the playerId of item shares the passed playerId if(playerId == player[itemKey]){ tempArray.push(newItems[newItem]); } } // Set newItems to what remains in tempArray newItems = tempArray; } // Reset tempArray so it can be reused tempArray = []; // CARD STATUS if(cardStatusId !== null){ for(let newItem = 0; newItem < newItems.length; newItem++){ let itemKey = newItems[newItem]; // If the playerId of item shares the passed playerId if(cardStatusId == cardStatus[itemKey]){ tempArray.push(newItems[newItem]); } } // Set newItems to what remains in tempArray newItems = tempArray; } // Reset tempArray so it can be reused tempArray = []; // LIST POSITION if(listPositionId !== null){ for(let newItem = 0; newItem < newItems.length; newItem++){ let itemKey = newItems[newItem]; // If the playerId of item shares the passed playerId if(listPositionId == listPosition[itemKey]){ tempArray.push(newItems[newItem]); } } // Set newItems to what remains in tempArray newItems = tempArray; } // Reset tempArray so it can be reused tempArray = []; // BOARD ELEMENT // Loop items for boardElement of elementType passed // Only do the loop if the element type was passed too, no reason to waste resources if(boardElementId !== null){ for(let newItem = 0; newItem < newItems.length; newItem++){ // Add the itemKey to tempArray // itemKey can be different to the array's elementId, and we want the original itemKey, // not the new elementId, as that would then add the wrong item // Set the itemKey, so things make sense let itemKey = newItems[newItem]; //newItems[0] could be itemKey 12, so want the content // If the boardElement of item shares the passed boardElement add it to current return if(boardElementId == boardElement[itemKey]){ tempArray.push(newItems[newItem]); } } // Set newItems to what remains in tempArray newItems = tempArray; } // Reset tempArray so it can be reused tempArray = []; return newItems; // TODO: May look at restructuring this again. Maybe one loop with each check against // the element data passed. Depends on which seems fastest, may do a compare on a large // dataset to see // TODO: ALSO in different func, Something like x.element.player.status to return the itemKey? } addFromBoardElement(playerFrom, fromPosition, elementFrom, elementTo, toPosition=null, playerTo=null){ // Move itemKey fromPosition in elementFrom to toPosition in elementTo // can also switch item between players (of from and to supplied) if(playerTo == null){ playerTo = playerFrom; } // Check if there's more than 1 thing add that position (something's gone wrong somewhere) let items = this.getItems(elementFrom, playerFrom, null, fromPosition); if(board.getItems(elementFrom, playerFrom, null, fromPosition).length > 1){ alert('ERROR: There are more than 1 card being added'); } // First (and only) item returned is the key thins should be done with let itemKey = items[0]; if(listPosition[itemKey] == fromPosition){ // Check if a toPostion supplied if(toPosition == null){ // Get the new position of item based on new boardElement toPosition = getCurrentPositionAndLength(elementTo, playerTo)[0]+1 } //console.log('itemKey: '+itemKey+' fromPosition: '+fromPosition+' elementFrom: '+elementFrom+' elementTo: '+elementTo+' toPosition: '+toPosition+' playerFrom: '+playerFrom+' playerTo: '+playerTo); // Move item to it's new position listPosition[itemKey] = toPosition; // Move item to it's new element boardElement[itemKey] = elementTo; // Move down(0) the positions of elementFrom, from fromPosition for player this.moveElementPositions(0, elementFrom, fromPosition, playerFrom); this.removeItemStatus(itemKey); this.drawBoard(); return 1; // Got a loop that calls a loop, and checks the new values atm, so this keeps counting down } } moveElementPositions(direction, elementFrom, fromPosition, playerFrom){ // Move the positions directionally 1 for elementFrom from fromPosition //console.log(listPosition); //console.log('Direction: '+direction+' elementFrom: '+elementFrom+' fromPosition: '+fromPosition+' player: '+playerId); // Loop the elementFrom, and move positions for anything after // item taken from position 34, 35..60 need to be moved down to 34..59 let items = this.getItems(elementFrom, playerFrom, null, null); for(let item = 0; item < items.length; item++){ let itemKey = items[item]; if(listPosition[itemKey] > fromPosition){ // Move items up, i.e. added to top of deck if(direction){ listPosition[itemKey]++; } // Move items down, i.e. taken from top of deck listPosition[itemKey]--; } } } // Draw a card, traditional TCG drawACard(playerId, cardsToDraw = 1){ for(let draw = 0; draw < cardsToDraw; draw++){ // Move from player1, position 0 (top) of deck, to hand // Check there's space in hand let elementLength = getCurrentPositionAndLength('hand', playerId)[1]; if(elementLength >= maxHandSize){ alert('Hand full '+elementLength+'/'+maxHandSize); return 0; } this.addFromBoardElement(playerId, 1, 'deck', 'hand', null, null); } } // Currently only functionality in hand playCardToBoard(positionFrom, fromElement, toElement, fromPlayer, toPlayer = null, cardsToPlay = 1){ // TODO: if from hand, use mana according to the card cost + mana types if(toPlayer === null){ toPlayer = fromPlayer; } // Loop probably not needed, but may be for eg. 'play X cards from top of deck' for(let play = 0; play < cardsToPlay; play++){ // Check there's space on the board/mana zone/shield/etc if(!this.hasSpaceInBoardElement(toElement, toPlayer)){ alert('No space in element'); return false; } let itemKey = this.getItemKey(fromElement, positionFrom, fromPlayer); console.log(itemKey); console.log(positionFrom); switch(fromElement){ case 'hand': // Mana cost required and mana tapping for playing a card from hand, etc // The player casting/summoning should pay, ofc let canPayMana = this.canPayMana(itemKey, fromPlayer); if(canPayMana !== true){ alert(canPayMana); return false; }else{ // Tap mana and play TODO: TEMP SOLUTION // TODO: May want to make this recursive, it's better and less bulky // but still lots of dupe code let items = this.getItems('mana', fromPlayer, null, null); for(let item = 0; item < items.length; item++){ let mana = items[item]; // For now just tapping the first untapped mana // TODO: Tap mana of correct colour (also allow player to select in fut) if(!this.isTapped(mana) && cardData[mana].colour == cardData[itemKey].colour){ this.tapCard(mana); break; // Temp TODO: Tap each mana needed until manacost/colours are met, also TODO: allow user to target own mana OR use non-multitypes first (not implemented yet) }else{ continue; } } } } // Move from player0, position 0 (top) of deck, to hand, to pos(null/auto) for toPlayer this.addFromBoardElement(fromPlayer, positionFrom, fromElement, toElement, null, toPlayer); } this.drawBoard(); } getItemKey(boardElementId, listPositionId, playerFrom){ let itemKey = this.getItems(boardElementId, playerFrom, null, listPositionId); if(itemKey.length < 1){ alert('Could not find key'); return false; } if(itemKey.length > 1){ alert('Found more than one key'); return false } // Return first index of array, aka the itemKey return itemKey[0]; } hasSpaceInBoardElement(toElement, toPlayer){ let elementLength = getCurrentPositionAndLength(toElement, toPlayer)[1]; if(elementLength >= maxHandSize){ alert('Board full '+elementLength+'/'+maxHandSize); return false; } return true; } canPayMana(itemToPlay, playerId){ // Check player has enough mana if(this.getItems('mana', playerId, null, null).length < cardData[itemToPlay].cost){ return 'Not enough mana'; } // TODO: Check mana colours are available to play, then tap them // TODO: Multicolours, and X of same colour checks let manaUsed = []; // For now, until adding colour, and other attributes to ECS modal let manaColourRequired = cardData[itemToPlay].colour; console.log(manaColourRequired); let items = this.getItems('mana', player[itemToPlay], null, null); for(let item = 0; item < items.length; item++){ let itemKey = items[item]; if(cardData[itemKey].colour == manaColourRequired && !this.isTapped(itemKey)){ manaUsed.push(itemKey); // This would be how I'd loop for X colour, or multi console.log(manaUsed); return true; } } return 'Do not have correct mana requirements'; } inspectCard(cardToInspect){ // Set inspectedCard (for now) to the card itemKey inEvent = ['inspect', cardToInspect]; this.drawBoard(); } cancelInspect(){ if(inEvent && inEvent[0] == 'inspect'){ inEvent = null; this.drawBoard(); } } startAttack(itemAttacking){ // Selects the card that will be attacking // Rename to attack intent? // Do error/legal checks here if(cardStatus[itemAttacking] == 'tapped'){ alert('Cannot attack, as tapped'); return false; } // Set event inEvent = ['attack', itemAttacking]; // Set the status of card to attacking for drawing. // TODO:In future this may be what's used for check (or other in case you can attack with multiple units at once) cardStatus[itemAttacking] = 'attacking'; this.drawBoard(); } // Do the attack makeAttack(itemDefending, itemAttacking = null){ // TODO: Check if mana owner has any 'block', etc. first, then prevent if so // If itemAttacking not defined, use the item from inEvent if(itemAttacking == null){ itemAttacking = inEvent[1]; } if(cardStatus[itemAttacking] == 'tapped'){ alert('Cannot attack, as tapped'); return false; } switch (boardElement[itemDefending]) { // If card on 'board' attacked // Compare attackingCard and defendingCard case 'board': let atkAttacker = cardData[itemAttacking]; let atkDefender = cardData[itemDefending]; // Does Attacker kill Defender if(atkDefender <= atkAttacker){ this.sendToGrave(itemDefending); } // Does Defender kill Attacker if(atkAttacker <= atkDefender){ this.sendToGrave(itemAttacking); this.endAttackFor(itemAttacking); }else{ // If not, end the attacker cards attack 'phase' this.endAttackFor(itemAttacking); } break; case 'shield': // If the shield is tapped 'destroy' it if(cardStatus[itemDefending] == 'tapped'){ // Remove from shield, add to hand // Untap, add to hand (when moving item between status, ANY ITEM. remove status) this.destroyShield(itemDefending); } else{ this.tapCard(itemDefending); } // End the attacker card attack 'phase' this.endAttackFor(itemAttacking); break; } this.drawBoard(); } endAttackFor(itemAttacking){ // Tap the card (this would check if after attack cards taps or not. this.tapCard(itemAttacking); // If the item attacking was from the 'attack' event, remove the event if(itemAttacking == inEvent[1]){ inEvent = null; } } cancelAttackFor(itemAttacking){ if(itemAttacking != inEvent[1]){ return false; } // If the card to attack is from inEvent of 'attack', remove it and its cardStatus cardStatus[itemAttacking] = null; inEvent = null; this.drawBoard(); } // Like everything else, need to consolidate into one function that // can work for both players, and even more for 2v2 3v1 combats, etc. playShield(fromPosition, fromElement, playerId, cardsToPlay = 1){ for(let shieldNo = 0; shieldNo < cardsToPlay; shieldNo++){ // Check there's space for shield TODO: change to locationTo // TODO: Normalise this for all element/player combos let elementLength = getCurrentPositionAndLength('shield', playerId)[1]; if(elementLength >= maxShield){ alert('Shield full '+elementLength+'/'+maxShield); // Kill loop if there's too many shiled already, no need re-notifying return 0; } // Move from player, position 0 (top) of deck, to hand (next available position) this.addFromBoardElement(playerId, fromPosition, fromElement, 'shield', null, null); } } playMana(fromPosition, fromElement, fromPlayer, cardsToPlay = 1){ // Move from player0, fromPosition of hand (for now), to mana // TODO: FOR ALL addFromBoardElements, if 'fromPosition' not passed get the // fromPosition and boardElementFrom from the itemId (will need to change to pass this) console.log('playMana('+fromPosition+','+fromElement+','+cardsToPlay+')'); this.addFromBoardElement(fromPlayer, fromPosition, fromElement, 'mana', null, null); } // HELPER METHODS, to simplify code-base for me in the future destroyShield(itemKey){ // Adds from 'shield' to 'hand' boardElement // Sets the listPosition to the next in line for new boardElement // Changes the listPosition of each item in previous boardElement // All for same player, although below playerFrom/playerTo can be set to switch between players // TODO:Check if shield can be destroyed (instantly, i.e. poisontouch, or some mechanic/spell akin) // Check the shield is not tapped, if not can't be destroyed if(!this.isTapped(itemKey)){ alert('Shield not tapped, cannot be destroyed'); return false; } // Get all shields, that belong to the player whose shield was targetted. // Check if they're all tapped before destroying the target let items = this.getItems('shield', player[itemKey], null, null); for(let item = 0; item < items.length; item++){ let itemKey = items[item]; // If ANY of their shields are untapped, you can't destroy target if(cardStatus[itemKey] != 'tapped'){ alert('There is an untapped shield, cannot destroy target'); return false; } } // Shield is now destroyed, move it from shield to owners hand (for the catchup mechanic) this.addFromBoardElement(player[itemKey], listPosition[itemKey], boardElement[itemKey], 'hand', null, null); return true; } sendToGrave(itemKey){ console.log('SendToGrave: '+itemKey); //addFromBoardElement(playerFrom, fromPosition, elementFrom, elementTo, toPosition=null, playerTo=null) this.addFromBoardElement(player[itemKey], listPosition[itemKey], boardElement[itemKey], 'grave', null, null); } tapCard(itemKey){ // Set the cardStatus to tapped cardStatus[itemKey] = 'tapped'; // Do any other 'on tap' effects, etc. in the future } untapCard(itemKey){ cardStatus[itemKey] = null; // Do any other 'on tap' effects, etc. in the future } remainingShieldCount(playerId){ return getCurrentPositionAndLength('shield', playerId)[1]; } removeItemStatus(itemKey){ // TODO: Remove status of card, and do/don't do effect depending on locationTo/locationFrom? cardStatus[itemKey] = null; } isTapped(itemKey){ if(cardStatus[itemKey] == 'tapped'){ return true; } return false; } } // Run board commands here for testing let board = new Board; // TODO: TEMP!! Replace soon createDeck(); //createDeckList(playerDeck, deckCount, 0); //createDeckList(opponentDeck, deckCountOpponent, 1); // Play 4 shield from top (0) of each players deck for(let currentPlayer = 0; currentPlayer <= players-1; currentPlayer++){ board.playShield(1, 'deck', currentPlayer, 4); } board.drawBoard(true); // Right Click, Rightclick, rightclick, right click canvas.addEventListener('contextmenu', function(event) { event.preventDefault(); var x = event.pageX - canvasLeft, y = event.pageY - canvasTop; console.log('RIGHT CLICK X: '+x+' Y: '+y); for(let itemKey = 0; itemKey < item.length; itemKey++){ if(elements[itemKey] == 'deck'){ continue; } // TODO: Normalise this too, may need to change how size+position work? // Check the item has a size and position if(itemKey in size && itemKey in position){ // Compare the event XY position to the item if(clickableCheck(x,y,false,itemKey)){ // Only want to happen once (for now) // Maybe in future add to hand would trigger another event if there's an effect? // Player the item belongs to, not who's doing the action let playerId = player[itemKey]; // Check the location of element switch(boardElement[itemKey]){ // Check item location, and trigger events based on it case 'board': // TODO: If the current event is attack, right clicking same item in event // will end the attack event // If there's an attack event, target other cards // If there is an event, the event is attack event, and the item currently loop // is the same one as currently in the event if(inEvent && inEvent[0] == 'attack' && itemKey == inEvent[1]){ board.cancelAttackFor(itemKey); } break; case 'hand': // Can be played as mana (right click for now) // Play item from boardElement hand. To boardElement mana (explanitory) board.playMana(listPosition[itemKey], 'hand', playerId); break; default: break; } board.drawBoard(); return true; } } } // Return false to prevent context opening: // https://stackoverflow.com/a/4236294 return false; }, false); // Left Click, Leftclick, leftclick, left click canvas.addEventListener('click', function(event) { var x = event.pageX - canvasLeft, y = event.pageY - canvasTop; // Check clicks against itemKey position+size // Will be the new way // TODO:Maybe write this into a function? If XY WH is hit return true, and the itemKey // then it can be re-used in contextclick, hover, etc without rewrite console.log('LEFT CLICK X: '+x+' Y: '+y); // TODO: Normalise this too for(let itemKey = 0; itemKey < item.length; itemKey++){ if(boardElement[itemKey] == 'deck'){ continue; }else{ //printECSData([itemKey]); } // Check the item has a size and position if(itemKey in size && itemKey in position){ // Compare the event XY position to the item if(clickableCheck(x,y,false,itemKey)){ // Only want to happen once (for now) // Maybe in future add to hand would trigger another event if there's an effect? // Get the playerId of who the item belongs to // TODO: Some other playerId may be needed to for 'mind control' cards let playerId = player[itemKey]; // Check the location of element switch(boardElement[itemKey]){ // Check item location, and trigger events based on it // TODO: Change inEvent locations, and checks elsewhere? // TODO: Make existing mechanics ECSey case 'realDeck': board.drawACard(playerId, 1); break; case 'board': // player/opponentBoard not seperated, as want to have // each player able to have effects, etc. to make others attack // etc. // yourPlayerId in for now to prevent using opponents card // TODO:the cards may be used to attack, there will be another // check like 'canUseOpponentsBoard' or something if(!inEvent && cardStatus[itemKey] != 'tapped' && playerId == yourPlayerId){ board.startAttack(itemKey); break; } // opponentBoard // If there's an attack event, target other cards // Again yourPlayerId check for now, this time so your can't hit your own if(inEvent && inEvent[0] == 'attack' && inEvent[1] != itemKey && playerId != yourPlayerId){ // Make attack on the card clicked, with card in inEvent[1] board.makeAttack(itemKey); break; } // If no event, and clicked an TODO:OPPONENT CARD (for now) // Wants to be on option on r/l click maybe // left click inspects then you choose play from there? // inspect the card (zoom in on it) if(!inEvent && playerId != yourPlayerId){ board.inspectCard(itemKey); break; } else if(inEvent && inEvent[0] == 'inspect' && inEvent[1] == itemKey){ board.cancelInspect(); break; } break; case 'hand': // TODO: Ensure it can be played board.playCardToBoard(listPosition[itemKey], 'hand', 'board', playerId, playerId, 1); break; case 'shield': // If you have an attack, and click opponent shield, // destroy (unless they can block, etc.) if(inEvent && inEvent[0] == 'attack' && inEvent[1] != itemKey){ board.makeAttack(itemKey); } break; default: break; } board.drawBoard(); return true; } } } }, false); function clickableCheck(x,y,clickable=false,itemKey=false){ // Collision detection between clicked offset and clickableItems // https://stackoverflow.com/a/9880302 // Temp solution to add the check while the old way also exists // simultaneously. It works, so that's nice if(clickable === false && itemKey !== false){ clickable = {}; //console.log(clickable); //console.log(itemKey); clickable.x = position[itemKey][0]; clickable.y = position[itemKey][1]; clickable.width = size[itemKey][0]; clickable.height = size[itemKey][1]; //console.log(clickable); } // Debug Stuff let debug = false if(debug){ console.log(clickable.y+' <'); console.log(y+ '<') console.log(clickable.y+clickable.height); } if( y > clickable.y && y < clickable.y + clickable.height && x > clickable.x && x < clickable.x + clickable.width) { return true; } return false; } function createDeck(){ // Create a 'realDeck' element for each player for(let i = 0; i < players; i++){ item.push(itemCount); boardElement[itemCount] = 'realDeck'; player[itemCount] = i; // TODO: Added these in to prevent error // In future want to remove, and add isset checks for non-used data cardStatus[itemCount] = null; listPosition[itemCount] = null; itemCount++; } // Loop again and create the deckLists for(let i = 0; i < players; i++){ createDeckList(i); } } // TODO: USE DATABASE FOR THIS!!! function createDeckList(playerId){ // TODO:Create the deckList by loading the deckDB // For now pulling from a deckList array that uses a cardArray let deckList = null; if(playerId == 0){ deckList = deckListPlayer; } if(playerId == 1){ deckList = deckListOpponent; } for(let deckItem = 0; deckItem < deckList.length; deckItem++){ // Create new item for ECS item.push(itemCount); // Set card data for new item // Use the ID from the deckList of player, and return the card of that ID from cardList/DB // ID 1 was adding id 0 as array index. Changed to +1 for now (will correct in DB form) cardData[itemCount] = cardArray[deckList[deckItem]+1]; // Set to base position of 'deck' boardElement[itemCount] = 'deck'; // Set Attack, ManaCost, ManaColours TODO: When these are implemented seperately // Set the player player[itemCount] = playerId; // Set the position in the deck (as was added), will be shuffled on game start listPosition[itemCount] = deckItem+1; // Increment the itemCount to prevent overwriting stuff itemCount++; } let cardsInDeck = board.getItems(null, playerId); shuffleDeck(playerId); } function shuffleDeck(playerId){ // Create a tempDeck array of same length of the player deck let deckLength = getCurrentPositionAndLength('deck', playerId)[1]; let tempDeck = Array.from(Array(deckLength).keys()) // Loop the tempDeck and shuffle // https://stackoverflow.com/a/73603221 for(let i = 0; i < deckLength; i++){ // picks the random number between 0 and length of the deck let shuffle = Math.floor(Math.random() * (tempDeck.length)); // swap the current listPosition with a random one with the deck count [ tempDeck[i], tempDeck[shuffle] ] = [ tempDeck[shuffle], tempDeck[i] ]; } // For each item in the actual deck, set the listPosition to the random number from tempDeck let items = board.getItems('deck', playerId, null, null); for(let item = 0; item < items.length; item++){ let itemKey = items[item]; //console.log('ITEM KEY: '+itemKey); //console.log('OLD LIST POSITION: '+listPosition[itemKey]); listPosition[itemKey] = tempDeck[item]; //console.log('NEW LIST POSITION: '+listPosition[itemKey]); } } function untapZone(elementFrom, playerFrom){ let items = board.getItems(elementFrom, playerFrom, null, null); for(let item = 0; item < items.length; item++){ let itemKey = items[item]; if(board.isTapped(itemKey)){ board.untapCard(itemKey); } } board.drawBoard(); } function untapAllZones(currentPlayer = null){ if(currentPlayer === null){ currentPlayer = 0; } for(let currentPlayer = 0; currentPlayer <= players-1; currentPlayer++){ let elements = ['deck','board','hand','mana','shield', 'grave']; // Loop all the elements, and utap each card in the zone for(let element = 0; element < elements.length; element++){ untapZone(elements[element], currentPlayer); } } } function getCurrentPositionAndLength(elementName, playerId){ let highestListPosition = 0; let length = 0; let items = board.getItems(elementName, playerId, null, null); for(let item = 0; item < items.length; item++){ let itemKey = items[item]; if(listPosition[itemKey] >= highestListPosition){ highestListPosition = listPosition[itemKey]; } length++; } return [highestListPosition, length]; } function calculateItemSizePosition(itemKey){ // Calc position and size of the item // Used for drawing and interacting // TODO: Make better calculations and positions for here (also rescaling in future) // TODO: Maybe instead of checking each player/element combo in if, do some fancy loop? let itemPlayer = player[itemKey]; let itemElement = boardElement[itemKey]; let itemListPositionLength = getCurrentPositionAndLength(itemElement, itemPlayer); let itemListPositionNext = itemListPositionLength[0]; let itemListLength = itemListPositionLength[1]; let cardMargin = 10; let positionX = 0; let positionY = 0; let width = 0; let height = 0; let i = listPosition[itemKey]; //console.log('cardName: '+cardData[itemKey].name+' listPosition/i: '+i); if(itemPlayer == 1 && itemElement == 'realDeck'){ positionX = 40; positionY = 60; width = cardWidth*1.5; height = cardHeight*1.5; } if(itemPlayer == 0 && itemElement == 'realDeck'){ positionX = canvas.width-cardWidth*1.5-40; positionY = canvas.height-cardHeight*1.5-60; width = cardWidth*1.5; height = cardHeight*1.5; } if(itemPlayer == 1 && itemElement == 'board'){ positionX = canvas.width/2 - (cardWidth * (itemListLength - i) - (cardMargin * i)); positionY = cardHeight + 30; width = cardWidth; height = cardHeight; } if(itemPlayer == 1 && itemElement == 'hand'){ positionX = canvas.width/2 - (cardWidth * (itemListLength - (i+1)) - (cardMargin * (i+1))); positionY = 20; width = cardWidth; height = cardHeight; } if(itemPlayer == 1 && itemElement == 'mana'){ // TODO: Opponent Mana } if(itemPlayer == 1 && itemElement == 'shield'){ let fromX = canvas.width-60; let fromY = 300; let cWidth = cardWidth*.8; let cHeight = cardHeight*.8; let split = 0; // i-1 here as it's based on 0 being start, like array. // If there's 2 in a row, split onto next row // TODO: Not sure if I want to start elements at 1 (for clienty) or 0 (for programmy) if(i-1>=2){ split = 1; } // i%2 0 = 0, 1 = 1, 2 = 0, 3 = 1 to prevent margin from X/Y axis, and just between cards positionX = (fromX+((i%2)*cardMargin))+(i%2*cWidth)-(cWidth*2); positionY = fromY+(split*cHeight+(cardMargin*split))-(cHeight*2 + cardMargin); width = cWidth; height = cHeight; } if(itemPlayer == 0 && itemElement == 'board'){ positionX = canvas.width/2 - (cardWidth * (itemListLength - (i+1)) - (cardMargin * (i+1))); positionY = canvas.height - cardHeight-30-(cardHeight); width = cardWidth; height = cardHeight; } if(itemPlayer == 0 && itemElement == 'hand'){ positionX = canvas.width/2 - (cardWidth * (itemListLength - (i+1)) - (cardMargin * (i+1))); positionY = canvas.height-cardWidth*1.5-20; width = cardWidth; height = cardHeight; } if(itemPlayer == 0 && itemElement == 'mana'){ let fromX = 60; let fromY = 60; let cWidth = cardWidth*.5; let cHeight = cardHeight*.5; positionX = (fromX+cardMargin)+(i*cWidth-cardMargin); positionY = canvas.height-fromY; width = cWidth; height = cHeight; } if(itemPlayer == 0 && itemElement == 'shield'){ let fromX = 60; let fromY = 300; let cWidth = cardWidth*.8; let cHeight = cardHeight*.8; let split = 0; // i-1 here as it's based on 0 being start, like array. // TODO: Not sure if I want to start elements at 1 (for clienty) or 0 (for programmy) if(i-1>=2){ split = 1; } positionX = (fromX+((i%2)*cardMargin)) +(i%2*cWidth); positionY = canvas.height-fromY+(split*cHeight+(cardMargin*split)); width = cWidth; height = cHeight; } // Inspected Card // TODO: May need to make a new itemKey for inspectedCard to loop through if(inEvent && inEvent[0] == 'inspect' && inEvent[1] == itemKey){ positionX = canvas.width/2 - cardWidth; positionY = canvas.height/2 - cardHeight; width = cardWidth*2; height = cardHeight*2; } //console.log('cardName: '+cardData[itemKey].name+', i/listPosition: '+i+', listPosition Length: '+itemListLength); // Set the size/position of the item size[itemKey] = [width, height]; position[itemKey] = [positionX,positionY]; } // For checking data without needing to change the codebase with logs function getItemsAndPrintFrontEnd(){ let boardElementId = document.getElementById("boardElementId").value; if(boardElementId == ""){ boardElementId = null; } let playerId = document.getElementById("playerId").value; if(playerId == ""){ playerId = null; } let cardStatusId = document.getElementById("cardStatusId").value; if(cardStatusId == ""){ cardStatusId = null; } let listPositionId = document.getElementById("listPositionId").value; if(listPositionId == ""){ listPositionId = null; } let itemOrCard = document.getElementById("itemOrCardData").value; getItemsAndPrint(boardElementId, playerId, cardStatusId, listPositionId, itemOrCard); } function getItemsAndPrint(boardElementId = null, playerId = null, cardStatusId = null, listPositionId = null, itemOrCard = 'item'){ let items = board.getItems(boardElementId, playerId, cardStatusId, listPositionId); if(itemOrCard == 'card'){ console.log('----- CardData -----'); printCardData(items); }else{ console.log('----- ItemData -----'); printECSData(items); } console.log('Items array length: '+items.length); } function printECSData(items){ for(let item = 0; item < items.length; item++){ let itemKey = items[item]; console.log( 'boardElement: '+boardElement[itemKey]+"\n"+ 'cardData: '+cardData[itemKey]+"\n"+ 'position: '+position[itemKey]+"\n"+ 'size: '+size[itemKey]+"\n"+ 'cardStatus: '+cardStatus[itemKey]+"\n"+ 'player: '+player[itemKey]+"\n"+ 'listPosition: '+listPosition[itemKey] ); } } function printCardData(items){ let cardArray = []; for(let item = 0; item < items.length; item++){ let itemKey = items[item]; cardArray.push(cardData[itemKey]); } console.log(cardArray); } function echoCards(){ console.log(cardArray); } function echoCardsInDeck(playerId){ // Get all the cards that belong to player let cardsInDeck = board.getItems(null, playerId); let deckList = []; for(let i = 0; i < cardsInDeck.length; i++){ // If it's a card TODO: Change this to check 'card' from some ECS element if(cardData[i] !== null && cardData[i] !== undefined){ deckList.push(cardData[i]); } } console.log(deckList); }