diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/renderers/256_colors.js | 23 | ||||
| -rw-r--r-- | lib/renderers/ansi.js | 17 | ||||
| -rw-r--r-- | lib/renderers/fake_color.js | 12 | ||||
| -rw-r--r-- | lib/renderers/true_color.js | 14 | ||||
| -rw-r--r-- | lib/screens/circle.js | 38 | ||||
| -rw-r--r-- | lib/screens/gradients.js | 22 | ||||
| -rw-r--r-- | lib/screens/mirrors.js | 38 | ||||
| -rw-r--r-- | lib/screens/random.js | 22 | ||||
| -rw-r--r-- | lib/screens/sprinkles.js | 34 | ||||
| -rw-r--r-- | lib/tomato_sauce.js | 162 | ||||
| -rw-r--r-- | lib/util.js | 62 |
11 files changed, 287 insertions, 157 deletions
diff --git a/lib/renderers/256_colors.js b/lib/renderers/256_colors.js index e8e6e37..f6e57d3 100644 --- a/lib/renderers/256_colors.js +++ b/lib/renderers/256_colors.js @@ -1,16 +1,19 @@ '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); +/** + * Returns a 256 color, see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + * for more info. + * + * @function 256ColorsRenderer + * @implements IRenderer + */ +module.exports = function (red, blue, green) { - let colorNumber = 16 + 36 * redValue + 6 * greenValue + blueValue; + const redValue = Math.round(red * 5 / 255); + const blueValue = Math.round(blue * 5 / 255); + const greenValue = Math.round(green * 5 / 255); + + const 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 index c3975da..b3a461d 100644 --- a/lib/renderers/ansi.js +++ b/lib/renderers/ansi.js @@ -1,13 +1,16 @@ '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)); +/** + * Returns a basic ansi color, see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + * for more info. + * + * @function ANSIRenderer + * @implements IRenderer + */ +module.exports = function (red, blue, green) { - let colorNumber = 40 + colorOffset; + const colorOffset = Math.round((red + blue + green) * 7 / (255 * 3)); + const colorNumber = 40 + colorOffset; return `\x1B[${colorNumber}m`; }; - -module.exports = ANSI; diff --git a/lib/renderers/fake_color.js b/lib/renderers/fake_color.js index b82bb45..b03309c 100644 --- a/lib/renderers/fake_color.js +++ b/lib/renderers/fake_color.js @@ -1,8 +1,12 @@ 'use strict'; -// Sends malformed ANSI 24-bit color strings -const FakeColor = function (red, blue, green) { +/** + * Returns a malformed 24-bit ansi color + * + * @function FakeColorRenderer + * @implements IRenderer + */ +module.exports = 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 index 5f7725f..811d186 100644 --- a/lib/renderers/true_color.js +++ b/lib/renderers/true_color.js @@ -4,8 +4,16 @@ // 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) { +/** + * Returns an ANSI code for 24-bit True Color, see + * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors and look for + * 24-bit colors for more info. Only looks good in supported terminals, + * otherwise looks like FakeColor + * + * @function TrueColorRenderer + * @implements IRenderer + */ +module.exports = 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 index d8a4d10..720e250 100644 --- a/lib/screens/circle.js +++ b/lib/screens/circle.js @@ -1,26 +1,32 @@ 'use strict'; -// Draws concentric circles. Each ring has its own color. -const Circle = function (modulation, width, height, renderer) { - let response = []; +/** + * Draws concentric circles. Each ring has its own color. + * + * @function CircleScreen + * @implements IScreen + */ +module.exports = function (modulation, width, height, renderer) { - let circles = width > height ? height : width; + const response = []; - for (let i = 0; i < circles; i++) { - let centerX = Math.round(width / 2) + 1; - let centerY = Math.round(height / 2) + 1; + const circles = width > height ? height : width; - 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 < circles; ++i) { + const centerX = Math.round(width / 2) + 1; + const centerY = Math.round(height / 2) + 1; - 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); + const red = Math.floor(Math.random() * 255); + const blue = Math.floor(Math.random() * 255); + const green = Math.floor(Math.random() * 255); + + for (let j = 0; j < 180; ++j) { + const angle = 2 * j * (Math.PI / 180); + const x = Math.round(centerX + Math.sin(angle) * i); + const 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) + const position = `\x1B[${y};${x}H`; // Move cursor to y,x (CSI y;x H) response += `${position}${renderer(red, blue, green)} `; } } @@ -28,5 +34,3 @@ const Circle = function (modulation, width, height, renderer) { return response; }; - -module.exports = Circle; diff --git a/lib/screens/gradients.js b/lib/screens/gradients.js index 5e63d5b..2d4a2f0 100644 --- a/lib/screens/gradients.js +++ b/lib/screens/gradients.js @@ -1,14 +1,20 @@ 'use strict'; -// Draws moving gradient boxes. -const Gradients = function (modulation, width, height, renderer) { +/** + * Draws moving gradient boxes + * + * @function GradientsScreen + * @implements IScreen + */ +module.exports = 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; + for (let i = 0; i < height; ++i) { + for (let j = 0; j < width; ++j) { + const red = ((modulation + i) * 255 / height) % 255; + const blue = ((modulation + j) * 255 / width) % 255; + const green = ((modulation + i * j) * 255 / (width * height)) % 255; response = response + renderer(red, blue, green); response = response + ' '; @@ -21,5 +27,3 @@ const Gradients = function (modulation, width, height, renderer) { return response; }; - -module.exports = Gradients; diff --git a/lib/screens/mirrors.js b/lib/screens/mirrors.js index d683fb6..e842217 100644 --- a/lib/screens/mirrors.js +++ b/lib/screens/mirrors.js @@ -1,30 +1,36 @@ 'use strict'; -// Draws small moving gradient boxes and repeats them. -const Mirrors = function (modulation, width, height, renderer) { - let response = []; +/** + * Draws small moving gradient boxes and repeats them. + * + * @function MirrorsScreen + * @implements IScreen + */ +module.exports = function (modulation, width, height, renderer) { - let scale = 2 + Math.round(Math.random() * 4); - let scaledHeight = Math.floor(height / scale); - let scaledWidth = Math.floor(width / scale); + const response = []; - 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; + const scale = 2 + Math.round(Math.random() * 4); + const scaledHeight = Math.floor(height / scale); + const scaledWidth = Math.floor(width / scale); - let cell = [renderer(red, blue, green), ' ']; + for (let i = 0; i < scaledHeight; ++i) { + const row = []; + for (let j = 0; j < scaledWidth; ++j) { + const red = ((modulation + i) * 255 / height) % 255; + const blue = ((modulation + j) * 255 / width) % 255; + const green = ((modulation + i * j) * 255 / (width * height)) % 255; + + const cell = [renderer(red, blue, green), ' ']; row.push(cell.join('')); } let rowText = ''; - for (let j = 0; j < scale; j++) { + for (let j = 0; j < scale; ++j) { rowText += row.reverse().join(''); } - for (let j = 1; j < scale; j++) { + for (let j = 1; j < scale; ++j) { response[j * i] = rowText; response[(height - 1 - (j * i))] = rowText; } @@ -32,5 +38,3 @@ const Mirrors = function (modulation, width, height, renderer) { return response.join('\n'); }; - -module.exports = Mirrors; diff --git a/lib/screens/random.js b/lib/screens/random.js index 6ac706f..0cacea7 100644 --- a/lib/screens/random.js +++ b/lib/screens/random.js @@ -1,14 +1,20 @@ 'use strict'; -// random colors -const Random = function (modulation, width, height, renderer) { +/** + * Draws random colors + * + * @function RandomScreen + * @implements IScreen + */ +module.exports = 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); + for (let i = 0; i < height; ++i) { + for (let j = 0; j < width; ++j) { + const red = Math.floor(Math.random() * 255); + const blue = Math.floor(Math.random() * 255); + const green = Math.floor(Math.random() * 255); response = response + renderer(red, blue, green); response = response + ' '; @@ -21,5 +27,3 @@ const Random = function (modulation, width, height, renderer) { return response; }; - -module.exports = Random; diff --git a/lib/screens/sprinkles.js b/lib/screens/sprinkles.js index 446f46e..f12200e 100644 --- a/lib/screens/sprinkles.js +++ b/lib/screens/sprinkles.js @@ -1,28 +1,32 @@ 'use strict'; -// Places random sprinkles in the screen each frame. Same color per -// frame. -const Sprinkles = function (modulation, width, height, renderer) { +/** + * Draws random sprinkles in the screen each frame. Same color per + * frame. + * + * @function SprinklesScreen + * @implements IScreen + */ +module.exports = 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; + const maxSprinkleCount = (width * height) / 2; + const minSprinkleCount = (width * height) / 8; + const 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); + const red = Math.floor(Math.random() * 255); + const blue = Math.floor(Math.random() * 255); + const 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; + for (let i = 0; i < sprinkleCount; ++i) { + const x = Math.round(Math.random() * (width - 1)) + 1; + const y = Math.round(Math.random() * (height - 1)) + 1; - let position = `\x1B[${y};${x}H`; // Move cursor to y,x (CSI y;x H) + const 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 index 0ff087a..f3017ea 100644 --- a/lib/tomato_sauce.js +++ b/lib/tomato_sauce.js @@ -5,89 +5,150 @@ 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 +const internals = { + // Interpret as Command Sequences. + kEscape: new Buffer([0xFF, 0xF4, 0XFF, 0xFD, 0x06]), // IAC IP IAC DO TIMING_MARK + kNAWSRequest: new Buffer([0xFF, 0xFD, 0X1F]), // IAC DO NAWS + kNAWSResponse: new Buffer([0xFF, 0xFB, 0X1F, 0xFF, 0xFA, 0X1F]) // IAC WILL NAWS IAC SB NAWS +}; + +/** + * A function that represents a screen, it is called frequently and should + * return a string consisting of commands to run. + * + * @interface IScreen + * @type function + * @param {Number} modulation A number between 0 and 255 representing the current + * step of the modulation + * @param {Number} width The width of the screen + * @param {Number} height The height of the screen + * @param {IRenderer} renderer The renderer used to colorize the scfeen + * @return {String} The commands used to render the screen elements + */ + +/** + * A function that represents a renderer, it should take in a color in RGB and + * return a string with the appropriate code to colorize the terminal. + * + * @interface IRenderer + * @type function + * @param {Number} red The red component of the color between 0 and 255 + * @param {Number} green The green component of the color between 0 and 255 + * @param {Number} blue The green component of the color between 0 and 255 + * @return {String} The commands used to colorize the terminal + */ -// 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. +/** + * The main application for tomato sauce. Listens for connections and serves + * random combinations of screens and renderers + * + * 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. + * + * @class TomatoSauce + * @extends EventEmitter + * + * @param {object} config the configuration object used to extend the properties. + * + * @property {Array<IScreen>} screens an array of screens available to serve + * @property {Array<IRenderer>} renderers an array of renderers available to colorize + * @property {Number} [port=9999] the port to listen on + * @property {Number} [frequency=333] how often to update the screen + * @property {Number} [modulation=5] number between 0-255 depicting current modulation step + */ const TomatoSauce = class TomatoSauce extends EventEmitter { - constructor (config) { + constructor(config = {}) { + super(); this.screens = []; this.renderers = []; - Object.assign(this, config || {}); + // Defaults. + this.port = 9999; + this.frequency = 333; + this.modulation = 5; + + Object.assign(this, config); } - // Here's where things get started. - run () { + /** + * Main entry point, initializes the server and binds events for connections + * + * @function run + * @instance + * @memberof TomatoSauce + */ + run() { + this._startServer(); this._bindEvents(); } // Creates a socket, server based on the configuration. Emits the // listening event when ready. - _startServer () { + + _startServer() { + const server = Net.createServer(); this.server = server; - server.listen(this.port, function () { + server.listen(this.port, () => { + this.emit('listening', { data: { - server: server + server } }); - }.bind(this)); + }); } - _bindEvents () { + // Binds the connection and error events + + _bindEvents() { + // Send the error event all the way up. - this.server.on('error', function (err) { + this.server.on('error', (error) => { + this.emit('error', { data: { - error: err + error } }); - }.bind(this)); + }); // Send the error event all the way up. - this.server.on('connection', function (socket) { + this.server.on('connection', (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 = { + + _renderScreen(socket) { + + const connectionData = { width: null, height: null, modulation: 0, screen: this._getScreen(), renderer: this._getRenderer(), - socket: socket + socket }; - let interval = null; + const interval = null; + + socket.write(internals.kNAWSRequest); - socket.write(kNAWSRequest); + socket.on('data', (data) => { - socket.on('data', function (data) { - if (data.slice(0, 6).compare(kNAWSResponse) === 0) { + if (data.slice(0, 6).compare(internals.kNAWSResponse) === 0) { connectionData.width = Util.parse16BitBuffer(data.slice(6, 8)); connectionData.height = Util.parse16BitBuffer(data.slice(8, 10)); @@ -95,35 +156,34 @@ const TomatoSauce = class TomatoSauce extends EventEmitter { interval = setInterval(this._writeMessage.bind(this, connectionData), this.frequency); } - if (data.compare(kEscape) === 0) { + if (data.compare(internals.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 + + _writeMessage(connectionData) { + + const payload = connectionData.screen(connectionData.modulation, connectionData.width, connectionData.height, connectionData.renderer); + const message = `\x1B[1;1H${payload}`; // reset cursor position before payload connectionData.modulation = (connectionData.modulation + this.modulation) % 256; connectionData.socket.write(message); } - _getScreen () { + _getScreen() { + return Util.pickRandom(this.screens); } - _getRenderer () { + _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 index 3234d3e..65f14ae 100644 --- a/lib/util.js +++ b/lib/util.js @@ -3,38 +3,70 @@ const Fs = require('fs'); const Path = require('path'); -// Module containing utility functions. +/** + * Module containing utility functions + * + * @name Util + * @type Object + */ const Util = { - // Parses a 16 bit number buffer. - parse16BitBuffer: function (buffer) { + /** + * Parses a 16 bit number buffer + * + * @function parse16BitBuffer + * @memberof Util + * @param {Array<String>} buffer the buffer to parse + * @return {Number} the parsed value + */ + parse16BitBuffer(buffer) { + return buffer[0] * 256 + buffer[1]; }, - // Picks a random element from an array. - pickRandom: function (array) { + /** + * Picks a random element from an array + * + * @function pickRandom + * @memberof Util + * @param {Array} array the array to use + * @return {Any} the picked element + */ + pickRandom(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) { + /** + * For a gi ven path, requires all of the files and returns an array + * with the results. If the directory contains any non-requireable + * files, it will fail. + * + * @function loadFiles + * @memberof Util + * @param {String} path the path where the files are located + * @return {Array} the array of all the loaded modules + */ + loadFiles(path) { + + return new Promise((resolve, reject) => { + + Fs.readdir(path, (err, files) => { + if (err) { return reject(err); } - let loadedFiles = []; + const loadedFiles = []; - for (let file of files) { - let filePath = Path.join(path, file); + for (const file of files) { + const filePath = Path.join(path, file); let loadedFile; try { loadedFile = require(filePath); - } catch (err) { + } + catch (err) { return reject(err); } |