Skip to content

Commit

Permalink
Rudimentry implementation of a ".graal" parser.
Browse files Browse the repository at this point in the history
Nothing is hooked up to this, so no exporting to png just yet.  But its
close!

With a test level that doesn't have any repeating tiles as input, the
parser is able to fill out a full board without overflowing, and
correctly identifies the cut off between tile data and other level data.

Levels feature repeating tiles, however, crash.

There is enough here that I could try to hook it up to the image
converter stuff later and see if the output is anywhere near correct.
This might also engender refactoring the nw parser a bit and providing a
common base class between the two.

This relates to issue #12.
  • Loading branch information
Aeva Palecek committed Dec 16, 2016
1 parent 63efbfb commit 016cd07
Showing 1 changed file with 111 additions and 0 deletions.
111 changes: 111 additions & 0 deletions graal_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@

# Copyright (c) 2016, Aeva M. Palecek

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import re
import sys
import math
import struct


class DotGraalParser(object):
"""
This class takes a path to a .graal encoded file, decodes it, and
provides a means of easily accessing the contained data.
"""
def __init__(self, path):
self._uri = path
self.version = None

self.parse()

def parse(self):
raw = open(self._uri, "r").read()
self.version = raw[:8]
assert(re.match(r'(GR|Z3)-V1.0[1-4]', self.version))

# we start at 8 to seek just after the file header
offset = 8
bits_read = 0

# tile data is packed in intervals of 13 bits
packet_size = 13
packet_mask = (2**packet_size)-1
repeat_mask = 2**(packet_size-1)
double_mask = 0x100
count_mask = 0xFF
tile_mask = packet_mask - repeat_mask

def get_packet(bit_index):
# pick out the two characters that contain the bytes for
# the packet, and unpack them as an unsigned short
seek = int(offset + math.floor((bit_index / 8.0)))
chaff = (bit_index % 8)
pick = struct.unpack("<H", raw[seek:seek+2])[0]

# bit shift and mask to get the 13 bits that are the
# actual packet
packet = (pick >> chaff) & packet_mask
#import pdb; pdb.set_trace()

# decode data from the packet
repeat_mode = packet & repeat_mask
if repeat_mode:
count = packet & count_mask
double_mode = packet & double_mask
return count, double_mode
else:
return None, packet & tile_mask

# now we loop until the tiles array is full
tiles = []
stop_at = 64**2
bit_index = 0
while len(tiles) < stop_at:
count, data = get_packet(bit_index)
bit_index += packet_size

if count is None:
# draw a singular tile
tiles.append(data)

else:
# packet describes a run length
packet = get_packet(bit_index)
bit_index += packet_size
assert packet[0] is None
stamp = [packet[1]]

if data:
# double repeat mode
packet = get_packet(bit_index)
bit_index += packet_size
assert packet[0] is None
stamp.append(packet[1])

for i in range(count):
tiles += stamp

after_tiles = int(offset + math.ceil((bit_index / 8.0)))
test = raw[after_tiles:]
assert len(tiles) == stop_at


if __name__ == "__main__":
path = sys.argv[1]
print "Debug info for parsing file:", path
level = DotGraalParser(path)
print " - file version:", level.version

0 comments on commit 016cd07

Please sign in to comment.