Prepare for dealing out saved seeds
This commit is contained in:
156
seed_compressor/compressor.py
Normal file
156
seed_compressor/compressor.py
Normal file
@ -0,0 +1,156 @@
|
||||
from parse_cart import Pico8Cart
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
def main():
|
||||
ff = load_seeds("input/fortunes_foundation.txt")
|
||||
|
||||
ffdata = delta_4b(ff)
|
||||
offset_ffdata = 0
|
||||
|
||||
augment_map("../main.p8", "../seed_constants.lua", ffdata, {
|
||||
"ffdata": 0
|
||||
})
|
||||
|
||||
|
||||
def augment_map(target, target2, constants_file, binary, offsets):
|
||||
assert isinstance(binary, bytes) and len(binary) < 8192 # length of mapdata
|
||||
print(f"Length of basic extra map data: {len(binary)}")
|
||||
mapdata = (binary + bytes([0] * 8192))[:8192]
|
||||
|
||||
cart = Pico8Cart.load(target)
|
||||
def touch_map(memory):
|
||||
memory[0x0:0x1000] = mapdata[0x1000:0x2000]
|
||||
def touch_gfx(memory):
|
||||
memory[0x1000:0x2000] = mapdata[0x0000:0x1000]
|
||||
|
||||
cart.touch("__map__", touch_map)
|
||||
cart.touch("__gfx__", touch_gfx)
|
||||
cart.save(target)
|
||||
|
||||
with open(constants_file, "wt") as f:
|
||||
f.write("seed_constants={\n")
|
||||
for i, (k, v) in enumerate(offsets.items()):
|
||||
sep = "," if i < len(offsets) - 1 else ""
|
||||
f.write(f" {k}={v+0x1000}{sep}\n")
|
||||
f.write("}\n")
|
||||
|
||||
|
||||
def load_seeds(fname):
|
||||
seeds = set()
|
||||
with open(fname, "rt") as f:
|
||||
for line in f:
|
||||
seeds.add(int(line))
|
||||
|
||||
return list(sorted(seeds))
|
||||
|
||||
|
||||
def analyze(seeds):
|
||||
def peek_at_seeds(seeds):
|
||||
print("Seeds modulo various")
|
||||
for i in range(0, 30):
|
||||
matches = [s for s in seeds if s%30 == i]
|
||||
print("- {}: {} (max {})".format(i, len(matches), max(matches)))
|
||||
print()
|
||||
|
||||
peek_at_seeds(seeds)
|
||||
# seeds=seeds[:8192] # stick to the range with a realistic distribution
|
||||
|
||||
print("{} seeds".format(len(seeds)))
|
||||
for encoding in [
|
||||
naive, delta_8b, delta_3b, delta_4b, delta_5b, zlib_delta_4b, zlib_delta_8b
|
||||
]:
|
||||
print("{} encoding: {} bytes".format(encoding.__name__, len(encoding(seeds))))
|
||||
|
||||
def naive(seeds):
|
||||
return b"".join(struct.pack("<I", s) for s in seeds)
|
||||
|
||||
def delta_8b(seeds):
|
||||
out = b""
|
||||
acc = 0
|
||||
i = 0
|
||||
while i < len(seeds):
|
||||
diff = seeds[i] - acc
|
||||
if diff > 255:
|
||||
out += struct.pack("<B", 0)
|
||||
acc += 255
|
||||
else:
|
||||
out += struct.pack("<B", diff)
|
||||
acc = seeds[i]
|
||||
i += 1
|
||||
|
||||
return out
|
||||
|
||||
def zlib_delta_8b(seeds):
|
||||
return zlib.compress(delta_8b(seeds))
|
||||
|
||||
|
||||
def delta_4b(seeds):
|
||||
out_nibbles = []
|
||||
acc = 0
|
||||
i = 0
|
||||
while i < len(seeds):
|
||||
diff = seeds[i] - acc
|
||||
if diff > 15:
|
||||
out_nibbles.append(0)
|
||||
acc += 15
|
||||
else:
|
||||
out_nibbles.append(diff)
|
||||
acc = seeds[i]
|
||||
i += 1
|
||||
|
||||
while len(out_nibbles) % 2 != 0:
|
||||
out_nibbles.append(0)
|
||||
|
||||
out = b""
|
||||
for i in range(0, len(out_nibbles), 2):
|
||||
out += bytes([(out_nibbles[i] << 4) + out_nibbles[i+1]])
|
||||
return out
|
||||
|
||||
def zlib_delta_4b(seeds):
|
||||
return zlib.compress(delta_4b(seeds))
|
||||
|
||||
def delta_3b(seeds):
|
||||
return delta_nb(seeds, 3)
|
||||
|
||||
def delta_5b(seeds):
|
||||
return delta_nb(seeds, 5)
|
||||
|
||||
def delta_nb(seeds, n):
|
||||
out_fibbles = []
|
||||
acc = 0
|
||||
i = 0
|
||||
while i < len(seeds):
|
||||
diff = seeds[i] - acc
|
||||
if diff > (1<<n)-1:
|
||||
out_fibbles.append(0)
|
||||
acc += (1<<n)-1
|
||||
else:
|
||||
out_fibbles.append(diff)
|
||||
acc = seeds[i]
|
||||
i += 1
|
||||
|
||||
while len(out_fibbles) % 8 != 0:
|
||||
out_fibbles.append(0)
|
||||
|
||||
out = b""
|
||||
for i in range(0, len(out_fibbles), 8):
|
||||
chunk = out_fibbles[i:i+8]
|
||||
chunk_bits = sum([
|
||||
[i&(1<<x)==0 for x in reversed(range(n))]
|
||||
for i in chunk
|
||||
], [])
|
||||
assert len(chunk_bits)==n*8
|
||||
chunk_bytes = [
|
||||
sum([v*1<<(7-i) for i, v in enumerate(byte)])
|
||||
for byte in [
|
||||
chunk_bits[o:o+8]
|
||||
for o in range(0, n*8, 8)
|
||||
]
|
||||
]
|
||||
out += bytes(chunk_bytes)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
10316
seed_compressor/input/fortunes_foundation.txt
Normal file
10316
seed_compressor/input/fortunes_foundation.txt
Normal file
File diff suppressed because it is too large
Load Diff
103
seed_compressor/parse_cart.py
Normal file
103
seed_compressor/parse_cart.py
Normal file
@ -0,0 +1,103 @@
|
||||
SECTIONS = ("PREHEADER", "__lua__", "__gfx__", "__gff__", "__label__", "__map__", "__sfx__", "__music__")
|
||||
|
||||
class Pico8Cart(object):
|
||||
def __init__(self, sections):
|
||||
self._sections = sections
|
||||
|
||||
@classmethod
|
||||
def load(cls, fname):
|
||||
sections = {}
|
||||
|
||||
with open(fname, "rt") as f:
|
||||
section_name = "PREHEADER"
|
||||
section_text = []
|
||||
|
||||
def add_section():
|
||||
sections[section_name] = "\n".join(section_text)
|
||||
section_text.clear()
|
||||
|
||||
for line in f:
|
||||
line = line.rstrip("\r\n")
|
||||
if line in SECTIONS:
|
||||
add_section()
|
||||
|
||||
section_name = line
|
||||
else:
|
||||
section_text.append(line)
|
||||
|
||||
add_section()
|
||||
|
||||
return Pico8Cart(sections)
|
||||
|
||||
def touch(self, section, cb):
|
||||
self._sections[section] = touch(section, self._sections.get(section), cb)
|
||||
|
||||
def save(self, fname):
|
||||
with open(fname, "wt") as f:
|
||||
for s in SECTIONS:
|
||||
val = self._sections.get(s)
|
||||
val = canonize(s, val)
|
||||
|
||||
if val:
|
||||
if s != "PREHEADER":
|
||||
f.write(f"{s}\n")
|
||||
f.write(f"{val}\n")
|
||||
|
||||
def canonize(section, val):
|
||||
return touch(section, val, lambda _: ())
|
||||
|
||||
def touch(section, val, cb):
|
||||
if section in ("__gfx__", "__map__"):
|
||||
if val is None:
|
||||
val = ""
|
||||
length = 0x2000 if section == "__gfx__" else 0x1000
|
||||
row_width=128 if section=="__gfx__" else 256
|
||||
msb_first = section == "__map__"
|
||||
memory = from_binary(val, length, msb_first)
|
||||
cb(memory)
|
||||
return to_binary(memory, length, msb_first, row_width)
|
||||
return val
|
||||
|
||||
|
||||
def from_binary(pico_data: str, length: int, msb_first: bool):
|
||||
hex_data = "".join(pico_data.split("\n"))
|
||||
byte_values = []
|
||||
for i in range(0,len(hex_data),2):
|
||||
x0 = int(hex_data[i],16)
|
||||
x1 = int(hex_data[i+1],16)
|
||||
byte_values.append(
|
||||
(x0 << 4) + x1
|
||||
if msb_first else
|
||||
(x1 << 4) + x0
|
||||
)
|
||||
|
||||
assert(len(byte_values) <= length)
|
||||
byte_values = (byte_values + [0] * length)[:length]
|
||||
return bytearray(byte_values)
|
||||
|
||||
def to_binary(memory: bytearray, length: int, msb_first: bool, row_width: int):
|
||||
assert(len(memory) == length)
|
||||
|
||||
HEX_CHARS = "0123456789abcdef"
|
||||
|
||||
chars = []
|
||||
for i in range(length):
|
||||
byte = memory[i]
|
||||
msb = (byte & 0xf0) >> 4
|
||||
lsb = byte & 0x0f
|
||||
|
||||
if msb_first:
|
||||
chars.append(HEX_CHARS[msb])
|
||||
chars.append(HEX_CHARS[lsb])
|
||||
else:
|
||||
chars.append(HEX_CHARS[lsb])
|
||||
chars.append(HEX_CHARS[msb])
|
||||
|
||||
lines = []
|
||||
for i in range(0, len(chars), row_width):
|
||||
lines.append("".join(chars[i:i+row_width]))
|
||||
zeroes = "0" * row_width
|
||||
|
||||
while lines and lines[-1] == zeroes:
|
||||
lines.pop()
|
||||
return "\n".join(lines)
|
Reference in New Issue
Block a user