From d9b810676619d28913d5ba26c5f6d8f385dfab50 Mon Sep 17 00:00:00 2001 From: Dennis Theisen Date: Mon, 3 Sep 2018 20:15:23 -0400 Subject: [PATCH] Refactor hex conversion without decimal and big --- package-lock.json | 14 +-- package.json | 2 - src/components/Board.vue | 19 +-- src/pixel_pic.js | 90 ++++++++------ src/router/index.js | 4 +- src/store.js | 4 +- test/unit/spec_helper.js | 38 +++++- test/unit/specs/pixel_pic.spec.js | 200 ++++++++++++------------------ 8 files changed, 181 insertions(+), 190 deletions(-) diff --git a/package-lock.json b/package-lock.json index e306f5a..bf4eae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1618,11 +1618,6 @@ "tryer": "^1.0.0" } }, - "big.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.1.2.tgz", - "integrity": "sha512-qG6ZOc1lY84Bn8p/z9xvJisj9F4PRyo0pOGqGNYc7gS3p1WciS/3XcLuNI3Z/yYZpMNFhHeX3YNENwgrQq0NTA==" - }, "bigi": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", @@ -3165,11 +3160,6 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, - "decimal.js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.1.tgz", - "integrity": "sha512-vklWB5C4Cj423xnaOtsUmAv0/7GqlXIgDv2ZKDyR64OV3OSzGHNx2mk4p/1EKnB5s70k73cIOOEcG9YzF0q4Lw==" - }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -13113,7 +13103,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -14965,7 +14955,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/package.json b/package.json index e9d9ff5..55a0a02 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,7 @@ "deploy": "npm run build; push-dir --dir=dist --branch=gh-pages --cleanup" }, "dependencies": { - "big.js": "^5.1.0", "bootstrap-vue": "^v2.0.0-rc.11", - "decimal.js": "^10.0.1", "scatter-js": "^2.4.0", "vue": "^2.5.2", "vue-analytics": "^5.14.0", diff --git a/src/components/Board.vue b/src/components/Board.vue index e371bda..3ea35c1 100644 --- a/src/components/Board.vue +++ b/src/components/Board.vue @@ -5,14 +5,7 @@ - - - - - - - - + @@ -106,7 +99,7 @@ import Cell from './Cell.vue' import HintSeries from './HintSeries.vue' import CopyButton from './CopyButton.vue' -import { resizeCells, cellsToBinaryString} from '../pixel_pic' +import { resizeCells, cellsToPixelMap, cellsToEosHex } from '../pixel_pic' import { hintsForCells } from '../hint_generator' import { mapState, mapGetters, mapMutations } from 'vuex' @@ -137,11 +130,11 @@ export default { ...mapGetters([ 'nextId' ]), - cellsToBinaryString () { - return cellsToBinaryString(this.rows) + cellsToPixelMap () { + return cellsToPixelMap(this.rows) }, - cellsToHex () { - return cellsToHex(this.rows) + cellsToEosHex () { + return cellsToEosHex(this.rows) }, rows () { return this.cells diff --git a/src/pixel_pic.js b/src/pixel_pic.js index 2357475..2f9584b 100644 --- a/src/pixel_pic.js +++ b/src/pixel_pic.js @@ -4,13 +4,11 @@ cells (Vue) <-> binaryString <-> binaryNumber <-> hex (EOS) cells: Array of objects with filled attributes set to true where a pixel should be - binaryString: Shareable format for pixelpics e.g. this represents a small square: 3x3:111101111 - binaryNumber: A specially crafted 128 bit number that represents the pixelpic data as well as dimension definitions + pixelMap: Shareable format for pixelpics, e.g. this represents a small square: 3x3:111101111 + binaryString: A specially crafted 128 bit binary number that represents the pixelpic data and dimension definitions hex: The binary number converted to hex in EOS format with leading bits reversed */ -import Decimal from 'decimal.js' - const DIMENSION_DELIMITER = 'x' const DATA_DELIMITER = ':' @@ -39,15 +37,23 @@ function resizeCells (cells, targetRowSize, targetColumnSize) { return newCells } -function cellsToHex (cells) { - const binaryString = cellsToBinaryString(cells) - const binaryNumber = binaryStringToBinaryNumber(binaryString) - return decimalToHex(binaryNumber) +function cellsToEosHex (cells) { + const pixelMap = cellsToPixelMap(cells) + const binaryString = pixelMapToBinaryString(pixelMap) + const hex = binaryStringToHex(binaryString) + return hexToEosHex(hex) +} + +function eosHexToCells (eosHex) { + const hex = eosHexToHex(eosHex) + const binaryString = hexToBinaryString(hex) + const pixelMap = binaryStringToPixelMap(binaryString) + return pixelMapToCells(pixelMap) } // Input: 2-dimensional Array of cells // Output: e.g."3x3:101101101" -function cellsToBinaryString (cells) { +function cellsToPixelMap (cells) { const rowSize = cells.length const columnSize = cells[0].length const binaryRows = cellsToBinaryRows(cells).map(rows => rows.join('')).join('') @@ -56,7 +62,7 @@ function cellsToBinaryString (cells) { // Input: e.g."3x3:101101101" // Output: 2-dimensional Array of cells -function binaryStringToCells (binaryString) { +function pixelMapToCells (binaryString) { const [sizeInfo, cellData] = binaryString.split(DATA_DELIMITER) if (cellData === undefined) throw new Error(`Expected binary string to contain ${DATA_DELIMITER} to denote the board size.`) @@ -71,40 +77,49 @@ function binaryStringToCells (binaryString) { }) } -function binaryStringToBinaryNumber (binaryString) { +function pixelMapToBinaryString (binaryString) { const [dimensions, binaryData] = binaryString.split(':') const [x, y] = dimensions.split(DIMENSION_DELIMITER) const xBinary = (parseInt(x)).toString(2).padStart(5, '0') const yBinary = (parseInt(y)).toString(2).padStart(5, '0') - const binaryNumber = `${binaryData}${yBinary}${xBinary}`.padStart(128, '0') - return Decimal(`0b${binaryNumber}`) + return `${binaryData}${yBinary}${xBinary}`.padStart(128, '0') } -function binaryNumberToBinaryString (binaryNumber) { - const decimalBinaryNumber = binaryExponentialToBinary(new Decimal(binaryNumber).toBinary(130)) - const binaryNumberAsString = decimalBinaryNumber.padStart(128, 0) - const x = parseInt(binaryNumberAsString.slice(-5), 2) - const y = parseInt(binaryNumberAsString.slice(-10, -5), 2) +function binaryStringToPixelMap (binaryString) { + const binaryStringPadded = binaryString.padStart(128, 0) + const x = parseInt(binaryStringPadded.slice(-5), 2) + const y = parseInt(binaryStringPadded.slice(-10, -5), 2) const pixelCount = (x * y) || 1 - const binaryDataPadded = binaryNumberAsString.slice(0, -10) + const binaryDataPadded = binaryStringPadded.slice(0, -10) const binaryData = binaryDataPadded.slice(-pixelCount) return `${x}${DIMENSION_DELIMITER}${y}${DATA_DELIMITER}${binaryData}` } -function decimalToHex (decimal, pad = 32) { - // TODO: Fix toHex - const [, hexdata] = new Decimal(decimal).toHex().split('x') - const hexdataPadded = hexdata.padStart(pad, '0') - const hexdataReversed = chunk(hexdataPadded, 2).reverse().join('') - return `0x${hexdataReversed}` +function binaryStringToHex (binaryString) { + const binaryStringPadded = binaryString.padStart(128, '0') + const bytes = chunk(binaryStringPadded, 8) + return bytes.map((byte) => { + return parseInt(byte, 2).toString(16).padStart(2, '0') + }).join('') +} + +function hexToBinaryString (hexString) { + const bytes = chunk(hexString, 2) + return bytes.map((byte) => { + return parseInt(byte, 16).toString(2).padStart(8, '0') + }).join('') +} + +function hexToEosHex (hex) { + const reversedHex = chunk(hex, 2).reverse().join('') + return `0x${reversedHex}` } -function hexToDecimal (hexString) { - const [, hexdata] = hexString.split('x') - const hexdataReversed = chunk(hexdata, 2).reverse().join('') - return new Decimal(`0x${hexdataReversed}`) +function eosHexToHex (eosHex) { + const hexReversed = eosHex.slice(2) + return chunk(hexReversed, 2).reverse().join('') } // private @@ -133,11 +148,14 @@ function cellsToBinaryRows (cells) { export { createCells, resizeCells, - cellsToHex, - cellsToBinaryString, - binaryStringToCells, - binaryStringToBinaryNumber, - binaryNumberToBinaryString, - decimalToHex, - hexToDecimal + cellsToEosHex, + eosHexToCells, + cellsToPixelMap, + pixelMapToCells, + pixelMapToBinaryString, + binaryStringToPixelMap, + binaryStringToHex, + hexToBinaryString, + hexToEosHex, + eosHexToHex } diff --git a/src/router/index.js b/src/router/index.js index 3172024..6df32bf 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -2,7 +2,7 @@ import Vue from 'vue' import Router from 'vue-router' import Board from '../components/Board.vue' import Tutorial from '../views/Tutorial.vue' -import { binaryStringToCells, createCells } from '../pixel_pic' +import { pixelMapToCells, createCells } from '../pixel_pic' import BINARY_CELLS from '../cell_data' Vue.use(Router) @@ -21,7 +21,7 @@ export default new Router({ props (route) { const indexInt = parseInt(route.params.id) - 1 const [title, binaryString] = Object.entries(BINARY_CELLS)[indexInt] - const cells = binaryStringToCells(binaryString) + const cells = pixelMapToCells(binaryString) return { params: { editMode: false, diff --git a/src/store.js b/src/store.js index 9e213fe..f57dd10 100644 --- a/src/store.js +++ b/src/store.js @@ -1,6 +1,6 @@ import Vue from 'vue' import Vuex from 'vuex' -import { binaryStringToCells } from './pixel_pics' +import { pixelMapToCells } from './pixel_pic' import BINARY_CELLS from './cell_data' import { EOS_MAIN_NET } from './constants' @@ -9,7 +9,7 @@ Vue.use(Vuex) const store = new Vuex.Store({ state: { default: { - cells: binaryStringToCells(Object.values(BINARY_CELLS)[1]) + cells: pixelMapToCells(Object.values(BINARY_CELLS)[1]) }, cells: [], editMode: true, diff --git a/test/unit/spec_helper.js b/test/unit/spec_helper.js index 4fd2a69..37ae125 100644 --- a/test/unit/spec_helper.js +++ b/test/unit/spec_helper.js @@ -1,9 +1,39 @@ +import { + binaryStringToHex, + hexToBinaryString +} from '../../src/pixel_pic' + +function normalizeCells (cells) { + cells.map((row) => { + return row.map((cell) => { + return { filled: cell.filled } + }) + }) +} + expect.extend({ - toBe64BitHex (received) { - const [prefix, bytes] = received.split('x') + fromBinaryStringToHexToBe (binaryString, expected) { + const hex = binaryStringToHex(binaryString.padStart(128, '0')) + const expectedPadded = expected.padStart(32, '0') return { - message: () => `expected ${received} to be 32 characters long hex string starting with 0x`, - pass: (prefix === '0') && (bytes.length === 32) + message: () => `expected ${expectedPadded} but was ${hex}`, + pass: hex === expectedPadded + } + }, + fromHexToBinaryStringToBe (hex, expected) { + const binaryString = hexToBinaryString(hex.padStart(32, '0')) + const expectedPadded = expected.padStart(128, '0') + return { + message: () => `expected ${expectedPadded} but was ${binaryString}`, + pass: binaryString === expectedPadded + } + }, + toEqualFilledCells (cells, expectedCells) { + const normalizedCells = normalizeCells(cells) + const normalizedExpectedCells = normalizeCells(expectedCells) + return { + message: () => `expected ${normalizedExpectedCells} but was ${normalizedCells}`, + pass: normalizedCells === normalizedExpectedCells } } }) diff --git a/test/unit/specs/pixel_pic.spec.js b/test/unit/specs/pixel_pic.spec.js index 795a3bf..68b57db 100644 --- a/test/unit/specs/pixel_pic.spec.js +++ b/test/unit/specs/pixel_pic.spec.js @@ -1,170 +1,132 @@ import '../spec_helper' -import { Decimal } from 'decimal.js' import { - binaryStringToBinaryNumber, - binaryNumberToBinaryString, - decimalToHex, - hexToDecimal, - cellsToHex, + cellsToEosHex, + eosHexToCells, createCells } from '../../../src/pixel_pic' -describe('binaryStringToBinaryNumber', () => { - it('returns 0 for empty board', () => { - const binaryString = '0x0:0' - const binaryNumber = binaryStringToBinaryNumber(binaryString) - expect(binaryNumber.toString()).toBe('0') +describe('cellsToEosHex', () => { + it('handles 1x1:0', () => { + const cells = createCells(1, 1) + expect(cellsToEosHex(cells)).toBe('0x21000000000000000000000000000000') }) - it('returns 33 for 00000100001', () => { - const binaryString = '1x1:0' - const binaryNumber = binaryStringToBinaryNumber(binaryString) - expect(binaryNumber.toString()).toBe('33') + it('handles 1x1:1', () => { + const cells = createCells(1, 1, () => { + return { filled: true } + }) + expect(cellsToEosHex(cells)).toBe('0x21040000000000000000000000000000') }) - it('returns 1057 for 100000100001', () => { - const binaryString = '1x1:1' - const binaryNumber = binaryStringToBinaryNumber(binaryString) - expect(binaryNumber.toString()).toBe('1057') + it('handles 10x10:1', () => { + const cells = createCells(10, 10, () => { + return { filled: true } + }) + expect(cellsToEosHex(cells)).toBe('0x4afdffffffffffffffffffffff3f0000') }) - it('handles 5x5 with every pixel filled out', () => { - const binaryString = '5x5:1111111111111111111111111' - const binaryNumber = binaryStringToBinaryNumber(binaryString) - // 2**5 * 2**5 * 2**25 - (16+8+2+512+256+64) <-- why does -1 have to be added to make test pass? - // 016 008 004 002 001 - // 512 256 128 064 032 - expect(binaryNumber.toFixed(0)).toBe('34359737509') + it('handles 10x10:0', () => { + const cells = createCells(10, 10, () => { + return { filled: false } + }) + expect(cellsToEosHex(cells)).toBe('0x4a010000000000000000000000000000') }) - it('handles 10x10 with every pixel filled out', () => { - const binaryString = '10x10:1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' - const binaryNumber = binaryStringToBinaryNumber(binaryString) - // 2**5 * 2**5 * 2**100 - (16+4+1+512+128+32) <-- why does -1 have to be added to make test pass? - // 016 008 004 002 001 - // 512 256 128 064 032 - expect(binaryNumber.toFixed(0)).toBe('1298074214633706907132624082304330') - }) -}) + it('returns different hex for differently filled cells', () => { + const cells = createCells(10, 10, () => { + return { filled: true } + }) + const differentCells = JSON.parse(JSON.stringify(cells)) + differentCells[0][0] = { filled: false } -describe('binaryNumberToBinaryString', () => { - it('returns 0x0:0 for 0', () => { - const binaryNumber = '0' - const binaryString = binaryNumberToBinaryString(binaryNumber) - expect(binaryString).toBe('0x0:0') + expect(cellsToEosHex(cells)).not.toBe(cellsToEosHex(differentCells)) }) +}) - it('returns 1x1:0 for 33', () => { - const binaryNumber = '33' - const binaryString = binaryNumberToBinaryString(binaryNumber) - expect(binaryString).toBe('1x1:0') +describe('eosHexToCells', () => { + it('handles 1x1:0', () => { + const cells = createCells(1, 1) + expect(eosHexToCells('0x21000000000000000000000000000000')).toEqualFilledCells(cells) }) - it('returns 1x1:1 for 1057', () => { - const binaryNumber = '1057' - const binaryString = binaryNumberToBinaryString(binaryNumber) - expect(binaryString).toBe('1x1:1') + it('handles 1x1:1', () => { + const cells = createCells(1, 1, () => { + return { filled: true } + }) + expect(eosHexToCells('0x21040000000000000000000000000000')).toEqualFilledCells(cells) }) - it('handles 5x5 with every pixel filled out', () => { - const binaryNumber = '34359737509' - const binaryString = binaryNumberToBinaryString(binaryNumber) - expect(binaryString).toBe('5x5:1111111111111111111111111') + it('handles 10x10:1', () => { + const cells = createCells(10, 10, () => { + return { filled: true } + }) + expect(eosHexToCells('0x4afdffffffffffffffffffffff3f0000')).toEqualFilledCells(cells) }) - it('handles 10x10 with every pixel filled out', () => { - const binaryNumber = '1298074214633706907132624082304330' - const binaryString = binaryNumberToBinaryString(binaryNumber) - expect(binaryString).toBe('10x10:1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111') + it('handles 10x10:0', () => { + const cells = createCells(10, 10, () => { + return { filled: false } + }) + expect(eosHexToCells('0x4a010000000000000000000000000000')).toEqualFilledCells(cells) }) }) -describe('decimalToHex', () => { - it('returns a 32 char hex string of zeroes for 0', () => { - const hex = decimalToHex(0) - expect(hex).toBe('0x00000000000000000000000000000000') +describe('binaryStringToHex', () => { + it('handles 0', () => { + expect('0').fromBinaryStringToHexToBe('00') }) - it('returns ff pairs for 64 bit max int', () => { - const hex = decimalToHex(new Decimal('18446744073709551615')) - expect(hex).toBe('0xffffffffffffffff0000000000000000') + it('handles 1', () => { + expect('00000001').fromBinaryStringToHexToBe('01') }) - it('returns one fe pair at the start for one less than 64 bit max int', () => { - const hex = decimalToHex(new Decimal('18446744073709551614')) - expect(hex).toBe('0xfeffffffffffffff0000000000000000') + it('handles 255', () => { + expect('11111111').fromBinaryStringToHexToBe('ff') }) - it('returns one ff pair only at the start for 255', () => { - const hex = decimalToHex(255) - expect(hex).toBe('0xff000000000000000000000000000000') + it('handles 256', () => { + expect('0000000100000000').fromBinaryStringToHexToBe('0100') }) - it('returns one ff pair only at the start for 256', () => { - const hex = decimalToHex(256) - expect(hex).toBe('0x00010000000000000000000000000000') - }) -}) - -describe('hexToDecimal', () => { - it('returns a string of 0 for a zero hex', () => { - const decimal = hexToDecimal('0x00000000000000000000000000000000') - expect(decimal.toString()).toBe('0') + it('handles 64 bit max int', () => { + const expected = '0000000000000000ffffffffffffffff' + expect('1111111111111111111111111111111111111111111111111111111111111111').fromBinaryStringToHexToBe(expected) }) - it('returns a string of 1 for a 1 hex', () => { - const decimal = hexToDecimal('0x01000000000000000000000000000000') - expect(decimal.toString()).toBe('1') + it('handles 64 bit max int - 1', () => { + const expected = '0000000000000000fffffffffffffffe' + expect('1111111111111111111111111111111111111111111111111111111111111110').fromBinaryStringToHexToBe(expected) }) - it('returns a string of 255 for a ff hex', () => { - const decimal = hexToDecimal('0xff000000000000000000000000000000') - expect(decimal.toString()).toBe('255') + it('handles 128 bit max int', () => { + const binary = '11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111' + const expected = 'ffffffffffffffffffffffffffffffff' + expect(binary).fromBinaryStringToHexToBe(expected) }) +}) - it('returns a string of 256 for a 0001 hex', () => { - const decimal = hexToDecimal('0x0001000000000000000000000000000000') - expect(decimal.toString()).toBe('256') +describe('hexToBinaryString', () => { + it('handles 0', () => { + expect('00').fromHexToBinaryStringToBe('0') }) - it('returns a string of max int for all ff hex pairs', () => { - const decimal = hexToDecimal('0xffffffffffffffff000000000000000000') - expect(decimal.toString()).toBe('18446744073709551615') + it('handles 1', () => { + expect('00000000000000000000000000000001').fromHexToBinaryStringToBe('1') }) - it('returns a string of max int64 for all ff hex pairs', () => { - const decimal = hexToDecimal('0x5eaffffffffffffffffffffffff00000000') - expect(decimal.toString()).toBe('1298074214633706907132624082304330') - }) -}) - -describe('cellsToHex', () => { - it('returns 33 as hex for 1x1:0', () => { - const cells = createCells(1, 1) - expect(cellsToHex(cells)).toBe('0x21000000000000000000000000000000') + it('handles 255', () => { + expect('000000000000000000000000000000ff').fromHexToBinaryStringToBe('11111111') }) - it('returns 1057 as hex for 1x1:1', () => { - const cells = createCells(1, 1, () => { - return { filled: true } - }) - expect(cellsToHex(cells)).toBe('0x21040000000000000000000000000000') + it('handles 256', () => { + expect('00000000000000000000000000000100').fromHexToBinaryStringToBe('0000000100000000') }) - it('returns 1298074214633706907132624082304330 as hex for 10x10:1', () => { - const cells = createCells(10, 10, () => { - return { filled: true } - }) - expect(cellsToHex(cells)).toBe('0x5peaffffffffffffffffffffffff00000000') + it('handles max int', () => { + expect('0000000000000000ffffffffffffffff').fromHexToBinaryStringToBe('1111111111111111111111111111111111111111111111111111111111111111') }) - it('returns different hex for differently filled cells', () => { - const cells = createCells(10, 10, () => { - return { filled: true } - }) - const differentCells = cells.slice() - differentCells[9][9].filled = false - - expect(cellsToHex(cells)).not.toBe(cellsToHex(differentCells)) + it('handles max int64', () => { + expect('ffffffffffffffffffffffffffffffff').fromHexToBinaryStringToBe('11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111') }) })