UNPKG

9.2 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _fs = require('fs');
8
9var _fs2 = _interopRequireDefault(_fs);
10
11var _path = require('path');
12
13var _path2 = _interopRequireDefault(_path);
14
15var _helperGetGradeLetter = require('@warriorjs/helper-get-grade-letter');
16
17var _helperGetGradeLetter2 = _interopRequireDefault(_helperGetGradeLetter);
18
19var _GameError = require('./GameError');
20
21var _GameError2 = _interopRequireDefault(_GameError);
22
23function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
25function _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; }
26
27const profileFile = '.profile';
28const playerCodeFile = 'Player.js';
29const readmeFile = 'README.md';
30
31/** Class representing a profile. */
32class Profile {
33 /**
34 * Loads a profile from a profile directory.
35 *
36 * @param {string} profileDirectoryPath The path to the profile directory.
37 * @param {Tower[]} towers The available towers.
38 *
39 * @returns {Profile} The loaded profile.
40 */
41 static load(profileDirectoryPath, towers) {
42 if (!Profile.isProfileDirectory(profileDirectoryPath)) {
43 return null;
44 }
45
46 const profileFilePath = _path2.default.join(profileDirectoryPath, profileFile);
47 const encodedProfile = Profile.read(profileFilePath);
48 if (!encodedProfile) {
49 return null;
50 }
51
52 const decodedProfile = Profile.decode(encodedProfile);
53 const {
54 warriorName,
55 towerId,
56 towerName, // TODO: Remove before v1.0.0.
57 directoryPath, // TODO: Remove before v1.0.0.
58 currentEpicScore, // TODO: Remove before v1.0.0.
59 currentEpicGrades } = decodedProfile,
60 profileData = _objectWithoutProperties(decodedProfile, ['warriorName', 'towerId', 'towerName', 'directoryPath', 'currentEpicScore', 'currentEpicGrades']);
61
62 const towerKey = towerId || towerName; // Support legacy profiles.
63 const profileTower = towers.find(tower => tower.id === towerKey);
64 if (!profileTower) {
65 throw new _GameError2.default(`Unable to find tower '${towerKey}', make sure it is available.`);
66 }
67
68 const profile = new Profile(warriorName, profileTower, profileDirectoryPath);
69 return Object.assign(profile, profileData);
70 }
71
72 /**
73 * Checks if the given path is a profile directory.
74 *
75 * For a directory to be considered a profile directory, it must contain two
76 * files: `.profile` and `Player.js`.
77 *
78 * @param {string} profileDirectoryPath The path to validate.
79 *
80 * @returns {boolean} Whether the path is a profile directory or not.
81 */
82 static isProfileDirectory(profileDirectoryPath) {
83 const profileFilePath = _path2.default.join(profileDirectoryPath, profileFile);
84 const playerCodeFilePath = _path2.default.join(profileDirectoryPath, playerCodeFile);
85 try {
86 return _fs2.default.statSync(profileFilePath).isFile() && _fs2.default.statSync(playerCodeFilePath).isFile();
87 } catch (err) {
88 if (err.code === 'ENOENT') {
89 return false;
90 }
91
92 throw err;
93 }
94 }
95
96 /**
97 * Reads a profile file.
98 *
99 * @param {string} profileFilePath The path to the profile file.
100 *
101 * @returns {string} The contents of the profile file.
102 */
103 static read(profileFilePath) {
104 try {
105 return _fs2.default.readFileSync(profileFilePath, 'utf8');
106 } catch (err) {
107 if (err.code === 'ENOENT') {
108 return null;
109 }
110
111 throw err;
112 }
113 }
114
115 /**
116 * Decodes an encoded profile.
117 *
118 * @param {string} encodedProfile The encoded profile.
119 *
120 * @returns {Object} The decoded profile.
121 */
122 static decode(encodedProfile) {
123 try {
124 return JSON.parse(Buffer.from(encodedProfile, 'base64').toString());
125 } catch (err) {
126 if (err instanceof SyntaxError) {
127 throw new _GameError2.default('Invalid .profile file. Try changing the directory under which you are running warriorjs.');
128 }
129
130 throw err;
131 }
132 }
133
134 /**
135 * Creates a profile.
136 *
137 * @param {string} warriorName The name of the warrior.
138 * @param {Tower} tower The tower.
139 * @param {string} directoryPath The path to the directory of the profile.
140 */
141 constructor(warriorName, tower, directoryPath) {
142 this.warriorName = warriorName;
143 this.tower = tower;
144 this.directoryPath = directoryPath;
145 this.levelNumber = 0;
146 this.score = 0;
147 this.clue = false;
148 this.epic = false;
149 this.epicScore = 0;
150 this.averageGrade = null;
151 this.currentEpicScore = 0;
152 this.currentEpicGrades = {};
153 }
154
155 /**
156 * Creates the profile directory.
157 */
158 makeProfileDirectory() {
159 _fs2.default.mkdirSync(this.directoryPath);
160 }
161
162 /**
163 * Reads the player code file.
164 *
165 * @returns {string} The player code.
166 */
167 readPlayerCode() {
168 try {
169 return _fs2.default.readFileSync(this.getPlayerCodeFilePath(), 'utf8');
170 } catch (err) {
171 if (err.code === 'ENOENT') {
172 return null;
173 }
174
175 throw err;
176 }
177 }
178
179 /**
180 * Returns the path to the player code file.
181 *
182 * @returns {string} The path to the player code file.
183 */
184 getPlayerCodeFilePath() {
185 return _path2.default.join(this.directoryPath, playerCodeFile);
186 }
187
188 /**
189 * Returns the path to the README file.
190 *
191 * @returns {string} The path to the README file.
192 */
193 getReadmeFilePath() {
194 return _path2.default.join(this.directoryPath, readmeFile);
195 }
196
197 /**
198 * Increments the level by one and saves the profile.
199 */
200 goToNextLevel() {
201 this.levelNumber += 1;
202 this.clue = false;
203 this.save();
204 }
205
206 /**
207 * Requests the clue to be shown.
208 */
209 requestClue() {
210 this.clue = true;
211 this.save();
212 }
213
214 /**
215 * Checks if the clue is being shown.
216 *
217 * @returns {boolean} Whether the clue is being shown or not.
218 */
219 isShowingClue() {
220 return this.clue;
221 }
222
223 /**
224 * Enables epic mode and saves the profile.
225 */
226 enableEpicMode() {
227 this.epic = true;
228 this.save();
229 }
230
231 /**
232 * Checks if the profile is in epic mode.
233 *
234 * @returns {boolean} Whether the profile is in epic mode or not.
235 */
236 isEpic() {
237 return this.epic;
238 }
239
240 /**
241 * Calculates the total score and secondary scores after playing a level.
242 *
243 * @param {number} levelNumber The number of the level.
244 * @param {Object} score The score of the play.
245 */
246 tallyPoints(levelNumber, { total: totalScore, grade }) {
247 if (this.isEpic()) {
248 this.currentEpicGrades[levelNumber] = grade;
249 this.currentEpicScore += totalScore;
250 } else {
251 this.score += totalScore;
252 }
253 }
254
255 /**
256 * Returns the epic score with corresponding grade letter.
257 *
258 * If the average grade cannot yet be calculated, returns only the epic score.
259 *
260 * @returns {string} The epic score with grade.
261 */
262 getEpicScoreWithGrade() {
263 if (this.averageGrade) {
264 return `${this.epicScore} (${(0, _helperGetGradeLetter2.default)(this.averageGrade)})`;
265 }
266
267 return this.epicScore.toString();
268 }
269
270 /**
271 * Updates the epic score and saves the profile.
272 */
273 updateEpicScore() {
274 if (this.currentEpicScore > this.epicScore) {
275 this.epicScore = this.currentEpicScore;
276 this.averageGrade = this.calculateAverageGrade();
277 }
278
279 this.save();
280 }
281
282 /**
283 * Calculates the average of all current level grades in epic mode.
284 *
285 * @returns {number} The average grade.
286 */
287 calculateAverageGrade() {
288 const grades = Object.values(this.currentEpicGrades);
289 if (!grades.length) {
290 return null;
291 }
292
293 return grades.reduce((sum, value) => sum + value) / grades.length;
294 }
295
296 /**
297 * Saves the profile to the profile file.
298 */
299 save() {
300 _fs2.default.writeFileSync(this.getProfileFilePath(), this.encode());
301 }
302
303 /**
304 * Returns the path to the profile file.
305 *
306 * @returns {string} The path to the profile file.
307 */
308 getProfileFilePath() {
309 return _path2.default.join(this.directoryPath, profileFile);
310 }
311
312 /**
313 * Encodes the JSON representation of the profile in base64.
314 *
315 * @returns {string} The encoded profile.
316 */
317 encode() {
318 return Buffer.from(JSON.stringify(this)).toString('base64');
319 }
320
321 /**
322 * Customizes the JSON stringification behavior of the profile.
323 *
324 * @returns {Object} The value to be serialized.
325 */
326 toJSON() {
327 return {
328 warriorName: this.warriorName,
329 towerId: this.tower.id,
330 levelNumber: this.levelNumber,
331 clue: this.clue,
332 epic: this.epic,
333 score: this.score,
334 epicScore: this.epicScore,
335 averageGrade: this.averageGrade
336 };
337 }
338
339 /**
340 * Returns the string representation of this profile.
341 *
342 * @returns {string} The string representation.
343 */
344 toString() {
345 let result = `${this.warriorName} - ${this.tower}`;
346 if (this.isEpic()) {
347 result += ` - first score ${this.score} - epic score ${this.getEpicScoreWithGrade()}`;
348 } else {
349 result += ` - level ${this.levelNumber} - score ${this.score}`;
350 }
351
352 return result;
353 }
354}
355
356exports.default = Profile;
357module.exports = exports.default;
\No newline at end of file