Skip to content

Commit

Permalink
Add serialisers for boxes and matrix 🧮.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmarty committed Apr 30, 2024
1 parent 82aa1c6 commit 5e9901f
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 42 deletions.
58 changes: 16 additions & 42 deletions src/lib/parser/parseRooms.js
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);

Expand All @@ -437,6 +410,7 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => {
objectImagesOffs,
objectsOffs,
boxes,
matrixUnks,
matrix,
nametable,
attributes,
Expand Down
39 changes: 39 additions & 0 deletions src/lib/parser/room/parseRoomBoxes.js
Original file line number Diff line number Diff line change
@@ -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;
35 changes: 35 additions & 0 deletions src/lib/parser/room/parseRoomMatrix.js
Original file line number Diff line number Diff line change
@@ -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;
22 changes: 22 additions & 0 deletions src/lib/serialiser/room/serialiseRoomBoxes.js
Original file line number Diff line number Diff line change
@@ -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;
18 changes: 18 additions & 0 deletions src/lib/serialiser/room/serialiseRoomMatrix.js
Original file line number Diff line number Diff line change
@@ -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;
68 changes: 68 additions & 0 deletions test/parseRoomBoxes.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
81 changes: 81 additions & 0 deletions test/parseRoomMatrix.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
35 changes: 35 additions & 0 deletions test/serialiseRoomBoxes.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading

0 comments on commit 5e9901f

Please sign in to comment.