summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorBen Beltran <ben@nsovocal.com>2016-05-09 00:31:25 -0500
committerBen Beltran <ben@nsovocal.com>2016-05-09 00:31:25 -0500
commita140f3add912ef4bd659c1c1e82184643be7d845 (patch)
tree131f33520cc40abc387474fa9c2ac4f536e6d7d1 /lib
parentae6dbe43bf54581dd7012f95581d4a1b6ee245f0 (diff)
parent09ab7e985d46cad6d3c89e37f983e286e9fda2f6 (diff)
Merge branch 'feature/the-actual-tomato-sauce' into develop
Diffstat (limited to 'lib')
-rw-r--r--lib/renderers/256_colors.js16
-rw-r--r--lib/renderers/ansi.js13
-rw-r--r--lib/renderers/fake_color.js8
-rw-r--r--lib/renderers/true_color.js11
-rw-r--r--lib/screens/circle.js32
-rw-r--r--lib/screens/gradients.js25
-rw-r--r--lib/screens/mirrors.js36
-rw-r--r--lib/screens/random.js25
-rw-r--r--lib/screens/sprinkles.js28
-rw-r--r--lib/tomato_sauce.js129
-rw-r--r--lib/util.js50
11 files changed, 373 insertions, 0 deletions
diff --git a/lib/renderers/256_colors.js b/lib/renderers/256_colors.js
new file mode 100644
index 0000000..e8e6e37
--- /dev/null
+++ b/lib/renderers/256_colors.js
@@ -0,0 +1,16 @@
+'use strict';
+
+// Returns a 256 color, see
+// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+// under 256 colors for more info.
+const TwoFiftySixColors = function (red, blue, green) {
+ let redValue = Math.round(red * 5 / 255);
+ let blueValue = Math.round(blue * 5 / 255);
+ let greenValue = Math.round(green * 5 / 255);
+
+ let colorNumber = 16 + 36 * redValue + 6 * greenValue + blueValue;
+
+ return `\x1B[48;5;${colorNumber}m`;
+};
+
+module.exports = TwoFiftySixColors;
diff --git a/lib/renderers/ansi.js b/lib/renderers/ansi.js
new file mode 100644
index 0000000..c3975da
--- /dev/null
+++ b/lib/renderers/ansi.js
@@ -0,0 +1,13 @@
+'use strict';
+
+// Returns a basic ANSI color. See the color table in:
+// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+const ANSI = function (red, blue, green) {
+ let colorOffset = Math.round((red + blue + green) * 7 / (255 * 3));
+
+ let colorNumber = 40 + colorOffset;
+
+ return `\x1B[${colorNumber}m`;
+};
+
+module.exports = ANSI;
diff --git a/lib/renderers/fake_color.js b/lib/renderers/fake_color.js
new file mode 100644
index 0000000..b82bb45
--- /dev/null
+++ b/lib/renderers/fake_color.js
@@ -0,0 +1,8 @@
+'use strict';
+
+// Sends malformed ANSI 24-bit color strings
+const FakeColor = function (red, blue, green) {
+ return `\x1B[28;2;${Math.round(red)};${Math.round(green)};${Math.round(blue)}m`;
+};
+
+module.exports = FakeColor;
diff --git a/lib/renderers/true_color.js b/lib/renderers/true_color.js
new file mode 100644
index 0000000..5f7725f
--- /dev/null
+++ b/lib/renderers/true_color.js
@@ -0,0 +1,11 @@
+'use strict';
+
+// Returns an ANSI Code for True Color, see:
+// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
+// and look for 24-bit colors. Only looks good in supported terminals,
+// otherwise looks like FakeColor.
+const TrueColor = function (red, blue, green) {
+ return `\x1B[48;2;${Math.round(red)};${Math.round(green)};${Math.round(blue)}m`;
+};
+
+module.exports = TrueColor;
diff --git a/lib/screens/circle.js b/lib/screens/circle.js
new file mode 100644
index 0000000..d8a4d10
--- /dev/null
+++ b/lib/screens/circle.js
@@ -0,0 +1,32 @@
+'use strict';
+
+// Draws concentric circles. Each ring has its own color.
+const Circle = function (modulation, width, height, renderer) {
+ let response = [];
+
+ let circles = width > height ? height : width;
+
+ for (let i = 0; i < circles; i++) {
+ let centerX = Math.round(width / 2) + 1;
+ let centerY = Math.round(height / 2) + 1;
+
+ let red = Math.floor(Math.random() * 255);
+ let blue = Math.floor(Math.random() * 255);
+ let green = Math.floor(Math.random() * 255);
+
+ for (let j = 0; j < 180; j++) {
+ let angle = 2 * j * (Math.PI / 180);
+ let x = Math.round(centerX + Math.sin(angle) * i);
+ let y = Math.round(centerY + Math.cos(angle) * i);
+
+ if (x <= width && x > 0 && y <= height && y > 0) {
+ let position = `\x1B[${y};${x}H`; // Move cursor to y,x (CSI y;x H)
+ response += `${position}${renderer(red, blue, green)} `;
+ }
+ }
+ }
+
+ return response;
+};
+
+module.exports = Circle;
diff --git a/lib/screens/gradients.js b/lib/screens/gradients.js
new file mode 100644
index 0000000..5e63d5b
--- /dev/null
+++ b/lib/screens/gradients.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// Draws moving gradient boxes.
+const Gradients = function (modulation, width, height, renderer) {
+ let response = '';
+
+ for (let i = 0; i < height; i++) {
+ for (let j = 0; j < width; j++) {
+ let red = ((modulation + i) * 255 / height) % 255;
+ let blue = ((modulation + j) * 255 / width) % 255;
+ let green = ((modulation + i * j) * 255 / (width * height)) % 255;
+
+ response = response + renderer(red, blue, green);
+ response = response + ' ';
+ }
+
+ if (i < height - 1) {
+ response = response + '\n';
+ }
+ }
+
+ return response;
+};
+
+module.exports = Gradients;
diff --git a/lib/screens/mirrors.js b/lib/screens/mirrors.js
new file mode 100644
index 0000000..d683fb6
--- /dev/null
+++ b/lib/screens/mirrors.js
@@ -0,0 +1,36 @@
+'use strict';
+
+// Draws small moving gradient boxes and repeats them.
+const Mirrors = function (modulation, width, height, renderer) {
+ let response = [];
+
+ let scale = 2 + Math.round(Math.random() * 4);
+ let scaledHeight = Math.floor(height / scale);
+ let scaledWidth = Math.floor(width / scale);
+
+ for (let i = 0; i < scaledHeight; i++) {
+ let row = [];
+ for (let j = 0; j < scaledWidth; j++) {
+ let red = ((modulation + i) * 255 / height) % 255;
+ let blue = ((modulation + j) * 255 / width) % 255;
+ let green = ((modulation + i * j) * 255 / (width * height)) % 255;
+
+ let cell = [renderer(red, blue, green), ' '];
+ row.push(cell.join(''));
+ }
+
+ let rowText = '';
+ for (let j = 0; j < scale; j++) {
+ rowText += row.reverse().join('');
+ }
+
+ for (let j = 1; j < scale; j++) {
+ response[j * i] = rowText;
+ response[(height - 1 - (j * i))] = rowText;
+ }
+ }
+
+ return response.join('\n');
+};
+
+module.exports = Mirrors;
diff --git a/lib/screens/random.js b/lib/screens/random.js
new file mode 100644
index 0000000..6ac706f
--- /dev/null
+++ b/lib/screens/random.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// random colors
+const Random = function (modulation, width, height, renderer) {
+ let response = '';
+
+ for (let i = 0; i < height; i++) {
+ for (let j = 0; j < width; j++) {
+ let red = Math.floor(Math.random() * 255);
+ let blue = Math.floor(Math.random() * 255);
+ let green = Math.floor(Math.random() * 255);
+
+ response = response + renderer(red, blue, green);
+ response = response + ' ';
+ }
+
+ if (i < height - 1) {
+ response = response + '\n';
+ }
+ }
+
+ return response;
+};
+
+module.exports = Random;
diff --git a/lib/screens/sprinkles.js b/lib/screens/sprinkles.js
new file mode 100644
index 0000000..446f46e
--- /dev/null
+++ b/lib/screens/sprinkles.js
@@ -0,0 +1,28 @@
+'use strict';
+
+// Places random sprinkles in the screen each frame. Same color per
+// frame.
+const Sprinkles = function (modulation, width, height, renderer) {
+ let response = '';
+
+ let maxSprinkleCount = (width * height) / 2;
+ let minSprinkleCount = (width * height) / 8;
+ let sprinkleCount = Math.round(Math.random() * (maxSprinkleCount - minSprinkleCount)) + minSprinkleCount;
+
+ let red = Math.floor(Math.random() * 255);
+ let blue = Math.floor(Math.random() * 255);
+ let green = Math.floor(Math.random() * 255);
+
+ for (let i = 0; i < sprinkleCount; i++) {
+ let x = Math.round(Math.random() * (width - 1)) + 1;
+ let y = Math.round(Math.random() * (height - 1)) + 1;
+
+ let position = `\x1B[${y};${x}H`; // Move cursor to y,x (CSI y;x H)
+
+ response += `${position}${renderer(red, blue, green)} `;
+ }
+
+ return response;
+};
+
+module.exports = Sprinkles;
diff --git a/lib/tomato_sauce.js b/lib/tomato_sauce.js
new file mode 100644
index 0000000..0ff087a
--- /dev/null
+++ b/lib/tomato_sauce.js
@@ -0,0 +1,129 @@
+'use strict';
+
+const Net = require('net');
+const EventEmitter = require('events');
+
+const Util = require('./util');
+
+// Interpret as Command Sequences.
+const kEscape = new Buffer([0xFF, 0xF4, 0XFF, 0xFD, 0x06]); // IAC IP IAC DO TIMING_MARK
+const kNAWSRequest = new Buffer([0xFF, 0xFD, 0X1F]); // IAC DO NAWS
+const kNAWSResponse = new Buffer([0xFF, 0xFB, 0X1F, 0xFF, 0xFA, 0X1F]); // IAC WILL NAWS IAC SB NAWS
+
+// Main tomato sauce class. Properties:
+// * screens <TomatoSauce.IScreen[]>
+// * renderers <TomatoSauce.IRenderer[]>
+// * port <int>
+// * frequency <int>
+// * modulation <int>
+//
+// The main entry point is the #run() function.
+//
+// It emits a listening event that contains the server information on
+// the `server` key inside the `data` property of the event.
+//
+// It also emits an error event that contains the error information on
+// the `error` key inside the `data` property of the event.
+const TomatoSauce = class TomatoSauce extends EventEmitter {
+
+ constructor (config) {
+ super();
+
+ this.screens = [];
+ this.renderers = [];
+
+ Object.assign(this, config || {});
+ }
+
+ // Here's where things get started.
+ run () {
+ this._startServer();
+ this._bindEvents();
+ }
+
+ // Creates a socket, server based on the configuration. Emits the
+ // listening event when ready.
+ _startServer () {
+ const server = Net.createServer();
+ this.server = server;
+ server.listen(this.port, function () {
+ this.emit('listening', {
+ data: {
+ server: server
+ }
+ });
+ }.bind(this));
+ }
+
+ _bindEvents () {
+ // Send the error event all the way up.
+ this.server.on('error', function (err) {
+ this.emit('error', {
+ data: {
+ error: err
+ }
+ });
+ }.bind(this));
+
+ // Send the error event all the way up.
+ this.server.on('connection', function (socket) {
+ this._renderScreen(socket);
+ }.bind(this));
+ }
+
+ // Obtains viewport size, and initializes a random screen with a random
+ // renderer, setting the required interval to draw.
+ _renderScreen (socket) {
+ let connectionData = {
+ width: null,
+ height: null,
+ modulation: 0,
+ screen: this._getScreen(),
+ renderer: this._getRenderer(),
+ socket: socket
+ };
+ let interval = null;
+
+ socket.write(kNAWSRequest);
+
+ socket.on('data', function (data) {
+ if (data.slice(0, 6).compare(kNAWSResponse) === 0) {
+ connectionData.width = Util.parse16BitBuffer(data.slice(6, 8));
+ connectionData.height = Util.parse16BitBuffer(data.slice(8, 10));
+
+ socket.write('\x1B[2J'); // Clear the Screen (CSI 2 J)
+ interval = setInterval(this._writeMessage.bind(this, connectionData), this.frequency);
+ }
+
+ if (data.compare(kEscape) === 0) {
+ socket.write('\n');
+ clearInterval(interval);
+ socket.end();
+ }
+ }.bind(this));
+ }
+
+ // Resets the cursor, gets a frame and sends it to the socket.
+ _writeMessage (connectionData) {
+ let payload = connectionData.screen(connectionData.modulation, connectionData.width, connectionData.height, connectionData.renderer);
+ let message = `\x1B[1;1H${payload}`; // reset cursor position before payload
+
+ connectionData.modulation = (connectionData.modulation + this.modulation) % 256;
+ connectionData.socket.write(message);
+ }
+
+ _getScreen () {
+ return Util.pickRandom(this.screens);
+ }
+
+ _getRenderer () {
+ return Util.pickRandom(this.renderers);
+ }
+};
+
+// Defaults.
+TomatoSauce.prototype.port = 9999;
+TomatoSauce.prototype.frequency = 333;
+TomatoSauce.prototype.modulation = 5;
+
+module.exports = TomatoSauce;
diff --git a/lib/util.js b/lib/util.js
new file mode 100644
index 0000000..3234d3e
--- /dev/null
+++ b/lib/util.js
@@ -0,0 +1,50 @@
+'use strict';
+
+const Fs = require('fs');
+const Path = require('path');
+
+// Module containing utility functions.
+const Util = {
+
+ // Parses a 16 bit number buffer.
+ parse16BitBuffer: function (buffer) {
+ return buffer[0] * 256 + buffer[1];
+ },
+
+ // Picks a random element from an array.
+ pickRandom: function (array) {
+ return array[Math.floor(Math.random() * array.length)];
+ },
+
+ // For a given path, requires all of the files and returns an array
+ // with the results. If the directory contains any non-requireable file,
+ // it will fail.
+ loadFiles: function (path) {
+ return new Promise(function (resolve, reject) {
+ Fs.readdir(path, function (err, files) {
+ if (err) {
+ return reject(err);
+ }
+
+ let loadedFiles = [];
+
+ for (let file of files) {
+ let filePath = Path.join(path, file);
+ let loadedFile;
+
+ try {
+ loadedFile = require(filePath);
+ } catch (err) {
+ return reject(err);
+ }
+
+ loadedFiles.push(loadedFile);
+ }
+
+ resolve(loadedFiles);
+ });
+ });
+ }
+};
+
+module.exports = Util;