diff --git a/src/cli.js b/src/cli.js index d8fe9fd..3ed76cb 100644 --- a/src/cli.js +++ b/src/cli.js @@ -31,6 +31,7 @@ function run(argv, { out, err }) { } const handlers = { encode: require('./commands/encode'), + decode: require('./commands/decode'), }; const handler = handlers[head]; if (!handler) { diff --git a/src/commands/decode.js b/src/commands/decode.js new file mode 100644 index 0000000..6263460 --- /dev/null +++ b/src/commands/decode.js @@ -0,0 +1,39 @@ +'use strict'; + +const { decodeGid } = require('../gid'); + +const USAGE = 'usage: sporel-mapper decode \n'; + +function parseGid(s) { + let n; + if (/^0x[0-9a-fA-F]+$/.test(s)) { + n = parseInt(s, 16); + } else if (/^\d+$/.test(s)) { + n = parseInt(s, 10); + } else { + throw new RangeError(`gid '${s}' is not a decimal or 0x-hex integer`); + } + return n; +} + +function run(args, { out, err }) { + if (args.length !== 1) { + err.write(USAGE); + return 1; + } + try { + const gid = parseGid(args[0]); + if (gid === 0) { + out.write('empty\n'); + return 0; + } + const { atlas, tile, rotation } = decodeGid(gid); + out.write(`atlas=${atlas} tile=${tile} rotation=${rotation}\n`); + return 0; + } catch (e) { + err.write(`error: ${e.message}\n`); + return 1; + } +} + +module.exports = { run }; diff --git a/tests/decode.test.js b/tests/decode.test.js new file mode 100644 index 0000000..281c058 --- /dev/null +++ b/tests/decode.test.js @@ -0,0 +1,61 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const { run } = require('../src/cli'); + +class Sink { + constructor() { this.chunks = []; } + write(s) { this.chunks.push(s); } + get text() { return this.chunks.join(''); } +} + +test('decode: 196 prints (0,12,1)', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['decode', '196'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, 'atlas=0 tile=12 rotation=1\n'); +}); + +test('decode: 0 prints "empty"', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['decode', '0'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, 'empty\n'); +}); + +test('decode: hex (0xc4) parses to 196', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['decode', '0xc4'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, 'atlas=0 tile=12 rotation=1\n'); +}); + +test('decode: non-zero with tile=0 prints components (not "empty")', () => { + // gid=4 = (0<<24) | (0<<4) | (1<<2) → atlas=0 tile=0 rot=1 + const out = new Sink(); const err = new Sink(); + const code = run(['decode', '4'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, 'atlas=0 tile=0 rotation=1\n'); +}); + +test('decode: 16777232 from demo_v2 wall layer → (1,1,0)', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['decode', '16777232'], { out, err }); + assert.equal(code, 0); + assert.equal(out.text, 'atlas=1 tile=1 rotation=0\n'); +}); + +test('decode: no args prints usage, exit 1', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['decode'], { out, err }); + assert.equal(code, 1); + assert.match(err.text, /usage: sporel-mapper decode/); +}); + +test('decode: bogus arg prints error, exit 1', () => { + const out = new Sink(); const err = new Sink(); + const code = run(['decode', 'xyz'], { out, err }); + assert.equal(code, 1); + assert.match(err.text, /error/i); +});