You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1755 lines
55 KiB
JavaScript
1755 lines
55 KiB
JavaScript
const ctx = canvas.getContext('2d');
|
|
const canvasLeft = canvas.offsetLeft + canvas.clientLeft;
|
|
const canvasTop = canvas.offsetTop + canvas.clientTop;
|
|
|
|
const cardWidth = 80;
|
|
const cardHeight = 120;
|
|
|
|
const cardArt = new Image();
|
|
const cardBackArt = new Image();
|
|
|
|
// Colours
|
|
const COLOUR = {
|
|
'white':{'id': 1, 'name':'White','colour':'#EEE'},
|
|
'blue':{'id':2, 'name':'Blue','colour':'#0033EE'},
|
|
'red':{'id':3, 'name':'Red','colour':'#ED344A'},
|
|
};
|
|
|
|
// 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 cardFace = {};
|
|
let cardSprite = {};
|
|
let deckIn = {}; // NEW, may be used, for now player substitute
|
|
let deckData = {};
|
|
let cardAttack = {}; // TODO: add to the logic
|
|
let cardColours = {};
|
|
let cardManaColour = {};
|
|
let cardEffect = {};
|
|
let inEvent = null;
|
|
|
|
// 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
|
|
|
|
const maxHandSize = 4;
|
|
const maxBoardSize = 3;
|
|
const maxShield = 2;
|
|
|
|
// Gonna need lots of refactoring, and sorting
|
|
class Board{
|
|
constructor(){
|
|
console.log('initBoard');
|
|
ctx.font = "12px Arial";
|
|
canvas.style.backgroundColor = 'rgb(143 153 150)';
|
|
cardArt.src = 'images/cardArt.jpg';
|
|
cardBackArt.src = 'images/cardBack.jpg';
|
|
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.drawItems(); // 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);
|
|
}
|
|
}
|
|
|
|
drawItems(){
|
|
// Loop all items
|
|
for(let itemKey = 0; itemKey < item.length; itemKey++){
|
|
// Loop each element to draw (each have distinct locations, and draw logic)
|
|
for(let elementCount in elements){
|
|
// Don't draw deck TODO:/gy/void
|
|
// TODO: Unless inspecting
|
|
let element = elements[elementCount];
|
|
if(element == 'deck' || element == 'grave'){
|
|
continue;
|
|
}
|
|
|
|
// Draw Elements
|
|
// Loop each item remaining, draw them
|
|
if(itemKey in boardElement && boardElement[itemKey] == element){
|
|
// Get the player the item belongs to
|
|
let itemPlayer = player[itemKey];
|
|
//console.log('Element: '+element+', Player: '+itemPlayer);
|
|
calculateItemSizePosition(itemKey);
|
|
|
|
this.printCardToCanvas(itemKey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
setCardFill(itemKey){
|
|
|
|
let fill = '#B0B0B0';
|
|
|
|
let coloursOfCard = cardColours[itemKey];
|
|
if(coloursOfCard.length > 1){
|
|
|
|
// Create a gradient for the card colours
|
|
// x-start,y-start,x-end,y-end
|
|
const grad=ctx.createLinearGradient(
|
|
position[itemKey][0],
|
|
0, //position[itemKey][1],
|
|
position[itemKey][0] + size[itemKey][0],
|
|
0, //position[itemKey][1] + size[itemKey][1]
|
|
);
|
|
|
|
for(let i = 0; i < coloursOfCard.length; i++){
|
|
|
|
let gradientPos = 0;
|
|
if(coloursOfCard.length == i){
|
|
gradientPos = 1;
|
|
}else{
|
|
// Colour stops from 0..1 (0 to 100% along)
|
|
// If 4, first is 0, second is 100/3 * 1 = 33.33%
|
|
// 0,33,66,100. Need last to always be 100 (1)
|
|
gradientPos = (coloursOfCard.length - 1)*i;
|
|
}
|
|
|
|
// TODO: Make new array ensuring colours are ordered by highest
|
|
// cost. This will do as it adds the colours, but in future
|
|
grad.addColorStop(gradientPos, this.setFillByManaColour(cardColours[itemKey][i][0]));
|
|
|
|
}
|
|
|
|
fill = grad;
|
|
}else{
|
|
// Set to a normal fill of the first (only) colour
|
|
fill = this.setFillByManaColour(cardColours[itemKey][0][0]);
|
|
}
|
|
|
|
return fill;
|
|
|
|
}
|
|
|
|
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(this.isTapped(itemKey)){border = '#E0BC00';}
|
|
if(this.isAttacking(itemKey)){border = '#C92D22';}
|
|
|
|
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];
|
|
|
|
// Set the card 'cardboard' colour based on the card colour type
|
|
let fill = '#B0B0B0';
|
|
if(boardElement[itemKey] != 'realDeck'){ // TODO: Change these to isset checks instead...
|
|
fill = this.setCardFill(itemKey);
|
|
}
|
|
|
|
// Draw the card shape itself
|
|
let shape = new Shape({
|
|
name: name,
|
|
x: positionX,
|
|
y: positionY,
|
|
width: width,
|
|
height: height,
|
|
fillStyle: fill,
|
|
strokeStyle: border
|
|
});
|
|
// Add a dropshadow to flight cards (they're flyin)
|
|
if(flight[itemKey]){
|
|
shape.setShadow(true);
|
|
}
|
|
shape.draw();
|
|
|
|
// Draw the card face-up
|
|
if(this.isFaceUp(itemKey)){
|
|
this.addCardImage(itemKey);
|
|
this.printCardDetails(itemKey);
|
|
this.printColourRequirements(itemKey);
|
|
this.printCardPassives(itemKey);
|
|
}
|
|
if(!this.isFaceUp(itemKey)){
|
|
this.addCardBack(itemKey);
|
|
}
|
|
// If it's the deck, draw the circle surrounding it
|
|
if(boardElement[itemKey] == 'realDeck'){
|
|
// 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);
|
|
}
|
|
// If the item is a mana, draw the mana colour within it
|
|
// Temp solution, but works for UI and testing
|
|
if(boardElement[itemKey] == 'mana'){
|
|
let manaFill = this.setFillByManaColour(cardManaColour[itemKey][0]);
|
|
// TODO: Seperate this into a 'drawMana' or something?
|
|
let manaColour = new Shape({
|
|
shape: 'circle',
|
|
//name: 'deckCountererer',
|
|
x: positionX + width/2,
|
|
y: positionY + height/2,
|
|
width: width*.75,
|
|
height: height*.75,
|
|
fillStyle: manaFill // Fill should be the card's main colour
|
|
});
|
|
manaColour.draw();
|
|
}
|
|
}
|
|
isFaceUp(itemKey){
|
|
if(cardFace[itemKey] == 1){
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
flipCard(itemKey, direction = null){
|
|
// TODO: Add a third param for 'triggerEffects', for instance
|
|
// flipping up from deck to hand, or shield to hand shouldn't trigger
|
|
// MOST TIMES
|
|
if(cardFace[itemKey] == 1 && (direction == null || direction == 0)){
|
|
cardFace[itemKey] = 0;
|
|
return true;
|
|
}
|
|
if(cardFace[itemKey] == 0 && (direction == null || direction == 1)){
|
|
cardFace[itemKey] = 1;
|
|
return true;
|
|
}
|
|
// TODO:Activate any flip effects, etc.
|
|
}
|
|
|
|
printCardImage(itemKey){
|
|
let positionX = position[itemKey][0];
|
|
let positionY = position[itemKey][1];
|
|
let width = size[itemKey][0];
|
|
let height = size[itemKey][1];
|
|
// Draw the image into the clipping mask
|
|
// image, dx,dy,dw,dh
|
|
// image, sx,sy, sw,sh,dx,dy,dw,dh
|
|
let spriteSheetX = 80*cardSprite[itemKey][0];
|
|
let spriteSheetY = 120*cardSprite[itemKey][1];
|
|
ctx.drawImage(cardArt, spriteSheetX,spriteSheetY, 80,120,positionX,positionY,width,height);
|
|
}
|
|
addCardImage(itemKey){
|
|
|
|
let positionX = position[itemKey][0];
|
|
let positionY = position[itemKey][1];
|
|
let width = size[itemKey][0];
|
|
let height = size[itemKey][1];
|
|
|
|
// Create the clipping shape
|
|
let cardImageContainer = new Shape({
|
|
shape: 'unit',
|
|
name: 'cardImageContainer_'+name,
|
|
x: positionX+height/3,
|
|
y: positionY+width/2,
|
|
width: width*.9,
|
|
height: height*.9
|
|
});
|
|
// Save canvas drawing, start the clip
|
|
cardImageContainer.startClip();
|
|
|
|
// Print the image to canvas, within the clipping mask
|
|
this.printCardImage(itemKey);
|
|
|
|
// Restore the canvas draw post clip applied, to get everything else back too
|
|
cardImageContainer.endClip();
|
|
}
|
|
addCardBack(itemKey){
|
|
let positionX = position[itemKey][0];
|
|
let positionY = position[itemKey][1];
|
|
let width = size[itemKey][0];
|
|
let height = size[itemKey][1];
|
|
|
|
// Print the sleeve image to cardPosition
|
|
this.printCardBack(itemKey);
|
|
}
|
|
printCardBack(itemKey){
|
|
let positionX = position[itemKey][0];
|
|
let positionY = position[itemKey][1];
|
|
let width = size[itemKey][0];
|
|
let height = size[itemKey][1];
|
|
// TODO: CardBack/Sleeves as spritesheet like cardArt
|
|
ctx.drawImage(cardBackArt, 0,0, 80,120,positionX,positionY,width,height);
|
|
}
|
|
// Check each passive, if the card has it, add the 'icon' to display it.
|
|
printCardPassives(itemKey){
|
|
|
|
let passiveCount = 0;
|
|
|
|
let positionX = position[itemKey][0];
|
|
let positionY = position[itemKey][1] - 10;
|
|
|
|
if(itemKey in flight){
|
|
this.printCenterText('F', positionX + (10*passiveCount), positionY);
|
|
passiveCount++;
|
|
}
|
|
if(itemKey in reach){
|
|
this.printCenterText('R', positionX + (10*passiveCount), positionY);
|
|
passiveCount++;
|
|
}
|
|
}
|
|
printColourRequirements(itemKey){
|
|
// Set the size(s)
|
|
let width = size[itemKey][0]*.1;
|
|
let height = size[itemKey][1]*.1;
|
|
let positionX = position[itemKey][0] + (size[itemKey][0]*.1);
|
|
let positionY = position[itemKey][1] + (size[itemKey][0]*.2);
|
|
|
|
|
|
// Get all the colour reqs needed.
|
|
let colourReqs = cardColours[itemKey];
|
|
|
|
// Loop them all
|
|
let totalColours = 1; // How many colours the card has, for positioning
|
|
colourReqs.forEach((colour) => {
|
|
|
|
// Add each to the card (colour BG, and required count within it)
|
|
// TODO: Maybe building a class for each draw is bad, eh.
|
|
// TODO: Should probably change the shapes.js class into functions
|
|
// Will look into once animations, etc are in, incase classes are the way
|
|
|
|
// TODO: change colours to have id(done), name, and hexcode as const/enum
|
|
let colourId = colour[0];
|
|
let fill = this.setFillByManaColour(colourId);
|
|
|
|
// Draw the mana shape/icon
|
|
let manaColour = new Shape({
|
|
shape: 'circle',
|
|
//name: 'deckCountererer',
|
|
x: positionX,
|
|
y: positionY + height*totalColours,
|
|
width: width,
|
|
height: height,
|
|
fillStyle: fill // Fill should be the card's main colour
|
|
});
|
|
manaColour.draw();
|
|
|
|
// Draw the requirement within the shape/icon
|
|
// Make sure the text scales with card size
|
|
let fontSize = (width*10/cardWidth)*10; // 10 = baseFontSize of 10pt
|
|
// ^ calced with the width of the card overall
|
|
context.font = fontSize+"pt Arial";
|
|
// Get a constrating B/W to the background and set text to that
|
|
context.fillStyle = invertColour(fill, true);
|
|
// Center the text (cost) within the shape
|
|
this.printCenterText(colour[1], positionX, positionY + height*totalColours);
|
|
// Reset context stylings
|
|
context.font = "10pt Arial"; // Reset to default
|
|
context.fillStyle = "#000";
|
|
|
|
// Inc totalColours in case there's more
|
|
totalColours++;
|
|
|
|
});
|
|
}
|
|
setFillByManaColour(colourId){
|
|
for (const colour in COLOUR) {
|
|
if(colourId == COLOUR[colour]['id']){
|
|
return COLOUR[colour]['colour']; // Lmao
|
|
}
|
|
}
|
|
|
|
return '#B0B0B0';
|
|
}
|
|
printCenterText(text, positionX, positionY){
|
|
context.textAlign = 'center';
|
|
context.textBaseline = 'middle';
|
|
context.fillText(text, positionX, positionY);
|
|
|
|
// Now reset the context aligns to the defaults
|
|
context.textAlign = 'left';
|
|
context.textBaseline = 'alphabetic';
|
|
}
|
|
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";
|
|
}
|
|
|
|
// boardElement, cardData?, position?, size?, cardStatus, player, listPosition
|
|
getItems(boardElementId = null, playerId = null, cardStatusId = null, listPositionId = null){
|
|
// Loop each item, building a new (smaller) loop each time an ECSey element
|
|
// matches what was passed. This is the core loop for practically every function.
|
|
let newItems = [];
|
|
let tempArray = [];
|
|
|
|
// Set to all items in itemArray to start
|
|
newItems = item;
|
|
|
|
// Check if each item shares the PLAYERID passed
|
|
if(playerId !== null){
|
|
for(let newItem = 0; newItem < newItems.length; newItem++){
|
|
// newItems[newItem] gets the original itemKey (needed), newItem would be
|
|
// the the array index, which would be wrong in most cases in this function.
|
|
let itemKey = newItems[newItem];
|
|
// If the item shares the playerId, add it to the tempArray
|
|
if(playerId == player[itemKey]){
|
|
tempArray.push(newItems[newItem]);
|
|
}
|
|
}
|
|
// Set newItems to tempArray so it can be looped again with only what matched
|
|
newItems = tempArray;
|
|
}
|
|
// Reset tempArray so it can be reused
|
|
tempArray = [];
|
|
|
|
// Next check if each remaining item shares the CARDSTATUS passed
|
|
if(cardStatusId !== null){
|
|
for(let newItem = 0; newItem < newItems.length; newItem++){
|
|
let itemKey = newItems[newItem];
|
|
// If the item shares the cardStatusId, add it to the tempArray
|
|
if(cardStatusId == cardStatus[itemKey]){
|
|
tempArray.push(newItems[newItem]);
|
|
}
|
|
}
|
|
// Set newItems to tempArray so it can be looped again with only what matched
|
|
newItems = tempArray;
|
|
}
|
|
// Reset tempArray so it can be reused
|
|
tempArray = [];
|
|
|
|
// Next check if each remaining item shares the LISTPOSITION passed
|
|
if(listPositionId !== null){
|
|
for(let newItem = 0; newItem < newItems.length; newItem++){
|
|
let itemKey = newItems[newItem];
|
|
// If the item shares the cardStatusId, add it to the tempArray
|
|
if(listPositionId == listPosition[itemKey]){
|
|
tempArray.push(newItems[newItem]);
|
|
}
|
|
}
|
|
// Set newItems to tempArray so it can be looped again with only what matched
|
|
newItems = tempArray;
|
|
}
|
|
// Reset tempArray so it can be reused
|
|
tempArray = [];
|
|
|
|
|
|
// Next check if each remaining item shares the BOARDELEMENT passed
|
|
if(boardElementId !== null){
|
|
for(let newItem = 0; newItem < newItems.length; newItem++){
|
|
let itemKey = newItems[newItem];
|
|
// If the item shares the boardElement, add it to the tempArray
|
|
if(boardElementId == boardElement[itemKey]){
|
|
tempArray.push(newItems[newItem]);
|
|
}
|
|
}
|
|
|
|
// Set newItems to tempArray so it can be looped again with only what matched
|
|
newItems = tempArray;
|
|
}
|
|
// Reset tempArray so it can be reused
|
|
tempArray = [];
|
|
|
|
// Return the new specified itemList
|
|
return newItems;
|
|
}
|
|
|
|
//TODO: Change vars to newPosition, newElement, newPlayer, oldPosition, oldElement, oldPlayer
|
|
addFromBoardElement(playerFrom, fromPosition, elementFrom, elementTo, toPosition=null, playerTo=null){
|
|
// Move itemKey fromPosition in elementFrom from playerFrom
|
|
// to toPosition in elementTo for playerTo
|
|
|
|
// If no playerTo provided (typical behavior), pass between elements for same player
|
|
if(playerTo == null){
|
|
playerTo = playerFrom;
|
|
}
|
|
|
|
// Check there is only 1 item that exists with the from info
|
|
let items = this.getItems(elementFrom, playerFrom, null, fromPosition);
|
|
if(items.length > 1){
|
|
alert('ERROR: There is more than 1 card being added');
|
|
}
|
|
|
|
// The first (and only) item returned
|
|
let itemKey = items[0];
|
|
|
|
// Check if there is a specific position the item needs to go to
|
|
if(toPosition == null){
|
|
// If not get the next available position of the elementTo
|
|
toPosition = getCurrentPositionAndLength(elementTo, playerTo)[0]+1
|
|
}
|
|
//console.log('itemKey: '+itemKey+' fromPosition: '+fromPosition+' elementFrom: '+elementFrom+' elementTo: '+elementTo+' toPosition: '+toPosition+' playerFrom: '+playerFrom+' playerTo: '+playerTo);
|
|
|
|
// TODO: rename function this lives in to vars accepted by setCardPosition function, more sense
|
|
this.setCardPosition(itemKey, toPosition, elementTo, playerTo, fromPosition, elementFrom, 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
|
|
}
|
|
// This took me a while to think through. It makes sense, don't change.
|
|
// Get a pen and paper and just work through an array of 0..5 moving some number about
|
|
setCardPosition(card, newPosition, newElement, newPlayer, oldPosition, oldElement, oldPlayer){
|
|
// Move anything in the old boardElement after the old listPosition down by one
|
|
// Coming from position 3, moves existing 4..10 down one to 3..9
|
|
// Moving from pos 5 = 6,7,8 go down to 5,6,7
|
|
this.moveElementPositions(0, oldElement, oldPosition, oldPlayer);
|
|
|
|
// Move anything in the new boardElement after (including) the new listPosition up by one
|
|
// Going to position 3, moves existing 3..10 up one to 4..11 to make space
|
|
// Moving back to pos 5 = current 5,6,7 go up to 6,7,8
|
|
this.moveElementPositions(1, newElement, newPosition, newPlayer);
|
|
|
|
// Then fit the card into the new gap that's opened up
|
|
listPosition[card] = newPosition;
|
|
boardElement[card] = newElement;
|
|
}
|
|
moveElementPositions(direction, elementFrom, fromPosition, playerFrom){
|
|
// Loop the elementFrom, and move positions up/down by one
|
|
let items = this.getItems(elementFrom, playerFrom, null, null);
|
|
for(let item = 0; item < items.length; item++){
|
|
let itemKey = items[item];
|
|
// Move everything after the old position down
|
|
// Moving from pos 5 = 6,7,8 go down to 5,6,7
|
|
if(direction == 0 && listPosition[itemKey] > fromPosition){
|
|
listPosition[itemKey]--;
|
|
}
|
|
// Move everything from the new position up
|
|
// Moving back to pos 5 = current 5,6,7 go up to 6,7,8
|
|
if(direction == 1 && listPosition[itemKey] >= fromPosition){
|
|
listPosition[itemKey]++;
|
|
}
|
|
}
|
|
}
|
|
// Draw a card, traditional TCG
|
|
drawACard(playerId, cardsToDraw = 1){
|
|
for(let draw = 0; draw < cardsToDraw; draw++){
|
|
|
|
// Check there's space in hand
|
|
let elementLength = getCurrentPositionAndLength('hand', playerId)[1];
|
|
if(elementLength >= maxHandSize){
|
|
alert('Hand full '+elementLength+'/'+maxHandSize);
|
|
return false;
|
|
}
|
|
let card = this.getItemKey('deck', 1, playerId);
|
|
// Move from players deck to hand, from position 1 (bottom deck)
|
|
this.addFromBoardElement(playerId, 1, 'deck', 'hand', null, null);
|
|
this.flipCard(card);
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
// TODO: May be best to tap in canPayMana while looping reqs. and if the reqs aren't hit untap the mana that were tapped in that call?
|
|
// This atm will loop manaReq and tap.
|
|
let tapped = this.tapManaRequired(itemKey, fromPlayer);
|
|
if(tapped != true){
|
|
alert('tapManaRequired: '+tapped);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// TODO: Make to check mana, and actually do it.
|
|
// also make work for effects
|
|
tapManaRequired(itemToPayCost, playerId, checkOnly = false, effectCosts = null){
|
|
|
|
// TODO: Look at combining or normalising this and canPayMana()
|
|
let manaRequired = [];
|
|
if(effectCosts !== null){ manaRequired = effectCosts; }
|
|
else{ manaRequired = this.getManaTotalOf(itemToPayCost); }
|
|
|
|
let noManaReq = {1:0, 2:0, 3:0, 4:0};
|
|
let manaToTap = [];
|
|
|
|
// Loop all the mana
|
|
let items = this.getItems('mana', playerId, null, null);
|
|
for(let item = 0; item < items.length; item++){
|
|
|
|
let mana = items[item];
|
|
|
|
// Deselect the mana at the start to ensure it's not doopid
|
|
this.deselectCard(mana);
|
|
|
|
if(this.isTapped(mana)){
|
|
continue;
|
|
}
|
|
|
|
let colourId = cardManaColour[mana][0];
|
|
|
|
console.log(JSON.stringify(manaRequired[0]));
|
|
// Loop the requirements of the cost to pay
|
|
for (const manaColour in manaRequired[0]) {
|
|
// If the colour of the mana is in the requirements
|
|
// of the cost to pay, reduce the cost for that colour by
|
|
// 1 and tap the mana
|
|
if(manaColour == colourId && manaRequired[0][manaColour] > 0){
|
|
// Reduce the required mana
|
|
manaRequired[0][colourId]--;
|
|
manaToTap.push(mana);
|
|
this.selectCard(mana); // Make selected so that we know to tap
|
|
}
|
|
}
|
|
|
|
// End loop once mana Req are fulfilled
|
|
if(JSON.stringify(manaRequired[0]) == JSON.stringify(noManaReq)){
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Now all the req. mana colours are tapped, check if the cost requires more
|
|
// For 3 cost cards that only require 1 red, two more mana (any colour)
|
|
// need to be tapped
|
|
// TODO: Better more efficiently and let user select...
|
|
let itemCostRemaining = 0;
|
|
let itemCost = 0;
|
|
if(itemToPayCost !== null){
|
|
itemCostRemaining = cardData[itemToPayCost].cost - manaRequired[1];
|
|
itemCost = cardData[itemToPayCost].cost;
|
|
}
|
|
else if(effectCosts !== null){
|
|
itemCostRemaining = manaRequired[1];
|
|
itemCost = manaRequired[1];
|
|
}
|
|
|
|
if(itemCostRemaining > 0){
|
|
|
|
// TODO: decide 100% how cards are to be played/payed for
|
|
// don't want to slam time into a mana system if it'll be nuked.
|
|
if(items.length < itemCostRemaining){
|
|
return false;
|
|
}
|
|
|
|
// For now, reloop and tap first available mana so that card
|
|
// payment is satiated
|
|
// Using same items from above
|
|
for(let item = 0; item < items.length; item++){
|
|
|
|
let mana = items[item];
|
|
|
|
if(itemCostRemaining <= 0){
|
|
break;
|
|
}
|
|
|
|
if(this.isTapped(mana) || this.isSelected(mana)){
|
|
continue;
|
|
}
|
|
|
|
manaToTap.push(mana);
|
|
itemCostRemaining--; // TODO: ??????
|
|
|
|
}
|
|
}
|
|
|
|
if(itemCost - manaToTap.length > 0){
|
|
return 'itemCostRemaining: '+itemCostRemaining; // Didn't find another mana to tap so cost not satiated
|
|
}
|
|
|
|
// If the mana to tap has been satitated then tap the mana selected
|
|
if(checkOnly == false){ // Tap the mana if this isn't a check
|
|
manaToTap.forEach(manaId => {
|
|
this.tapCard(manaId);
|
|
});
|
|
}
|
|
|
|
// And continue with whatever we were doing
|
|
return true;
|
|
|
|
}
|
|
|
|
isSelected(itemKey){
|
|
if(cardStatus[itemKey] == 'selected'){ return true; }
|
|
return false;
|
|
}
|
|
selectCard(itemKey){
|
|
// Sets card to 'selected/targetted' to show which have been selected
|
|
if(!this.isSelected[itemKey]){
|
|
cardStatus[itemKey] = 'selected';
|
|
}
|
|
return 'Cannot select a selected card';
|
|
}
|
|
deselectCard(itemKey){
|
|
if(this.isSelected[itemKey]){
|
|
cardStatus[itemKey] = null;
|
|
return true;
|
|
}
|
|
return 'Cannot unselect an unselected card';
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
// TODO: Make this work for each element
|
|
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';
|
|
}
|
|
|
|
// Check if the player has X amount of each mana colour required
|
|
// may not be needed to tap them all (if cost reduced), but they need
|
|
// to have those colours available.
|
|
// Setting them to 0 to start so they can be added to. Could be nicer
|
|
let manaAvailable = {1:0, 2:0, 3:0, 4:0};
|
|
|
|
// TODO: Thought, maybe instead of tapping colour used, it's just if you
|
|
// have the colour? May be a silly thought, but
|
|
|
|
// Loop the card to be played, and total its mana requirements
|
|
let manaRequired = this.getManaTotalOf(itemToPlay)[0];
|
|
|
|
|
|
// player check for this may need changing for "play from opp hand" etc?
|
|
let items = this.getItems('mana', playerId, null, null);
|
|
for(let item = 0; item < items.length; item++){
|
|
// Looping the mana to check player has equal mana/colours to req.
|
|
let itemKey = items[item];
|
|
|
|
// Check if the mana is tapped. Ignore if it is (for now)
|
|
if(this.isTapped(itemKey)){
|
|
continue;
|
|
}
|
|
|
|
let colourId = cardManaColour[itemKey][0];
|
|
manaAvailable[colourId] += 1;
|
|
|
|
}
|
|
|
|
// If the manaReq is satiated by the manaAvailable then return true
|
|
for (const manaColour in manaRequired) {
|
|
//console.log('req');
|
|
//console.log(manaRequired[manaColour]);
|
|
//console.log('av');
|
|
//console.log(manaAvailable[manaColour]);
|
|
if(manaRequired[manaColour] > manaAvailable[manaColour]){
|
|
return 'Do not have enough: '+manaColour+' mana';
|
|
}
|
|
}
|
|
|
|
//console.log('manaAvailable: ');
|
|
//console.log(manaAvailable);
|
|
|
|
return true;
|
|
}
|
|
|
|
tempGetPrimaryManaOfCard(targetCard){
|
|
|
|
// Loop the mana card's colours
|
|
// can be multi-coloured, and can be used as diff costs
|
|
// TODO: This currently takes a White/Red mana as 2 available manas
|
|
// when it should be 1 available of either type...
|
|
// TODO: FOR NOW, the colour of the mana is the primary colour
|
|
// will set 'manaColour' in DB at some point. So multi-colour
|
|
// cards aren't all those when in mana (ie. 6 colour decks can't play
|
|
// everything all the time)
|
|
let manaType = null; // TODO: Will use the highest mana req as it's colour for now
|
|
let manaColours = cardColours[targetCard];
|
|
for(let i = 0; i < manaColours.length; i++){
|
|
// Set mana colour for card if there isn't one
|
|
if(manaType == null){ manaType = manaColours[i]; }
|
|
// Check each other colour, use the highest cost colour
|
|
// as the colour type for this specific mana
|
|
// TODO: Do better, and nicer, manaType in DB with the colour?
|
|
if(manaColours[i][1] > manaType[1]){
|
|
manaType = manaColours[i];
|
|
}
|
|
}
|
|
let colourId = manaType[0];
|
|
return colourId;
|
|
|
|
}
|
|
|
|
getManaTotalOf(itemKey){
|
|
let manaTotal = {1:0, 2:0, 3:0, 4:0};
|
|
let manaColours = cardColours[itemKey];
|
|
let totalManaColourReq = 0;
|
|
|
|
console.log(manaColours.length);
|
|
for(let i = 0; i < manaColours.length; i++){
|
|
// Add one to the available mana
|
|
let colourId = manaColours[i][0];
|
|
manaTotal[colourId] += manaColours[i][1]; // Add the cost
|
|
totalManaColourReq += manaColours[i][1];
|
|
}
|
|
|
|
//console.log('manaTotal: ');
|
|
//console.log(manaTotal);
|
|
return [manaTotal, totalManaColourReq];
|
|
}
|
|
|
|
inspectCard(cardToInspect){
|
|
// Set inspectedCard (for now) to the card itemKey
|
|
inEvent = ['inspect', cardToInspect];
|
|
this.drawBoard();
|
|
}
|
|
isInEvent(eventToCheck = null){
|
|
if(eventToCheck == null && inEvent){
|
|
// If in any event
|
|
return true;
|
|
}
|
|
if(inEvent && inEvent[0] == eventToCheck){
|
|
// If in specified event
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
cancelInspect(){
|
|
if(this.isInEvent('inspect')){
|
|
this.clearEvent();
|
|
this.drawBoard();
|
|
}
|
|
}
|
|
|
|
startAttack(itemAttacking){
|
|
// Selects the card that will be attacking
|
|
|
|
if(this.isTapped(itemAttacking)){
|
|
alert('Cannot attack, as tapped');
|
|
return false;
|
|
}
|
|
this.setEvent('attack', itemAttacking);
|
|
// TODO:In future this MAY be what's used for check
|
|
this.setCardStatus(itemAttacking, 'attacking');
|
|
|
|
this.drawBoard();
|
|
}
|
|
setEvent(eventName, itemInEvent){
|
|
// TODO: Think this may need a better name
|
|
inEvent = [eventName, itemInEvent];
|
|
}
|
|
clearEvent(){
|
|
inEvent = null;
|
|
}
|
|
setCardStatus(itemToUpdate, newStatus = null){
|
|
// TODO: Check if valid status change, if not ignore
|
|
cardStatus[itemToUpdate] = newStatus;
|
|
}
|
|
// 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(this.isTapped(itemAttacking)){
|
|
alert('Cannot attack, as tapped');
|
|
return false;
|
|
}
|
|
|
|
switch (boardElement[itemDefending]) {
|
|
// If card on 'board' attacked
|
|
// Compare attackingCard and defendingCard
|
|
case 'board':
|
|
let atkAttacker = this.attackOf(itemAttacking);
|
|
let atkDefender = this.attackOf(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(this.isTapped(itemDefending)){
|
|
if(!this.destroyShield(itemDefending)){
|
|
break;
|
|
}
|
|
}
|
|
// Otherwise tap the shield, so it can be destroyed in future
|
|
else{
|
|
this.tapCard(itemDefending);
|
|
}
|
|
// End the attacker card attack 'phase'
|
|
this.endAttackFor(itemAttacking);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
this.drawBoard();
|
|
}
|
|
attackOf(itemKey){
|
|
// TODO: Change this to ECSey element when added
|
|
return cardData[itemKey];
|
|
}
|
|
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();
|
|
}
|
|
|
|
playShield(fromPosition, fromElement, playerId, cardsToPlay = 1){
|
|
for(let shieldNo = 0; shieldNo < cardsToPlay; shieldNo++){
|
|
let shieldLength = this.shieldCount(playerId);
|
|
// Check shield zone isn't already full
|
|
if(shieldLength >= maxShield){
|
|
alert('Shield full '+shieldLength+'/'+maxShield);
|
|
return false;
|
|
}
|
|
|
|
// Move from player, position 0 (top) of deck, to hand (next available position)
|
|
this.addFromBoardElement(playerId, fromPosition, fromElement, 'shield', null, null);
|
|
}
|
|
}
|
|
shieldCount(playerId){
|
|
return getCurrentPositionAndLength('shield', playerId)[1];
|
|
}
|
|
|
|
playMana(fromPosition, fromElement, fromPlayer){
|
|
// TODO: getItemKey may be the best way to normalise other function calls instead of searching for item key within certain other funcs. (such as addFromBoardElement)
|
|
let itemKey = this.getItemKey(fromElement, fromPosition, fromPlayer);
|
|
|
|
console.log(itemKey);
|
|
// Now add to mana zone
|
|
this.addFromBoardElement(fromPlayer, fromPosition, fromElement, 'mana', null, null);
|
|
// Flip the card face down (for now) TODO: just add colour atop
|
|
this.flipCard(itemKey, 0); // Face down (TODO: Change to have spec. style)
|
|
}
|
|
|
|
// HELPER METHODS, to simplify code-base for me in the future
|
|
destroyShield(itemKey){
|
|
// Adds from 'shield' to 'hand' boardElement for player shield belongs to
|
|
|
|
// 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(!board.isTapped(itemKey)){
|
|
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.addShieldToHand(player[itemKey], listPosition[itemKey]);
|
|
return true;
|
|
}
|
|
addShieldToHand(playerFrom = null, listPositionFrom = null){
|
|
// Flip the card around
|
|
this.flipCard(this.getItemKey('shield', listPositionFrom, playerFrom));
|
|
this.addFromBoardElement(playerFrom, listPositionFrom, 'shield', 'hand', null, null);
|
|
}
|
|
|
|
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
|
|
}
|
|
tap(itemKey){ tapCard(itemKey); }
|
|
untapCard(itemKey){
|
|
cardStatus[itemKey] = null;
|
|
// Do any other 'on tap' effects, etc. in the future
|
|
}
|
|
untap(itemKey){ untapCard(itemKey); }
|
|
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;
|
|
}
|
|
isAttacking(itemKey){
|
|
if(cardStatus[itemKey] == 'attacking'){ return true; }
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Run board commands here for testing
|
|
let board = new Board;
|
|
|
|
// Board is now async (initialisation)
|
|
// Everything beyond initialisation shouldn't be required so no need to wait
|
|
// At least I think, for now. May need more stuff in future
|
|
|
|
// Request the deck(s) from server/db
|
|
// When the page loads (for now) start the game
|
|
requestStartGame();
|
|
// This will trigger an emit to and on from the server will trigger loadBoard();
|
|
// public/main.js // socket.on('responseStartGame', function (data) {
|
|
|
|
function loadBoard(data) {
|
|
|
|
console.log(data);
|
|
|
|
// Built the original boardstate using the data passed from server
|
|
// This is only for the first time recieve (TODO: ALL) game data at match start
|
|
// Then ideally only what's needed to be passed to/from client/server sent after
|
|
item = data.item;
|
|
itemCount = data.itemCount;
|
|
boardElement = data.boardElement;
|
|
cardData = data.cardData;
|
|
// position; // Not to do, as calculated per client (and will be per screen-size)
|
|
// size; // Like position, calculated per client.
|
|
cardStatus = data.cardStatus;
|
|
player = data.player; // Set each item to correct player
|
|
listPosition = data.listPosition;
|
|
cardFace = data.cardFace; // Like status, for DCs and reloads
|
|
|
|
cardSprite = {}; // TODO: ? Maybe, or this could be done clientside based
|
|
// ^ on card id?
|
|
deckIn = data.deckIn; // NEW, may be used, for now player substitute
|
|
deckData = data.deckData;
|
|
cardAttack = data.cardAttack; // TODO: add to the logic
|
|
cardColours = data.cardColours;
|
|
cardManaColour = data.cardManaColour;
|
|
cardEffect = data.cardEffect;
|
|
|
|
// TODO: JANK IN, CHANGE CODE TO USE NEW ARRAY!!
|
|
// Temp jank, set colour to first colour req.
|
|
for(let i = 0; i < itemCount; i++){
|
|
//console.log(itemCount);
|
|
//console.log(cardColours[itemCount]);
|
|
// after && to check if there is a colourReq record in cardColours
|
|
if(cardData[i] !== undefined && i in cardColours){ // i may not have carddata, as realDeck
|
|
//console.log(i);
|
|
cardData[i].colour = board.tempGetPrimaryManaOfCard(i);
|
|
//cardData[i].colour = cardColours[i][0][0]; // Colour Id of first req
|
|
// Set the artwork (this would be done front-end I think)
|
|
cardSprite[i] = [0,0];
|
|
// Temp sprite set based on colour TODO: Change to set correct sprite from DB
|
|
switch (cardData[i].colour){
|
|
case COLOUR.white.id: // White
|
|
cardSprite[i] = [0,0];
|
|
break;
|
|
case COLOUR.blue.id: // Blue
|
|
cardSprite[i] = [0,1];
|
|
break;
|
|
case COLOUR.red.id: // Red
|
|
cardSprite[i] = [1,0];
|
|
break;
|
|
case COLOUR.green.id: // Green
|
|
cardSprite[i] = [1,1];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Stuff not currently in the getDecks TODO: To be stripped down and
|
|
// rewritten/made into generateItems or something, for each match
|
|
// Decks can be as-is for getting deckLists for deckselection, but
|
|
// TODO: for matches need something to generate and return every item, and attribute
|
|
|
|
// TODO: Loop these for the amount of players in players (and use the correct itemKey)
|
|
shuffleDeck(0); // Shuffle player 0 deck
|
|
shuffleDeck(1); // Shuffle player 1 deck
|
|
|
|
// Play shield from top of each players deck to the maximum allowed (4 typically)
|
|
for(let currentPlayer = 0; currentPlayer <= players-1; currentPlayer++){
|
|
board.playShield(1, 'deck', currentPlayer, maxShield);
|
|
}
|
|
|
|
// Draw the graphics of the board/game
|
|
// Wait for the cardArt to load on first load
|
|
// Otherwise it'll be boxes, and art will flash in on first click event triggered
|
|
cardArt.onload = function(){
|
|
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,itemKey)){
|
|
console.log('itemId: '+itemKey);
|
|
document.getElementById('effectTargetId').value = itemKey;
|
|
document.getElementById('triggerTargetId').value = 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(board.isInEvent('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)
|
|
if(!inEvent){
|
|
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{
|
|
// Will print everything that's clickable (or should be clickable)
|
|
// any undefined attributes and such should display with this too
|
|
//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,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':
|
|
if(!inEvent){
|
|
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 && !board.isTapped(itemKey) && 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
|
|
if(!inEvent){
|
|
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(cursorX,cursorY,itemKey){
|
|
// Collision detection between clicked offset and clickableItems
|
|
// https://stackoverflow.com/a/9880302
|
|
let itemX = position[itemKey][0];
|
|
let itemY = position[itemKey][1];
|
|
let itemWidth = size[itemKey][0];
|
|
let itemHeight = size[itemKey][1];
|
|
|
|
// Debug Stuff
|
|
let debug = false
|
|
if(debug){
|
|
console.log(itemY+' <');
|
|
console.log(cursorY+ '<')
|
|
console.log(itemY+itemHeight);
|
|
}
|
|
|
|
if(
|
|
cursorY > itemY && cursorY < itemY + itemHeight
|
|
&& cursorX > itemX && cursorX < itemX + itemWidth)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
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];
|
|
listPosition[itemKey] = tempDeck[item];
|
|
}
|
|
|
|
}
|
|
|
|
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++){
|
|
// 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);
|
|
// Move up to denote 'Flight' keyword
|
|
if(flight[itemKey]){
|
|
positionY-=10;
|
|
}
|
|
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(
|
|
'itemId: '+itemKey+"\n"+
|
|
'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]+"\n"+
|
|
'cardFace: '+cardFace[itemKey]+"\n"+
|
|
'cardSprite: '+cardSprite[itemKey]+"\n"+
|
|
'deckIn: '+deckIn[itemKey]+"\n"+
|
|
'deckData: '+deckData[itemKey]+"\n"+
|
|
'cardAttack: '+cardAttack[itemKey]+"\n"+
|
|
'cardColours: '+cardColours[itemKey]+"\n"+
|
|
'cardManaColour: '+cardManaColour[itemKey]+"\n"+
|
|
'cardEffect: '+cardEffect[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);
|
|
}
|
|
|
|
// https://stackoverflow.com/a/35970186
|
|
function invertColour(hex, bw = true) {
|
|
// Split the hex code into individual alphanumerics
|
|
if (hex.indexOf('#') === 0) {
|
|
hex = hex.slice(1);
|
|
}
|
|
|
|
// convert 3-digit hex to 6-digits.
|
|
if (hex.length === 3) {
|
|
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
}
|
|
if (hex.length !== 6) { throw new Error('Invalid HEX colour.'); }
|
|
|
|
let r = parseInt(hex.slice(0, 2), 16),
|
|
g = parseInt(hex.slice(2, 4), 16),
|
|
b = parseInt(hex.slice(4, 6), 16);
|
|
|
|
// Make it black/white for contrast (typical behaviour here)
|
|
if (bw) {
|
|
// https://stackoverflow.com/a/3943023/112731
|
|
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
|
|
? '#000000' : '#FFFFFF';
|
|
}
|
|
|
|
// Invert colour components
|
|
r = (255 - r).toString(16);
|
|
g = (255 - g).toString(16);
|
|
b = (255 - b).toString(16);
|
|
// Pad each with zeros and return
|
|
return "#" + padZero(r) + padZero(g) + padZero(b);
|
|
}
|
|
|