diff --git a/README.md b/README.md index 060f899..53138d9 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,7 @@ node-games spacecraft ---------- ![spacecraft](https://raw.githubusercontent.com/mdibaiee/node-games/master/spacecraft.png) + +snake +----- +![snake](https://raw.githubusercontent.com/mdibaiee/node-games/master/snake.png) diff --git a/build/classes/interface.js b/build/classes/interface.js index bc745e7..7baddb8 100644 --- a/build/classes/interface.js +++ b/build/classes/interface.js @@ -45,10 +45,15 @@ var Interface = (function () { this.cursor = (0, _ansi2.default)(this.output).hide(); - this.columns = this.output.columns; - this.rows = this.output.rows; - this.input.addListener('data', function (data) { + var always = listeners.filter(function (listener) { + return listener.key === ''; + }); + + always.forEach(function (listener) { + return listener.fn(); + }); + var key = Object.keys(keys).find(function (value, i) { return keys[value] === data; }); @@ -80,8 +85,30 @@ var Interface = (function () { }, { key: 'onKey', value: function onKey(key, fn) { + if (typeof key === 'function') { + fn = key; + key = ''; + } listeners.push({ key: key, fn: fn }); } + }, { + key: 'columns', + get: function get() { + return this.output.columns; + } + }, { + key: 'rows', + get: function get() { + return this.output.rows; + } + }, { + key: 'center', + get: function get() { + return { + x: this.output.columns / 2, + y: this.output.rows / 2 + }; + } }]); return Interface; diff --git a/build/classes/unit.js b/build/classes/unit.js index 2b31149..777f077 100644 --- a/build/classes/unit.js +++ b/build/classes/unit.js @@ -54,6 +54,12 @@ var Unit = (function () { this.x = x; this.y = y; } + }, { + key: 'random', + value: function random() { + this.x = Math.max(1, Math.floor(Math.random() * this.output.columns)); + this.y = Math.max(1, Math.floor(Math.random() * this.output.rows)); + } }, { key: 'speed', value: function speed() { diff --git a/build/log b/build/log new file mode 100644 index 0000000..e69de29 diff --git a/build/snake.js b/build/snake.js new file mode 100644 index 0000000..4bcf85a --- /dev/null +++ b/build/snake.js @@ -0,0 +1,189 @@ +'use strict'; + +var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); + +var _unit = require('./classes/unit'); + +var _unit2 = _interopRequireDefault(_unit); + +var _interface = require('./classes/interface'); + +var _interface2 = _interopRequireDefault(_interface); + +var _fs = require('fs'); + +var _fs2 = _interopRequireDefault(_fs); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var log = _fs2.default.createWriteStream(__dirname + '/log'); + +var FRAME = 100; + +var ui = new _interface2.default(); + +var snake = []; +var UP = 0; +var DOWN = 1; +var LEFT = 2; +var RIGHT = 3; +var head = createPart(); +head.color = '#71da29'; +head.dieOnExit = true; +createPart(); +createPart(); + +var point = new _unit2.default(ui); +point.shape = '+'; +point.color = '#f3ff6a'; +point.random(); + +var score = 0; + +var stop = false; +function loop() { + if (stop) return; + ui.clear(); + + point.draw(); + + snake.forEach(function (part, i) { + part.draw(); + + if (i > 0) part.findWay(i); + + part.move(); + }); + + if (head.collides(point)) { + point.random(); + createPart(); + score++; + + FRAME--; + } + + if (head.collides(snake.slice(2))) { + gameover(); + } + + ui.cursor.goto(0, 0).yellow().write('Score: ' + score); + ui.cursor.reset(); + + setTimeout(loop, FRAME); +} + +loop(); + +ui.onKey('right', function () { + changeDirection(RIGHT); +}); +ui.onKey('down', function () { + changeDirection(DOWN); +}); +ui.onKey('up', function () { + changeDirection(UP); +}); +ui.onKey('left', function () { + changeDirection(LEFT); +}); + +ui.onKey(function () { + if (!stop) return; + + stop = false; + snake = []; + head = createPart(); + head.color = '#71da29'; + createPart(); + createPart(); + + score = 0; + + point.random(); + + loop(); +}); + +function changeDirection(dir) { + head.direction = dir; +} + +function createPart() { + var part = new _unit2.default(ui); + var last = snake[snake.length - 1]; + + var direction = undefined; + if (!last) { + direction = UP; + } else { + direction = last.direction; + } + + part.shape = '•'; + part.color = '#bdfe91'; + part.direction = direction; + part.changeTo = null; + + part.findWay = function (i) { + var ahead = snake[i - 1]; + + if (this.changeTo !== null) { + this.direction = this.changeTo; + this.changeTo = null; + } + if (this.direction !== ahead.direction) { + this.changeTo = ahead.direction; + } + }; + + part.speed = function () { + var multiplier = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0]; + var direction = part.direction; + + var x = direction == LEFT ? -1 : direction == RIGHT ? 1 : 0; + var y = direction == UP ? -1 : direction == DOWN ? 1 : 0; + + return [x * multiplier, y * multiplier]; + }; + + var _part$speed = part.speed(); + + var _part$speed2 = _slicedToArray(_part$speed, 2); + + var dX = _part$speed2[0]; + var dY = _part$speed2[1]; + + dX *= -1; + dY *= -1; + + var x = last ? last.x + dX : ui.center.x; + var y = last ? last.y + dY : ui.center.y; + + part.go(x, y); + + snake.push(part); + return part; +} + +function gameover() { + var MSG = 'Game Over!'; + ui.cursor.goto(ui.center.x - MSG.length / 2, ui.center.y); + ui.cursor.red(); + ui.cursor.bold(); + ui.write(MSG); + + ui.cursor.reset(); + ui.cursor.hex('#f65590'); + var RETRY = 'Press any key to play again'; + ui.cursor.goto(ui.center.x - RETRY.length / 2, ui.center.y + 2); + ui.write(RETRY); + + stop = true; +} + +process.on('exit', function () { + ui.cursor.horizontalAbsolute(0).eraseLine(); + ui.cursor.show(); + log.end(); +}); diff --git a/build/spacecraft.js b/build/spacecraft.js index ffe1f16..02315a2 100644 --- a/build/spacecraft.js +++ b/build/spacecraft.js @@ -11,15 +11,17 @@ var _interface2 = _interopRequireDefault(_interface); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var FRAME = 20; -var ENEMY_SPAWN_RATE = 400; +var ENEMY_SPAWN_RATE = 1000; +var RELOAD_TIME = 200; var ui = new _interface2.default(); var player = new _unit2.default(ui); -player.go(1, ui.output.rows / 2); +player.go(1, ui.center.y); player.shape = '=>'; player.color = '#77d6ff'; player.bold = true; +player.canShoot = true; var explosion = new _unit2.default(ui); explosion.dead = true; @@ -43,6 +45,8 @@ setInterval(function () { enemy.shape = '*'; missle.dead = true; + ENEMY_SPAWN_RATE -= 5; + score++; } @@ -80,18 +84,25 @@ ui.onKey('up', function () { ui.onKey('left', function () { player.move(-1, 0); }); + ui.onKey('space', function () { + if (!player.canShoot) return; + + player.canShoot = false; + var missle = new _unit2.default(ui); missle.go(player.x, player.y); missle.shape = '+'; missle.dieOnExit = true; missles.push(missle); + + setTimeout(function () { + player.canShoot = true; + }, RELOAD_TIME); }); -setInterval(function () { - if (enemies.length > 5) return; - +(function loop() { var enemy = new _unit2.default(ui); enemy.go(Math.random() * ui.output.columns, 0); enemy.shape = 'o'; @@ -103,7 +114,9 @@ setInterval(function () { }; enemies.push(enemy); -}, 1000); + + setTimeout(loop, ENEMY_SPAWN_RATE); +})(); process.on('exit', function () { ui.cursor.horizontalAbsolute(0).eraseLine(); diff --git a/index.js b/index.js index 8d3d36f..0ed644a 100755 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ if (!game) { console.log(''); console.log('Games'); console.log('- spacecraft'); + console.log('- snake'); return; } diff --git a/package.json b/package.json index a2f6627..e9e1495 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-games", - "version": "1.0.2", + "version": "2.0.0", "description": "Simple node console games", "main": "index.js", "scripts": { diff --git a/snake.png b/snake.png new file mode 100644 index 0000000..8e4b355 Binary files /dev/null and b/snake.png differ diff --git a/src/classes/interface.js b/src/classes/interface.js index 92cb0c8..4052876 100644 --- a/src/classes/interface.js +++ b/src/classes/interface.js @@ -23,10 +23,13 @@ export default class Interface { this.cursor = ansi(this.output).hide(); - this.columns = this.output.columns; - this.rows = this.output.rows; - this.input.addListener('data', data => { + let always = listeners.filter(listener => { + return listener.key === ''; + }); + + always.forEach(listener => listener.fn()); + let key = Object.keys(keys).find((value, i) => { return keys[value] === data; }); @@ -41,6 +44,14 @@ export default class Interface { }) } + get columns() { + return this.output.columns; + } + + get rows() { + return this.output.rows; + } + clear() { this.output.write('\u001b[2J\u001b[0;0H'); } @@ -50,6 +61,17 @@ export default class Interface { } onKey(key, fn) { + if (typeof key === 'function') { + fn = key; + key = ''; + } listeners.push({ key, fn }); } + + get center() { + return { + x: this.output.columns / 2, + y: this.output.rows / 2 + } + } } diff --git a/src/classes/unit.js b/src/classes/unit.js index 31fdb66..bdbb8ab 100644 --- a/src/classes/unit.js +++ b/src/classes/unit.js @@ -71,6 +71,11 @@ export default class Unit { this.y = y; } + random() { + this.x = Math.max(1, Math.floor(Math.random() * this.output.columns)); + this.y = Math.max(1, Math.floor(Math.random() * this.output.rows)); + } + speed() { let signs = [Math.random() > 0.5 ? -1 : 1, Math.random() > 0.5 ? -1 : 1]; return [Math.random() * signs[0], Math.random() * signs[1]]; diff --git a/src/snake.js b/src/snake.js new file mode 100644 index 0000000..fd73196 --- /dev/null +++ b/src/snake.js @@ -0,0 +1,169 @@ +import Unit from './classes/unit'; +import Interface from './classes/interface'; +import fs from 'fs'; + +let log = fs.createWriteStream(__dirname + '/log'); + +let FRAME = 100; + +let ui = new Interface(); + +let snake = []; +const UP = 0; +const DOWN = 1; +const LEFT = 2; +const RIGHT = 3; +let head = createPart(); +head.color = '#71da29'; +head.dieOnExit = true; +createPart(); +createPart(); + +let point = new Unit(ui); +point.shape = '+'; +point.color = '#f3ff6a'; +point.random(); + +let score = 0; + +let stop = false; +function loop() { + if (stop) return; + ui.clear(); + + point.draw(); + + snake.forEach((part, i) => { + part.draw(); + + if (i > 0) part.findWay(i); + + part.move(); + }); + + if (head.collides(point)) { + point.random(); + createPart(); + score++; + + FRAME--; + } + + if (head.collides(snake.slice(2))) { + gameover(); + } + + ui.cursor.goto(0, 0).yellow().write(`Score: ${score}`); + ui.cursor.reset(); + + setTimeout(loop, FRAME); +} + +loop(); + +ui.onKey('right', () => { + changeDirection(RIGHT); +}); +ui.onKey('down', () => { + changeDirection(DOWN); +}); +ui.onKey('up', () => { + changeDirection(UP); +}); +ui.onKey('left', () => { + changeDirection(LEFT); +}); + +ui.onKey(() => { + if (!stop) return; + + stop = false; + snake = []; + head = createPart(); + head.color = '#71da29'; + createPart(); + createPart(); + + score = 0; + + point.random(); + + loop(); +}) + +function changeDirection(dir) { + head.direction = dir; +} + +function createPart() { + let part = new Unit(ui); + let last = snake[snake.length - 1]; + + let direction; + if (!last) { + direction = UP; + } else { + direction = last.direction; + } + + part.shape = '•'; + part.color = '#bdfe91'; + part.direction = direction; + part.changeTo = null; + + part.findWay = function(i) { + let ahead = snake[i - 1]; + + if (this.changeTo !== null) { + this.direction = this.changeTo; + this.changeTo = null; + } + if (this.direction !== ahead.direction) { + this.changeTo = ahead.direction; + } + } + + part.speed = function(multiplier = 1) { + let { direction } = part; + let x = direction == LEFT ? -1 : + direction == RIGHT ? 1 : 0; + let y = direction == UP ? -1 : + direction == DOWN ? 1 : 0; + + return [x * multiplier, y * multiplier]; + } + + let [dX, dY] = part.speed(); + dX *= -1; + dY *= -1; + + let x = last ? last.x + dX : ui.center.x; + let y = last ? last.y + dY : ui.center.y; + + part.go(x, y); + + snake.push(part); + return part; +} + +function gameover() { + const MSG = 'Game Over!'; + ui.cursor.goto(ui.center.x - MSG.length / 2, ui.center.y); + ui.cursor.red(); + ui.cursor.bold(); + ui.write(MSG); + + ui.cursor.reset(); + ui.cursor.hex('#f65590'); + const RETRY = 'Press any key to play again'; + ui.cursor.goto(ui.center.x - RETRY.length / 2, ui.center.y + 2); + ui.write(RETRY); + + stop = true; +} + +process.on('exit', function() { + ui.cursor.horizontalAbsolute(0).eraseLine() + ui.cursor.show(); + log.end(); +}); diff --git a/src/spacecraft.js b/src/spacecraft.js index bc1ab2c..17203fa 100644 --- a/src/spacecraft.js +++ b/src/spacecraft.js @@ -2,15 +2,17 @@ import Unit from './classes/unit'; import Interface from './classes/interface'; const FRAME = 20; -const ENEMY_SPAWN_RATE = 400; +let ENEMY_SPAWN_RATE = 1000; +let RELOAD_TIME = 200; let ui = new Interface(); let player = new Unit(ui); -player.go(1, ui.output.rows / 2); +player.go(1, ui.center.y); player.shape = '=>'; player.color = '#77d6ff'; player.bold = true; +player.canShoot = true; let explosion = new Unit(ui); explosion.dead = true; @@ -34,6 +36,8 @@ setInterval(() => { enemy.shape = '*'; missle.dead = true; + ENEMY_SPAWN_RATE -= 5; + score++; } @@ -72,18 +76,25 @@ ui.onKey('up', () => { ui.onKey('left', () => { player.move(-1, 0); }); + ui.onKey('space', () => { + if (!player.canShoot) return; + + player.canShoot = false; + let missle = new Unit(ui); missle.go(player.x, player.y); missle.shape = '+'; missle.dieOnExit = true; missles.push(missle); + + setTimeout(() => { + player.canShoot = true; + }, RELOAD_TIME) }); -setInterval(() => { - if (enemies.length > 5) return; - +(function loop() { let enemy = new Unit(ui); enemy.go(Math.random() * ui.output.columns, 0); enemy.shape = 'o'; @@ -95,7 +106,9 @@ setInterval(() => { } enemies.push(enemy); -}, 1000); + + setTimeout(loop, ENEMY_SPAWN_RATE); +}()); process.on('exit', function() { ui.cursor.horizontalAbsolute(0).eraseLine()