diff --git a/README.md b/README.md index 94eea97..b27282b 100755 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ `git pull ` `npm install express --save` +socketio, mysql + ## Run `node server.js` @@ -21,3 +23,17 @@ Access through `http://localhost:3000` +### Development + +BACKEND/SERVER DEVELOPMENT ONLY + +To prevent the exhausting task of restarting the server on change +install and run with nodemon, it'll handle server restart each time +a js file is saved (this does also restart for front-end) + +`npm install -g nodemon` + +Run with + +`nodemon --inspect server.js` + diff --git a/database.js b/database.js new file mode 100644 index 0000000..5f5d6a7 --- /dev/null +++ b/database.js @@ -0,0 +1,277 @@ +const mysql = require('mysql'); +const con = mysql.createConnection({ + host: "localhost", + user: "realmsdivided", + password: "realmsdivided", + database: "realms_divided" +}); + +function connect(){ + con.connect(function(err) { + if (err) { console.log(err); } + else { console.log('DB Connected'); } + }); +} +function disconnect(){ + con.end(); +} + +// Is calling a promise in a promise the best way to do this? +// Keeping because this is how I've figured to call other funcs from database +function getCardById(cardId){ + + const dPromise = new Promise((resolve, reject) => { + + getCards(' card.id = '+cardId).then(cards => { + resolve(cards); + }) + .catch(err => { + throw err; reject(new Error(err)); + }); + + }); + return dPromise; +} + +function getCards(whereClause){ + const cPromise = new Promise((resolve, reject) => { + // Get main card info + let cards = []; + let sql = `SELECT + card.id AS cardId -- TEMP UNTIL UID + ,cardName + ,cardCost + ,typeName + ,cardAttack + ,rarityName + FROM card + LEFT JOIN type ON type.id = card.id + LEFT JOIN rarity ON rarity.id = card.cardRarity + `; + // TODO: Rewrite this so it's not so insecure!!!! + if(whereClause){ sql = sql + " WHERE "+whereClause; } + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + cards = dbResultToCards(result); + //console.log(cards); + resolve(cards); + }); + + // Now get the + // class + // colour requirements + // card effects + }); + return cPromise; +} + +// May want a FromDecks to get the SQL done in one blast +// then filter the results according? +function getCardsFromDeck(playerId, deckId){ + const cPromise = new Promise((resolve, reject) => { + if(playerId == null || deckId == null){ reject(new Error('Player/Deck Id not provided')); } + let cards = []; + // TODO: Change rarity, and maybe cardName to be the id (prolly card name for fancy effects) + // Then have a SQL loop at start of game that adds [name, id], [rariry, id] etc. + let sql = `SELECT + cardName + ,cardCost + ,typeName + ,cardAttack + ,rarityName + ,cardCount + FROM deck + INNER JOIN deck_cards ON deck_cards.deckId = deck.deckId AND deck_cards.playerId = deck.playerId + INNER JOIN card ON card.id = deck_cards.cardId + LEFT JOIN type ON type.id = card.id + LEFT JOIN rarity ON rarity.id = card.cardRarity`; + // TODO: Make more secure!! + sql += ' WHERE deck.deckId ='+deckId+' AND deck.playerId = '+playerId; + sql += ';'; + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + cards = dbResultToCards(result); + resolve(cards); + }); + + // Card list has been built + // Resolve/return the data of the cards Array/deck + + }); + return cPromise; +} + +// This may be more jank than I'd like. Was going procedure route, or JOINs +// but thinking of future effects that could have 4-5 effects, that check 2-3 colours +// and X amounts of cards/classes, to finally trigger something, those feel like they +// wouldn't work. So this is it for now, and when I think of something better I'll rewrite +function dbGetCardClasses(cardId){ + // Maybe this isn't too bad as async? + // But I imagine for lets say 100 1v1s 200 loops of 35cards each, eekers + + const cPromise = new Promise((resolve, reject) => { + if(cardId == null){ reject(new Error('cardId not provided')); } + let classes = []; + // Just getting the class IDs, as intend to load all the class data [id, name] into + // an array or file on game load. This way just the ID is needed, and no text compares + let sql = `SELECT classId + FROM card_class + WHERE cardId = `+cardId + ; // TODO: As all these SQL, need to make more secure + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + result.forEach((card) => { + // Add the classId to array to be used in card buildery doodad + classes.push(result[0]); + }); + }); + + // Card list has been built + // Resolve/return the data of the cards Array/deck + resolve(classes); + + }); + return cPromise; +} + +// Don't really want this in DB layer, but it wouldn't play +// in the main server file, so it's here until I figure out why +function dbResultToCards(result){ + let cards = []; + result.forEach((card) => { + let tempCard = {}; + tempCard.id = card.cardId; + tempCard.name = card.cardName; + tempCard.colour = null; + tempCard.cost = card.cardCost; + tempCard.type = card.typeName; + tempCard.atk = card.cardAttack; + tempCard.rarity = card.rarityName; + tempCard.effect = null; + // TODO: Will need more SQL statements, or some function/procedure + // class + // colour requirements + // card effects + + // Add the 'completed' cards into the deck + if(card.cardCount){ + // Add as many cards into the deck as is in cardCount + for(let i = 0; i < card.cardCount; i++){ + cards.push(tempCard); + } + }else{ + // Or just one + cards.push(tempCard); + } + }); + return cards; +} + + +// Testing, and trialing rewrites +function dbGetDecks(){ + const dPromise = new Promise((resolve, reject) => { + let cards = []; + let sql = `SELECT + deckId + ,playerId + ,deckName + FROM deck + LIMIT 10 + `; // TODO: Remove limit when happy/this accepts params + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + resolve(result); + }); + }); + return dPromise; +} +function dbGetDeckList(){ + const dPromise = new Promise((resolve, reject) => { + let cards = []; + let sql = `SELECT + deckId + ,playerId + ,cardId + ,cardCount + FROM deck_cards + `; + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + resolve(result); + }); + }); + return dPromise; +} +function dbGetCards(){ + // Start with basic stuff in card table + const dPromise = new Promise((resolve, reject) => { + let sql = `SELECT + id + ,cardName + ,cardCost + ,cardType + ,cardAttack + ,cardRarity + FROM card + `; + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + resolve(result); + }); + }); + return dPromise; +} +function dbGetCardClasses(){ + // Get the classes assoc. on each card + const dPromise = new Promise((resolve, reject) => { + let sql = `SELECT + cardId + ,classId + FROM card_class + `; + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + resolve(result); + }); + }); + return dPromise; +} +function dbGetCardColourRequirement(){ + // Get the classes assoc. on each card + const dPromise = new Promise((resolve, reject) => { + let sql = `SELECT + cardId + ,colourId + ,cost + FROM card_colour_requirement + `; + + con.query(sql, function (err, result, fields) { + if (err) { throw err; reject(new Error(err)); } + resolve(result); + }); + }); + return dPromise; +} + + +module.exports = { + connect, disconnect + , getCards + , getCardById + , getCardsFromDeck + // Testing, and trailing + , dbGetDecks + , dbGetDeckList + , dbGetCards + , dbGetCardClasses + , dbGetCardColourRequirement +}; diff --git a/db/db_export.sql b/db/db_export.sql new file mode 100644 index 0000000..eb8a41d --- /dev/null +++ b/db/db_export.sql @@ -0,0 +1,206 @@ +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Server version: 10.7.8-MariaDB-1:10.7.8+maria~ubu2004 - mariadb.org binary distribution +-- Server OS: debian-linux-gnu +-- HeidiSQL Version: 12.6.0.6765 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +-- Dumping database structure for realms_divided +CREATE DATABASE IF NOT EXISTS `realms_divided` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */; +USE `realms_divided`; + +-- Dumping structure for table realms_divided.card +CREATE TABLE IF NOT EXISTS `card` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `cardName` varchar(255) NOT NULL, + `cardCost` tinyint(4) NOT NULL DEFAULT 2, + `cardType` tinyint(4) DEFAULT NULL, + `cardAttack` int(11) DEFAULT NULL, + `cardRarity` tinyint(4) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.card: ~5 rows (approximately) +INSERT INTO `card` (`id`, `cardName`, `cardCost`, `cardType`, `cardAttack`, `cardRarity`) VALUES + (1, 'Red1', 1, 1, 500, 1), + (2, 'White1', 1, 1, 1000, 1), + (3, 'Blue1', 2, 1, 1000, 1), + (4, 'White 8', 3, 2, NULL, 2), + (5, 'Red7', 2, 2, NULL, 2); + +-- Dumping structure for table realms_divided.card_class +CREATE TABLE IF NOT EXISTS `card_class` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `cardId` int(11) DEFAULT NULL, + `classId` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.card_class: ~4 rows (approximately) +INSERT INTO `card_class` (`id`, `cardId`, `classId`) VALUES + (1, 1, 1), + (2, 2, 1), + (3, 3, 1), + (4, 1, 2); + +-- Dumping structure for table realms_divided.card_colour_requirement +CREATE TABLE IF NOT EXISTS `card_colour_requirement` ( + `id` tinyint(4) NOT NULL AUTO_INCREMENT, + `cardId` int(11) DEFAULT NULL, + `colourId` tinyint(4) DEFAULT NULL, + `cost` tinyint(4) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.card_colour_requirement: ~5 rows (approximately) +INSERT INTO `card_colour_requirement` (`id`, `cardId`, `colourId`, `cost`) VALUES + (1, 1, 3, 1), + (2, 2, 1, 1), + (3, 3, 2, 1), + (4, 4, 1, 2), + (5, 5, 3, 2); + +-- Dumping structure for table realms_divided.card_effect +CREATE TABLE IF NOT EXISTS `card_effect` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `cardId` int(11) DEFAULT NULL, + `effectId` smallint(6) DEFAULT NULL, + `value` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.card_effect: ~5 rows (approximately) +INSERT INTO `card_effect` (`id`, `cardId`, `effectId`, `value`) VALUES + (1, 1, 2, NULL), + (2, 3, 1, NULL), + (3, 2, 3, NULL), + (4, 4, 4, 1000), + (5, 5, 5, 3000); + +-- Dumping structure for table realms_divided.class +CREATE TABLE IF NOT EXISTS `class` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `parentId` tinyint(4) DEFAULT NULL, + `className` tinytext NOT NULL, + `primaryColour` smallint(6) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.class: ~10 rows (approximately) +INSERT INTO `class` (`id`, `parentId`, `className`, `primaryColour`) VALUES + (1, NULL, 'Goblin', 3), + (2, NULL, 'Human', 1), + (3, NULL, 'Spirit', 2), + (4, NULL, 'Warrior', 1), + (5, NULL, 'Orc', 3), + (6, NULL, 'Beast', 5), + (7, NULL, 'Plant', 5), + (8, NULL, 'Undead', 4), + (9, NULL, 'Mechanical', 6), + (10, NULL, 'Void', 7); + +-- Dumping structure for table realms_divided.colour +CREATE TABLE IF NOT EXISTS `colour` ( + `id` tinyint(4) NOT NULL AUTO_INCREMENT, + `colourName` tinytext DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.colour: ~7 rows (approximately) +INSERT INTO `colour` (`id`, `colourName`) VALUES + (1, 'White'), + (2, 'Blue'), + (3, 'Red'), + (4, 'Black'), + (5, 'Green'), + (6, 'Brown'), + (7, 'Void'); + +-- Dumping structure for table realms_divided.deck +CREATE TABLE IF NOT EXISTS `deck` ( + `deckId` int(11) NOT NULL, + `playerId` int(11) NOT NULL, + `deckName` tinytext NOT NULL, + PRIMARY KEY (`deckId`,`playerId`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.deck: ~2 rows (approximately) +INSERT INTO `deck` (`deckId`, `playerId`, `deckName`) VALUES + (1, 1, 'Deck 1 P1'), + (1, 2, 'Deck 1 P2'); + +-- Dumping structure for table realms_divided.deck_cards +CREATE TABLE IF NOT EXISTS `deck_cards` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `deckId` int(11) DEFAULT NULL, + `playerId` int(11) DEFAULT NULL, + `cardId` int(11) DEFAULT NULL, + `cardCount` tinyint(4) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.deck_cards: ~5 rows (approximately) +INSERT INTO `deck_cards` (`id`, `deckId`, `playerId`, `cardId`, `cardCount`) VALUES + (1, 1, 1, 1, 25), + (2, 1, 1, 5, 10), + (3, 1, 2, 2, 15), + (4, 1, 2, 3, 14), + (5, 1, 2, 4, 6); + +-- Dumping structure for table realms_divided.effect +CREATE TABLE IF NOT EXISTS `effect` ( + `id` smallint(6) NOT NULL AUTO_INCREMENT, + `effectName` tinytext DEFAULT NULL, + `effectDescription` varchar(250) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.effect: ~5 rows (approximately) +INSERT INTO `effect` (`id`, `effectName`, `effectDescription`) VALUES + (1, 'Flight', 'Can ignore Taunt, cannot be tagetted by attack'), + (2, 'Reach', 'Can attack Flight units'), + (3, 'Equip', 'Add this cards attack, and effect(s) to another'), + (4, 'Heal', 'Untap X shield(s)'), + (5, 'Hurt', 'Deal X damage to target unit, this combat'); + +-- Dumping structure for table realms_divided.rarity +CREATE TABLE IF NOT EXISTS `rarity` ( + `id` tinyint(4) NOT NULL AUTO_INCREMENT, + `rarityName` tinytext DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.rarity: ~2 rows (approximately) +INSERT INTO `rarity` (`id`, `rarityName`) VALUES + (1, 'Common'), + (2, 'Uncommon'), + (3, 'Rare'); + +-- Dumping structure for table realms_divided.type +CREATE TABLE IF NOT EXISTS `type` ( + `id` tinyint(4) NOT NULL AUTO_INCREMENT, + `typeName` tinytext DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- Dumping data for table realms_divided.type: ~2 rows (approximately) +INSERT INTO `type` (`id`, `typeName`) VALUES + (1, 'Unit'), + (2, 'Spell'), + (3, 'Token'); + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/package.json b/package.json index c15e726..b4d1bc1 100755 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "homepage": "anetwork.uk", "dependencies": { "express": "^4.21.0", + "mysql": "^2.18.1", "socket.io": "^4.8.0" } } diff --git a/public/board.js b/public/board.js index a1383b6..30dcc15 100644 --- a/public/board.js +++ b/public/board.js @@ -802,21 +802,127 @@ class Board{ // Run board commands here for testing let board = new Board; -// Fill each players deck with their cards -createDecks(); +// 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 +requestDeck(); +// This will trigger an emit to and on from the server will trigger loadBoard(); +// public/main.js +// socket.on('responseGetDeck', function (data) { + +function loadBoard(data) { + + console.log('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; // TODO: ? Statuses on load?, TODO: OR if player DCs and needs to reload! + player = {}; // TODO: Set each item to correct player + listPosition = {}; // TODO: + cardFace = {}; // TODO: Like status, for DCs and reloads + cardSprite = {}; // TODO: ? Maybe, or this could be done clientside based + // ^ on card id? + cardFace = {}; // TODO: For DB/Reconnect more-so + let deckIn = data.deckIn; // NEW, may be used, for now player substitute + let deckData = data.deckData; + + + // 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 + + console.log(cardData[0]); + + // Temp solution + // Loop all items, and set their related missing attributes + // TODO: some more jank to get it 'playable' with DB entries + let player0DeckCount = 0; + let player1DeckCount = 0; + for(let i = 0; i < itemCount; i++){ + // Set player for decks (to just be their array index for now) TODO: Properly + if(boardElement[i] == 'realDeck'){ + player[i] = i; // Jank... + //player[i] = deckData[i]; + //console.log(deckData[i]); + }else{ + // Everything else missing in a jank fashion + player[i] = deckIn[i]; // 1-1 here, but likely not in future + } -// 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); -} + cardStatus[i] = null; // Just building on first load, as deck gen + // substite at the mo, so all cards are free of status + cardFace[i] = 0; // As above, deck gen sub. Everything starts facedown -// 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); -}; + // Set position for cards in each deck... + // TODO: JANK, tidy, remove, make good!! + // using the new player set just above, which in itself is jank + if(player[i] == 0 && boardElement[i] != 'realDeck'){ + listPosition[i] = player0DeckCount+1; + player0DeckCount++; + } + // Just an else, as in DB/for this test only 2 decks + // TODO: MAKE BETTER! + else{ + listPosition[i] = player0DeckCount+1; + player0DeckCount++; + } + // TODO: JANK IN, CHANGE CODE TO USE NEW ARRAY!! + // Temp jank, set colour to first colour req. + if(cardData[i] !== undefined){ // i may not have carddata, as realDeck + cardData[i].colour = 0;//cardData[itemCount].colour[0]; + // 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 0: // White + cardSprite[i] = [0,0]; + break; + case 1: // Blue + cardSprite[i] = [0,1]; + break; + case 2: // Red + cardSprite[i] = [1,0]; + break; + case 3: // Green + cardSprite[i] = [1,1]; + break; + default: + break; + } + } + + } + shuffleDeck(0); // Shuffle player 0 deck + shuffleDeck(1); // Shuffle player 1 deck + + //return false; + // Fill each players deck with their cards + //createDecks(); + + // 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) { @@ -1171,6 +1277,7 @@ function calculateItemSizePosition(itemKey){ positionY = 60; width = cardWidth*1.5; height = cardHeight*1.5; + console.log('HELLO 1'); } if(itemPlayer == 0 && itemElement == 'realDeck'){ @@ -1178,6 +1285,7 @@ function calculateItemSizePosition(itemKey){ positionY = canvas.height-cardHeight*1.5-60; width = cardWidth*1.5; height = cardHeight*1.5; + console.log('HELLO 0'); } if(itemPlayer == 1 && itemElement == 'board'){ diff --git a/public/index.html b/public/index.html index 8cc583f..265c1de 100644 --- a/public/index.html +++ b/public/index.html @@ -56,11 +56,16 @@ +
+
+ +
+ - + diff --git a/public/main.js b/public/main.js index 53be0b7..433553f 100644 --- a/public/main.js +++ b/public/main.js @@ -148,3 +148,43 @@ socket.on('responseJoinRoom', function (name) { } }); +// getCards (TODO: remove if test below is a-okay) +function requestGetCards(deckId, playerId){ + console.log('+ requestGetCards'); + socket.emit('requestGetCards', deckId, playerId); +} +function responseGetCards(data){ + console.log(data); + if(!data.success){ + alert(data.message); + } +} +socket.on('responseGetCards', function (data) { + console.log('<< responseGetCards'); + responseGetCards(data); +}); + +// Testing getting cards from server/DB +function requestDeck(){ + console.log('+ requestDeck'); + socket.emit('requestDeck'); +} +function responseGetDeck(data){ + console.log(data); + if(!data.success){ + alert(data.message); + return false; + } + return data; +} +socket.on('responseGetDeck', function (data) { + console.log('<< responseGetDeck'); + responseGetDeck(data); + console.log('Load board?'); + if(data.success !== true){ + alert('Err with responseGetDeck. '+data.message); + } + // Pass only the data to loadBoard + loadBoard(data.message); +}); + diff --git a/server.js b/server.js index de34cb3..cf3e629 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,21 @@ + const express = require('express'); +const database = require('./database'); const app = express(); const http = require('http').Server(app); const port = process.env.PORT || 3000; const io = require('socket.io')(http); +// util is what nodejs uses for console.log, but has a depth of 2 set +// so console.logs show [Array]/[Object] instead of useful info. +// This can be overridden console.log(util.inspect(LOGDATA, true, 4, true)) +// 4 being new depth, true (last one) is to show colours +const util = require('util') app.use(express.static(__dirname + '/public')); http.listen(port, () => console.log('listening on port ' + port)); +database.connect(); io.on('connection', onConnection); @@ -31,11 +39,427 @@ for (let roomId = 1; roomId <= numRoomsToPreGen; roomId++) { createRoom(roomId); } + +// For testing DB/makeshift ORM/building my objects +// Maybe if this works, put it into a deck and card module +// Doing similar to I have to other stuff, that will need to be +// migrated serverside anyways. + +/* +let item = []; +let deckItem = {}; +let deckList = {}; +let cardItem = {}; +*/ + +// cardClass, cardColourRequirement +// may want to be seperate too (as well as in cardItem), so that +// during match they can be altered by effects while keeping the OG card +// for inspecting (and compare against new stats,reqs,etc.) +// same with attack, cost, etc. things that will can be visually shown as +// changed in game + +// Just grabbing everything from DB for now, as it's quite small at current +// then will rejig when needed. +// Should all cards, effects, classes etc. be loaded in on server start +// then just load decks and decklists when needed? +function getDecks(deckId, playerId, item = [], deckItem = {}){ + // Await promise, once it's done get the data, if errors send err + const dPromise = new Promise((resolve, reject) => { + + database.dbGetDecks().then(data => { + + let decks = []; + + data.forEach((deck) => { + //let itemId = item.length; + //item.push(itemId); // Add the next available item + // Add the deck info to deckItem + // deckItem[itemId] = {}; + decks.push({ + 'deckId': deck.deckId, + 'playerId': deck.playerId, + 'deckName': deck.deckName, + }); + }); + + // Resolve the decks pulled + resolve(decks); + + //console.log(item); + //console.log(deckItem); + //console.log(decks); + }) + .catch(err => { throw err; }) + + }); + return dPromise; +} +//getDecks(); +function getDeckList(){ + const dPromise = new Promise((resolve, reject) => { + + database.dbGetDeckList().then(data => { + + let deckList = []; + + data.forEach((listItem) => { + // Add the deck info to deckItem + deckList.push({ + 'deckId': listItem.deckId, + 'playerId': listItem.playerId, + 'cardId': listItem.cardId, + 'cardCount': listItem.cardCount, + }); + resolve(deckList); + }); + }) + .catch(err => { throw err; }) + }); + return dPromise; +} +//getDeckList(); +function getCards(){ + const dPromise = new Promise((resolve, reject) => { + + database.dbGetCards().then(data => { + + let cards = []; + + data.forEach((card) => { + cards.push({ + 'id': card.id, + 'cardName': card.cardName, + 'cardCost': card.cardCost, + 'cardType': card.cardType, + 'cardAttack': card.cardAttack, + 'cardRarity': card.cardRarity, + 'cardClass': [], + 'cardColourRequirement': [], + }); + }); + resolve(cards); + }) + .catch(err => { throw err; }) + + }); + return dPromise; +} +//getCards(); +function getCardClasses(){ + const dPromise = new Promise((resolve, reject) => { + database.dbGetCardClasses().then(data => { + + let cardClasses = []; + + data.forEach((cardClass) => { + + cardClasses.push({ + 'cardId': cardClass.cardId, + 'classId': cardClass.classId, + }); + + }); + resolve(cardClasses); + }) + .catch(err => { throw err; }) + }); + return dPromise; +} +//getCardClasses(); +function getCardColourRequirement(){ + const dPromise = new Promise((resolve, reject) => { + database.dbGetCardColourRequirement().then(data => { + + let colourRequirements = []; + + data.forEach((cardColourReq) => { + + colourRequirements.push({ + 'cardId': cardColourReq.cardId, + 'colourId': cardColourReq.colourId, + 'cost': cardColourReq.cost, + }); + + }); + resolve(colourRequirements); + + }) + .catch(err => { throw err; }) + }); + return dPromise; +} +//getCardColourRequirement(); +// Then effects which will have effects with parent triggers, and unit type checks +// colour checks, all sorts. So will be more difficult. Basic (flight, etc) +// shouldn't be too bad +// something like effect_basic, card_effect, effect_trigger, effect_requirement, effect_option +// effect_stage, effect_advanced, with advanced being an id with x triggers, triggers have +// x req, advanced as with x options, x stages to be able to fine-tune fancy stuff +// combining all other effects, units, cards, colours, etc. Will be a lot of though, +// but better than hard coding anything more than basic effects and effect check logic +// TODO: effect (as above) + +// request a deck in the format CURRENTLY used in the game +// decks will likely be changed around + +// https://www.geeksforgeeks.org/how-to-wait-for-multiple-promises-in-javascript/ +// https://medium.com/@nikolozz/using-socket-io-with-async-await-13fa8c2dc9d9 +// using last example +function requestDeck(socket, playerId, deckId){ + return new Promise((resolve, reject) => { + (async () => { + + // Get the deck(s) requested. + // Not 100% on how to do two differening atm + // Besides all of playerId, and all of deckId. But 1,2/2,3 for instance + const [decks, deckList] = + await Promise.all([ + getDecks(), + getDeckList() + // Don't need to do this for each when awaiting all + // Instead it'll wait until each is done, set the data to the const + // Then do the stuff after the variable's are set!! + /* + getDecks().then(data => { + //console.log(data); + }).catch(err => { throw err; }), + */ + + ]); + + //console.log(decks); + //console.log(deckList); + // Now loop the deckList for the cardIds + let deckCardIds = []; + deckList.forEach((deckItem) => { + deckCardIds.push(deckItem.cardId); + + }); + //console.log(deckCardIds); + + // Next, get the cards in the deck by their ID + // TODO: https://stackoverflow.com/a/65510676 + // Change SQL to accept for just the cards passed + + // Get each cards data, colourReqs, and classes + const [cards, cardClasses, cardColourRequirements] = + await Promise.all([ + getCards(), + getCardClasses(), + getCardColourRequirement(), + ]); + // ^^^^ Classes async? Can pass the cardsIds, then loop classes, if the class cardId + // matches the cardId in cards[] then push the class to cards[x].classes + + // TODO: Build the card so far async for it's done alongside getting effects? + // Then when both done add the effects to the existing cardObjects? Yep good idea me + //console.log(cards); + //console.info(cardClasses); + //console.log(cardColourRequirements); + + const [builtCards] = + await Promise.all([ + buildCards(cards, cardClasses, cardColourRequirements), + // TODO: builtEffects + ]); + + //console.log(builtCards); + // TODO: addEffectsToCards + // TODO: Finally do the effects for each card. This will be the complicater + // since it's not 100% thought out yet... ( + + // Add the cards (x of how many cardObjects with cardId are in count in decklist) + // to a deck array to pass through. Or decks array with deckId specified? + //console.log(deckList); + // These are the four basic fellas for this + // from public/board.js where current game testing/logic is applied + // So creating as a test for a 'room' to see if it's simple enough to + // send and play with + // BUILD THE DATA TO SEND TO CLIENTS! (Kinda) + let item = []; + let itemCount = 0; + let deckData = {}; // New but may be useful + let deckIn = {}; // Which deck the item is in? Kinda the player/boardelement thing? + let cardData = {}; + let boardElement = {}; + // TODO: Set the player. For now will do this in front-end as testing currently + + // Loop and create the deck first + //console.log(decks); + decks.forEach((deck) => { + item.push(itemCount); // Add new item to add stuff for + deckData[itemCount] = {'deckId':deck.deckId, 'playerId':deck.playerId, 'deckName':deck.deckName}; + boardElement[itemCount] = 'realDeck'; + itemCount++; + }) + console.log(deckData); + console.log(deckList); + + // Loop each item in the deckList + // Loop inside it X times where X is cardCount + // Add the builtCard with same cardId as deckList item X times + deckList.forEach((deckListItem) => { + + let deckItem = null; + // Loop each deck, if the deck/playerIds match, add association + for(key in deckData){ + //Object.keys(deckData).forEach(function(key) { // Less efficient than for + // Needs to check deck AND player id, as that's the primary key (together) + if(deckData[key].deckId == deckListItem.deckId && deckData[key].playerId == deckListItem.playerId){ + deckItem = key; // Key is the `item` key + } + + }; + // For each new card, loop to the cardCount (how many cards in deck) + // and add to the deck + for(let i = 0; i < deckListItem.cardCount; i++){ + item.push(itemCount); // Add new item to add stuff for + // ^ not using item.length incase anything in future gets deleted + // from item for instance + cardData[itemCount] = builtCards[deckListItem.cardId]; // builtCards id set to cardId from DB, so adding the builtCard object is based on the deckList's cardId (from DB) + boardElement[itemCount] = 'deck'; // Add all cards to deck at match start + // Associate the card with the deck + // TODO: Change deckIn to something more sensical + deckIn[itemCount] = deckItem; + itemCount++; // Increment item to not overwrite + } + + }); + + // item, itemCount, deckData, cardData, boardElement + //console.log(cardData); + + // Returning everything to be looped in the front-end + // This won't be looped as it will at final, instead just for deck generation + // Returned as object over array, as easier to disect when gets to front-end + let dataReturn = { + item: item, + itemCount: itemCount, + deckData: deckData, + deckIn: deckIn, + cardData: cardData, + boardElement: boardElement, + }; + return resolve(dataReturn); + //return resolve(cardData); + + // Promise stuff testing + })() + }); + + // TODO: In future the item inc. all the decks, cards, locations, and other attributes + // will come from here + + // TODO: unrelated, but thought. If lots of players on, generating cards each time + // will be a big hit, as well as all the DB hits, so may need to load all cardObjects + // into memory/array when server starts. This then prevents any DB changes to alter + // things, but outside of testing that won't occur(?), may need to test this at some + // point to see. For now DB, and generating is ok, as still working on it +} +// For testing +requestDeck(); + +function requestDeckStart(socket){ + // For future: + // Don't try to use promises between server/client + // It's painful and doesn't appear to work. Instead, let client have a loading + // screen, or wait for a while before doing something that requires server data + // like loading the decks... Then have the socket.on clientside trigger + // whatever functions, and stuff needs to happen + // Wasted so much time trying to do async server-client stuff. Don't bother + response = {success: false, message: 'Failed requestDeckStart() server.js'}; + requestDeck().then(data => { + response.success = true; + response.message = data; + io.to(socket.id).emit('responseGetDeck', response); + + }) + .catch(err => { + response.message = err; + io.to(socket.id).emit('responseGetDeck', err); + }); +} + +function buildCards(cards, cardClasses, cardColourRequirements){ + + const dPromise = new Promise((resolve, reject) => { + + builtCards = {}; + + //console.log(cards); + // Loop the cards and build the base + cards.forEach((card) => { + + let builtCard = { + id: card.id, + name: card.cardName, + colour: [], + cost: card.cardCost, + costReq: [], + type: card.cardType, + atk: card.cardAttack, + rarity: card.cardRarity, + effect: [], + cardClass: [], + }; + // Give the card an easily accessible Id for compares + // and to add to the cardItem being built + builtCards[card.id] = builtCard; + + }); + //console.log(builtCards); + + // Next loop each class, and add to the new builtCard (if cardIds match) + cardClasses.forEach((cardClass) => { + + // Check the card exists (it should always, but don't want jank) + if(cardClass.cardId in builtCards){ + // It's the Id for now + // TODO: Send the text too (from join) but don't use for compares + //console.log(cardClass.cardId); + //console.log(cardClass.classId); + //console.log('---'); + //console.log(builtCards[cardClass.cardId]); + + // Add the class to the class array (cards can have multiple) + builtCards[cardClass.cardId].cardClass.push(cardClass.classId); + + // TODO: As an array [classId, className] then + // card.classes[x][0] can be compared as numeric in code + } + + }); + + resolve(builtCards); + + }); + return dPromise; +} +// TODO: Add the effects onto the cards passed through +// Takes cards array (of obj), and array of effects (from DB or built, probabaly pre-built?) +function addCardsEffects(){} + +// End For testing + function onConnection(socket){ console.log('+ User connected'); console.log(''); + socket.on('requestGetCards', function(deckId, playerId) { + requestGetCards(socket, deckId, playerId); + }); + // New testing fella (working afaik) + // TODO: request specific deckId/playerId (and multiples, i.e. get 6 decks at same + // time, based on deckId/playerId combo. Maybe pass as array [deckId, playerId],[deck + socket.on('requestDeck', function() { + requestDeckStart(socket); + }); + socket.on("exampleEvent", (data) => { + io.emit("exampleEvent", "hello from server"); + }); + socket.on('requestRooms', function(filter) { requestRooms(socket, filter); }); @@ -195,3 +619,23 @@ function requestJoinRoom(socket, playerName, roomId){ } +// Change to Decks, or 'getPlayerDecks' for the game +// Could also be a specific deck if player clicks into one? Although maybe when entering +// decks they get all their decks (maybe just id actually) then load from there? +function requestGetCards(socket, deckId, playerId){ + console.log(deckId); + console.log(playerId); + let response = {'success':false, 'message':'Nothing happened'}; + response.success = true; + + // Await promise, once it's done get the data, if errors send err + database.getCardsFromDeck(deckId, playerId).then(data => { + console.log(data); + response.message = data; + io.to(socket.id).emit('responseGetCards', response); + }) + .catch(err => { + response.message = err; + io.to(socket.id).emit('responseGetCards', err); + }) +}