UNPKG

@warriorjs/cli

Version:

WarriorJS command line

428 lines (333 loc) 13.6 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _globby = require('globby'); var _globby2 = _interopRequireDefault(_globby); var _helperGetLevelConfig = require('@warriorjs/helper-get-level-config'); var _helperGetLevelConfig2 = _interopRequireDefault(_helperGetLevelConfig); var _helperGetLevelScore = require('@warriorjs/helper-get-level-score'); var _helperGetLevelScore2 = _interopRequireDefault(_helperGetLevelScore); var _core = require('@warriorjs/core'); var _GameError = require('./GameError'); var _GameError2 = _interopRequireDefault(_GameError); var _Profile = require('./Profile'); var _Profile2 = _interopRequireDefault(_Profile); var _ProfileGenerator = require('./ProfileGenerator'); var _ProfileGenerator2 = _interopRequireDefault(_ProfileGenerator); var _getWarriorNameSuggestions = require('./utils/getWarriorNameSuggestions'); var _getWarriorNameSuggestions2 = _interopRequireDefault(_getWarriorNameSuggestions); var _loadTowers = require('./loadTowers'); var _loadTowers2 = _interopRequireDefault(_loadTowers); var _printFailureLine = require('./ui/printFailureLine'); var _printFailureLine2 = _interopRequireDefault(_printFailureLine); var _printLevelReport = require('./ui/printLevelReport'); var _printLevelReport2 = _interopRequireDefault(_printLevelReport); var _printLevel = require('./ui/printLevel'); var _printLevel2 = _interopRequireDefault(_printLevel); var _printLine = require('./ui/printLine'); var _printLine2 = _interopRequireDefault(_printLine); var _printPlay = require('./ui/printPlay'); var _printPlay2 = _interopRequireDefault(_printPlay); var _printSeparator = require('./ui/printSeparator'); var _printSeparator2 = _interopRequireDefault(_printSeparator); var _printSuccessLine = require('./ui/printSuccessLine'); var _printSuccessLine2 = _interopRequireDefault(_printSuccessLine); var _printTowerReport = require('./ui/printTowerReport'); var _printTowerReport2 = _interopRequireDefault(_printTowerReport); var _printWarningLine = require('./ui/printWarningLine'); var _printWarningLine2 = _interopRequireDefault(_printWarningLine); var _printWelcomeHeader = require('./ui/printWelcomeHeader'); var _printWelcomeHeader2 = _interopRequireDefault(_printWelcomeHeader); var _requestChoice = require('./ui/requestChoice'); var _requestChoice2 = _interopRequireDefault(_requestChoice); var _requestConfirmation = require('./ui/requestConfirmation'); var _requestConfirmation2 = _interopRequireDefault(_requestConfirmation); var _requestInput = require('./ui/requestInput'); var _requestInput2 = _interopRequireDefault(_requestInput); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const gameDirectory = 'warriorjs'; /** Class representing a game. */ class Game { /** * Creates a game. * * @param {string} runDirectoryPath The directory under which to run the game. * @param {number} practiceLevel The level to practice. * @param {boolean} silencePlay Whether to suppress play log or not. * @param {number} delay The delay between each turn in seconds. * @param {boolean} assumeYes Whether to answer yes to every question or not. */ constructor(runDirectoryPath, practiceLevel, silencePlay, delay, assumeYes) { this.runDirectoryPath = runDirectoryPath; this.practiceLevel = practiceLevel; this.silencePlay = silencePlay; this.delay = delay * 1000; this.assumeYes = assumeYes; this.gameDirectoryPath = _path2.default.join(this.runDirectoryPath, gameDirectory); } /** * Starts the game. */ async start() { (0, _printWelcomeHeader2.default)(); try { this.towers = (0, _loadTowers2.default)(); this.profile = await this.loadProfile(); if (this.profile.isEpic()) { await this.playEpicMode(); } else { await this.playNormalMode(); } } catch (err) { if (err instanceof _GameError2.default || err.code === 'InvalidPlayerCode') { (0, _printFailureLine2.default)(err.message); } else { throw err; } } } /** * Loads a profile into the game. * * If the game is being run from a profile directory, that profile will be * loaded. If not, the player will be given the option to choose a profile. * * @returns {Profile} The loaded profile. */ async loadProfile() { const profile = _Profile2.default.load(this.runDirectoryPath, this.towers); if (profile) { return profile; } return this.chooseProfile(); } /** * Gives the player the option to choose one of the available profiles. * * If there are no profiles available in the game directory or the player * chooses to, creates a new profile. * * @returns {Profile} The chosen profile. */ async chooseProfile() { const profiles = this.getProfiles(); if (profiles.length) { const newProfileChoice = 'New profile'; const profileChoices = [...profiles, _requestChoice.SEPARATOR, newProfileChoice]; const profile = await (0, _requestChoice2.default)('Choose a profile:', profileChoices); if (profile !== newProfileChoice) { return profile; } } return this.createProfile(); } /** * Returns the profiles available in the game directory. * * @returns {Profile[]} The available profiles. */ getProfiles() { const profileDirectoriesPaths = this.getProfileDirectoriesPaths(); return profileDirectoriesPaths.map(profileDirectoryPath => _Profile2.default.load(profileDirectoryPath, this.towers)); } /** * Creates a new profile. * * @returns {Profile} The created profile. */ async createProfile() { const warriorNameSuggestions = (0, _getWarriorNameSuggestions2.default)(); const warriorName = await (0, _requestInput2.default)('Enter a name for your warrior:', warriorNameSuggestions); if (!warriorName) { throw new _GameError2.default('Your warrior must have a name if you want him or her to become a legend!'); } const towerChoices = this.towers; const tower = await (0, _requestChoice2.default)('Choose a tower:', towerChoices); const profileDirectoryPath = _path2.default.join(this.gameDirectoryPath, `${warriorName}-${tower.id}`.toLowerCase().replace(/[^a-z0-9]+/g, '-')); const profile = new _Profile2.default(warriorName, tower, profileDirectoryPath); if (this.isExistingProfile(profile)) { (0, _printWarningLine2.default)(`There's already a warrior named ${warriorName} climbing the ${tower} tower.`); const replaceExisting = await (0, _requestConfirmation2.default)('Do you want to replace your existing profile for this tower?'); if (!replaceExisting) { throw new _GameError2.default('Unable to continue without a profile.'); } (0, _printLine2.default)('Replacing existing profile...'); } else { profile.makeProfileDirectory(); } return profile; } /** * Checks if the given profile exists in the game directory. * * @param {Profile} profile A profile to check existance for. * * @returns {boolean} Whether the profile exists or not.. */ isExistingProfile(profile) { const profileDirectoriesPaths = this.getProfileDirectoriesPaths(); return profileDirectoriesPaths.some(profileDirectoryPath => profileDirectoryPath === profile.directoryPath); } /** * Returns the paths to the profiles available in the game directory. * * @returns {string[]} The paths to the available profiles. */ getProfileDirectoriesPaths() { this.ensureGameDirectory(); const profileDirectoryPattern = _path2.default.join(this.gameDirectoryPath, '*'); return _globby2.default.sync(profileDirectoryPattern, { onlyDirectories: true }); } /** * Ensures the game directory exists. */ ensureGameDirectory() { try { if (!_fs2.default.statSync(this.gameDirectoryPath).isDirectory()) { throw new _GameError2.default('A file named warriorjs exists at this location. Please change the directory under which you are running warriorjs.'); } } catch (err) { if (err.code !== 'ENOENT') { throw err; } _fs2.default.mkdirSync(this.gameDirectoryPath); } } /** * Plays through epic mode. */ async playEpicMode() { this.delay /= 2; if (this.practiceLevel) { const hasPracticeLevel = this.profile.tower.hasLevel(this.practiceLevel); if (!hasPracticeLevel) { throw new _GameError2.default('Unable to practice non-existent level, try another.'); } await this.playLevel(this.practiceLevel); } else { let levelNumber = 0; let playing = true; while (playing) { levelNumber += 1; playing = await this.playLevel(levelNumber); // eslint-disable-line no-await-in-loop } this.profile.updateEpicScore(); } } /** * Plays through normal mode. */ async playNormalMode() { if (this.practiceLevel) { throw new _GameError2.default('Unable to practice level while not in epic mode, remove -l option.'); } if (this.profile.levelNumber === 0) { this.prepareNextLevel(); (0, _printSuccessLine2.default)(`First level has been generated. See ${this.profile.getReadmeFilePath()} for instructions.`); } else { await this.playLevel(this.profile.levelNumber); } } /** * Plays the level with the given number. * * @param {number} levelNumber The number of the level to play. * * @returns {boolean} Whether playing can continue or not (for epic mode), */ async playLevel(levelNumber) { const { tower, warriorName, epic } = this.profile; const levelConfig = (0, _helperGetLevelConfig2.default)(tower, levelNumber, warriorName, epic); const level = (0, _core.getLevel)(levelConfig); (0, _printLevel2.default)(level); const playerCode = this.profile.readPlayerCode(); const levelResult = (0, _core.runLevel)(levelConfig, playerCode); if (!this.silencePlay) { await (0, _printPlay2.default)(levelResult.events, this.delay); } (0, _printSeparator2.default)(); if (!levelResult.passed) { (0, _printFailureLine2.default)(`Sorry, you failed level ${levelNumber}. Change your script and try again.`); if (levelConfig.clue && !this.profile.isShowingClue()) { const showClue = this.assumeYes || (await (0, _requestConfirmation2.default)('Would you like to read the additional clues for this level?')); if (showClue) { this.profile.requestClue(); this.generateProfileFiles(); (0, _printSuccessLine2.default)(`See ${this.profile.getReadmeFilePath()} for the clues.`); } } return false; } const hasNextLevel = this.profile.tower.hasLevel(levelNumber + 1); if (hasNextLevel) { (0, _printSuccessLine2.default)('Success! You have found the stairs.'); } else { (0, _printSuccessLine2.default)('CONGRATULATIONS! You have climbed to the top of the tower.'); } const scoreParts = (0, _helperGetLevelScore2.default)(levelResult, levelConfig); const totalScore = Object.values(scoreParts).reduce((sum, value) => sum + value, 0); const grade = totalScore * 1.0 / levelConfig.aceScore; (0, _printLevelReport2.default)(this.profile, scoreParts, totalScore, grade); this.profile.tallyPoints(levelNumber, totalScore, grade); if (this.profile.isEpic()) { if (!hasNextLevel && !this.practiceLevel) { (0, _printTowerReport2.default)(this.profile); } } else { await this.requestNextLevel(); } return hasNextLevel; } /** * Gives the player the option to continue on to the next level. * * If the last level has already being reached, the player can choose to * continue on to epic mode. */ async requestNextLevel() { if (this.profile.tower.hasLevel(this.profile.levelNumber + 1)) { const continueToNextLevel = this.assumeYes || (await (0, _requestConfirmation2.default)('Would you like to continue on to the next level?', true)); if (continueToNextLevel) { this.prepareNextLevel(); (0, _printSuccessLine2.default)(`See ${this.profile.getReadmeFilePath()} for updated instructions.`); } else { (0, _printLine2.default)('Staying on current level. Try to earn more points next time.'); } } else { const continueToEpicMode = this.assumeYes || (await (0, _requestConfirmation2.default)('Would you like to continue on to epic mode?', true)); if (continueToEpicMode) { this.prepareEpicMode(); (0, _printSuccessLine2.default)('Run warriorjs again to play epic mode.'); } } } /** * Prepares the next level. */ prepareNextLevel() { this.profile.goToNextLevel(); this.generateProfileFiles(); } /** * Generates the profile files. */ generateProfileFiles() { const { tower, levelNumber, warriorName, epic } = this.profile; const levelConfig = (0, _helperGetLevelConfig2.default)(tower, levelNumber, warriorName, epic); const level = (0, _core.getLevel)(levelConfig); new _ProfileGenerator2.default(this.profile, level).generate(); } /** * Prepares the epic mode. */ prepareEpicMode() { this.profile.enableEpicMode(); } } exports.default = Game; module.exports = exports.default;