Skip to content

Commit

Permalink
feat: start parsing replay files
Browse files Browse the repository at this point in the history
  • Loading branch information
hexjelly committed Jan 26, 2017
1 parent 4a859ac commit 3f9bf03
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 3 deletions.
6 changes: 5 additions & 1 deletion src/const.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

147 changes: 146 additions & 1 deletion src/rec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const fs = require('fs')
const trimString = require('./util').trimString
const EOR_MARKER = require('./const').EOR_MARKER

/**
* Class containing all replay attributes.
Expand All @@ -23,11 +25,154 @@ class Replay {
fs.readFile(filePath, (error, buffer) => {
if (error) reject(error)
let replay = new Replay()
resolve(replay)
replay._parseFile(buffer).then(results => resolve(results)).catch(error => reject(error))
})
})
}

/**
* Parses file buffer data into a Replay.
* @private
* @returns {Promise}
*/
_parseFile (buffer) {
return new Promise((resolve, reject) => {
let offset = 0
// frame count
let numFrames = buffer.readUInt32LE(offset)
offset += 8 // + 4 unused extra bytes
// multireplay?
this.multi = Boolean(buffer.readInt32LE(offset))
offset += 4
// flag-tag replay?
this.flagTag = Boolean(buffer.readInt32LE(offset))
offset += 4
// level link
this.link = buffer.readUInt32LE(offset)
offset += 4
// level filename with extension
this.level = trimString(buffer.slice(offset, offset + 12))
offset += 16 // + 4 unused extra bytes

// frames
this.frames[0] = Replay._parseFrames(buffer.slice(offset, offset + (27 * numFrames)), numFrames)
offset += 27 * numFrames
// events
let numEvents = buffer.readUInt32LE(offset)
offset += 4
this.events[0] = Replay._parseEvents(buffer.slice(offset, offset + (16 * numEvents)), numEvents)
offset += 16 * numEvents

// end of replay marker
let expected = buffer.readInt32LE(offset)
if (expected !== EOR_MARKER) {
reject('End of replay marker mismatch')
return
}

// if multi rec, parse another set of frames and events while skipping
// other fields we already gathered from the first half. probably?
if (this.multi) {
offset += 4
let numFrames = buffer.readUInt32LE(offset)
offset += 36 // +32 bytes where skipping other fields
this.frames[1] = Replay._parseFrames(buffer.slice(offset, offset + (27 * numFrames)), numFrames)
offset += 27 * numFrames
let numEvents = buffer.readUInt32LE(offset)
offset += 4
this.events[1] = Replay._parseEvents(buffer.slice(offset, offset + (16 * numEvents)), numEvents)
offset += 16 * numEvents
let expected = buffer.readInt32LE(offset)
if (expected !== EOR_MARKER) {
reject('End of replay marker mismatch')
return
}
}

resolve(this)
})
}

/**
* Parses frame data into an array of frame objects.
* @private
* @param {Buffer} buffer Frame data to parse.
* @param {Number} numFrames Number of frames to parse.
* @returns {Array}
*/
static _parseFrames (buffer, numFrames) {
let frames = []
for (let i = 0; i < numFrames; i++) {
let data = buffer.readUint8(i + (numFrames * 23)) // read in data field first to process it
let frame = {
bike_x: buffer.readFloatLE(i * 4),
bike_y: buffer.readFloatLE((i * 4) + (numFrames * 4)),
left_x: buffer.readInt16LE((i * 2) + (numFrames * 8)),
left_y: buffer.readInt16LE((i * 2) + (numFrames * 10)),
right_x: buffer.readInt16LE((i * 2) + (numFrames * 12)),
right_y: buffer.readInt16LE((i * 2) + (numFrames * 14)),
head_x: buffer.readInt16LE((i * 2) + (numFrames * 16)),
head_y: buffer.readInt16LE((i * 2) + (numFrames * 18)),
rotation: buffer.readInt16LE((i * 2) + (numFrames * 20)),
left_rotation: buffer.readUint8(i + (numFrames * 21)),
right_rotation: buffer.readUint8(i + (numFrames * 22)),
throttle: data & 1 !== 0,
right: data & (1 << 1) !== 0,
volume: buffer.readInt16LE((i * 2) + (numFrames * 25))
}
frames.push(frame)
}
return frames
}

/**
* Parses event data into an array of event objects.
* @private
* @param {Buffer} buffer Event data to parse.
* @param {Number} numEvents Number of events to parse.
* @returns {Array}
*/
static _parseEvents (buffer, numEvents) {
let events = []
let offset = 0
for (let i = 0; i < numEvents; i++) {
let event = {}
event.time = buffer.readDoubleLE(offset)
offset += 8
event.info = buffer.readInt16LE(offset)
offset += 2
let eventType = buffer.readUint8(offset)
offset += 6 // 1 + 5 unknown bytes
switch (eventType) {
case 0:
event.eventType = 'apple'
break
case 1:
event.eventType = 'ground1'
break
case 4:
event.eventType = 'ground2'
break
case 5:
event.eventType = 'turn'
break
case 6:
event.eventType = 'voltRight'
break
case 7:
event.eventType = 'voltLeft'
break
default:
event.eventType = undefined
break
}

events.push(event)
}

return events
}

/**
* Get time of replay in milliseconds.
* @param {bool} hs Return hundredths
Expand Down
18 changes: 17 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,30 @@ test('Level save() method without modifications matches original level', t => {
/* * * * * * * * *
* Replay tests *
* * * * * * * * */
test('Replay load() static method returns instance of Replay', t => {
test('Valid replay 1: load() parses level correctly', t => {
t.plan(1)

return Replay.load('test/assets/replays/rec_valid_1.rec').then(result => {
t.true(result instanceof Replay)
}).catch(error => t.fail(error.Error))
})

test('Valid replay 2: load() parses level correctly', t => {
t.plan(1)

return Replay.load('test/assets/replays/rec_valid_2.rec').then(result => {
t.true(result instanceof Replay)
}).catch(error => t.fail(error.Error))
})

test('Valid replay 3: load() parses level correctly', t => {
t.plan(1)

return Replay.load('test/assets/replays/rec_valid_3.rec').then(result => {
t.true(result instanceof Replay)
}).catch(error => t.fail(error.Error))
})

test.todo('read replay file')
test.todo('reject Across replays')
test.todo('check all replay attributes with 3+ replays')
Expand Down

0 comments on commit 3f9bf03

Please sign in to comment.