diff --git a/src/lib/parser/parseRooms.js b/src/lib/parser/parseRooms.js index ed9d5c1..e95281d 100644 --- a/src/lib/parser/parseRooms.js +++ b/src/lib/parser/parseRooms.js @@ -1,5 +1,7 @@ import Parser from './parser.js'; import parseRoomHeader from './room/parseRoomHeader.js'; +import parseRoomBoxes from './room/parseRoomBoxes.js'; +import parseRoomMatrix from './room/parseRoomMatrix.js'; const assert = console.assert; @@ -43,7 +45,6 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => { const objectImages = []; const objects = []; - const boxes = []; // These 2 are optional. if (excdOffs > 0) { @@ -382,49 +383,21 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => { map.push(objectCodeMap, objectImageMap); } - // Parse boxes. - // @todo Set the end of the ArrayBuffer slice. - const boxesParser = new Parser(arrayBuffer.slice(boxOffs)); - const boxNum = boxesParser.getUint8(); - - for (let j = 0; j < boxNum; j++) { - const uy = boxesParser.getUint8(); - const ly = boxesParser.getUint8(); - const ulx = boxesParser.getUint8(); - const urx = boxesParser.getUint8(); - const llx = boxesParser.getUint8(); - const lrx = boxesParser.getUint8(); - const mask = boxesParser.getUint8(); - const flags = boxesParser.getUint8(); - - assert(ly >= uy, 'Y box bounds are out of order.'); - - assert(mask === 0 || mask === 1, 'Box mask is neither 0 nor 1.'); - assert(flags === 0 || flags === 5, 'Box flag is neither 0 nor 5.'); - - boxes.push({ uy, ly, ulx, urx, llx, lrx, mask, flags }); - } - - assert(boxes.length > 0, 'Room has no boxes.'); + // Parse boxes and matrix. + const { boxes, boxesMap } = parseRoomBoxes( + // @todo Set the end of the ArrayBuffer slice. + arrayBuffer.slice(boxOffs), + boxOffs, + ); - map.push({ - type: 'boxes', - from: boxOffs, - to: boxesParser.pointer - 1, - }); + map.push(boxesMap); - const matrixSize = boxNum * (boxNum + 1); - const matrixMap = { - type: 'matrix', - from: boxesParser.pointer - 1, - to: boxesParser.pointer - 1 + matrixSize, - }; - const matrix = []; - for (let j = 0; j < boxNum; j++) { - for (let k = 0; k < boxNum; k++) { - matrix.push(boxesParser.getUint8()); - } - } + const { matrixUnks, matrix, matrixMap } = parseRoomMatrix( + // @todo Set the end of the ArrayBuffer slice. + arrayBuffer.slice(boxesMap.to + 1), + boxesMap.to + 1, + boxes.length, + ); map.push(matrixMap); @@ -437,6 +410,7 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => { objectImagesOffs, objectsOffs, boxes, + matrixUnks, matrix, nametable, attributes, diff --git a/src/lib/parser/room/parseRoomBoxes.js b/src/lib/parser/room/parseRoomBoxes.js new file mode 100644 index 0000000..41b28d5 --- /dev/null +++ b/src/lib/parser/room/parseRoomBoxes.js @@ -0,0 +1,39 @@ +import Parser from '../parser.js'; + +const assert = console.assert; + +const parseRoomBoxes = (arrayBuffer, offset = 0) => { + const parser = new Parser(arrayBuffer); + const boxNum = parser.getUint8(); + + const boxes = []; + for (let i = 0; i < boxNum; i++) { + const uy = parser.getUint8(); + const ly = parser.getUint8(); + const ulx = parser.getUint8(); + const urx = parser.getUint8(); + const llx = parser.getUint8(); + const lrx = parser.getUint8(); + const mask = parser.getUint8(); + const flags = parser.getUint8(); + + assert(ly >= uy, 'Y box bounds are out of order.'); + + assert(mask === 0 || mask === 1, 'Box mask is neither 0 nor 1.'); + assert(flags === 0 || flags === 5, 'Box flag is neither 0 nor 5.'); + + boxes.push({ uy, ly, ulx, urx, llx, lrx, mask, flags }); + } + + assert(boxes.length > 0, 'Room has no boxes.'); + + const boxesMap = { + type: 'boxes', + from: offset, + to: offset + parser.pointer - 1, + }; + + return { boxes, boxesMap }; +}; + +export default parseRoomBoxes; diff --git a/src/lib/parser/room/parseRoomMatrix.js b/src/lib/parser/room/parseRoomMatrix.js new file mode 100644 index 0000000..bb1ce0b --- /dev/null +++ b/src/lib/parser/room/parseRoomMatrix.js @@ -0,0 +1,35 @@ +import Parser from '../parser.js'; + +const assert = console.assert; + +const parseRoomMatrix = (arrayBuffer, offset = 0, boxNum = 1) => { + const parser = new Parser(arrayBuffer); + + const matrixUnks = []; + for (let i = 0; i < boxNum; i++) { + const unk = parser.getUint8(); + assert(unk === i * boxNum, 'The matrix does not have the expected values.'); + matrixUnks.push(unk); + } + + const matrix = []; + for (let i = 0; i < boxNum; i++) { + for (let j = 0; j < boxNum; j++) { + const box = parser.getUint8(); + assert(box >= 0, 'The matrix has an out of bound value.'); // Always true since it's a uint8. + // box should be strictly less than boxNum, but this fails in some cases. + assert(box <= boxNum, 'The matrix has an out of bound value.'); + matrix.push(box); + } + } + + const matrixMap = { + type: 'matrix', + from: offset, + to: offset + parser.pointer - 1, + }; + + return { matrixUnks, matrix, matrixMap }; +}; + +export default parseRoomMatrix; diff --git a/src/lib/serialiser/room/serialiseRoomBoxes.js b/src/lib/serialiser/room/serialiseRoomBoxes.js new file mode 100644 index 0000000..2cb8169 --- /dev/null +++ b/src/lib/serialiser/room/serialiseRoomBoxes.js @@ -0,0 +1,22 @@ +import Serialiser from '../serialiser.js'; + +const serialiseRoomBoxes = (boxes = []) => { + const serialiser = new Serialiser(); + + serialiser.setUint8(boxes.length); + for (let i = 0; i < boxes.length; i++) { + const { uy, ly, ulx, urx, llx, lrx, mask, flags } = boxes[i]; + serialiser.setUint8(uy); + serialiser.setUint8(ly); + serialiser.setUint8(ulx); + serialiser.setUint8(urx); + serialiser.setUint8(llx); + serialiser.setUint8(lrx); + serialiser.setUint8(mask); + serialiser.setUint8(flags); + } + + return serialiser.buffer; +}; + +export default serialiseRoomBoxes; diff --git a/src/lib/serialiser/room/serialiseRoomMatrix.js b/src/lib/serialiser/room/serialiseRoomMatrix.js new file mode 100644 index 0000000..4c3be5d --- /dev/null +++ b/src/lib/serialiser/room/serialiseRoomMatrix.js @@ -0,0 +1,18 @@ +import Serialiser from '../serialiser.js'; + +const serialiseRoomMatrix = (matrix = []) => { + const serialiser = new Serialiser(); + const boxNum = Math.sqrt(matrix.length); + + for (let i = 0; i < boxNum; i++) { + serialiser.setUint8(i * boxNum); + } + + for (let i = 0; i < boxNum * boxNum; i++) { + serialiser.setUint8(matrix[i]); + } + + return serialiser.buffer; +}; + +export default serialiseRoomMatrix; diff --git a/test/parseRoomBoxes.test.js b/test/parseRoomBoxes.test.js new file mode 100644 index 0000000..9774c63 --- /dev/null +++ b/test/parseRoomBoxes.test.js @@ -0,0 +1,68 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import parseRoomBoxes from '../src/lib/parser/room/parseRoomBoxes.js'; +import serialiseRoomBoxes from '../src/lib/serialiser/room/serialiseRoomBoxes.js'; + +const roomBoxesEmptyBuffer = (boxNum) => { + const buffer = new ArrayBuffer(1 + boxNum * 8); + const view = new DataView(buffer); + view.setUint8(0x00, boxNum); // Set the value of boxNum. + return buffer; +}; + +const roomBoxesBuffer = () => { + const array = [ + 0x03, 0x3b, 0x3e, 0x04, 0x09, 0x02, 0x09, 0x00, 0x00, 0x3c, 0x3e, 0x0a, + 0x23, 0x0a, 0x23, 0x00, 0x00, 0x3c, 0x3e, 0x24, 0x38, 0x24, 0x3a, 0x00, + 0x00, + ]; + const buffer = new ArrayBuffer(array.length); + const view = new DataView(buffer); + array.forEach((v, i) => view.setUint8(i, v)); + return buffer; +}; + +describe('parseRoomBoxes', () => { + it('should return an array.', () => { + const { boxes } = parseRoomBoxes(roomBoxesEmptyBuffer(1)); + + assert.ok(Array.isArray(boxes)); + assert.ok(boxes.length === 1); + }); + + it('should parse several room boxes.', () => { + const { boxes } = parseRoomBoxes(roomBoxesEmptyBuffer(5)); + + assert.ok(boxes.length === 5); + }); + + it('should return a map object.', () => { + const { boxesMap } = parseRoomBoxes(roomBoxesEmptyBuffer(1)); + + assert.equal(typeof boxesMap, 'object'); + assert.equal(boxesMap.from, 0); + assert.equal(boxesMap.to, 8); + }); + + it('should return a map object with a start offset.', () => { + const { boxesMap } = parseRoomBoxes(roomBoxesEmptyBuffer(1), 0xabc); + + assert.equal(boxesMap.from, 0xabc); + assert.equal(boxesMap.to, 0xac4); + }); + + it('should return a map object with an end offset.', () => { + const { boxesMap } = parseRoomBoxes(roomBoxesEmptyBuffer(5), 0xabc); + + assert.equal(boxesMap.from, 0xabc); + assert.equal(boxesMap.to, 0xae4); + }); + + it('should be the inverse of serialiseRoomBoxes.', () => { + const initialBuffer = roomBoxesBuffer(); + const { boxes } = parseRoomBoxes(initialBuffer); + const buffer = serialiseRoomBoxes(boxes); + + assert.deepEqual(initialBuffer, buffer); + }); +}); diff --git a/test/parseRoomMatrix.test.js b/test/parseRoomMatrix.test.js new file mode 100644 index 0000000..1ce88e2 --- /dev/null +++ b/test/parseRoomMatrix.test.js @@ -0,0 +1,81 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import parseRoomMatrix from '../src/lib/parser/room/parseRoomMatrix.js'; +import serialiseRoomMatrix from '../src/lib/serialiser/room/serialiseRoomMatrix.js'; + +const roomMatrixEmptyBuffer = (boxNum) => { + const buffer = new ArrayBuffer((1 + boxNum) * boxNum); + const view = new DataView(buffer); + for (let i = 0; i < boxNum; i++) { + view.setUint8(i, i * boxNum); + } + return buffer; +}; + +const roomMatrixBuffer = () => { + const array = [ + 0x00, 0x03, 0x06, 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x01, 0x01, 0x02, + ]; + const buffer = new ArrayBuffer(array.length); + const view = new DataView(buffer); + array.forEach((v, i) => view.setUint8(i, v)); + return buffer; +}; + +describe('parseRoomMatrix', () => { + it('should return an array.', () => { + const { matrix } = parseRoomMatrix(roomMatrixEmptyBuffer(0), 0, 0); + + assert.ok(Array.isArray(matrix)); + assert.ok(matrix.length === 0); + }); + + it('should parse several matrices.', () => { + const { matrix } = parseRoomMatrix(roomMatrixEmptyBuffer(5), 0, 5); + + assert.ok(matrix.length === 25); + }); + + it('should return an array of unknown values.', () => { + const { matrixUnks } = parseRoomMatrix(roomMatrixEmptyBuffer(0), 0, 0); + + assert.ok(Array.isArray(matrixUnks)); + assert.ok(matrixUnks.length === 0); + }); + + it('should parse several unknown values.', () => { + const { matrixUnks } = parseRoomMatrix(roomMatrixEmptyBuffer(5), 0, 5); + + assert.ok(matrixUnks.length === 5); + }); + + it('should return a map object.', () => { + const { matrixMap } = parseRoomMatrix(roomMatrixEmptyBuffer(1), 0, 1); + + assert.equal(typeof matrixMap, 'object'); + assert.equal(matrixMap.from, 0); + assert.equal(matrixMap.to, 1); + }); + + it('should return a map object with a start offset.', () => { + const { matrixMap } = parseRoomMatrix(roomMatrixEmptyBuffer(1), 0xabc, 1); + + assert.equal(matrixMap.from, 0xabc); + assert.equal(matrixMap.to, 0xabd); + }); + + it('should return a map object with an end offset.', () => { + const { matrixMap } = parseRoomMatrix(roomMatrixEmptyBuffer(5), 0xabc, 5); + + assert.equal(matrixMap.from, 0xabc); + assert.equal(matrixMap.to, 0xad9); + }); + + it('should be the inverse of serialiseRoomMatrix.', () => { + const initialBuffer = roomMatrixBuffer(); + const { matrix } = parseRoomMatrix(initialBuffer, 0, 3); + const buffer = serialiseRoomMatrix(matrix); + + assert.deepEqual(initialBuffer, buffer); + }); +}); diff --git a/test/serialiseRoomBoxes.test.js b/test/serialiseRoomBoxes.test.js new file mode 100644 index 0000000..ea7a7ef --- /dev/null +++ b/test/serialiseRoomBoxes.test.js @@ -0,0 +1,35 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import serialiseRoomBoxes from '../src/lib/serialiser/room/serialiseRoomBoxes.js'; +import parseRoomBoxes from '../src/lib/parser/room/parseRoomBoxes.js'; + +const roomBoxes = [ + { uy: 59, ly: 62, ulx: 4, urx: 9, llx: 2, lrx: 9, mask: 0, flags: 0 }, + { uy: 60, ly: 62, ulx: 10, urx: 35, llx: 10, lrx: 35, mask: 0, flags: 0 }, + { uy: 60, ly: 62, ulx: 36, urx: 56, llx: 36, lrx: 58, mask: 0, flags: 0 }, +]; + +describe('serialiseRoomBoxes', () => { + it('should return an instance of ArrayBuffer.', () => { + const buffer = serialiseRoomBoxes(roomBoxes); + assert.ok(buffer instanceof ArrayBuffer); + }); + + it('should serialise a room box.', () => { + const buffer = serialiseRoomBoxes(roomBoxes); + const view = new DataView(buffer); + + assert.equal(view.getUint8(0x00), 3); + assert.equal(view.getUint8(0x01), 59); + assert.equal(view.getUint8(0x09), 60); + assert.equal(view.getUint8(0x11), 60); + assert.equal(buffer.byteLength, 25); + }); + + it('should be the inverse of parseRoomHeader.', () => { + const buffer = serialiseRoomBoxes(roomBoxes); + const { boxes } = parseRoomBoxes(buffer); + + assert.deepEqual(roomBoxes, boxes); + }); +}); diff --git a/test/serialiseRoomMatrix.test.js b/test/serialiseRoomMatrix.test.js new file mode 100644 index 0000000..9f2f4e9 --- /dev/null +++ b/test/serialiseRoomMatrix.test.js @@ -0,0 +1,31 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import serialiseRoomMatrix from '../src/lib/serialiser/room/serialiseRoomMatrix.js'; +import parseRoomMatrix from '../src/lib/parser/room/parseRoomMatrix.js'; + +const roomMatrix = [0, 1, 1, 0, 1, 2, 1, 1, 2]; + +describe('serialiseRoomMatrix', () => { + it('should return an instance of ArrayBuffer.', () => { + const buffer = serialiseRoomMatrix(roomMatrix); + assert.ok(buffer instanceof ArrayBuffer); + }); + + it('should serialise a room matrix.', () => { + const buffer = serialiseRoomMatrix(roomMatrix); + const view = new DataView(buffer); + + assert.equal(view.getUint8(0x00), 0); + assert.equal(view.getUint8(0x03), 0); + assert.equal(view.getUint8(0x06), 0); + assert.equal(view.getUint8(0x09), 1); + assert.equal(buffer.byteLength, 12); + }); + + it('should be the inverse of parseRoomMatrix.', () => { + const buffer = serialiseRoomMatrix(roomMatrix); + const { matrix } = parseRoomMatrix(buffer, 0, 3); + + assert.deepEqual(roomMatrix, matrix); + }); +});