class Pangrid::AcrossLiteBinary
Binary format
Constants
- DESCRIPTION
- EXTENSIONS
- EXT_HEADER_FORMAT
- FILE_MAGIC
- HEADER_CHECKSUM_FORMAT
- HEADER_FORMAT
Attributes
cs[RW]
crossword, checksums
xw[RW]
crossword, checksums
Public Class Methods
new()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 86 def initialize @xw = XWord.new @cs = OpenStruct.new @xw.extensions = [] end
Public Instance Methods
read(data)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 92 def read(data) s = data.force_encoding("ISO-8859-1") i = s.index(FILE_MAGIC) check("Could not recognise AcrossLite binary file") { i } # read the header h_start, h_end = i - 2, i - 2 + 0x34 header = s[h_start .. h_end] cs.global, _, cs.cib, cs.masked_low, cs.masked_high, xw.version, _, cs.scrambled, _, xw.width, xw.height, xw.n_clues, xw.puzzle_type, xw.scrambled_state = header.unpack(HEADER_FORMAT) # solution and fill = blocks of w*h bytes each size = xw.width * xw.height xw.solution = unpack_solution xw, s[h_end, size] xw.fill = s[h_end + size, size] s = s[h_end + 2 * size .. -1] # title, author, copyright, clues * n, notes = zero-terminated strings xw.title, xw.author, xw.copyright, *xw.clues, xw.notes, s = s.split("\0", xw.n_clues + 5) # extensions: 8-byte header + len bytes data + \0 while (s.length > 8) do e = OpenStruct.new e.section, e.len, e.checksum = s.unpack(EXT_HEADER_FORMAT) check("Unrecognised extension #{e.section}") { EXTENSIONS.include? e.section } size = 8 + e.len + 1 break if s.length < size e.data = s[8 ... size] self.send(:"read_#{e.section.downcase}", e) xw.extensions << e s = s[size .. -1] end # verify checksums check("Failed checksum") { checksums == cs } process_extensions unpack_clues xw end
write(xw)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 139 def write(xw) @xw = xw # fill in some fields that might not be present (checksums needs this) pack_clues xw.clues = xw.clues.map(&:to_s) xw.n_clues = xw.clues.length xw.fill ||= empty_fill(xw) xw.puzzle_type ||= 1 xw.scrambled_state ||= 0 xw.version = "1.3" xw.notes ||= "" xw.extensions ||= [] xw.title ||= "" xw.author ||= "" xw.copyright ||= "" # extensions xw.encode_rebus! if not xw.rebus.empty? # GRBS e = OpenStruct.new e.section = "GRBS" e.grid = xw.to_array({:black => 0, :null => 0}) {|s| s.rebus? ? s.solution.symbol.to_i : 0 }.flatten xw.extensions << e # RTBL e = OpenStruct.new e.section = "RTBL" e.rebus = {} xw.rebus.each do |long, (k, short)| e.rebus[k] = [long, short] end xw.extensions << e end # calculate checksums @cs = checksums h = [cs.global, FILE_MAGIC, cs.cib, cs.masked_low, cs.masked_high, xw.version + "\0", 0, cs.scrambled, "\0" * 12, xw.width, xw.height, xw.n_clues, xw.puzzle_type, xw.scrambled_state] header = h.pack(HEADER_FORMAT) strings = [xw.title, xw.author, xw.copyright] + xw.clues + [xw.notes] strings = strings.map {|x| x + "\0"}.join [header, pack_solution(xw), xw.fill, strings, write_extensions].map {|x| x.force_encoding("ISO-8859-1") }.join end
Private Instance Methods
checksums()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 348 def checksums c = OpenStruct.new c.masked_low, c.masked_high = magic_checksums c.cib = header_checksum c.global = global_checksum c.scrambled = 0 c end
get_extension(s)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 227 def get_extension(s) return nil unless xw.extensions xw.extensions.find {|e| e.section == s} end
global_checksum()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 324 def global_checksum c = Checksum.new header_checksum c.add_string pack_solution(xw) c.add_string xw.fill text_checksum c.sum end
header_checksum()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 319 def header_checksum h = [xw.width, xw.height, xw.n_clues, xw.puzzle_type, xw.scrambled_state] Checksum.of_string h.pack(HEADER_CHECKSUM_FORMAT) end
magic_checksums()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 331 def magic_checksums mask = "ICHEATED".bytes sums = [ text_checksum(0), Checksum.of_string(xw.fill), Checksum.of_string(pack_solution(xw)), header_checksum ] l, h = 0, 0 sums.each_with_index do |sum, i| l = (l << 8) | (mask[3 - i] ^ (sum & 0xff)) h = (h << 8) | (mask[7 - i] ^ (sum >> 8)) end [l, h] end
pack_clues()
click to toggle source
combine across and down clues -> xw.clues
# File lib/pangrid/plugins/acrosslite.rb, line 210 def pack_clues across, down = xw.number clues = across.map {|x| [x, :a]} + down.map {|x| [x, :d]} clues.sort! ac, dn = xw.across_clues.dup, xw.down_clues.dup xw.clues = [] clues.each do |n, dir| if dir == :a xw.clues << ac.shift else xw.clues << dn.shift end end check("Extra across clue") { ac.empty? } check("Extra down clue") { dn.empty? } end
process_extensions()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 232 def process_extensions # record these for file inspection, though they're unlikely to be useful if (ltim = get_extension("LTIM")) xw.time_elapsed = ltim.elapsed xw.paused end # we need both grbs and rtbl grbs, rtbl = get_extension("GRBS"), get_extension("RTBL") if grbs and rtbl grbs.grid.each_with_index do |n, i| if n > 0 and (v = rtbl.rebus[n]) x, y = i % xw.width, i / xw.width cell = xw.solution[y][x] cell.solution = Rebus.new(v[0]) end end end end
read_gext(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 279 def read_gext(e) e.grid = e.data.bytes end
read_grbs(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 287 def read_grbs(e) e.grid = e.data.bytes.map {|b| b == 0 ? 0 : b - 1 } end
read_ltim(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 252 def read_ltim(e) m = e.data.match /^(\d+),(\d+)\0$/ check("Could not read extension LTIM") { m } e.elapsed = m[1].to_i e.stopped = m[2] == "1" end
read_rtbl(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 263 def read_rtbl(e) rx = /(([\d ]\d):(\w+);)/ m = e.data.match /^#{rx}*\0$/ check("Could not read extension RTBL") { m } e.rebus = {} e.data.scan(rx).each {|_, k, v| e.rebus[k.to_i] = [v, '-'] } end
text_checksum(seed)
click to toggle source
checksums
# File lib/pangrid/plugins/acrosslite.rb, line 307 def text_checksum(seed) c = Checksum.new(seed) c.add_string_0 xw.title c.add_string_0 xw.author c.add_string_0 xw.copyright xw.clues.each {|cl| c.add_string cl} if (xw.version == '1.3') c.add_string_0 xw.notes end c.sum end
unpack_clues()
click to toggle source
sort incoming clues in xw.clues -> across and down
# File lib/pangrid/plugins/acrosslite.rb, line 194 def unpack_clues across, down = xw.number clues = across.map {|x| [x, :a]} + down.map {|x| [x, :d]} clues.sort! xw.across_clues = [] xw.down_clues = [] clues.zip(xw.clues).each do |(n, dir), clue| if dir == :a xw.across_clues << clue else xw.down_clues << clue end end end
write_extensions()
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 295 def write_extensions xw.extensions.map {|e| e.data = self.send(:"write_#{e.section.downcase}", e) e.len = e.data.length e.data += "\0" e.checksum = Checksum.of_string(e.data) [e.section, e.len, e.checksum].pack(EXT_HEADER_FORMAT) + e.data }.join end
write_gext(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 283 def write_gext(e) e.grid.map(&:chr).join end
write_grbs(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 291 def write_grbs(e) e.grid.map {|x| x == 0 ? 0 : x + 1}.map(&:chr).join end
write_ltim(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 259 def write_ltim(e) e.elapsed.to_s + "," + (e.stopped ? "1" : "0") + "\0" end
write_rtbl(e)
click to toggle source
# File lib/pangrid/plugins/acrosslite.rb, line 273 def write_rtbl(e) e.rebus.keys.sort.map {|x| x.to_s.rjust(2) + ":" + e.rebus[x][0] + ";" }.join end