-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add unit tests for room gfx nametable and a serialiser 📡.
- Loading branch information
Showing
5 changed files
with
408 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import Parser from '../parser.js'; | ||
|
||
const assert = console.assert; | ||
|
||
const parseRoomNametable = (arrayBuffer, offset = 0, width = 0) => { | ||
const parser = new Parser(arrayBuffer); | ||
const tileset = parser.getUint8(); | ||
|
||
const palette = []; | ||
for (let i = 0; i < 16; i++) { | ||
palette[i] = parser.getUint8(); | ||
} | ||
|
||
const nametableObj = Array(16); | ||
for (let i = 0; i < 16; i++) { | ||
nametableObj[i] = Array(64).fill(0); | ||
nametableObj[i][0] = 0; | ||
nametableObj[i][1] = 0; | ||
|
||
assert( | ||
nametableObj[i][0] === 0, | ||
'Gfx nametable strip does not start with 0x00 0x00.', | ||
); | ||
assert( | ||
nametableObj[i][1] === 0, | ||
'Gfx nametable strip does not start with 0x00 0x00.', | ||
); | ||
|
||
let n = 0; | ||
while (n < width) { | ||
const loop = parser.getUint8(); | ||
if (loop & 0x80) { | ||
for (let j = 0; j < (loop & 0x7f); j++) { | ||
nametableObj[i][2 + n++] = parser.getUint8(); | ||
} | ||
} else { | ||
const data = parser.getUint8(); | ||
for (let j = 0; j < (loop & 0x7f); j++) { | ||
nametableObj[i][2 + n++] = data; | ||
} | ||
} | ||
} | ||
|
||
assert( | ||
nametableObj[i][62] === 0, | ||
'Gfx nametable strip does not end with 0x00 0x00.', | ||
); | ||
assert( | ||
nametableObj[i][63] === 0, | ||
'Gfx nametable strip does not end with 0x00 0x00.', | ||
); | ||
} | ||
|
||
const nametableMap = { | ||
type: 'nametable', | ||
from: offset, | ||
to: offset + parser.pointer - 1, | ||
}; | ||
|
||
return { | ||
nametable: { | ||
tileset, | ||
palette, | ||
nametableObj, | ||
}, | ||
nametableMap, | ||
}; | ||
}; | ||
|
||
export default parseRoomNametable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import Serialiser from '../serialiser.js'; | ||
|
||
const serialiseRoomNametable = (nametable = {}) => { | ||
const serialiser = new Serialiser(); | ||
const { tileset, palette, nametableObj } = nametable; | ||
|
||
serialiser.setUint8(tileset); | ||
|
||
for (let i = 0; i < 16; i++) { | ||
serialiser.setUint8(palette[i]); | ||
} | ||
|
||
for (let i = 0; i < 16; i++) { | ||
serialiseOneStrip(nametableObj[i].slice(2, 62), serialiser); | ||
} | ||
|
||
return serialiser.buffer; | ||
}; | ||
|
||
// Serialise one strip using the compression algorithm. | ||
const serialiseOneStrip = (nametableObj, serialiser) => { | ||
let n = 0; | ||
while (n < 60) { | ||
let initialTile = nametableObj[n]; | ||
let loop = 0; | ||
|
||
// Look 2 tiles ahead. | ||
if ( | ||
nametableObj[n] !== nametableObj[n + 1] || | ||
nametableObj[n] !== nametableObj[n + 2] | ||
) { | ||
// The next 3 tiles are different. Count how many unique tiles there are. | ||
// It stops once it encounters 3 identical tiles in a row. | ||
const initialN = n; | ||
|
||
do { | ||
loop++; | ||
initialTile = nametableObj[++n]; | ||
} while ( | ||
initialTile !== nametableObj[n + 1] || | ||
initialTile !== nametableObj[n + 2] | ||
); | ||
|
||
serialiser.setUint8(loop | 0x80); // Set the type of loop. | ||
for (let i = initialN; i < n; i++) { | ||
serialiser.setUint8(nametableObj[i]); | ||
} | ||
} else { | ||
// The next 3 tiles are identical. Count how many of them in a row. | ||
// It stops when it finds a different tile. | ||
do { | ||
loop++; | ||
} while (initialTile === nametableObj[++n]); | ||
|
||
serialiser.setUint8(loop); | ||
serialiser.setUint8(initialTile); | ||
} | ||
} | ||
}; | ||
|
||
export default serialiseRoomNametable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { describe, it } from 'node:test'; | ||
import assert from 'node:assert/strict'; | ||
import parseRoomNametable from '../src/lib/parser/room/parseRoomNametable.js'; | ||
import serialiseRoomNametable from '../src/lib/serialiser/room/serialiseRoomNametable.js'; | ||
|
||
const roomNametableEmptyBuffer = (tileset) => { | ||
const buffer = new ArrayBuffer(1 + 16 + 16 * 64); | ||
const view = new DataView(buffer); | ||
view.setUint8(0x00, tileset); // Set the value of tileset. | ||
return buffer; | ||
}; | ||
|
||
const roomNametableBuffer = () => { | ||
const array = [ | ||
0x01, 0x0d, 0x07, 0x27, 0x3c, 0x0d, 0x17, 0x2a, 0x3a, 0x0d, 0x0d, 0x2a, | ||
0x28, 0x0d, 0x0d, 0x37, 0x20, 0x87, 0x01, 0x01, 0x62, 0x01, 0x63, 0x01, | ||
0x64, 0x03, 0x65, 0x82, 0x63, 0x64, 0x04, 0x65, 0x82, 0x63, 0x64, 0x09, | ||
0x65, 0x81, 0x63, 0x05, 0x65, 0x82, 0x63, 0x64, 0x03, 0x65, 0x83, 0x01, | ||
0x63, 0x74, 0x05, 0x01, 0x81, 0x62, 0x05, 0x01, 0x84, 0x74, 0x01, 0x01, | ||
0x74, 0x04, 0x01, 0x93, 0x01, 0x62, 0x62, 0x01, 0x63, 0x01, 0x64, 0x66, | ||
0x67, 0x68, 0x63, 0x66, 0x67, 0x68, 0x65, 0x66, 0x63, 0x68, 0x69, 0x06, | ||
0x6a, 0x8f, 0x6b, 0x66, 0x63, 0x68, 0x65, 0x66, 0x67, 0x68, 0x63, 0x66, | ||
0x67, 0x68, 0x65, 0x01, 0x63, 0x0c, 0x01, 0x88, 0x62, 0x01, 0x01, 0x62, | ||
0x01, 0x01, 0x62, 0x74, 0x04, 0x01, 0xa4, 0x63, 0x01, 0x64, 0x6c, 0x01, | ||
0x6d, 0x63, 0x6c, 0x01, 0x6d, 0x65, 0x6c, 0x63, 0x6d, 0x6e, 0x36, 0x39, | ||
0x37, 0x36, 0x39, 0x37, 0x6f, 0x6c, 0x63, 0x6d, 0x65, 0x6c, 0x01, 0x6d, | ||
0x63, 0x6c, 0x01, 0x6d, 0x65, 0x01, 0x63, 0x03, 0x01, 0x87, 0x74, 0x01, | ||
0x01, 0x62, 0x01, 0x01, 0x74, 0x04, 0x01, 0x86, 0x74, 0x01, 0x01, 0x74, | ||
0x01, 0x01, 0x81, 0x62, 0x03, 0x01, 0xa5, 0x63, 0x01, 0x64, 0x70, 0x71, | ||
0x72, 0x63, 0x70, 0x71, 0x72, 0x65, 0x70, 0x63, 0x72, 0x6e, 0x3e, 0x73, | ||
0x40, 0x3b, 0x73, 0x3c, 0x6f, 0x70, 0x63, 0xb4, 0x65, 0x70, 0xb5, 0xb4, | ||
0x63, 0x70, 0xb5, 0xb4, 0x65, 0x01, 0x63, 0x62, 0x13, 0x01, 0xa8, 0x01, | ||
0x62, 0x01, 0x74, 0x63, 0x01, 0x64, 0x6c, 0x01, 0x6d, 0x63, 0x6c, 0x01, | ||
0x6d, 0x65, 0x6c, 0x63, 0x6d, 0x6e, 0x3b, 0x3d, 0x40, 0x3b, 0x3d, 0x40, | ||
0x6f, 0x6c, 0x63, 0x6d, 0x65, 0x6c, 0x01, 0x6d, 0x63, 0x6c, 0x01, 0x6d, | ||
0x65, 0x01, 0x63, 0x03, 0x01, 0x83, 0x62, 0x01, 0x74, 0x03, 0x01, 0x81, | ||
0x62, 0x04, 0x01, 0x83, 0x62, 0x01, 0x74, 0x03, 0x01, 0x04, 0x01, 0xa4, | ||
0x63, 0x01, 0x64, 0x75, 0x76, 0x77, 0x63, 0x75, 0x76, 0x77, 0x65, 0x75, | ||
0x63, 0x77, 0x6e, 0x3b, 0x3d, 0x40, 0x3b, 0x3d, 0x40, 0x78, 0x75, 0x63, | ||
0x77, 0x65, 0x75, 0x76, 0x77, 0x63, 0x75, 0x76, 0x77, 0x65, 0x01, 0x63, | ||
0x0b, 0x01, 0x81, 0x74, 0x08, 0x01, 0x87, 0x62, 0x01, 0x01, 0x79, 0x7a, | ||
0x7b, 0x7c, 0x03, 0x7d, 0x82, 0x7a, 0x7c, 0x04, 0x7d, 0x8c, 0x7a, 0x64, | ||
0x6e, 0x3b, 0x3d, 0x49, 0x49, 0x3d, 0x40, 0x6f, 0x65, 0x7a, 0x05, 0x7d, | ||
0x81, 0x7a, 0x04, 0x7d, 0x83, 0x7b, 0x7a, 0xb6, 0x03, 0x01, 0x82, 0x74, | ||
0x62, 0x03, 0x01, 0x81, 0x62, 0x08, 0x01, 0x82, 0x62, 0x01, 0xab, 0x01, | ||
0x74, 0x01, 0x7e, 0x63, 0x01, 0x7f, 0x64, 0x7f, 0x64, 0x7f, 0x64, 0x7f, | ||
0x64, 0x7f, 0x64, 0x7f, 0x64, 0x6e, 0x3b, 0x3d, 0x40, 0x3b, 0x3d, 0x40, | ||
0x6f, 0x65, 0x7f, 0x64, 0x7f, 0x64, 0x7f, 0x64, 0x7f, 0x64, 0x7f, 0x64, | ||
0x7f, 0x01, 0x63, 0x7e, 0x01, 0x74, 0x03, 0x01, 0x84, 0x74, 0x01, 0xb7, | ||
0x62, 0x0a, 0xb7, 0x03, 0x01, 0xb4, 0x80, 0x63, 0x01, 0x81, 0x64, 0x81, | ||
0x64, 0x81, 0x64, 0x81, 0x64, 0x81, 0x64, 0x81, 0x64, 0x6e, 0x3e, 0x3d, | ||
0x40, 0x3b, 0x3d, 0x3c, 0x6f, 0x65, 0x81, 0x64, 0x81, 0x64, 0x81, 0x64, | ||
0x81, 0x64, 0x81, 0x64, 0x81, 0x01, 0x63, 0x80, 0x01, 0x62, 0x01, 0x01, | ||
0x62, 0x01, 0x01, 0xb8, 0xb9, 0xb8, 0xb8, 0xba, 0xb8, 0xb8, 0x05, 0xba, | ||
0xb7, 0x62, 0x01, 0x01, 0x82, 0x82, 0x83, 0x82, 0x84, 0x82, 0x84, 0x82, | ||
0x84, 0x82, 0x84, 0x82, 0x84, 0x82, 0x64, 0x6e, 0x41, 0x43, 0x44, 0x41, | ||
0x43, 0x44, 0x6f, 0x65, 0x82, 0x84, 0xbb, 0x84, 0xbb, 0x84, 0xbb, 0x84, | ||
0xbb, 0x84, 0xbb, 0x83, 0xbb, 0xbb, 0x01, 0xc4, 0xc5, 0x01, 0xc4, 0xc5, | ||
0x01, 0xc4, 0xc5, 0xbc, 0xbc, 0xbd, 0xbc, 0xbc, 0x05, 0xbd, 0x84, 0x85, | ||
0x86, 0x87, 0x01, 0x0d, 0x88, 0x82, 0x89, 0x84, 0x06, 0x8a, 0x83, 0x84, | ||
0x8b, 0x88, 0x0c, 0xbe, 0x8a, 0x01, 0x01, 0xc7, 0xc7, 0x01, 0xc7, 0xc7, | ||
0x01, 0xc7, 0xc7, 0x04, 0xbd, 0x82, 0xbf, 0xc0, 0x04, 0xbd, 0x84, 0x8c, | ||
0x8d, 0x8e, 0x01, 0x08, 0x8f, 0x04, 0x90, 0x82, 0x91, 0x92, 0x08, 0x93, | ||
0x82, 0x94, 0x91, 0x04, 0xce, 0x08, 0x8f, 0x8b, 0x01, 0x62, 0xbf, 0xc0, | ||
0x01, 0xbf, 0xc0, 0x01, 0xbf, 0xc0, 0xbc, 0x05, 0xbd, 0x81, 0xbc, 0x03, | ||
0xbd, 0x84, 0x01, 0x95, 0x01, 0x01, 0x06, 0x8f, 0x88, 0x96, 0x96, 0x97, | ||
0x98, 0x97, 0x98, 0x99, 0x9a, 0x08, 0x9b, 0x86, 0x9c, 0x9d, 0x97, 0xcf, | ||
0x97, 0xcf, 0x08, 0x8f, 0x14, 0xc1, 0x84, 0x9e, 0x9f, 0x9e, 0x9e, 0x08, | ||
0x90, 0x85, 0xa0, 0xa1, 0xa0, 0xa1, 0x82, 0x0a, 0x84, 0x85, 0x82, 0xa0, | ||
0xd1, 0xa0, 0xd1, 0x08, 0xc2, 0x14, 0x3d, 0x88, 0x3d, 0xa2, 0x3d, 0x3d, | ||
0xa3, 0xa4, 0xa4, 0xa5, 0x16, 0x3d, 0x81, 0xc6, 0x1d, 0x3d, 0x88, 0x3d, | ||
0xa2, 0x3d, 0x3d, 0xa6, 0xa7, 0xa8, 0xa7, 0x34, 0x3d, | ||
]; | ||
const buffer = new ArrayBuffer(array.length); | ||
const view = new DataView(buffer); | ||
array.forEach((v, i) => view.setUint8(i, v)); | ||
return buffer; | ||
}; | ||
|
||
describe('parseRoomNametable', () => { | ||
it('should return a non empty object.', () => { | ||
const { nametable } = parseRoomNametable(roomNametableEmptyBuffer(0)); | ||
|
||
assert.equal(typeof nametable, 'object'); | ||
assert.deepEqual(Object.keys(nametable), [ | ||
'tileset', | ||
'palette', | ||
'nametableObj', | ||
]); | ||
assert.ok(Number.isInteger(nametable.tileset)); | ||
assert.ok(Array.isArray(nametable.palette)); | ||
assert.equal(nametable.palette.length, 16); | ||
assert.ok(Array.isArray(nametable.nametableObj)); | ||
assert.equal(nametable.nametableObj.length, 16); | ||
assert.ok(Array.isArray(nametable.nametableObj[0])); | ||
assert.equal(nametable.nametableObj[0].length, 64); | ||
}); | ||
|
||
it('should parse the tileset.', () => { | ||
const { nametable } = parseRoomNametable(roomNametableEmptyBuffer(5)); | ||
|
||
assert.equal(nametable.tileset, 5); | ||
}); | ||
|
||
it('should return a map object.', () => { | ||
const { nametableMap } = parseRoomNametable(roomNametableEmptyBuffer(0)); | ||
|
||
assert.equal(typeof nametableMap, 'object'); | ||
assert.equal(nametableMap.from, 0); | ||
assert.equal(nametableMap.to, 16); | ||
}); | ||
|
||
it('should return a map object with a start offset.', () => { | ||
const { nametableMap } = parseRoomNametable( | ||
roomNametableEmptyBuffer(0), | ||
0xabc, | ||
); | ||
|
||
assert.equal(nametableMap.from, 0xabc); | ||
assert.equal(nametableMap.to, 0xacc); | ||
}); | ||
|
||
it('should be the inverse of serialiseRoomNametable.', () => { | ||
const initialBuffer = roomNametableBuffer(); | ||
const { nametable } = parseRoomNametable(initialBuffer, 0, 60); | ||
const buffer = serialiseRoomNametable(nametable); | ||
|
||
assert.deepEqual(initialBuffer, buffer); | ||
}); | ||
}); |
Oops, something went wrong.