@warriorjs/cli
Version:
WarriorJS command line
428 lines (333 loc) • 13.6 kB
JavaScript
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;
;