From 6ef4f8890fcfe7f7f14422f60c317508933eece9 Mon Sep 17 00:00:00 2001 From: Mahdi Dibaiee Date: Sat, 12 Dec 2015 09:44:22 +0330 Subject: [PATCH] feat: snake game feat: unit.random feat: ui.center --- README.md | 4 + build/classes/interface.js | 33 ++++++- build/classes/unit.js | 6 ++ build/log | 0 build/snake.js | 189 +++++++++++++++++++++++++++++++++++++ build/spacecraft.js | 25 +++-- index.js | 1 + package.json | 2 +- snake.png | Bin 0 -> 42781 bytes src/classes/interface.js | 28 +++++- src/classes/unit.js | 5 + src/snake.js | 169 +++++++++++++++++++++++++++++++++ src/spacecraft.js | 25 +++-- 13 files changed, 468 insertions(+), 19 deletions(-) create mode 100644 build/log create mode 100644 build/snake.js create mode 100644 snake.png create mode 100644 src/snake.js 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 0000000000000000000000000000000000000000..8e4b355534c1fc57370e88953d7bc8d399120499 GIT binary patch literal 42781 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 { + 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()