randomart-js
Version:
Generates a randomart image from a buffer or hexadecimal string
229 lines (202 loc) • 7.67 kB
JavaScript
var R = require('ramda')
// Default options
var defaults = {
height: 9,
width: 17,
trail: false,
values: [' ', '.', 'o', '+', '=', '*', 'B', 'O', 'X', '@', '%', '&', '#', '/', '^']
}
// Renders the randomart image from a given hash
var render = function render(hash, options) {
var options = R.merge(defaults, typeof options === 'object' ? options : {})
if (!(hash instanceof Buffer || typeof hash === 'string') || hash.length === 0) {
throw TypeError('You must pass in a non-zero length hash or string')
}
if (!(typeof options.height === 'number') || !(typeof options.width === 'number')) {
throw TypeError('The height and width options must be numbers')
}
if (!(typeof options.trail === 'boolean')) {
throw TypeError('The trail option must be a boolean')
}
if (!(options.values instanceof Array) ||
!options.values.every(function(value) { return typeof value === 'string' && value.length === 1 })) {
throw TypeError('The values option must an array of single character strings')
}
if ((options.height % 2 !== 1) || (options.width % 2 !== 1)) {
throw Error('The height and width options must be odd numbers')
}
if (hash.length % 2 !== 0) {
throw Error('The hash length must have an even number')
}
var width = options.width || 17
, height = options.height || 9
, trail = options.trail || false
, values = options.values || [' ', '.', 'o', '+', '=', '*', 'B', 'O', 'X', '@', '%', '&', '#', '/', '^']
var pairs = bufferToBinaryPairs(typeof hash === 'string' ? Buffer.from(hash, 'hex') : hash)
, walk = getWalk(pairs, width, height)
, grid = R.repeat([], height).map(function(line) { return R.repeat(' ', width) })
var updateGrid = gridReducer(values)
if (trail)
return walk.reduce(function(grids, coord, idx, walk) {
return grids.concat([updateGrid(R.last(grids), coord, idx, walk)])
}, [grid])
else
return walk.reduce(updateGrid, grid)
}
// Converts a hex string to a buffer
var hexToBuffer = function hexToBuffer(str) {
return Buffer.from(str, 'hex')
}
// The reducer for converting the walk into the grid
var gridReducer = function gridReducer(values) {
return function updateGrid(grid, coord, idx, walk) {
if (idx === walk.length - 1) {
return R.pipe( R.set(R.lensPath([walk[0].y, walk[0].x]), 'S')
, R.set(R.lensPath([coord.y, coord.x]), 'E')
)(grid)
}
var newValue = values[values.indexOf(grid[coord.y][coord.x]) + 1]
return R.set(R.lensPath([coord.y, coord.x]), newValue, grid)
}
}
// Pretty prints a 2d array (matrix)
var gridToString = function gridToString(grid) {
return grid.map(function(line) { return '['+ line.join('][') +']' }).join('\n')
}
// Converts a buffer to a binary pairs array
var bufferToBinaryPairs = function bufferToBinaryPairs(buffer) {
var str = []
buffer.forEach(function(value) {
var binaryValue = value.toString(2)
if (binaryValue.length < 8)
binaryValue = R.repeat('0', 8 - binaryValue.length).join('') + binaryValue
str.push(binaryValue)
})
return R.flatten(str.map(R.pipe(R.splitEvery(2), R.reverse)))
}
// Gets the walk from a set of pairs and the box's height and width
var getWalk = function getWalk(pairs, width, height) {
var width = width || defaults.width
, height = height || defaults.height
var initialPosition = { x: (width - 1) / 2, y: (height - 1) / 2 }
return pairs.reduce(function(acc, pair) {
var position = acc[acc.length - 1]
var noMove = position
, X = position.x
, Y = position.y
, leftEdge = 0
, topEdge = 0
, rightEdge = width - 1
, bottomEdge = height - 1
, leftX = position.x - 1
, rightX = position.x + 1
, upY = position.y - 1
, downY = position.y + 1
, moveLeft = { x: leftX , y: Y }
, moveRight = { x: rightX, y: Y }
, moveUp = { x: X , y: upY }
, moveDown = { x: X , y: downY }
, moveUpLeft = { x: leftX , y: upY }
, moveUpRight = { x: rightX, y: upY }
, moveDownLeft = { x: leftX , y: downY }
, moveDownRight = { x: rightX, y: downY }
// Top-left position (a)
if (R.equals(position, { x: leftEdge, y: topEdge })) {
switch (pair) {
case '00': return acc.concat(position)
case '01': return acc.concat(moveRight)
case '10': return acc.concat(moveDown)
case '11': return acc.concat(moveDownRight)
}
}
// Top-right position (b)
else if (R.equals(position, { x: rightEdge, y: topEdge })) {
switch (pair) {
case '00': return acc.concat(moveLeft)
case '01': return acc.concat(position)
case '10': return acc.concat(moveDownLeft)
case '11': return acc.concat(moveDown)
}
}
// Bottom-left position (c)
else if (R.equals(position, { x: leftEdge, y: bottomEdge })) {
switch (pair) {
case '00': return acc.concat(moveUp)
case '01': return acc.concat(moveUpRight)
case '10': return acc.concat(position)
case '11': return acc.concat(moveRight)
}
}
// Bottom-right position (d)
else if (R.equals(position, { x: rightEdge, y: bottomEdge })) {
switch (pair) {
case '00': return acc.concat(moveUpLeft)
case '01': return acc.concat(moveUp)
case '10': return acc.concat(moveLeft)
case '11': return acc.concat(position)
}
}
// Top position (T)
else if (position.y === topEdge) {
switch (pair) {
case '00': return acc.concat(moveLeft)
case '01': return acc.concat(moveRight)
case '10': return acc.concat(moveDownLeft)
case '11': return acc.concat(moveDownRight)
}
}
// Bottom position (B)
else if (position.y === bottomEdge) {
switch (pair) {
case '00': return acc.concat(moveUpLeft)
case '01': return acc.concat(moveUpRight)
case '10': return acc.concat(moveLeft)
case '11': return acc.concat(moveRight)
}
}
// Left position (L)
else if (position.x === leftEdge) {
switch (pair) {
case '00': return acc.concat(moveUp)
case '01': return acc.concat(moveUpRight)
case '10': return acc.concat(moveDown)
case '11': return acc.concat(moveDownRight)
}
}
// Right position (R)
else if (position.x === rightEdge) {
switch (pair) {
case '00': return acc.concat(moveUpLeft)
case '01': return acc.concat(moveUp)
case '10': return acc.concat(moveDownLeft)
case '11': return acc.concat(moveDown)
}
}
// Middle position (M)
else {
switch (pair) {
case '00': return acc.concat(moveUpLeft)
case '01': return acc.concat(moveUpRight)
case '10': return acc.concat(moveDownLeft)
case '11': return acc.concat(moveDownRight)
}
}
}, [initialPosition])
}
// Converts the walk into the numeric format found in the drunken bishop paper
var walkToNumeric = function(walk, width, height) {
var width = width || defaults.width
, height = height || defaults.height
var initialPosition = (height - 1) / 2 * width + (width - 1) / 2
return walk.reduce(function(acc, coord) {
return acc.concat(coord.y * width + coord.x)
}, [])
}
module.exports = {
render: render,
hexToBuffer: hexToBuffer,
gridToString: gridToString,
bufferToBinaryPairs: bufferToBinaryPairs,
getWalk: getWalk,
walkToNumeric: walkToNumeric
}