game.js (3836B)
1 import * as direction from '/static/js/minotaur/direction.js'; 2 3 // Game represents a maze game with player interaction. 4 export default class Game { 5 constructor({ document, maze }) { 6 this.document = document; 7 this.escaped = false; 8 this.initialized = false; 9 this.intervals = { score: null, minotaur: null }; 10 this.killed = false; 11 this.maze = maze; 12 this.pressed = { 13 down: false, 14 left: false, 15 right: false, 16 space: false, 17 up: false, 18 }; 19 this.score = 0; 20 21 // Add the event listeners. 22 document.addEventListener('keydown', (e) => this.keyDownHandler(e), false); 23 document.addEventListener('keyup', (e) => this.keyUpHandler(e), false); 24 } 25 26 // End handles the game over condition. 27 end() { 28 // Clear the intervals. 29 clearInterval(this.intervals.score); 30 clearInterval(this.intervals.minotaur); 31 32 // Disable the event handlers. 33 this.document.removeEventListener('keydown', this.keyDownHandler, false); 34 this.document.removeEventListener('keyup', this.keyUpHandler, false); 35 36 // Display the game over text. 37 if (this.escaped) { 38 this.document.getElementById('game-over').innerHTML = `You escaped the labyrinth!\nScore: ${this.score}.`; 39 } 40 if (this.killed) { 41 this.document.getElementById('game-over').innerHTML = `You were slain by the Minotaur. Score: ${this.score}.`; 42 } 43 44 // Render the last frame. 45 return this.maze.render(); 46 } 47 48 // init starts the game score and the Minotaur's movement. 49 init() { 50 // Start measuring the player's score. 51 this.intervals.score = setInterval(() => { this.score += 1; }, 1000); 52 53 // Move the minotaur. 54 this.intervals.minotaur = setInterval(() => this.maze.moveMinotaur(), 250); 55 56 this.initialized = true; 57 } 58 59 // keyDownHandler handles keydown events. 60 keyDownHandler(e) { 61 if (e.code === 'ArrowUp') { 62 this.pressed.up = true; 63 return e.preventDefault(); 64 } 65 if (e.code === 'ArrowRight') { 66 this.pressed.right = true; 67 return e.preventDefault(); 68 } 69 if (e.code === 'ArrowDown') { 70 this.pressed.down = true; 71 return e.preventDefault(); 72 } 73 if (e.code === 'ArrowLeft') { 74 this.pressed.left = true; 75 return e.preventDefault(); 76 } 77 if (e.code === 'Space') { 78 this.pressed.space = true; 79 return e.preventDefault(); 80 } 81 82 return null; 83 } 84 85 // keyUpHandler handles keyup events. 86 keyUpHandler(e) { 87 if (e.code === 'ArrowUp') this.pressed.up = false; 88 if (e.code === 'ArrowRight') this.pressed.right = false; 89 if (e.code === 'ArrowDown') this.pressed.down = false; 90 if (e.code === 'ArrowLeft') this.pressed.left = false; 91 if (e.code === 'Space') this.pressed.space = false; 92 } 93 94 // run executes the game loop. 95 run() { 96 if (!this.initialized) this.init(); 97 98 // Render the solution. 99 if (this.pressed.space) { 100 this.maze.setEscapePath(); 101 } else { 102 this.maze.clearEscapePath(); 103 } 104 105 // Move Theseus. 106 let dir = null; 107 if (this.pressed.up) dir = direction.up; 108 if (this.pressed.right) dir = direction.right; 109 if (this.pressed.down) dir = direction.down; 110 if (this.pressed.left) dir = direction.left; 111 if (dir) { 112 this.maze.moveTheseus({ d: dir }); 113 114 // Prevent the user from moving more than one cell per keypress. 115 this.pressed.up = false; 116 this.pressed.right = false; 117 this.pressed.down = false; 118 this.pressed.left = false; 119 this.pressed.space = false; 120 } 121 122 // Render the maze. 123 this.maze.render(); 124 125 // Check for game over conditions. 126 if (this.maze.escaped()) { 127 this.escaped = true; 128 return this.end(); 129 } 130 if (this.maze.killed()) { 131 this.killed = true; 132 return this.end(); 133 } 134 135 return requestAnimationFrame(() => this.run()); 136 } 137 }