const { EventEmitter } = require('events'); const { stdin } = require('process'); class Game extends EventEmitter { constructor(width, height, sprites) { super(); this.width = width; this.height = height; this.sprites = sprites; this.goals = 0; this.okGoals = 0; this.on('levelEnd', this.nextLevel); this.on('move', this.onMove); }; resetLines() { this.lines = []; for(let y = 0; y < this.height; y ++) { const line = new Array(this.width).fill('air'); this.lines.push(line); }; }; nextLevel() { this.okGoals = 0; this.resetLines(); this.addBorderWalls(); const walls = 1 + Math.random() * 2; for(let i = 0; i < walls; i ++) this.spawn('wall'); const boxes = 1 + Math.floor(Math.random() * 2); this.goals = boxes; for(let i = 0; i < boxes; i ++) { this.spawn('box', 2); this.spawn('goal'); }; this.spawn('player'); this.update(); }; getPlayerPos() { const y = this.lines.findIndex((line) => line.includes('player')); const line = this.lines[y]; if(!line) { this.spawn('player'); return this.getPlayerPos(); }; const x = line.indexOf('player'); return { x, y }; }; setItemPos(x, y, newX, newY) { const char = this.lines[y][x]; this.lines[y][x] = 'air'; this.lines[newY][newX] = char; }; moveItem(x, y, side) { const sides = { left: [ 'x', -1 ], up: [ 'y', -1 ], down: [ 'y', 1 ], right: [ 'x', 1 ] }; const [ coord, apply ] = sides[side]; const newPos = { x, y }; newPos[coord] += apply; const collide = this.lines[newPos.y][newPos.x]; const char = this.lines[y][x]; if(collide === 'box') { const ok = this.moveItem(newPos.x, newPos.y, side); if(!ok) return; } else { if(char === 'box' && collide === 'goal') { this.okGoals ++; if(this.okGoals >= this.goals) this.emit('levelEnd'); this.lines[newPos.y][newPos.x] = 'air'; return true; }; if(collide !== 'air') return; }; this.setItemPos(x, y, newPos.x, newPos.y); return true; }; onMove(side) { const pos = this.getPlayerPos(); this.moveItem(pos.x, pos.y, side); this.update(); }; spawn(item, offset = 1) { const randomPos = (max) => Math.floor(offset + Math.random() * (max - offset * 2)); const x = randomPos(this.width); const y = randomPos(this.height); if(this.lines[y][x] === 'air') { this.lines[y][x] = item; } else { this.spawn(item, offset); }; }; addBorderWalls() { for(let x = 0; x < this.width; x ++) { this.lines[0][x] = 'wall'; this.lines[this.height - 1][x] = 'wall'; }; for(let y = 0; y < this.height; y ++) { this.lines[y][0] = 'wall'; this.lines[y][this.width - 1] = 'wall'; }; }; update() { const frame = this.lines .map((line) => line.map((char) => this.sprites[char]).join('')) .join('\n'); this.emit('update', frame); }; }; const game = new Game(16, 7, { player: '@', air: ' ', wall: '#', box: '%', goal: '$' }); game.on('update', (state) => { console.clear(); console.log(state); }); game.nextLevel(); stdin.setRawMode(true); stdin.setEncoding('utf-8') stdin.resume(); const sides = { a: 'left', w: 'up', s: 'down', d: 'right' }; stdin.on('data', (key) => { if(key === '\u0003') process.exit(); const side = sides[key]; if(side) game.emit('move', side); });