@warriorjs/cli
Version:
WarriorJS command line
357 lines (309 loc) • 9.19 kB
JavaScript
'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 _helperGetGradeLetter = require('@warriorjs/helper-get-grade-letter');
var _helperGetGradeLetter2 = _interopRequireDefault(_helperGetGradeLetter);
var _GameError = require('./GameError');
var _GameError2 = _interopRequireDefault(_GameError);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
const profileFile = '.profile';
const playerCodeFile = 'Player.js';
const readmeFile = 'README.md';
/** Class representing a profile. */
class Profile {
/**
* Loads a profile from a profile directory.
*
* @param {string} profileDirectoryPath The path to the profile directory.
* @param {Tower[]} towers The available towers.
*
* @returns {Profile} The loaded profile.
*/
static load(profileDirectoryPath, towers) {
if (!Profile.isProfileDirectory(profileDirectoryPath)) {
return null;
}
const profileFilePath = _path2.default.join(profileDirectoryPath, profileFile);
const encodedProfile = Profile.read(profileFilePath);
if (!encodedProfile) {
return null;
}
const decodedProfile = Profile.decode(encodedProfile);
const {
warriorName,
towerId,
towerName, // TODO: Remove before v1.0.0.
directoryPath, // TODO: Remove before v1.0.0.
currentEpicScore, // TODO: Remove before v1.0.0.
currentEpicGrades } = decodedProfile,
profileData = _objectWithoutProperties(decodedProfile, ['warriorName', 'towerId', 'towerName', 'directoryPath', 'currentEpicScore', 'currentEpicGrades']);
const towerKey = towerId || towerName; // Support legacy profiles.
const profileTower = towers.find(tower => tower.id === towerKey);
if (!profileTower) {
throw new _GameError2.default(`Unable to find tower '${towerKey}', make sure it is available.`);
}
const profile = new Profile(warriorName, profileTower, profileDirectoryPath);
return Object.assign(profile, profileData);
}
/**
* Checks if the given path is a profile directory.
*
* For a directory to be considered a profile directory, it must contain two
* files: `.profile` and `Player.js`.
*
* @param {string} profileDirectoryPath The path to validate.
*
* @returns {boolean} Whether the path is a profile directory or not.
*/
static isProfileDirectory(profileDirectoryPath) {
const profileFilePath = _path2.default.join(profileDirectoryPath, profileFile);
const playerCodeFilePath = _path2.default.join(profileDirectoryPath, playerCodeFile);
try {
return _fs2.default.statSync(profileFilePath).isFile() && _fs2.default.statSync(playerCodeFilePath).isFile();
} catch (err) {
if (err.code === 'ENOENT') {
return false;
}
throw err;
}
}
/**
* Reads a profile file.
*
* @param {string} profileFilePath The path to the profile file.
*
* @returns {string} The contents of the profile file.
*/
static read(profileFilePath) {
try {
return _fs2.default.readFileSync(profileFilePath, 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
return null;
}
throw err;
}
}
/**
* Decodes an encoded profile.
*
* @param {string} encodedProfile The encoded profile.
*
* @returns {Object} The decoded profile.
*/
static decode(encodedProfile) {
try {
return JSON.parse(Buffer.from(encodedProfile, 'base64').toString());
} catch (err) {
if (err instanceof SyntaxError) {
throw new _GameError2.default('Invalid .profile file. Try changing the directory under which you are running warriorjs.');
}
throw err;
}
}
/**
* Creates a profile.
*
* @param {string} warriorName The name of the warrior.
* @param {Tower} tower The tower.
* @param {string} directoryPath The path to the directory of the profile.
*/
constructor(warriorName, tower, directoryPath) {
this.warriorName = warriorName;
this.tower = tower;
this.directoryPath = directoryPath;
this.levelNumber = 0;
this.score = 0;
this.clue = false;
this.epic = false;
this.epicScore = 0;
this.averageGrade = null;
this.currentEpicScore = 0;
this.currentEpicGrades = {};
}
/**
* Creates the profile directory.
*/
makeProfileDirectory() {
_fs2.default.mkdirSync(this.directoryPath);
}
/**
* Reads the player code file.
*
* @returns {string} The player code.
*/
readPlayerCode() {
try {
return _fs2.default.readFileSync(this.getPlayerCodeFilePath(), 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
return null;
}
throw err;
}
}
/**
* Returns the path to the player code file.
*
* @returns {string} The path to the player code file.
*/
getPlayerCodeFilePath() {
return _path2.default.join(this.directoryPath, playerCodeFile);
}
/**
* Returns the path to the README file.
*
* @returns {string} The path to the README file.
*/
getReadmeFilePath() {
return _path2.default.join(this.directoryPath, readmeFile);
}
/**
* Increments the level by one and saves the profile.
*/
goToNextLevel() {
this.levelNumber += 1;
this.clue = false;
this.save();
}
/**
* Requests the clue to be shown.
*/
requestClue() {
this.clue = true;
this.save();
}
/**
* Checks if the clue is being shown.
*
* @returns {boolean} Whether the clue is being shown or not.
*/
isShowingClue() {
return this.clue;
}
/**
* Enables epic mode and saves the profile.
*/
enableEpicMode() {
this.epic = true;
this.save();
}
/**
* Checks if the profile is in epic mode.
*
* @returns {boolean} Whether the profile is in epic mode or not.
*/
isEpic() {
return this.epic;
}
/**
* Calculates the total score and secondary scores after playing a level.
*
* @param {number} levelNumber The number of the level.
* @param {Object} score The score of the play.
*/
tallyPoints(levelNumber, totalScore, grade) {
if (this.isEpic()) {
this.currentEpicGrades[levelNumber] = grade;
this.currentEpicScore += totalScore;
} else {
this.score += totalScore;
}
}
/**
* Returns the epic score with corresponding grade letter.
*
* If the average grade cannot yet be calculated, returns only the epic score.
*
* @returns {string} The epic score with grade.
*/
getEpicScoreWithGrade() {
if (this.averageGrade) {
return `${this.epicScore} (${(0, _helperGetGradeLetter2.default)(this.averageGrade)})`;
}
return this.epicScore.toString();
}
/**
* Updates the epic score and saves the profile.
*/
updateEpicScore() {
if (this.currentEpicScore > this.epicScore) {
this.epicScore = this.currentEpicScore;
this.averageGrade = this.calculateAverageGrade();
}
this.save();
}
/**
* Calculates the average of all current level grades in epic mode.
*
* @returns {number} The average grade.
*/
calculateAverageGrade() {
const grades = Object.values(this.currentEpicGrades);
if (!grades.length) {
return null;
}
return grades.reduce((sum, value) => sum + value) / grades.length;
}
/**
* Saves the profile to the profile file.
*/
save() {
_fs2.default.writeFileSync(this.getProfileFilePath(), this.encode());
}
/**
* Returns the path to the profile file.
*
* @returns {string} The path to the profile file.
*/
getProfileFilePath() {
return _path2.default.join(this.directoryPath, profileFile);
}
/**
* Encodes the JSON representation of the profile in base64.
*
* @returns {string} The encoded profile.
*/
encode() {
return Buffer.from(JSON.stringify(this)).toString('base64');
}
/**
* Customizes the JSON stringification behavior of the profile.
*
* @returns {Object} The value to be serialized.
*/
toJSON() {
return {
warriorName: this.warriorName,
towerId: this.tower.id,
levelNumber: this.levelNumber,
clue: this.clue,
epic: this.epic,
score: this.score,
epicScore: this.epicScore,
averageGrade: this.averageGrade
};
}
/**
* Returns the string representation of this profile.
*
* @returns {string} The string representation.
*/
toString() {
let result = `${this.warriorName} - ${this.tower}`;
if (this.isEpic()) {
result += ` - first score ${this.score} - epic score ${this.getEpicScoreWithGrade()}`;
} else {
result += ` - level ${this.levelNumber} - score ${this.score}`;
}
return result;
}
}
exports.default = Profile;
module.exports = exports.default;