chore: add spacecraft image
This commit is contained in:
commit
4830e50375
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
node-games
|
||||||
|
==========
|
||||||
|
|
||||||
|
Some fun console games written using Node.
|
||||||
|
|
||||||
|
spacecraft
|
||||||
|
----------
|
||||||
|
![spacecraft](https://raw.githubusercontent.com/mdibaiee/node-games/master/spacecraft.png)
|
55
classes/interface.js
Normal file
55
classes/interface.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import ansi from 'ansi';
|
||||||
|
const { stdout, stdin } = process;
|
||||||
|
|
||||||
|
let listeners = [];
|
||||||
|
|
||||||
|
const prefix = '\u001b';
|
||||||
|
const keys = {
|
||||||
|
right: `${prefix}[C`,
|
||||||
|
up: `${prefix}[A`,
|
||||||
|
left: `${prefix}[D`,
|
||||||
|
down: `${prefix}[B`,
|
||||||
|
exit: '\u0003',
|
||||||
|
space: ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Interface {
|
||||||
|
constructor(output = stdout, input = stdin) {
|
||||||
|
this.output = output;
|
||||||
|
this.input = input;
|
||||||
|
|
||||||
|
this.input.setRawMode(true);
|
||||||
|
this.input.setEncoding('utf8');
|
||||||
|
|
||||||
|
this.cursor = ansi(this.output).hide();
|
||||||
|
|
||||||
|
this.columns = this.output.columns;
|
||||||
|
this.rows = this.output.rows;
|
||||||
|
|
||||||
|
this.input.addListener('data', data => {
|
||||||
|
let key = Object.keys(keys).find((value, i) => {
|
||||||
|
return keys[value] === data;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( key === 'exit' ) process.exit();
|
||||||
|
|
||||||
|
let match = listeners.filter(listener => {
|
||||||
|
return listener.key === key || listener.key === data;
|
||||||
|
});
|
||||||
|
|
||||||
|
match.forEach(listener => listener.fn());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.output.write('\u001b[2J\u001b[0;0H');
|
||||||
|
}
|
||||||
|
|
||||||
|
write(...args) {
|
||||||
|
this.cursor.write(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
onKey(key, fn) {
|
||||||
|
listeners.push({ key, fn });
|
||||||
|
}
|
||||||
|
}
|
91
classes/unit.js
Normal file
91
classes/unit.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
export default class Unit {
|
||||||
|
constructor(output) {
|
||||||
|
this._health = 0;
|
||||||
|
this.dead = false;
|
||||||
|
this.output = output;
|
||||||
|
|
||||||
|
this.dieOnExit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set health(value) {
|
||||||
|
this._health = Math.min(0, value);
|
||||||
|
|
||||||
|
if (this.health === 0) {
|
||||||
|
this.dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get health() {
|
||||||
|
return this._health;
|
||||||
|
}
|
||||||
|
|
||||||
|
set x(value) {
|
||||||
|
this._x = value;
|
||||||
|
|
||||||
|
if (this.dieOnExit && this._x > this.output.columns) {
|
||||||
|
this.dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get x() {
|
||||||
|
return this._x;
|
||||||
|
}
|
||||||
|
|
||||||
|
set y(value) {
|
||||||
|
this._y = value;
|
||||||
|
|
||||||
|
if (this.dieOnExit && this._y > this.output.rows) {
|
||||||
|
this.dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get y() {
|
||||||
|
return this._y;
|
||||||
|
}
|
||||||
|
|
||||||
|
move(x, y) {
|
||||||
|
if (!x && !y) return this.move(...this.speed());
|
||||||
|
this.x += x;
|
||||||
|
this.y += y;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
if (this.dead) return;
|
||||||
|
|
||||||
|
let { x, y, shape } = this;
|
||||||
|
if (this.color && this.color[0] === '#') {
|
||||||
|
this.output.cursor.hex(this.color);
|
||||||
|
} else if (this.color) {
|
||||||
|
this.output.cursor[this.color]();
|
||||||
|
}
|
||||||
|
if (this.bold) this.output.cursor.bold();
|
||||||
|
|
||||||
|
|
||||||
|
this.output.cursor.goto(Math.round(x), Math.round(y));
|
||||||
|
this.output.write(shape);
|
||||||
|
this.output.cursor.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
go(x, y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
speed() {
|
||||||
|
let signs = [Math.random() > 0.5 ? -1 : 1, Math.random() > 0.5 ? -1 : 1];
|
||||||
|
return [Math.random() * signs[0], Math.random() * signs[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
collides(target) {
|
||||||
|
if (Array.isArray(target)) {
|
||||||
|
return target.find(t => this.collides(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetX = Math.round(target.x);
|
||||||
|
let targetY = Math.round(target.y);
|
||||||
|
let x = Math.round(this.x);
|
||||||
|
let y = Math.round(this.y);
|
||||||
|
|
||||||
|
return x === targetX && y == targetY;
|
||||||
|
}
|
||||||
|
}
|
103
spacecraft.js
Normal file
103
spacecraft.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import Unit from './classes/unit';
|
||||||
|
import Interface from './classes/interface';
|
||||||
|
|
||||||
|
const FRAME = 20;
|
||||||
|
const ENEMY_SPAWN_RATE = 400;
|
||||||
|
|
||||||
|
let ui = new Interface();
|
||||||
|
|
||||||
|
let player = new Unit(ui);
|
||||||
|
player.go(1, ui.output.rows / 2);
|
||||||
|
player.shape = '=>';
|
||||||
|
player.color = '#77d6ff';
|
||||||
|
player.bold = true;
|
||||||
|
|
||||||
|
let explosion = new Unit(ui);
|
||||||
|
explosion.dead = true;
|
||||||
|
|
||||||
|
let missles = [];
|
||||||
|
let enemies = [];
|
||||||
|
let score = 0;
|
||||||
|
setInterval(() => {
|
||||||
|
ui.clear();
|
||||||
|
|
||||||
|
player.draw();
|
||||||
|
|
||||||
|
missles.forEach((missle, i) => {
|
||||||
|
missle.move(1, 0);
|
||||||
|
missle.draw();
|
||||||
|
|
||||||
|
let enemy = missle.collides(enemies)
|
||||||
|
if (enemy) {
|
||||||
|
enemy.killed = 1;
|
||||||
|
enemy.color = 'red';
|
||||||
|
enemy.shape = '*';
|
||||||
|
missle.dead = true;
|
||||||
|
|
||||||
|
score++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missle.dead) {
|
||||||
|
missles.splice(i, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
enemies.forEach((enemy, i) => {
|
||||||
|
// move with speed
|
||||||
|
enemy.move();
|
||||||
|
enemy.draw();
|
||||||
|
|
||||||
|
if (enemy.dead) {
|
||||||
|
enemies.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enemy.killed == 3) enemy.dead = true;
|
||||||
|
if (enemy.killed < 3) enemy.killed++;
|
||||||
|
})
|
||||||
|
|
||||||
|
ui.cursor.goto(0, 0).yellow().write(`Score: ${score}`);
|
||||||
|
ui.cursor.reset();
|
||||||
|
}, FRAME);
|
||||||
|
|
||||||
|
|
||||||
|
ui.onKey('right', () => {
|
||||||
|
player.move(1, 0);
|
||||||
|
});
|
||||||
|
ui.onKey('down', () => {
|
||||||
|
player.move(0, 1);
|
||||||
|
});
|
||||||
|
ui.onKey('up', () => {
|
||||||
|
player.move(0, -1);
|
||||||
|
});
|
||||||
|
ui.onKey('left', () => {
|
||||||
|
player.move(-1, 0);
|
||||||
|
});
|
||||||
|
ui.onKey('space', () => {
|
||||||
|
let missle = new Unit(ui);
|
||||||
|
missle.go(player.x, player.y);
|
||||||
|
missle.shape = '+';
|
||||||
|
missle.dieOnExit = true;
|
||||||
|
|
||||||
|
missles.push(missle);
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (enemies.length > 5) return;
|
||||||
|
|
||||||
|
let enemy = new Unit(ui);
|
||||||
|
enemy.go(Math.random() * ui.output.columns, 0);
|
||||||
|
enemy.shape = 'o';
|
||||||
|
enemy.color = '#f7c71e';
|
||||||
|
enemy.dieOnExit = true;
|
||||||
|
|
||||||
|
enemy.speed = () => {
|
||||||
|
return [Math.random() > 0.9 ? 0.4 : 0, 0.06];
|
||||||
|
}
|
||||||
|
|
||||||
|
enemies.push(enemy);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
ui.cursor.horizontalAbsolute(0).eraseLine()
|
||||||
|
ui.cursor.show();
|
||||||
|
});
|
BIN
spacecraft.png
Normal file
BIN
spacecraft.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
Loading…
Reference in New Issue
Block a user