UNPKG

13.4 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 _globby = require('globby');
16
17var _globby2 = _interopRequireDefault(_globby);
18
19var _helperGetLevelConfig = require('@warriorjs/helper-get-level-config');
20
21var _helperGetLevelConfig2 = _interopRequireDefault(_helperGetLevelConfig);
22
23var _helperGetPlayScore = require('@warriorjs/helper-get-play-score');
24
25var _helperGetPlayScore2 = _interopRequireDefault(_helperGetPlayScore);
26
27var _core = require('@warriorjs/core');
28
29var _GameError = require('./GameError');
30
31var _GameError2 = _interopRequireDefault(_GameError);
32
33var _Profile = require('./Profile');
34
35var _Profile2 = _interopRequireDefault(_Profile);
36
37var _ProfileGenerator = require('./ProfileGenerator');
38
39var _ProfileGenerator2 = _interopRequireDefault(_ProfileGenerator);
40
41var _getWarriorNameSuggestions = require('./utils/getWarriorNameSuggestions');
42
43var _getWarriorNameSuggestions2 = _interopRequireDefault(_getWarriorNameSuggestions);
44
45var _loadTowers = require('./loadTowers');
46
47var _loadTowers2 = _interopRequireDefault(_loadTowers);
48
49var _printFailureLine = require('./ui/printFailureLine');
50
51var _printFailureLine2 = _interopRequireDefault(_printFailureLine);
52
53var _printLevelReport = require('./ui/printLevelReport');
54
55var _printLevelReport2 = _interopRequireDefault(_printLevelReport);
56
57var _printLevel = require('./ui/printLevel');
58
59var _printLevel2 = _interopRequireDefault(_printLevel);
60
61var _printLine = require('./ui/printLine');
62
63var _printLine2 = _interopRequireDefault(_printLine);
64
65var _printPlay = require('./ui/printPlay');
66
67var _printPlay2 = _interopRequireDefault(_printPlay);
68
69var _printSeparator = require('./ui/printSeparator');
70
71var _printSeparator2 = _interopRequireDefault(_printSeparator);
72
73var _printSuccessLine = require('./ui/printSuccessLine');
74
75var _printSuccessLine2 = _interopRequireDefault(_printSuccessLine);
76
77var _printTowerReport = require('./ui/printTowerReport');
78
79var _printTowerReport2 = _interopRequireDefault(_printTowerReport);
80
81var _printWarningLine = require('./ui/printWarningLine');
82
83var _printWarningLine2 = _interopRequireDefault(_printWarningLine);
84
85var _printWelcomeHeader = require('./ui/printWelcomeHeader');
86
87var _printWelcomeHeader2 = _interopRequireDefault(_printWelcomeHeader);
88
89var _requestChoice = require('./ui/requestChoice');
90
91var _requestChoice2 = _interopRequireDefault(_requestChoice);
92
93var _requestConfirmation = require('./ui/requestConfirmation');
94
95var _requestConfirmation2 = _interopRequireDefault(_requestConfirmation);
96
97var _requestInput = require('./ui/requestInput');
98
99var _requestInput2 = _interopRequireDefault(_requestInput);
100
101function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
102
103const gameDirectory = 'warriorjs';
104
105/** Class representing a game. */
106class Game {
107 /**
108 * Creates a game.
109 *
110 * @param {string} runDirectoryPath The directory under which to run the game.
111 * @param {number} practiceLevel The level to practice.
112 * @param {boolean} silencePlay Whether to suppress play log or not.
113 * @param {number} delay The delay between each turn in seconds.
114 * @param {boolean} assumeYes Whether to answer yes to every question or not.
115 */
116 constructor(runDirectoryPath, practiceLevel, silencePlay, delay, assumeYes) {
117 this.runDirectoryPath = runDirectoryPath;
118 this.practiceLevel = practiceLevel;
119 this.silencePlay = silencePlay;
120 this.delay = delay * 1000;
121 this.assumeYes = assumeYes;
122 this.gameDirectoryPath = _path2.default.join(this.runDirectoryPath, gameDirectory);
123 }
124
125 /**
126 * Starts the game.
127 */
128 async start() {
129 (0, _printWelcomeHeader2.default)();
130
131 try {
132 this.towers = (0, _loadTowers2.default)();
133
134 this.profile = await this.loadProfile();
135
136 if (this.profile.isEpic()) {
137 await this.playEpicMode();
138 } else {
139 await this.playNormalMode();
140 }
141 } catch (err) {
142 if (err instanceof _GameError2.default || err.code === 'InvalidPlayerCode') {
143 (0, _printFailureLine2.default)(err.message);
144 } else {
145 throw err;
146 }
147 }
148 }
149
150 /**
151 * Loads a profile into the game.
152 *
153 * If the game is being run from a profile directory, that profile will be
154 * loaded. If not, the player will be given the option to choose a profile.
155 *
156 * @returns {Profile} The loaded profile.
157 */
158 async loadProfile() {
159 const profile = _Profile2.default.load(this.runDirectoryPath, this.towers);
160 if (profile) {
161 return profile;
162 }
163
164 return this.chooseProfile();
165 }
166
167 /**
168 * Gives the player the option to choose one of the available profiles.
169 *
170 * If there are no profiles available in the game directory or the player
171 * chooses to, creates a new profile.
172 *
173 * @returns {Profile} The chosen profile.
174 */
175 async chooseProfile() {
176 const profiles = this.getProfiles();
177 if (profiles.length) {
178 const newProfileChoice = 'New profile';
179 const profileChoices = [...profiles, _requestChoice.SEPARATOR, newProfileChoice];
180 const profile = await (0, _requestChoice2.default)('Choose a profile:', profileChoices);
181 if (profile !== newProfileChoice) {
182 return profile;
183 }
184 }
185
186 return this.createProfile();
187 }
188
189 /**
190 * Returns the profiles available in the game directory.
191 *
192 * @returns {Profile[]} The available profiles.
193 */
194 getProfiles() {
195 const profileDirectoriesPaths = this.getProfileDirectoriesPaths();
196 return profileDirectoriesPaths.map(profileDirectoryPath => _Profile2.default.load(profileDirectoryPath, this.towers));
197 }
198
199 /**
200 * Creates a new profile.
201 *
202 * @returns {Profile} The created profile.
203 */
204 async createProfile() {
205 const warriorNameSuggestions = (0, _getWarriorNameSuggestions2.default)();
206 const warriorName = await (0, _requestInput2.default)('Enter a name for your warrior:', warriorNameSuggestions);
207 if (!warriorName) {
208 throw new _GameError2.default('Your warrior must have a name if you want him or her to become a legend!');
209 }
210
211 const towerChoices = this.towers;
212 const tower = await (0, _requestChoice2.default)('Choose a tower:', towerChoices);
213
214 const profileDirectoryPath = _path2.default.join(this.gameDirectoryPath, `${warriorName}-${tower.id}`.toLowerCase().replace(/[^a-z0-9]+/g, '-'));
215
216 const profile = new _Profile2.default(warriorName, tower, profileDirectoryPath);
217
218 if (this.isExistingProfile(profile)) {
219 (0, _printWarningLine2.default)(`There's already a warrior named ${warriorName} climbing the ${tower} tower.`);
220 const replaceExisting = await (0, _requestConfirmation2.default)('Do you want to replace your existing profile for this tower?');
221 if (!replaceExisting) {
222 throw new _GameError2.default('Unable to continue without a profile.');
223 }
224
225 (0, _printLine2.default)('Replacing existing profile...');
226 } else {
227 profile.makeProfileDirectory();
228 }
229
230 return profile;
231 }
232
233 /**
234 * Checks if the given profile exists in the game directory.
235 *
236 * @param {Profile} profile A profile to check existance for.
237 *
238 * @returns {boolean} Whether the profile exists or not..
239 */
240 isExistingProfile(profile) {
241 const profileDirectoriesPaths = this.getProfileDirectoriesPaths();
242 return profileDirectoriesPaths.some(profileDirectoryPath => profileDirectoryPath === profile.directoryPath);
243 }
244
245 /**
246 * Returns the paths to the profiles available in the game directory.
247 *
248 * @returns {string[]} The paths to the available profiles.
249 */
250 getProfileDirectoriesPaths() {
251 this.ensureGameDirectory();
252 const profileDirectoryPattern = _path2.default.join(this.gameDirectoryPath, '*');
253 return _globby2.default.sync(profileDirectoryPattern, { onlyDirectories: true });
254 }
255
256 /**
257 * Ensures the game directory exists.
258 */
259 ensureGameDirectory() {
260 try {
261 if (!_fs2.default.statSync(this.gameDirectoryPath).isDirectory()) {
262 throw new _GameError2.default('A file named warriorjs exists at this location. Please change the directory under which you are running warriorjs.');
263 }
264 } catch (err) {
265 if (err.code !== 'ENOENT') {
266 throw err;
267 }
268
269 _fs2.default.mkdirSync(this.gameDirectoryPath);
270 }
271 }
272
273 /**
274 * Plays through epic mode.
275 */
276 async playEpicMode() {
277 this.delay /= 2;
278
279 if (this.practiceLevel) {
280 const hasPracticeLevel = this.profile.tower.hasLevel(this.practiceLevel);
281 if (!hasPracticeLevel) {
282 throw new _GameError2.default('Unable to practice non-existent level, try another.');
283 }
284
285 await this.playLevel(this.practiceLevel);
286 } else {
287 let levelNumber = 0;
288 let playing = true;
289 while (playing) {
290 levelNumber += 1;
291 playing = await this.playLevel(levelNumber); // eslint-disable-line no-await-in-loop
292 }
293
294 this.profile.updateEpicScore();
295 }
296 }
297
298 /**
299 * Plays through normal mode.
300 */
301 async playNormalMode() {
302 if (this.practiceLevel) {
303 throw new _GameError2.default('Unable to practice level while not in epic mode, remove -l option.');
304 }
305
306 if (this.profile.levelNumber === 0) {
307 this.prepareNextLevel();
308 (0, _printSuccessLine2.default)(`First level has been generated. See ${this.profile.getReadmeFilePath()} for instructions.`);
309 } else {
310 await this.playLevel(this.profile.levelNumber);
311 }
312 }
313
314 /**
315 * Plays the level with the given number.
316 *
317 * @param {number} levelNumber The number of the level to play.
318 *
319 * @returns {boolean} Whether playing can continue or not (for epic mode),
320 */
321 async playLevel(levelNumber) {
322 const { tower, warriorName, epic } = this.profile;
323 const levelConfig = (0, _helperGetLevelConfig2.default)(tower, levelNumber, warriorName, epic);
324
325 const level = (0, _core.getLevel)(levelConfig);
326 (0, _printLevel2.default)(level);
327
328 const playerCode = this.profile.readPlayerCode();
329 const playResult = (0, _core.runLevel)(levelConfig, playerCode);
330
331 if (!this.silencePlay) {
332 await (0, _printPlay2.default)(playResult.events, this.delay);
333 }
334
335 (0, _printSeparator2.default)();
336
337 if (!playResult.passed) {
338 (0, _printFailureLine2.default)(`Sorry, you failed level ${levelNumber}. Change your script and try again.`);
339
340 if (levelConfig.clue && !this.profile.isShowingClue()) {
341 const showClue = this.assumeYes || (await (0, _requestConfirmation2.default)('Would you like to read the additional clues for this level?'));
342 if (showClue) {
343 this.profile.requestClue();
344 this.generateProfileFiles();
345 (0, _printSuccessLine2.default)(`See ${this.profile.getReadmeFilePath()} for the clues.`);
346 }
347 }
348
349 return false;
350 }
351
352 const hasNextLevel = this.profile.tower.hasLevel(levelNumber + 1);
353
354 if (hasNextLevel) {
355 (0, _printSuccessLine2.default)('Success! You have found the stairs.');
356 } else {
357 (0, _printSuccessLine2.default)('CONGRATULATIONS! You have climbed to the top of the tower.');
358 }
359
360 const score = (0, _helperGetPlayScore2.default)(playResult, levelConfig);
361 (0, _printLevelReport2.default)(this.profile, score);
362 this.profile.tallyPoints(levelNumber, score);
363
364 if (this.profile.isEpic()) {
365 if (!hasNextLevel && !this.practiceLevel) {
366 (0, _printTowerReport2.default)(this.profile);
367 }
368 } else {
369 await this.requestNextLevel();
370 }
371
372 return hasNextLevel;
373 }
374
375 /**
376 * Gives the player the option to continue on to the next level.
377 *
378 * If the last level has already being reached, the player can choose to
379 * continue on to epic mode.
380 */
381 async requestNextLevel() {
382 if (this.profile.tower.hasLevel(this.profile.levelNumber + 1)) {
383 const continueToNextLevel = this.assumeYes || (await (0, _requestConfirmation2.default)('Would you like to continue on to the next level?', true));
384 if (continueToNextLevel) {
385 this.prepareNextLevel();
386 (0, _printSuccessLine2.default)(`See ${this.profile.getReadmeFilePath()} for updated instructions.`);
387 } else {
388 (0, _printLine2.default)('Staying on current level. Try to earn more points next time.');
389 }
390 } else {
391 const continueToEpicMode = this.assumeYes || (await (0, _requestConfirmation2.default)('Would you like to continue on to epic mode?', true));
392 if (continueToEpicMode) {
393 this.prepareEpicMode();
394 (0, _printSuccessLine2.default)('Run warriorjs again to play epic mode.');
395 }
396 }
397 }
398
399 /**
400 * Prepares the next level.
401 */
402 prepareNextLevel() {
403 this.profile.goToNextLevel();
404 this.generateProfileFiles();
405 }
406
407 /**
408 * Generates the profile files.
409 */
410 generateProfileFiles() {
411 const { tower, levelNumber, warriorName, epic } = this.profile;
412 const levelConfig = (0, _helperGetLevelConfig2.default)(tower, levelNumber, warriorName, epic);
413 const level = (0, _core.getLevel)(levelConfig);
414 new _ProfileGenerator2.default(this.profile, level).generate();
415 }
416
417 /**
418 * Prepares the epic mode.
419 */
420 prepareEpicMode() {
421 this.profile.enableEpicMode();
422 }
423}
424
425exports.default = Game;
426module.exports = exports.default;
\No newline at end of file