diff options
| author | Ben Beltran <ben@nsovocal.com> | 2016-05-09 00:31:25 -0500 |
|---|---|---|
| committer | Ben Beltran <ben@nsovocal.com> | 2016-05-09 00:31:25 -0500 |
| commit | a140f3add912ef4bd659c1c1e82184643be7d845 (patch) | |
| tree | 131f33520cc40abc387474fa9c2ac4f536e6d7d1 /lib | |
| parent | ae6dbe43bf54581dd7012f95581d4a1b6ee245f0 (diff) | |
| parent | 09ab7e985d46cad6d3c89e37f983e286e9fda2f6 (diff) | |
Merge branch 'feature/the-actual-tomato-sauce' into develop
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/renderers/256_colors.js | 16 | ||||
| -rw-r--r-- | lib/renderers/ansi.js | 13 | ||||
| -rw-r--r-- | lib/renderers/fake_color.js | 8 | ||||
| -rw-r--r-- | lib/renderers/true_color.js | 11 | ||||
| -rw-r--r-- | lib/screens/circle.js | 32 | ||||
| -rw-r--r-- | lib/screens/gradients.js | 25 | ||||
| -rw-r--r-- | lib/screens/mirrors.js | 36 | ||||
| -rw-r--r-- | lib/screens/random.js | 25 | ||||
| -rw-r--r-- | lib/screens/sprinkles.js | 28 | ||||
| -rw-r--r-- | lib/tomato_sauce.js | 129 | ||||
| -rw-r--r-- | lib/util.js | 50 |
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; |