/*eslint no-console: 0*/ // /** // * A CLI environment-like game // * @module cmdpp-core // */ import filesize from 'filesize'; import { loadCommands as loadCMDs } from './commands'; import { getStorage } from './storage'; import pJSON from '../package.json'; // TODO: Replace current CMD#storage with an object containing storage data // TODO: Move CMD#_commands to CMD#Commands // TODO: Create more JSDoc /** Class representing the main game logic. * @typicalname cmd * @class * @example <caption>Instantiate a CMD object.</caption> * import { CMD } from 'cmdpp-core'; * import fs from 'fs'; * var cmdContainer = { * data: 0, * money: 0 * }; * var cmd = new CMD({ * (...txt) => console.log(...txt), * (cmdData) => fs.writeFileSync('save.json', JSON.stringify(cmdData, null, 2)), * () => return JSON.parse(fs.readFileSync('save.json')), * (cmdObj) => { * cmdContainer.data = cmdObj.data; * cmdContainer.money = cmdObj.money; * }, * function() { * return { * stringDesc: { * func: () => this.respond("First test run!"), * desc: "Desc can be a string" * }, * functionDesc: { * func: () => this.respond("Second test run!"), * desc: () => "Desc can also be a function that returns a string or an array of strings." * }, * buyableCommand: { * func: () => this.respond("buyable command!"), * desc: 'This command must be bought with the "buyCommand" command.', * price: 10 * } * }; * }, * (err) => console.error(err), * true * }); */ class CMD { /** * Instantiate the CMD object * @param {!CMD~respondCallback} respond - Function for responding. * @param {!CMD~saveCallback} save - Function for saving. * @param {!CMD~loadCallback} load - Function for loading. * @param {!CMD~updateCallback} update - Function for updating. * @param {!CMD~commandProviderCallback} commandProvider - Function to provide custom commands. Cannot be ES6 arrow function. * @param {!CMD~errorHandlerCallback} errorHandler - Function for error handling. * @param {boolean} debug=false - Debug mode. */ // constructor(options) { constructor(respond, save, load, update, commandProvider, errorHandler, debug) { // var defaults = { // debug: false, // funcs: { // respond: (...txt) => console.log(...txt), // save: () => console.warn('No save function has been set.'), // load: () => console.warn('No load function has been set.'), // update: () => console.warn('No update function has been set.'), // // reset: () => console.warn('No reset function has been set.'), // errorHandler: (e) => console.error(e) // }, // errorHandler: (e) => console.error(e), // commandProvider: function() {} // }; respond = respond || ((...txt) => console.log(...txt)); save = save || (() => console.warn('No save function has been set.')); load = load || (() => console.warn('No load function has been set.')); update = update || (() => console.warn('No update function has been set.')); commandProvider = commandProvider || function() {}; errorHandler = errorHandler || ((e) => console.error(e)); debug = debug || false; var options = { funcs: { respond, save, load, update }, commandProvider, errorHandler, debug }; // var opts = Object.assign({}, defaults, options); // options = opts; this.version = pJSON.version; this.loadCommands = loadCMDs; this.commandProvider = options.commandProvider; this.money = 0; this.increment = 1; this.autoIncrement = 1; this.isAutoMining = false; // this.storage = "selectronTube"; this.data = 0; this.counter = 0; this.debug = options.debug; this.respondFunc = options.funcs.respond; this.saveFunc = options.funcs.save; this.loadFunc = options.funcs.load; this.updateFunc = options.funcs.update; this.resetFunc = options.funcs.reset; this.errHandlerFunc = options.funcs.errorHandler; // this.loadStorage(); this.storage = getStorage(); // this.storage = this.storages["selectronTube"]; this.loadCommands(); var customCommands = this.commandProvider(); Object.assign(this._commands, customCommands); for (let cmdName in this._commands) { var cmd = this._commands[cmdName]; if (!('price' in cmd)) { cmd.price = 0; } cmd.unlocked = cmd.price === 0; this._commands[cmdName] = cmd; } // this.command("load"); this.load(); this.gameLoopInterval = undefined; this.gameLoop(); } /** * Function to handle responses from the CMD object. * @callback CMD~respondCallback * @param {...*} txt - Responses */ /** * Function to handle saving progress. * @callback CMD~saveCallback * @param {!Object} cmdData - Game progress to be saved. * @param {number} cmdData.data - Data collected. * @param {number} cmdData.money - Money collected. * @param {number} cmdData.increment - Increment value for mineData. * @param {number} cmdData.autoIncrement - Increment value for autoMine. * @param {string} cmdData.storage - Current storage value. * @param {string[]} cmdData.unlocked - Commands bought with buyCommand. * @return {?Error} An error if encountered. */ /** * Function to handle saving progress. * @callback CMD~loadCallback * @return {Object} Game progress loaded from save. */ /** * Function to handle updating game values. * @callback CMD~updateCallback * @param {CMD} cmdObj - CMD object. */ /** * An object representing a command. * @typedef {Object} CMD~Command * @property {function} func - Function called when command is run. * @property {(string|function)} desc - Description for command. * @property {?(string|function)} usage=undefined - How to use the command. * @property {?number} price=0 - Price to pay in bytes for command. */ /** * Function to provide custom commands. * @callback CMD~commandProviderCallback * @return {CMD~Command} Object of custom commands. */ /** * Function to handle thrown errors. * @callback CMD~errorHandlerCallback * @param {Error} err - Error thrown. */ /** * Start the game loop. */ gameLoop() { if (this.gameLoopInterval === undefined) { this.gameLoopInterval = setInterval(() => { this.counter++; if (this.counter % 10 === 0) { // this.command.save(false); // this.command("save"); this.save(); } if (this.isAutoMining) { if (this.checkStorage(this.autoIncrement)) { this.addData(this.autoIncrement); } else { this.respond("Please upgrade your storage with upgradeStorage"); this.command("autoMine stop"); } this.update(); } }, 1000); } else { console.error('Game loop has already been started.'); } } /** * Send response to respond function from constructor * @param {...*} txt - Strings to be sent to respond function. */ respond(...txt) { this.respondFunc(...txt); } /** * Check if storage is full. * @param {?number} increment - Increment to check against. If undefined, equal to CMD#increment. * @return {boolean} If storage has enough space. */ checkStorage(increment = this.increment) { // if (increment === undefined) { // increment = this.increment; // } // return (this.data <= this.storages[this.storage].capacity); // return ((this.data + increment) <= this.storages[this.storage].capacity); var check = this.storage.checkStorage(this.data, increment); if (this.debug) { console.log("Current data:", this.data); console.log("Increment:", increment); console.log("Check storage:", check); } return check; } /** * Run command * @param {!string} str - Command to be ran. */ command(str) { if (str) { this.runCommand(str); if (this.historyBufferEnabled) { if (this.historyBuffer[0] !== str) { this.historyBuffer.unshift(str); } if (this.historyBuffer.length > 10) { this.historyBuffer.pop(); } } } } runCommand(cmd) { if (this.debug) { console.log("Command:", cmd); } if (cmd.indexOf(" ") !== -1 && cmd[cmd.indexOf(" ") + 1] === undefined) { this.respond("Command not found."); if (this.debug) { console.log('Command not found.'); } } else { var cmdWArgs = cmd.split(' '); if (!(cmdWArgs[0] in this._commands)) { this.respond("Command not found."); } else { // console.log(cmdWArgs); if (this._commands[cmdWArgs[0]].unlocked) { this._commands[cmdWArgs[0]].func(...cmdWArgs.slice(1)); } } } } /** * Run update function from constructor to update game values */ update() { this.updateFunc(this); } /** * Save game progress */ save() { var saveObj = { data: this.data, money: this.money, increment: this.increment, autoIncrement: this.autoIncrement, // storage: this.storage, storage: this.storage.name, unlocked: [] }; for (let cmdName in this._commands) { var cmd = this._commands[cmdName]; if ('price' in cmd && cmd.price !== 0 && cmd.unlocked) { saveObj.unlocked.push(cmdName); } } this.saveFunc(saveObj); } /** * Load game progress */ load() { var loadData = this.loadFunc(); var previousSave = true; if (!loadData) { previousSave = false; } for (var k in loadData) { if (loadData[k] === null) { previousSave = false; break; } } if (previousSave) { if (this.debug) { console.log(loadData); } this.data = loadData.data; this.money = loadData.money; this.increment = loadData.increment; this.autoIncrement = loadData.autoIncrement; this.storage.current = loadData.storage; for (let cmdName in this._commands) { var cmd = this._commands[cmdName]; if (loadData.unlocked.indexOf(cmdName) === -1 && 'price' in cmd && cmd.price !== 0) { this._commands[cmdName].unlocked = false; } else { this._commands[cmdName].unlocked = true; } } // for (var unlockedCMD of loadData.unlocked) { // this._commands[unlockedCMD].unlocked = true; // } this.respond("Save loaded."); } else { this.respond("No save found."); } this.update(); } /** * Add data * @param {number} amt - Amount to add. * @return {boolean} if data was able to be added. */ addData(amt) { var hasRoom = false; if (this.checkStorage(amt)) { this.data += amt; hasRoom = true; } this.update(); return hasRoom; } /** * Remove data * @param {number} amt - Amount to remove. * @return {boolean} if data was able to be removed. */ removeData(amt) { var hasEnough = false; if (this.data >= amt) { this.data -= amt; hasEnough = true; } this.update(); return hasEnough; } /** * Add money * @param {number} amt - Amount to add. */ addMoney(amt) { this.money += amt; this.update(); } /** * Remove money * @param {number} amt - Amount to remove. * @return {boolean} if money was able to be removed. */ removeMoney(amt) { var hasEnough = false; if (this.money >= amt) { this.money -= amt; hasEnough = true; } this.update(); return hasEnough; } /** * Format bytes into a human-readable format * @return {string} CMD#data in human-readable format */ formatBytes() { return this.formatter(this.data); } /** * Format number into a human-readable format * @param {number} size - Number to be formatted. * @return {string} size in human-readable format */ formatter(size) { return filesize(size); } /** * Reset game progress */ reset() { this.saveFunc({ data: 0, money: 0, increment: 1, autoIncrement: 1, storage: "selectronTube", unlocked: [] }); this.load(); } } export { CMD }; export default CMD; // /** // * Function to handle responses from the CMD object. // * @callback respondCallback // * @param {...*} txt - Responses // */ // // /** // * Function to handle saving progress. // * @callback CMD~saveCallback // * @param {Object} cmdData - Game progress to be saved. // * @param {number} cmdData.data - Data collected. // * @param {number} cmdData.money - Money collected. // * @param {number} cmdData.increment - Increment value for mineData. // * @param {number} cmdData.autoIncrement - Increment value for autoMine. // * @param {string} cmdData.storage - Current storage value. // * @param {string[]} cmdData.unlocked - Commands bought with buyCommand. // * @return {Error | null} An error if encountered. // */ // // /** // * Function to handle saving progress. // * @callback CMD~loadCallback // * @return {Object} Game progress loaded from save. // */ // // /** // * Function to handle updating game values. // * @callback CMD~updateCallback // * @param {CMD} cmdObj - CMD object. // */ // // /** // * Function to handle thrown errors. // * @callback CMD~errorHandlerCallback // * @param {Error} err - Error thrown. // */ // // /** // * An object representing a command. // * @typedef {Object} Command // * @property {function} func - Function called when command is run. // * @property {string|function} desc - Description for command. // * @property {string|function|undefined} usage=null - How to use the command. // * @property {number|undefined} price=0 - Price to pay in bytes for command. // */ // // /** // * Function to provide custom commands. // * @callback CMD~commandProviderCallback // * @return {Command} Object of custom commands. // */