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); // Variables let numRooms = 0; let numRoomsToPreGen = 1; const maxRooms = 3; const maxPlayersPerRoom = 2; const maxSpectatorsPerRoom = 0; // All the room //let data = []; // Normal array let data = {}; // Object array (this one for returning to player, and JSON stringify while keeping named ids) let roomData = {}; for (let roomId = 1; roomId <= numRoomsToPreGen; roomId++) { // Never have more rooms than max rooms!!! if(numRooms > maxRooms){ break; } 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); }); socket.on('requestJoinRoom', function(playerName, roomId) { requestJoinRoom(socket, playerName, roomId); }); socket.on('requestCreateRoom', function(playerName) { requestCreateRoom(socket, playerName); }); } function requestRooms(socket, filter){ console.log('+ requestRooms recieved'); console.log('- filter: '+filter); let response = getRooms(filter, dump = true); io.to(socket.id).emit('returnRooms', response); console.log(''); } function getRooms(filter = 'all', dump = false){ console.log('+ getRooms'); let response = { random: 'randomStuff', roomData: roomData, }; if(dump){ console.log(response); console.log(''); } return response; } function requestCreateRoom(socket, playerName){ console.log('+ createRoom recieved'); console.log('- requested by: '+playerName); response = createRoom(roomId = false, dump = true); io.to(socket.id).emit('returnCreateRoom', response); if(response.success){ let response = getRooms(filter = 'all', dump = true); io.to(socket.id).emit('returnRooms', response); } console.log(''); } function createRoom(roomId = false, dump = true){ let roomName = false; if(roomId == false){ roomId = numRooms + 1; } console.log(roomId); let response = { success: false, message: 'No idea bossman' }; // Max room limit reached console.log(numRooms); console.log(maxRooms); if(numRooms >= maxRooms){ console.log('- Room limit reached'); response = { success: false, message: 'No space '+numRooms+' out of '+maxRooms+' created.' }; // Create room }else{ console.log('- Creating room') let room = {}; room['id'] = roomId; room['name'] = 'Room:'+room['id']; roomName = room['name']; room['password'] = ''; room['timeout'] = {}; room['timeout']['s'] = 10; room['people'] = 0; //room['deck'] = []; //room['turn'] = 0; // Removed players for now, players may be seperate // and back-end only with an assoc. to current room //let players = {}; //for (let j = 0; j < maxPlayersPerRoom; j++) { //let p = {}; //p['id'] = 0; //p['name'] = ""; //p['hand'] = {}; //players[j] = p; //} //room['players'] = players; roomData[roomId] = room; numRooms = numRooms + 1; response = { success: true, message: 'Room Created: '+roomName, }; } if(dump){ console.log(response); console.log(''); } return response; } // TODO: break into requestJoinRoom, and JoinRoom? // Maybe not needed here? As won't be done via backend? function requestJoinRoom(socket, playerName, roomId){ console.log('+ requestJoinRoom recieved'); socket.playerName = playerName; for (let i = 1; i <= numRooms; i++) { let name = 'Room_' + i; let people = roomData[i]['people']; console.log('- people: '+people); console.log('- maxPlayersPerRoom: '+maxPlayersPerRoom); if (true || people < maxPlayersPerRoom) { people = roomData[i]['people'] += 1; socket.join(name); console.log('>> User ' + socket.playerName + ' connected on ' + name + ' (' + (people) + '/' + maxPlayersPerRoom + ')'); io.to(socket.id).emit('responseJoinRoom', name); if (people >= maxPlayersPerRoom) { console.log('- starting game'); //startGame(name); } return; } } io.to(socket.id).emit('responseRoom', 'error'); console.log('>> Rooms exceeded'); } // 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); }) }