Add missing python tools, update README, and more data from James Bowman website about the Gameduino (still looking for the original Sphynx documentation file)
This commit is contained in:
BIN
python/471fcf9e.png
Normal file
BIN
python/471fcf9e.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
8
python/duplicator.py
Normal file
8
python/duplicator.py
Normal file
@@ -0,0 +1,8 @@
|
||||
class Duplicator(object):
|
||||
def __init__(self, objs):
|
||||
self.objs = objs
|
||||
def __getattr__(self, name):
|
||||
def bcast(*args):
|
||||
return [o.__getattribute__(name)(*args) for o in self.objs]
|
||||
return bcast
|
||||
|
||||
1
python/gameduino/__init__.py
Normal file
1
python/gameduino/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from gameduino.registers import *
|
||||
207
python/gameduino/base.py
Normal file
207
python/gameduino/base.py
Normal file
@@ -0,0 +1,207 @@
|
||||
import struct
|
||||
|
||||
ascii_glyphs = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00,
|
||||
0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x7f, 0x36, 0x7f, 0x36, 0x36, 0x00,
|
||||
0x0c, 0x3f, 0x68, 0x3e, 0x0b, 0x7e, 0x18, 0x00, 0x60, 0x66, 0x0c, 0x18, 0x30, 0x66, 0x06, 0x00,
|
||||
0x38, 0x6c, 0x6c, 0x38, 0x6d, 0x66, 0x3b, 0x00, 0x0c, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00, 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00,
|
||||
0x00, 0x18, 0x7e, 0x3c, 0x7e, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00,
|
||||
0x3c, 0x66, 0x6e, 0x7e, 0x76, 0x66, 0x3c, 0x00, 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00,
|
||||
0x3c, 0x66, 0x06, 0x0c, 0x18, 0x30, 0x7e, 0x00, 0x3c, 0x66, 0x06, 0x1c, 0x06, 0x66, 0x3c, 0x00,
|
||||
0x0c, 0x1c, 0x3c, 0x6c, 0x7e, 0x0c, 0x0c, 0x00, 0x7e, 0x60, 0x7c, 0x06, 0x06, 0x66, 0x3c, 0x00,
|
||||
0x1c, 0x30, 0x60, 0x7c, 0x66, 0x66, 0x3c, 0x00, 0x7e, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x00,
|
||||
0x3c, 0x66, 0x66, 0x3c, 0x66, 0x66, 0x3c, 0x00, 0x3c, 0x66, 0x66, 0x3e, 0x06, 0x0c, 0x38, 0x00,
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30,
|
||||
0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00,
|
||||
0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, 0x3c, 0x66, 0x0c, 0x18, 0x18, 0x00, 0x18, 0x00,
|
||||
|
||||
0x3c, 0x66, 0x6e, 0x6a, 0x6e, 0x60, 0x3c, 0x00, 0x3c, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00,
|
||||
0x7c, 0x66, 0x66, 0x7c, 0x66, 0x66, 0x7c, 0x00, 0x3c, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3c, 0x00,
|
||||
0x78, 0x6c, 0x66, 0x66, 0x66, 0x6c, 0x78, 0x00, 0x7e, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x7e, 0x00,
|
||||
0x7e, 0x60, 0x60, 0x7c, 0x60, 0x60, 0x60, 0x00, 0x3c, 0x66, 0x60, 0x6e, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x66, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x66, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00,
|
||||
0x3e, 0x0c, 0x0c, 0x0c, 0x0c, 0x6c, 0x38, 0x00, 0x66, 0x6c, 0x78, 0x70, 0x78, 0x6c, 0x66, 0x00,
|
||||
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7e, 0x00, 0x63, 0x77, 0x7f, 0x6b, 0x6b, 0x63, 0x63, 0x00,
|
||||
0x66, 0x66, 0x76, 0x7e, 0x6e, 0x66, 0x66, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x60, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x6a, 0x6c, 0x36, 0x00,
|
||||
0x7c, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0x66, 0x00, 0x3c, 0x66, 0x60, 0x3c, 0x06, 0x66, 0x3c, 0x00,
|
||||
0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x63, 0x63, 0x6b, 0x6b, 0x7f, 0x77, 0x63, 0x00,
|
||||
0x66, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00,
|
||||
0x7e, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x7e, 0x00, 0x7c, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7c, 0x00,
|
||||
0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x00,
|
||||
0x18, 0x3c, 0x66, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
|
||||
0x1c, 0x36, 0x30, 0x7c, 0x30, 0x30, 0x7e, 0x00, 0x00, 0x00, 0x3c, 0x06, 0x3e, 0x66, 0x3e, 0x00,
|
||||
0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x60, 0x66, 0x3c, 0x00,
|
||||
0x06, 0x06, 0x3e, 0x66, 0x66, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x7e, 0x60, 0x3c, 0x00,
|
||||
0x1c, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x3c,
|
||||
0x60, 0x60, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3c, 0x00,
|
||||
0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x70, 0x60, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0x00,
|
||||
0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, 0x00, 0x00, 0x36, 0x7f, 0x6b, 0x6b, 0x63, 0x00,
|
||||
0x00, 0x00, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x00,
|
||||
0x00, 0x00, 0x7c, 0x66, 0x66, 0x7c, 0x60, 0x60, 0x00, 0x00, 0x3e, 0x66, 0x66, 0x3e, 0x06, 0x07,
|
||||
0x00, 0x00, 0x6c, 0x76, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x3e, 0x60, 0x3c, 0x06, 0x7c, 0x00,
|
||||
0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x1c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3e, 0x00,
|
||||
0x00, 0x00, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x63, 0x6b, 0x6b, 0x7f, 0x36, 0x00,
|
||||
0x00, 0x00, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3e, 0x06, 0x3c,
|
||||
0x00, 0x00, 0x7e, 0x0c, 0x18, 0x30, 0x7e, 0x00, 0x0c, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0c, 0x00,
|
||||
0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, 0x30, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x30, 0x00,
|
||||
0x31, 0x6b, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
]
|
||||
|
||||
from gameduino.registers import *
|
||||
|
||||
# BaseGameduino is the common base for the Gameduino objects in remote and sim
|
||||
|
||||
class BaseGameduino(object):
|
||||
|
||||
def coldstart(self):
|
||||
self.wr(J1_RESET, 1)
|
||||
self.fill(RAM_PIC, 0, 10 * 1024)
|
||||
for i in range(512):
|
||||
self.sprite(i, 400, 400, 0, 0, 0)
|
||||
self.wr16(SPR_DISABLE, 0)
|
||||
self.wr16(SPR_PAGE, 0)
|
||||
self.wr16(JK_MODE, 0)
|
||||
self.wr16(SCROLL_X, 0)
|
||||
self.wr16(SCROLL_Y, 0)
|
||||
self.wr16(BG_COLOR, 0)
|
||||
self.wr16(SAMPLE_L, 0)
|
||||
self.wr16(SAMPLE_R, 0)
|
||||
self.wr16(SCREENSHOT_Y, 0)
|
||||
|
||||
def dump(self, a, l):
|
||||
""" Dump ``l`` bytes memory starting at address ``a`` """
|
||||
for i in range(0, l, 16):
|
||||
d16 = self.rdstr(a + i, 16)
|
||||
print "%04x %s" % (a + i, " ".join(["%02x" % ord(c) for c in d16]))
|
||||
|
||||
def wr(self, a, v):
|
||||
""" Write a single byte ``v`` to address ``a``. """
|
||||
self.wrstr(a, chr(v))
|
||||
|
||||
def fill(self, a, v, c):
|
||||
""" Fill ``c`` bytes of memory at address ``a`` with value ``v`` """
|
||||
self.wrstr(a, chr(v) * c)
|
||||
|
||||
def putstr(self, x, y, v):
|
||||
""" Write string ``v`` at screen position (x,y) """
|
||||
a = y * 64 + x
|
||||
self.wrstr(a, v)
|
||||
|
||||
def rd16(self, a):
|
||||
return struct.unpack("<H", self.rdstr(a, 2))[0]
|
||||
|
||||
def rd32(self, a):
|
||||
return struct.unpack("<L", self.rdstr(a, 4))[0]
|
||||
|
||||
def wr16(self, a, v):
|
||||
""" Write 16-bit value ``v`` at to address ``a`` """
|
||||
self.wrstr(a, struct.pack("<H", v))
|
||||
|
||||
def wr32(self, a, v):
|
||||
""" Write 32-bit value ``v`` at to address ``a`` """
|
||||
self.wrstr(a, struct.pack("<L", v))
|
||||
|
||||
def setpal(self, pal, rgb):
|
||||
self.wr16(RAM_PAL + (pal << 1), rgb);
|
||||
|
||||
def ascii(self):
|
||||
stretch = [
|
||||
0x00, 0x03, 0x0c, 0x0f,
|
||||
0x30, 0x33, 0x3c, 0x3f,
|
||||
0xc0, 0xc3, 0xcc, 0xcf,
|
||||
0xf0, 0xf3, 0xfc, 0xff ]
|
||||
gstr = ""
|
||||
for i in range(768):
|
||||
b = ascii_glyphs[i]
|
||||
h = stretch[b >> 4]
|
||||
l = stretch[b & 0xf]
|
||||
gstr += chr(h)
|
||||
gstr += chr(l)
|
||||
self.wrstr(0x1000 + (16 * ord(' ')), gstr)
|
||||
for i in range(0x20, 0x80):
|
||||
self.setpal(4 * i + 0, TRANSPARENT);
|
||||
self.setpal(4 * i + 3, RGB(255,255,255));
|
||||
self.fill(RAM_PIC, ord(' '), 4096);
|
||||
|
||||
def voice(self, v, wave, freq, lamp, ramp = None):
|
||||
"""
|
||||
Set the state of a voice.
|
||||
|
||||
:param v: voice number 0-63
|
||||
:type v: int
|
||||
:param wave: wave type, 0 for sine 1 for noise
|
||||
:type wave: int
|
||||
:param freq: frequency control, in quarter-hertz
|
||||
:type freq: int
|
||||
:param lamp: left amplitude 0-255
|
||||
:type lamp: int
|
||||
:param ramp: right amplitude 0-255, defaults to same ``lamp``
|
||||
:type ramp: int
|
||||
"""
|
||||
if ramp is None:
|
||||
ramp = lamp
|
||||
self.wr32(VOICES + (4 * v), freq | (wave << 15) | (lamp << 16) | (ramp << 24))
|
||||
|
||||
def silence(self):
|
||||
""" Switch all voices off """
|
||||
for i in range(64):
|
||||
self.voice(i, 0, 4 * 440, 0, 0)
|
||||
|
||||
def copy(self, a, v):
|
||||
self.wrstr(a, v)
|
||||
|
||||
def microcode(self, src):
|
||||
"""
|
||||
Halt coprocessor, load microprogram, restart coprocessor
|
||||
|
||||
:param src: the microprogram, as a string, expressed
|
||||
:type src: string
|
||||
|
||||
The string is loaded into the Gameduino's microprogram area,
|
||||
and can be up to 256 bytes. For example, to load the
|
||||
:ref:`splitscreen` microprogram::
|
||||
|
||||
splitscreen_code = open("splitscreen.binle", "b").read()
|
||||
gd.microcode(splitscreen_code)
|
||||
|
||||
"""
|
||||
|
||||
self.wr(J1_RESET, 1)
|
||||
self.copy(J1_CODE, src)
|
||||
self.wr(J1_RESET, 0)
|
||||
|
||||
def sprite(self, spr, x, y, image, palette, rot, jk = 0):
|
||||
"""
|
||||
Set the state of a hardware sprite
|
||||
|
||||
:param spr: sprite number 0-511
|
||||
:param x: x coordinate
|
||||
:param y: y coordinate
|
||||
:param image: sprite source image 0-63
|
||||
:param palette: sprite palette select, 0-15, see below
|
||||
:param rot: sprite rotate control 0-7, see :ref:`rotate`
|
||||
:param jk: collision class control, 0-1
|
||||
|
||||
Palette select controls the number of colors used for the sprite, the source palette, and which data bits
|
||||
to use as source.
|
||||
|
||||
"""
|
||||
|
||||
self.wr32(RAM_SPR + (4 * spr),
|
||||
(x & 511) | ((rot & 7) << 9) | ((palette & 15) << 12) | ((y & 511) << 16) | ((image & 63) << 25) | ((jk & 1) << 31))
|
||||
|
||||
def im(self):
|
||||
"""
|
||||
Return the current screen as a 400x300 RGB PIL Image::
|
||||
|
||||
>>> import gameduino.sim
|
||||
>>> gd = gameduino.sim.Gameduino()
|
||||
>>> gd.im().save("screenshot.png")
|
||||
"""
|
||||
return self._im()
|
||||
174
python/gameduino/compress.py
Normal file
174
python/gameduino/compress.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import time
|
||||
import sys
|
||||
import array
|
||||
|
||||
def prefix(s1, s2):
|
||||
""" Return the length of the common prefix of s1 and s2 """
|
||||
sz = len(s2)
|
||||
for i in range(sz):
|
||||
if s1[i % len(s1)] != s2[i]:
|
||||
return i
|
||||
return sz
|
||||
|
||||
def runlength(blk, p0, p1):
|
||||
""" p0 is the source position, p1 is the dest position
|
||||
Return the length of run of common chars
|
||||
"""
|
||||
for i in range(1, len(blk) - p1):
|
||||
if blk[p0:p0+i] != blk[p1:p1+i]:
|
||||
return i - 1
|
||||
return len(blk) - p1 - 1
|
||||
|
||||
def runlength2(blk, p0, p1, bsf):
|
||||
""" p0 is the source position, p1 is the dest position
|
||||
Return the length of run of common chars
|
||||
"""
|
||||
if blk[p0:p0+bsf+1] != blk[p1:p1+bsf+1]:
|
||||
return 0
|
||||
for i in range(bsf + 1, len(blk) - p1):
|
||||
if blk[p0:p0+i] != blk[p1:p1+i]:
|
||||
return i - 1
|
||||
return len(blk) - p1 - 1
|
||||
|
||||
class Bitstream(object):
|
||||
def __init__(self):
|
||||
self.b = []
|
||||
def append(self, sz, v):
|
||||
assert 0 <= v
|
||||
assert v < (1 << sz)
|
||||
for i in range(sz):
|
||||
self.b.append(1 & (v >> (sz - 1 - i)))
|
||||
def toarray(self):
|
||||
bb = [0] * ((len(self.b) + 7) / 8)
|
||||
for i,b in enumerate(self.b):
|
||||
if b:
|
||||
bb[i / 8] |= (1 << (i & 7));
|
||||
return array.array('B', bb)
|
||||
|
||||
class Codec(object):
|
||||
def __init__(self, b_off, b_len):
|
||||
self.b_off = b_off
|
||||
self.b_len = b_len
|
||||
self.history = 2 ** b_off
|
||||
refsize = (1 + self.b_off + self.b_len) # bits needed for a backreference
|
||||
if refsize < 9:
|
||||
self.M = 1
|
||||
elif refsize < 18:
|
||||
self.M = 2
|
||||
else:
|
||||
self.M = 3
|
||||
# print "M", self.M
|
||||
# e.g. M 2, b_len 4, so: 0->2, 15->17
|
||||
self.maxlen = self.M + (2**self.b_len) - 1
|
||||
|
||||
def compress(self, blk):
|
||||
lempel = {}
|
||||
sched = []
|
||||
pos = 0
|
||||
while pos < len(blk):
|
||||
k = blk[pos:pos+self.M]
|
||||
older = (pos - self.history - 1)
|
||||
candidates = set([p for p in lempel.get(k, []) if (older < p)])
|
||||
lempel[k] = candidates # prune old
|
||||
# print pos, repr(k), len(lempel), "lempels", "candidates", len(candidates)
|
||||
def addlempel(c):
|
||||
if c in lempel:
|
||||
lempel[c].add(pos)
|
||||
else:
|
||||
lempel[c] = set([pos])
|
||||
if 0:
|
||||
# (bestlen, bestpos) = max([(0, 0)] + [(prefix(blk[p:pos], blk[pos:]), p) for p in candidates])
|
||||
(bestlen, bestpos) = max([(0, 0)] + [(runlength(blk, p, pos), p) for p in candidates])
|
||||
bestlen = min(bestlen, self.maxlen)
|
||||
else:
|
||||
(bestlen, bestpos) = (0, None)
|
||||
for p in candidates:
|
||||
cl = runlength2(blk, p, pos, bestlen)
|
||||
if cl > bestlen:
|
||||
bestlen,bestpos = cl,p
|
||||
if self.maxlen <= bestlen:
|
||||
bestlen = min(bestlen, self.maxlen)
|
||||
break
|
||||
if bestlen >= self.M:
|
||||
sched.append((bestpos - pos, bestlen))
|
||||
for i in range(bestlen):
|
||||
addlempel(blk[pos:pos+self.M])
|
||||
pos += 1
|
||||
else:
|
||||
addlempel(k)
|
||||
sched.append(blk[pos])
|
||||
pos += 1
|
||||
return sched
|
||||
|
||||
def toarray(self, blk):
|
||||
sched = self.compress(blk)
|
||||
return self.sched2bs(sched)
|
||||
|
||||
def sched2bs(self, sched):
|
||||
bs = Bitstream()
|
||||
bs.append(4, self.b_off)
|
||||
bs.append(4, self.b_len)
|
||||
bs.append(2, self.M)
|
||||
bs.append(16, len(sched))
|
||||
for c in sched:
|
||||
if len(c) != 1:
|
||||
(offset, l) = c
|
||||
bs.append(1, 1)
|
||||
bs.append(self.b_off, -offset - 1)
|
||||
bs.append(self.b_len, l - self.M)
|
||||
else:
|
||||
bs.append(1, 0)
|
||||
bs.append(8, ord(c))
|
||||
return bs.toarray()
|
||||
|
||||
def to_cfile(self, hh, blk, name):
|
||||
print >>hh, "static PROGMEM prog_uchar %s[] = {" % name
|
||||
bb = self.toarray(blk)
|
||||
for i in range(0, len(bb), 16):
|
||||
if (i & 0xff) == 0:
|
||||
print >>hh
|
||||
for c in bb[i:i+16]:
|
||||
print >>hh, "0x%02x, " % c,
|
||||
print >>hh
|
||||
print >>hh, "};"
|
||||
|
||||
def decompress(self, sched):
|
||||
s = ""
|
||||
for c in sched:
|
||||
if len(c) == 1:
|
||||
s += c
|
||||
else:
|
||||
(offset, l) = c
|
||||
for i in range(l):
|
||||
s += s[offset]
|
||||
return s
|
||||
|
||||
def main():
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser("%prog [ --lookback O ] [ --length L ] --name NAME inputfile outputfile")
|
||||
|
||||
parser.add_option("--lookback", type=int, default=8, dest="O", help="lookback field size in bits")
|
||||
parser.add_option("--length", type=int, default=3, dest="L", help="length field size in bits")
|
||||
parser.add_option("--name", type=str, default="data", dest="NAME", help="name for generated C array")
|
||||
parser.add_option("--binary", action="store_true", default=False, dest="binary", help="write a binary file (default is to write a C++ header file)")
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if len(args) != 2:
|
||||
parser.error("must specify input and output files");
|
||||
|
||||
print options.O
|
||||
print options.L
|
||||
print options.NAME
|
||||
print args
|
||||
(inputfile, outputfile) = args
|
||||
cc = Codec(b_off = options.O, b_len = options.L)
|
||||
uncompressed = open(inputfile, "rb").read()
|
||||
if options.binary:
|
||||
compressed = cc.toarray(uncompressed)
|
||||
open(outputfile, "wb").write(compressed.tostring())
|
||||
else:
|
||||
outfile = open(outputfile, "w")
|
||||
cc.to_cfile(outfile, uncompressed, options.NAME)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
498
python/gameduino/prep.py
Normal file
498
python/gameduino/prep.py
Normal file
@@ -0,0 +1,498 @@
|
||||
"""
|
||||
gameduino.prep - for graphics and sound preparation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The prep module provides utilities for
|
||||
preparing Gameduino media: images and sound.
|
||||
These utilities can be used, for example, to take image files
|
||||
and encode them so that the Gameduino can display them as backgrounds
|
||||
or sprites.
|
||||
|
||||
|
||||
..
|
||||
This module defines mnemonics for the sprite palette select field:
|
||||
+-----------------------+-------+
|
||||
| PALETTE256A | 0 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE256B | 1 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE256C | 2 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE256D | 3 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE16A_BITS0123 | 4 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE16A_BITS4567 | 6 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE16B_BITS0123 | 5 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE16B_BITS4567 | 7 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4A_BITS01 | 8 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4A_BITS23 | 10 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4A_BITS45 | 12 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4A_BITS67 | 14 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4B_BITS01 | 9 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4B_BITS23 | 11 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4B_BITS45 | 13 |
|
||||
+-----------------------+-------+
|
||||
| PALETTE4B_BITS67 | 15 |
|
||||
+-----------------------+-------+
|
||||
|
||||
The module defines these constants for use as the ``palset`` argument to :meth:`ImageRAM.addsprites`:
|
||||
|
||||
+-------------+-------------------------+
|
||||
| PALETTE256A | 256-color palette A |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE256B | 256-color palette B |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE256C | 256-color palette C |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE256D | 256-color palette D |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE4A | Four-color palette A |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE4B | Four-color palette B |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE16A | Sixteen-color palette A |
|
||||
+-------------+-------------------------+
|
||||
| PALETTE16B | Sixteen-color palette B |
|
||||
+-------------+-------------------------+
|
||||
|
||||
"""
|
||||
|
||||
PALETTE256A = [0]
|
||||
PALETTE256B = [1]
|
||||
PALETTE256C = [2]
|
||||
PALETTE256D = [3]
|
||||
PALETTE4A_BITS01 = (0x8 + (0 << 1))
|
||||
PALETTE4A_BITS23 = (0x8 + (1 << 1))
|
||||
PALETTE4A_BITS45 = (0x8 + (2 << 1))
|
||||
PALETTE4A_BITS67 = (0x8 + (3 << 1))
|
||||
PALETTE4A = (PALETTE4A_BITS01, PALETTE4A_BITS23, PALETTE4A_BITS45, PALETTE4A_BITS67)
|
||||
PALETTE4B_BITS01 = (0x8 + (0 << 1) + 1)
|
||||
PALETTE4B_BITS23 = (0x8 + (1 << 1) + 1)
|
||||
PALETTE4B_BITS45 = (0x8 + (2 << 1) + 1)
|
||||
PALETTE4B_BITS67 = (0x8 + (3 << 1) + 1)
|
||||
PALETTE4B = (PALETTE4B_BITS01, PALETTE4B_BITS23, PALETTE4B_BITS45, PALETTE4B_BITS67)
|
||||
|
||||
PALETTE16A_BITS0123 = (0x4 + (0 << 1))
|
||||
PALETTE16A_BITS4567 = (0x4 + (1 << 1))
|
||||
PALETTE16A = (PALETTE16A_BITS0123, PALETTE16A_BITS4567)
|
||||
PALETTE16B_BITS0123 = (0x4 + (0 << 1) + 1)
|
||||
PALETTE16B_BITS4567 = (0x4 + (1 << 1) + 1)
|
||||
PALETTE16B = (PALETTE16B_BITS0123, PALETTE16B_BITS4567)
|
||||
|
||||
from array import array
|
||||
import Image
|
||||
|
||||
def dump(hh, name, data):
|
||||
"""
|
||||
Writes data to a header file for use in an Arduino Sketch.
|
||||
|
||||
:param hh: destination header file
|
||||
:type hh: :class:`file`
|
||||
:param name: the name of the object, as it will appear in the header file
|
||||
:type name: string
|
||||
:param data: the data to be dumped
|
||||
:type data: :class:`array.array`
|
||||
"""
|
||||
print >>hh, "static PROGMEM prog_uchar %s[] = {" % name
|
||||
bb = array('B', data.tostring())
|
||||
for i in range(0, len(bb), 16):
|
||||
if (i & 0xff) == 0:
|
||||
print >>hh
|
||||
for c in bb[i:i+16]:
|
||||
print >>hh, "0x%02x, " % c,
|
||||
print >>hh
|
||||
print >>hh, "};"
|
||||
|
||||
def rgbpal(imdata):
|
||||
# For RGBA imdata, return list of (r,g,b) triples and the palette
|
||||
li = array('B', imdata).tolist()
|
||||
rgbas = zip(li[0::4], li[1::4], li[2::4], li[3::4])
|
||||
palette = list(set(rgbas))
|
||||
return (rgbas, palette)
|
||||
|
||||
def getch(im, x, y):
|
||||
# return the RGBA data for the 8x8 character at (x, y) in im
|
||||
# if the 8x8 RGB contains more than 4 colors, quantize it using
|
||||
# `scolorq <http://www.cs.berkeley.edu/~dcoetzee/downloads/scolorq/>`_.
|
||||
|
||||
sub88 = im.crop((x, y, x + 8, y + 8))
|
||||
sub88d = sub88.tostring()
|
||||
|
||||
(_, pal) = rgbpal(sub88d)
|
||||
if len(pal) > 4:
|
||||
return sub88.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=4).convert("RGBA")
|
||||
else:
|
||||
return sub88
|
||||
|
||||
def rgb555(r, g, b):
|
||||
return ((r / 8) << 10) + ((g / 8) << 5) + (b / 8)
|
||||
|
||||
def rgba1555(r, g, b, a):
|
||||
return ((a < 128) << 15) + ((r / 8) << 10) + ((g / 8) << 5) + (b / 8)
|
||||
|
||||
def encodech(imdata):
|
||||
"""
|
||||
imdata is 8x8x4 RGBA character, string of length 256
|
||||
return the pixel and palette data for it as
|
||||
:class:`array.array` of type 'B' and 'H' respectively.
|
||||
"""
|
||||
|
||||
assert len(imdata) == (4 * 8 * 8)
|
||||
(rgbs, palette) = rgbpal(imdata)
|
||||
indices = [palette.index(c) for c in rgbs]
|
||||
indices_b = ""
|
||||
for i in range(0, len(indices), 4):
|
||||
c = ((indices[i] << 6) +
|
||||
(indices[i + 1] << 4) +
|
||||
(indices[i + 2] << 2) +
|
||||
(indices[i + 3]))
|
||||
indices_b += (chr(c))
|
||||
palette = (palette + ([(0,0,0,255)] * 4))[:4] # unused palette entries: opaque black
|
||||
ph = array('H', [rgba1555(*p) for p in palette])
|
||||
return (indices_b, ph)
|
||||
|
||||
def getpal(im):
|
||||
""" im is a paletted image. Return its palette as a Gameduino sprite palette
|
||||
in an :class:`array.array` of type 'H'. This form can be used directly with :func:`dump`::
|
||||
|
||||
import gameduino.prep as gdprep
|
||||
...
|
||||
gdprep.dump(hfile, "paletteA", gdprep.getpal(im))
|
||||
|
||||
"""
|
||||
|
||||
ncol = ord(max(im.tostring())) + 1
|
||||
ncol = min([c for c in [4,16,256] if c >= ncol])
|
||||
lut = im.resize((ncol, 1))
|
||||
lut.putdata(range(ncol))
|
||||
palstr = lut.convert("RGB").tostring()
|
||||
rgbs = zip(*(array('B', palstr[i::3]) for i in range(3)))
|
||||
rgb555 = [(((r / 8) << 10) | ((g / 8) << 5) | (b / 8)) for (r,g,b) in rgbs]
|
||||
if 'transparency' in im.info:
|
||||
rgb555[im.info['transparency']] = 0x8000
|
||||
return array('H', rgb555)
|
||||
|
||||
def encode(im):
|
||||
"""
|
||||
Convert a PIL image to a Gameduino character background image.
|
||||
|
||||
:param im: A Python Imaging Library image
|
||||
:rtype: tuple of data for (picture, character, font) all :class:`array.array`.
|
||||
|
||||
The image must have dimensions that are multiples of 8.
|
||||
If any character cell contains more than four colors, then the cell's pixel are quantized to four colors before encoding.
|
||||
If the image requires more than 256 unique character cells, this function throws exception OverflowError.
|
||||
The tuple returned contains three pieces of data:
|
||||
|
||||
* picture - the bytes representing the character cells. For input image sized (w, h) this array has size (w/8)*(h/8). Type of this array is 'B' (unsigned byte)
|
||||
* character - the glyphs for all used 8x8 characters. One character is 16 bytes. Type of this array is 'B' (unsigned byte)
|
||||
* palette - the 4-color palettes for all used 8x8 characters. One character is 8 bytes. Type of this array is 'H' (unsigned short)
|
||||
|
||||
To display the image, load these three arrays into Gameduino memory. For example,
|
||||
to encode a single image and
|
||||
write its data to a header file ``titlescreen.h``::
|
||||
|
||||
import gameduino.prep as gdprep
|
||||
(dpic, dchr, dpal) = gdprep.encode(Image.open("titlescreen.png"))
|
||||
hdr = open("titlescreen.h", "w")
|
||||
gdprep.dump(hdr, "titlescreen_pic", dpic)
|
||||
gdprep.dump(hdr, "titlescreen_chr", dchr)
|
||||
gdprep.dump(hdr, "titlescreen_pal", dpal)
|
||||
|
||||
and to display the image on the screen, an Arduino sketch might do::
|
||||
|
||||
#include "titlescreen.h"
|
||||
|
||||
void setup()
|
||||
{
|
||||
...
|
||||
GD.copy(RAM_PIC, titlescreen_pic, sizeof(titlescreen_pic));
|
||||
GD.copy(RAM_CHR, titlescreen_chr, sizeof(titlescreen_chr));
|
||||
GD.copy(RAM_PAL, titlescreen_pal, sizeof(titlescreen_pal));
|
||||
|
||||
"""
|
||||
|
||||
if im.mode != "RGBA":
|
||||
im = im.convert("RGBA")
|
||||
charset = {} # dict that maps 8x8 images to byte charcodes
|
||||
picture = [] # 64x64 byte picture RAM
|
||||
for y in range(0, im.size[1], 8):
|
||||
for x in range(0, im.size[0], 8):
|
||||
iglyph = getch(im, x, y)
|
||||
glyph = iglyph.tostring()
|
||||
if not glyph in charset:
|
||||
if len(charset) == 256:
|
||||
raise OverflowError
|
||||
charset[glyph] = len(charset)
|
||||
picture.append(charset[glyph])
|
||||
picd = array('B', picture)
|
||||
cd = array('B', [0] * 16 * len(charset))
|
||||
pd = array('H', [0] * 4 * len(charset))
|
||||
for d,i in charset.items():
|
||||
for y in range(8):
|
||||
(char, pal) = encodech(d)
|
||||
cd[16 * i:16 * (i+1)] = array('B', char)
|
||||
pd[4 * i:4 * (i+1)] = pal
|
||||
return (picd, cd, pd)
|
||||
|
||||
def preview(picd, cd, pd):
|
||||
preview = Image.new("RGB", im.size)
|
||||
preview.paste(iglyph, (x, y))
|
||||
return preview
|
||||
|
||||
def glom(sizes):
|
||||
""" Returns a master size and a list of crop/paste coordinates """
|
||||
mw = max(w for (w,h) in sizes)
|
||||
mh = sum(h for (w,h) in sizes)
|
||||
y = 0
|
||||
r = []
|
||||
for (w,h) in sizes:
|
||||
r.append((0, y, w, y + h))
|
||||
y += h
|
||||
return ((mw,mh), r)
|
||||
|
||||
|
||||
def palettize(im, ncol):
|
||||
""" Given an input image or list of images, convert to a palettized version using at most ``ncol`` colors.
|
||||
This function preserves transparency: if the input(s) have transparency then the returned
|
||||
image(s) have ``.info['transparency']`` set to the transparent color.
|
||||
|
||||
If ``im`` is a single image, returns a single image. If ``im`` is a list of images, returns a list of images.
|
||||
"""
|
||||
|
||||
assert ncol in (4, 16, 256)
|
||||
if isinstance(im, list):
|
||||
# For a list of images, paste them all into a single image,
|
||||
# palettize the single image, then return cropped subimages
|
||||
|
||||
for i in im:
|
||||
i.load()
|
||||
(ms, lpos) = glom([i.size for i in im])
|
||||
master = Image.new(im[0].mode, ms)
|
||||
for i,ps in zip(im, lpos):
|
||||
master.paste(i, ps)
|
||||
master = palettize(master, ncol)
|
||||
ims = [master.crop(ps) for ps in lpos]
|
||||
for i in ims:
|
||||
i.info = master.info
|
||||
return ims
|
||||
else:
|
||||
im.load()
|
||||
|
||||
if im.mode == 'P':
|
||||
if ord(max(im.tostring())) < ncol:
|
||||
return im # already done
|
||||
if 'transparency' in im.info:
|
||||
im = im.convert("RGBA")
|
||||
else:
|
||||
im = im.convert("RGB")
|
||||
assert im.mode in ("RGBA", "RGB")
|
||||
if im.mode == "RGB":
|
||||
return im.convert('P', palette=Image.ADAPTIVE, colors=ncol)
|
||||
else:
|
||||
alpha = im.split()[3]
|
||||
mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0)
|
||||
im.paste((0,0,0), mask)
|
||||
im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors = (ncol - 1))
|
||||
im.paste(ncol - 1, mask)
|
||||
im.info['transparency'] = ncol - 1
|
||||
return im
|
||||
|
||||
def isnonblank(im):
|
||||
assert im.mode == 'P'
|
||||
if 'transparency' in im.info:
|
||||
transparent = im.info['transparency']
|
||||
(w,h) = im.size
|
||||
colors = set([im.getpixel((i, j)) for i in range(w) for j in range(h)])
|
||||
return colors != set([transparent])
|
||||
else:
|
||||
return True
|
||||
|
||||
class ImageRAM(object):
|
||||
"""
|
||||
|
||||
The ImageRAM object simplifies loading of the Gameduino's 16K sprite image RAM.
|
||||
A caller adds sprite images to the ImageRAM, and finally obtains a memory image
|
||||
using :meth:`ImageRAM.used`.
|
||||
|
||||
"""
|
||||
def __init__(self, hh):
|
||||
self.hh = hh
|
||||
self.data = array('B', [0] * 16384)
|
||||
self.nxtpage = 0 # next available page
|
||||
self.nxtbit = 0 # next available bit
|
||||
|
||||
def __bump(self, b):
|
||||
self.nxtbit += b
|
||||
if 8 == self.nxtbit:
|
||||
self.nxtbit = 0
|
||||
self.nxtpage += 1
|
||||
|
||||
def add(self, page, size):
|
||||
"""
|
||||
Add a sprite image to the ImageRAM
|
||||
|
||||
:param page: image data, a list length 256
|
||||
:param size: size of data elements, either 4, 16 or 256
|
||||
:rtype: Returns a tuple (image, pal)
|
||||
|
||||
This method adds the data in ``page`` to the ImageRAM, and the returns the assigned location. ``image`` is the
|
||||
sprite image 0-63 containing the data, and ``pal`` is the palette bit select for the data.
|
||||
|
||||
For a 4-color image, ``pal`` is 0-3, for 16-color image ``pal`` is 0-1 and for a 256-color image ``pal`` is 0.
|
||||
|
||||
The ``image`` and ``pal`` values may be used to display the sprite using :cpp:func:`GD::sprite`.
|
||||
|
||||
If the data would cause the ImageRAM to increase beyond 16K, this method throws exception OverflowError.
|
||||
"""
|
||||
assert size in (4,16,256)
|
||||
assert max(page) < size, "%d colors allowed, but page contains %d" % (size, max(page))
|
||||
assert len(page) == 256
|
||||
|
||||
bits = {4:2, 16:4, 256:8}[size]
|
||||
while (self.nxtbit % bits) != 0:
|
||||
self.__bump(2)
|
||||
if self.nxtpage == 64:
|
||||
raise OverflowError
|
||||
if size == 4:
|
||||
pal = self.nxtbit / 2
|
||||
elif size == 16:
|
||||
pal = self.nxtbit / 4
|
||||
else:
|
||||
pal = 0
|
||||
pg = self.nxtpage
|
||||
for i in range(256):
|
||||
self.data[256 * self.nxtpage + i] |= (page[i] << self.nxtbit)
|
||||
self.__bump(bits)
|
||||
return (pg, pal)
|
||||
|
||||
def addsprites(self, name, size, im, palset = PALETTE256A, center = (0,0)):
|
||||
"""
|
||||
Extract multiple sprite frames from a source image, and generate the code to draw them.
|
||||
|
||||
:param name: name of the sprite set; used to name the generated ``draw_`` function
|
||||
:param size: size of each sprite frame (width, height)
|
||||
:param im: source image, mode must be 'P' - paletted
|
||||
:param palset: palette set to use for the sprite, one of PALETTE256A-D, PALETTE16A-B, PALETTE4A-B
|
||||
:param center: the center pixel of the sprite image. Default is (0,0) meaning top left pixel.
|
||||
|
||||
Given a sequence of sprite frames in ``im``, this method extracts their data and adds it to the ImageRAM.
|
||||
In addition, it writes the code to draw the sprite to the ImageRAM's header file. For example::
|
||||
|
||||
import gameduino.prep as gdprep
|
||||
ir = gdprep.ImageRAM(open("hdr.h", "w"))
|
||||
rock0 = gdprep.palettize(Image.open("rock0r.png"), 16)
|
||||
ir.addsprites("rock0", (16, 16), rock0, gdprep.PALETTE16A, center = (8,8))
|
||||
|
||||
would extract the four 16x16 frames from the ``rock0r.png`` image:
|
||||
|
||||
.. image:: rock0r.png
|
||||
|
||||
and write the following code to ``hdr.h``::
|
||||
|
||||
#define ROCK0_FRAMES 4
|
||||
static void draw_rock0(int x, int y, byte anim, byte rot, byte jk = 0) {
|
||||
...
|
||||
}
|
||||
|
||||
For more more examples, see the :ref:`asteroids` demo game.
|
||||
"""
|
||||
|
||||
def get16x16(sheet, x, y):
|
||||
return sheet.crop((16*x, 16*y, 16*(x+1), 16*(y+1)))
|
||||
|
||||
def walktile(im, size):
|
||||
for y in range(0, im.size[1], size[1]):
|
||||
for x in range(0, im.size[0], size[0]):
|
||||
yield im.crop((x, y, x + size[0], y + size[1]))
|
||||
tiles = list(walktile(im, size))
|
||||
|
||||
print >>self.hh, "#define %s_FRAMES %d" % (name.upper(), len(tiles))
|
||||
animtype = ["byte", "int"][len(tiles) > 255]
|
||||
print >>self.hh, """static void draw_%s(int x, int y, %s anim, byte rot, byte jk = 0) {\n switch (anim) {""" % (name, animtype)
|
||||
if palset == PALETTE256A:
|
||||
ncolors = 256
|
||||
elif palset == PALETTE256B:
|
||||
ncolors = 256
|
||||
elif palset == PALETTE256C:
|
||||
ncolors = 256
|
||||
elif palset == PALETTE256D:
|
||||
ncolors = 256
|
||||
elif palset == PALETTE4A:
|
||||
ncolors = 4
|
||||
elif palset == PALETTE4B:
|
||||
ncolors = 4
|
||||
elif palset == PALETTE16A:
|
||||
ncolors = 16
|
||||
elif palset == PALETTE16B:
|
||||
ncolors = 16
|
||||
else:
|
||||
highest = ord(max(im.tostring()))
|
||||
ncolors = min([c for c in [4,16,256] if (highest < c)])
|
||||
for spr,spriteimage in enumerate(tiles):
|
||||
loads = []
|
||||
for y in range((size[1] + 15) / 16):
|
||||
for x in range((size[0] + 15) / 16):
|
||||
t = get16x16(spriteimage, x, y)
|
||||
t.info = im.info # workaround: PIL does not copy .info when cropping
|
||||
if isnonblank(t):
|
||||
(page, palsel) = self.add(array('B', t.tostring()), ncolors)
|
||||
loads += [" GD.xsprite(x, y, %d, %d, %d, %d, rot, jk);" % (x * 16 - center[0], y * 16 - center[1], page, palset[palsel])]
|
||||
if loads:
|
||||
print >>self.hh, " case %d:" % spr
|
||||
print >>self.hh, "\n".join(loads)
|
||||
print >>self.hh, " break;"
|
||||
|
||||
print >>self.hh, """ }\n}\n"""
|
||||
|
||||
def used(self):
|
||||
"""
|
||||
Return the contents of the ImageRAM, as an :class:`array.array` of type 'B'.
|
||||
The size of the array depends on the amount of data added, up to a limit
|
||||
of 16K.
|
||||
"""
|
||||
if self.nxtbit == 0:
|
||||
past = self.nxtpage
|
||||
else:
|
||||
past = self.nxtpage + 1
|
||||
return array('B', self.data[:256*past])
|
||||
|
||||
import math
|
||||
|
||||
def spectrum(specfile, cutoff = 64, volume = 255):
|
||||
"""
|
||||
Read an Audacity spectrum file and return a list of (frequency, amplitude)
|
||||
pairs, loudest first.
|
||||
|
||||
:param cutoff: length of the list of returned pairs
|
||||
:param volume: total volume of the returned pairs
|
||||
:rtype: list of tuples (frequency, amplitude) where frequency is a floating-point frequency in Hz, and amplitude in an integer amplitude.
|
||||
|
||||
This function can be used to create voice profiles for instruments
|
||||
and sounds. For example to load a choir sound, previously saved
|
||||
as ``choir.txt``::
|
||||
|
||||
for (i, (f, a)) in enumerate(spectrum("choir.txt")):
|
||||
gd.voice(i, 0, int(4 * f), a, a)
|
||||
|
||||
"""
|
||||
|
||||
snd = [[float(t) for t in l.split()] for l in open(specfile) if not "Freq" in l]
|
||||
snd = [(f,db) for (f,db) in snd if 40 < f < 8192]
|
||||
snd = sorted(snd, reverse=True, key=lambda t:t[1])
|
||||
top = snd[:cutoff]
|
||||
amps = [(f,math.pow(2, .1 * db)) for (f, db) in top]
|
||||
samps = sum([a for (f,a) in amps])
|
||||
return [(f, int(volume * a / samps)) for (f, a) in amps]
|
||||
|
||||
__all__ = [ "encode", "dump", "palettize", "getpal", "ImageRAM", "spectrum", ]
|
||||
48
python/gameduino/registers.py
Normal file
48
python/gameduino/registers.py
Normal file
@@ -0,0 +1,48 @@
|
||||
RAM_PIC = 0x0000 # Screen Picture, 64 x 64 = 4096 bytes
|
||||
RAM_CHR = 0x1000 # Screen Characters, 256 x 16 = 4096 bytes
|
||||
RAM_PAL = 0x2000 # Screen Character Palette, 256 x 8 = 2048 bytes
|
||||
|
||||
IDENT = 0x2800
|
||||
REV = 0x2801
|
||||
FRAME = 0x2802
|
||||
VBLANK = 0x2803
|
||||
SCROLL_X = 0x2804
|
||||
SCROLL_Y = 0x2806
|
||||
JK_MODE = 0x2808
|
||||
J1_RESET = 0x2809
|
||||
SPR_DISABLE = 0x280a
|
||||
SPR_PAGE = 0x280b
|
||||
IOMODE = 0x280c
|
||||
|
||||
BG_COLOR = 0x280e
|
||||
SAMPLE_L = 0x2810
|
||||
SAMPLE_R = 0x2812
|
||||
|
||||
SCREENSHOT_Y = 0x281e
|
||||
|
||||
PALETTE16A = 0x2840 # 16-color palette RAM A, 32 bytes
|
||||
PALETTE16B = 0x2860 # 16-color palette RAM B, 32 bytes
|
||||
PALETTE4A = 0x2880 # 4-color palette RAM A, 8 bytes
|
||||
PALETTE4B = 0x2888 # 4-color palette RAM A, 8 bytes
|
||||
COMM = 0x2890 # Communication buffer
|
||||
COLLISION = 0x2900 # Collision detection RAM, 256 bytes
|
||||
VOICES = 0x2a00 # Voice controls
|
||||
J1_CODE = 0x2b00 # J1 coprocessor microcode RAM
|
||||
SCREENSHOT = 0x2c00 # screenshot line RAM
|
||||
|
||||
RAM_SPR = 0x3000 # Sprite Control, 512 x 4 = 2048 bytes
|
||||
RAM_SPRPAL = 0x3800 # Sprite Palettes, 4 x 256 = 2048 bytes
|
||||
RAM_SPRIMG = 0x4000 # Sprite Image, 64 x 256 = 16384 bytes
|
||||
|
||||
def RGB(r, g, b):
|
||||
""" Return the 16-bit hardware encoding of color (R,G,B).
|
||||
|
||||
:param R: red value 0-255
|
||||
:param G: green value 0-255
|
||||
:param B: blue value 0-255
|
||||
:rtype: int
|
||||
"""
|
||||
return ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3)
|
||||
|
||||
TRANSPARENT = (1 << 15)
|
||||
|
||||
130
python/gameduino/remote.py
Normal file
130
python/gameduino/remote.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
gameduino.remote - remote interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The remote interface lets Python scripts read and write
|
||||
Gameduino memory, via the USB connection and a
|
||||
simple client running on the Arduino.
|
||||
|
||||
The remote interface can be more convenient than compiling and uploading a
|
||||
Sketch when developing media and coprocessor microprograms::
|
||||
|
||||
import gameduino.remote
|
||||
gd = gameduino.remote.Gameduino("/dev/ttyUSB0", 115200)
|
||||
gd.ascii()
|
||||
gd.putstr(0, 5, "Hello from Python")
|
||||
|
||||
The Gameduino in this module is similar to the one in :mod:`gameduino.sim`.
|
||||
|
||||
Because this module uses USB serial interface to communicate with the Arduino,
|
||||
it requires the `PySerial module <http://pyserial.sourceforge.net/>`_.
|
||||
|
||||
.. image:: remote.png
|
||||
|
||||
The Arduino runs a simple program ``memloader`` that
|
||||
listens for serial commands and accesses Gameduino memory.
|
||||
"""
|
||||
|
||||
import struct
|
||||
import serial
|
||||
import time
|
||||
import array
|
||||
|
||||
from gameduino.registers import *
|
||||
from gameduino.base import BaseGameduino
|
||||
|
||||
class Gameduino(BaseGameduino):
|
||||
|
||||
def __init__(self, usbport, speed):
|
||||
self.ser = serial.Serial(usbport, speed)
|
||||
time.sleep(2)
|
||||
assert self.rd(IDENT) == 0x6d, "Missing IDENT"
|
||||
self.mem = array.array('B', [0] * 32768)
|
||||
self.coldstart()
|
||||
|
||||
def wrstr(self, a, s):
|
||||
if not isinstance(s, str):
|
||||
s = s.tostring()
|
||||
self.mem[a:a+len(s)] = array.array('B', s)
|
||||
for i in range(0, len(s), 255):
|
||||
sub = s[i:i+255]
|
||||
ff = struct.pack(">BH", len(sub), 0x8000 | (a + i)) + sub
|
||||
self.ser.write(ff)
|
||||
|
||||
def rd(self, a):
|
||||
""" Read byte at address ``a`` """
|
||||
ff = struct.pack(">BH", 1, a)
|
||||
self.ser.write(ff)
|
||||
return ord(self.ser.read(1))
|
||||
|
||||
def rdstr(self, a, n):
|
||||
"""
|
||||
Read ``n`` bytes starting at address ``a``
|
||||
|
||||
:rtype: string of length ``n``.
|
||||
"""
|
||||
r = ""
|
||||
while n:
|
||||
cnt = min(255, n)
|
||||
ff = struct.pack(">BH", cnt, a)
|
||||
self.ser.write(ff)
|
||||
r += self.ser.read(cnt)
|
||||
a += cnt
|
||||
n -= cnt
|
||||
return r
|
||||
|
||||
def waitvblank(self):
|
||||
while self.rd(VBLANK) == 1:
|
||||
pass
|
||||
while self.rd(VBLANK) == 0:
|
||||
pass
|
||||
|
||||
def linecrc(self, y):
|
||||
self.ser.write(struct.pack(">BBH", 0, ord('L'), y))
|
||||
return struct.unpack(">L", self.ser.read(4))[0]
|
||||
|
||||
def coll(self):
|
||||
"""
|
||||
Return the 256 bytes of COLLISION RAM.
|
||||
|
||||
:rtype: list of byte values.
|
||||
"""
|
||||
self.ser.write(struct.pack(">BB", 0, ord('c')))
|
||||
return array.array('B', self.ser.read(256)).tolist()
|
||||
|
||||
def collcrc(self):
|
||||
self.ser.write(struct.pack(">BB", 0, ord('C')))
|
||||
return struct.unpack(">L", self.ser.read(4))[0]
|
||||
|
||||
def memcrc(self, a, s):
|
||||
self.ser.write(struct.pack(">BBHH", 0, ord('M'), a, s))
|
||||
return struct.unpack(">L", self.ser.read(4))[0]
|
||||
|
||||
def _im(self):
|
||||
"""
|
||||
Return the current screen as a 400x300 RGB PIL Image::
|
||||
|
||||
>>> import gameduino.sim
|
||||
>>> gd = gameduino.sim.Gameduino()
|
||||
>>> gd.im().save("screenshot.png")
|
||||
"""
|
||||
import Image
|
||||
fi = Image.new("RGB", (400,300))
|
||||
for y in range(300):
|
||||
self.wr16(SCREENSHOT_Y, 0x8000 | y)
|
||||
while (self.rd16(SCREENSHOT_Y) & 0x8000) == 0:
|
||||
pass
|
||||
ld = array.array('H', self.rdstr(SCREENSHOT, 800))
|
||||
r = [8 * (31 & (v >> 10)) for v in ld]
|
||||
g = [8 * (31 & (v >> 5)) for v in ld]
|
||||
b = [8 * (31 & (v >> 0)) for v in ld]
|
||||
rgb = sum(zip(r,g,b), ())
|
||||
li = Image.fromstring("RGB", (400,1), array.array('B', rgb).tostring())
|
||||
fi.paste(li, (0, y))
|
||||
self.wr16(SCREENSHOT_Y, 0)
|
||||
return fi
|
||||
|
||||
|
||||
import array
|
||||
def readarray(filename):
|
||||
return array.array('B', open(filename).read())
|
||||
274
python/gameduino/sim.py
Normal file
274
python/gameduino/sim.py
Normal file
@@ -0,0 +1,274 @@
|
||||
"""
|
||||
gameduino.sim - simple simulator
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Gameduino simulator can simulate some aspects of Gameduino hardware, both video and audio.
|
||||
It can be a useful tool for previewing media before loading on actual hardware.
|
||||
|
||||
The Gameduino in this module is similar to the one in :mod:`gameduino.remote`::
|
||||
|
||||
import gameduino
|
||||
import gameduino.prep as gdprep
|
||||
import gameduino.sim as gdsim
|
||||
|
||||
im = Image.open("platformer.png").convert("RGB")
|
||||
(picd,chrd,pald) = gdprep.encode(im)
|
||||
gd = gdsim.Gameduino()
|
||||
gd.wrstr(gameduino.RAM_PIC, picd)
|
||||
gd.wrstr(gameduino.RAM_CHR, chrd)
|
||||
gd.wrstr(gameduino.RAM_PAL, pald)
|
||||
gd.im().save("preview.png")
|
||||
|
||||
The simulator can produce screenshots (:meth:`Gameduino.im`), generate
|
||||
single-note sounds (:meth:`Gameduino.writewave`), and simulate collision RAM
|
||||
(:meth:`Gameduino.coll`). It does not currently simulate the coprocessor.
|
||||
|
||||
"""
|
||||
|
||||
import struct
|
||||
import time
|
||||
import array
|
||||
import math
|
||||
import itertools
|
||||
import wave
|
||||
import binascii
|
||||
|
||||
import Image
|
||||
|
||||
from gameduino.registers import *
|
||||
from gameduino.base import BaseGameduino
|
||||
|
||||
def sum512(a, b):
|
||||
return 511 & (a + b)
|
||||
def s9(vv):
|
||||
vv &= 0x1ff
|
||||
if vv > 400:
|
||||
vv -= 512;
|
||||
return vv
|
||||
|
||||
class Gameduino(BaseGameduino):
|
||||
"""
|
||||
The Gameduino object simulates some aspects of the Gameduino hardware. For example::
|
||||
|
||||
>>> import gameduino
|
||||
>>> import gameduino.sim as gdsim
|
||||
>>> gd = gdsim.Gameduino()
|
||||
>>> print hex(gd.rd(gameduino.IDENT))
|
||||
0x6d
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.mem = array.array('B', [0] * 32768)
|
||||
self.mem[IDENT] = 0x6d
|
||||
self.coldstart()
|
||||
|
||||
def wrstr(self, a, s):
|
||||
if not isinstance(s, str):
|
||||
s = s.tostring()
|
||||
self.mem[a:a+len(s)] = array.array('B', s)
|
||||
|
||||
def rd(self, a):
|
||||
""" Read byte at address ``a`` """
|
||||
return self.mem[a]
|
||||
|
||||
def rdstr(self, a, n):
|
||||
"""
|
||||
Read ``n`` bytes starting at address ``a``
|
||||
|
||||
:rtype: string of length ``n``.
|
||||
"""
|
||||
return self.mem[a:a+n].tostring()
|
||||
|
||||
def writewave(self, duration, dst):
|
||||
"""
|
||||
Write the simulated output of the sound system to a wave file
|
||||
|
||||
:param duration: length of clip in seconds
|
||||
:param dst: destination wave filename
|
||||
"""
|
||||
sintab = [int(127 * math.sin(2 * math.pi * i / 128.)) for i in range(128)]
|
||||
nsamples = int(8000 * duration)
|
||||
master = [i/8000. for i in range(nsamples)]
|
||||
lacc = [0] * nsamples
|
||||
racc = [0] * nsamples
|
||||
for v in range(64):
|
||||
if v == self.rd(RING_START):
|
||||
print v, max(lacc)
|
||||
ring = [s/256 for s in lacc]
|
||||
lacc = [0] * nsamples
|
||||
racc = [0] * nsamples
|
||||
|
||||
(freq,la,ra) = struct.unpack("<HBB", self.rdstr(VOICES + 4 * v, 4))
|
||||
if la or ra:
|
||||
tone = [sintab[int(m * freq * 32) & 0x7f] for m in master]
|
||||
if self.rd(RING_START) <= v < self.rd(RING_END):
|
||||
lacc = [o + la * (r * t) / 256 for (o,r,t) in zip(lacc, ring, tone)]
|
||||
racc = [o + ra * (r * t) / 256 for (o,r,t) in zip(racc, ring, tone)]
|
||||
else:
|
||||
lacc = [o + la * t for (o,t) in zip(lacc, tone)]
|
||||
racc = [o + ra * t for (o,t) in zip(racc, tone)]
|
||||
merged = [None,None] * nsamples
|
||||
merged[0::2] = lacc
|
||||
merged[1::2] = racc
|
||||
raw = array.array('h', merged)
|
||||
w = wave.open(dst, "wb")
|
||||
w.setnchannels(2)
|
||||
w.setsampwidth(2)
|
||||
w.setframerate(8000)
|
||||
w.writeframesraw(raw)
|
||||
w.close()
|
||||
|
||||
def bg(self, lines = range(512)):
|
||||
bg_color = self.rd16(BG_COLOR) & 0x7fff
|
||||
glyphs = []
|
||||
for i in range(256):
|
||||
pals = array.array('H', self.mem[RAM_PAL + 8 * i:RAM_PAL + 8 * i + 8].tostring())
|
||||
for j in range(4):
|
||||
if pals[j] & 0x8000:
|
||||
pals[j] = bg_color
|
||||
glyph = []
|
||||
for y in range(8):
|
||||
for x in range(8):
|
||||
pix = 3 & (self.mem[RAM_CHR + 16 * i + 2 * y + (x / 4)] >> [6,4,2,0][x&3])
|
||||
glyph.append(pals[pix])
|
||||
glyphs.append(glyph)
|
||||
img = {}
|
||||
for y in lines:
|
||||
line = []
|
||||
for x in range(512):
|
||||
c = self.mem[RAM_PIC + 64 * (y >> 3) + (x >> 3)]
|
||||
line.append(glyphs[c][8 * (y & 7) + (x & 7)])
|
||||
img[y] = line
|
||||
return img
|
||||
|
||||
def sprfetch(self, img, pal, rot, x, y):
|
||||
if rot & 1:
|
||||
exo,eyo = y,x
|
||||
else:
|
||||
exo,eyo = x,y
|
||||
if rot & 2:
|
||||
exo = 15 - exo
|
||||
if rot & 4:
|
||||
eyo = 15 - eyo
|
||||
ix = self.rd(RAM_SPRIMG + 256 * img + 16 * eyo + exo)
|
||||
if (pal & 0xc) == 0:
|
||||
pix = self.rd16(RAM_SPRPAL + 512 * (pal & 3) + 2 * ix)
|
||||
elif (pal & 0xc) == 4:
|
||||
nyb = 15 & (ix >> [0,4][1 & (pal >> 1)])
|
||||
pix = self.rd16(PALETTE16A + 32 * (pal & 1) + 2 * nyb)
|
||||
else:
|
||||
nyb = 3 & (ix >> [0,2,4,6][3 & (pal >> 1)])
|
||||
pix = self.rd16(PALETTE4A + 8 * (pal & 1) + 2 * nyb)
|
||||
return pix
|
||||
|
||||
def spr_page(self):
|
||||
return RAM_SPR + 1024 * (self.rd(SPR_PAGE) & 1)
|
||||
|
||||
def sp(self, y, line):
|
||||
if self.rd(SPR_DISABLE) & 1:
|
||||
return line
|
||||
page = self.spr_page()
|
||||
for i in range(256):
|
||||
sprval = self.rd32(page + 4 * i)
|
||||
sx = s9(sprval)
|
||||
sy = s9(sprval >> 16)
|
||||
simg = (sprval >> 25) & 63
|
||||
spal = (sprval >> 12) & 15
|
||||
srot = (sprval >> 9) & 7
|
||||
yo = y - sy
|
||||
if 0 <= yo < 16:
|
||||
for xo in range(16):
|
||||
if 0 <= (sx + xo) < 400:
|
||||
pix = self.sprfetch(simg, spal, srot, xo, yo)
|
||||
if pix < 32768:
|
||||
line[sx + xo] = pix
|
||||
return line
|
||||
def coll(self):
|
||||
"""
|
||||
Return the 256 bytes of COLLISION RAM.
|
||||
|
||||
:rtype: list of byte values.
|
||||
"""
|
||||
coll = 256 * [0xff]
|
||||
page = self.spr_page()
|
||||
jkmode = (self.rd(JK_MODE) & 1) != 0
|
||||
if 0 == (self.rd(SPR_DISABLE) & 1):
|
||||
yocc = [[] for i in range(300)]
|
||||
for i in range(256):
|
||||
sprval = self.rd32(page + 4 * i)
|
||||
sy = s9(sprval >> 16)
|
||||
for j in range(16):
|
||||
if 0 <= (sy + j) < 300:
|
||||
yocc[sy + j].append(i)
|
||||
for y in range(300):
|
||||
tag = [None] * 400
|
||||
jk = [None] * 400
|
||||
for i in yocc[y]:
|
||||
sprval = self.rd32(page + 4 * i)
|
||||
sy = s9(sprval >> 16)
|
||||
yo = y - sy
|
||||
if 0 <= yo < 16:
|
||||
sx = s9(sprval)
|
||||
simg = (sprval >> 25) & 63
|
||||
spal = (sprval >> 12) & 15
|
||||
srot = (sprval >> 9) & 7
|
||||
sjk = (sprval >> 31)
|
||||
for xo in range(16):
|
||||
x = sx + xo
|
||||
if 0 <= x < 400:
|
||||
if self.sprfetch(simg, spal, srot, xo, yo) < 32768:
|
||||
if tag[x] != None:
|
||||
if (not jkmode) or (jk[x] != sjk):
|
||||
coll[i] = tag[x]
|
||||
tag[x] = i
|
||||
jk[x] = sjk
|
||||
return coll
|
||||
|
||||
def screen(self, lines, w = 400):
|
||||
sx = self.rd16(SCROLL_X) & 511
|
||||
sy = self.rd16(SCROLL_Y)
|
||||
bg = self.bg([sum512(y, sy) for y in lines])
|
||||
def wrapx(l):
|
||||
return (l + l)[sx:sx+w]
|
||||
return dict([(y, self.sp(y, wrapx(bg[sum512(y, sy)]))) for y in lines])
|
||||
|
||||
def _im(self):
|
||||
return self._imwh(400, 300)
|
||||
|
||||
def fullim(self):
|
||||
""" Return the entire 512x512 pixel screen image """
|
||||
return _imwh(512, 512)
|
||||
|
||||
def _imwh(self, w, h):
|
||||
import Image
|
||||
fi = Image.new("RGB", (w, h))
|
||||
lines = self.screen(range(h), w)
|
||||
for y in range(h):
|
||||
ld = lines[y]
|
||||
r = [8 * (31 & (v >> 10)) for v in ld]
|
||||
g = [8 * (31 & (v >> 5)) for v in ld]
|
||||
b = [8 * (31 & (v >> 0)) for v in ld]
|
||||
rgb = sum(zip(r,g,b), ())
|
||||
li = Image.fromstring("RGB", (w,1), array.array('B', rgb).tostring())
|
||||
fi.paste(li, (0, y))
|
||||
return fi
|
||||
|
||||
def linecrc(self, y):
|
||||
line = array.array('H', self.screen([y])[y]).tostring()
|
||||
return 0xffffffff & binascii.crc32(line)
|
||||
|
||||
def collcrc(self):
|
||||
return 0xffffffff & binascii.crc32(array.array('B', self.coll()).tostring())
|
||||
|
||||
def memcrc(self, a, s):
|
||||
return 0xffffffff & binascii.crc32(self.mem[a:a+s])
|
||||
|
||||
def memory(self):
|
||||
""" Returns current image of memory as a 32768 byte string """
|
||||
return self.mem.tostring()
|
||||
|
||||
def readarray(filename):
|
||||
return array.array('B', open(filename).read())
|
||||
|
||||
|
||||
__all__ = [ "Gameduino" ]
|
||||
4
python/go
Normal file
4
python/go
Normal file
@@ -0,0 +1,4 @@
|
||||
sudo python setup.py install || exit
|
||||
# python remoteunit.py /dev/ttyUSB0 ; exit
|
||||
python unit.py TestGameduino.test_471fcf9e || exit
|
||||
# python randoms.py /dev/ttyUSB0
|
||||
BIN
python/platformer.png
Normal file
BIN
python/platformer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
190
python/randoms.py
Normal file
190
python/randoms.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import time
|
||||
import math
|
||||
import array
|
||||
import random
|
||||
|
||||
import gameduino
|
||||
import gameduino.remote
|
||||
import gameduino.sim
|
||||
|
||||
from duplicator import Duplicator
|
||||
|
||||
random.seed(10)
|
||||
|
||||
def read_y(y):
|
||||
gd.wr16(gameduino.SCREENSHOT_Y, 0x8000 | y)
|
||||
gd.waitvblank()
|
||||
gd.waitvblank()
|
||||
line = "".join([gd.rdstr(gameduino.SCREENSHOT + i, 200) for i in range(0, 800, 200)])
|
||||
gd.wr16(gameduino.SCREENSHOT_Y, 0)
|
||||
return array.array('H', line).tolist()
|
||||
|
||||
def sling(n):
|
||||
n1s = random.randrange(n + 1)
|
||||
return sum([(1 << i) for i in random.sample(range(n), n1s)])
|
||||
|
||||
def r(n):
|
||||
return random.randrange(n)
|
||||
|
||||
def randbytes(n):
|
||||
return array.array('B', [r(256) for i in range(n)])
|
||||
|
||||
def matches(r):
|
||||
return r[0] == r[1]
|
||||
|
||||
class TestRegime(object):
|
||||
def __init__(self, dd):
|
||||
self.dd = dd
|
||||
assert matches(dd.rd(gameduino.IDENT))
|
||||
self.dd.microcode(open("../synth/sketches/j1firmware/thrasher.binle").read())
|
||||
self.setup()
|
||||
|
||||
def scramble(self):
|
||||
""" Scramble all registers and memories """
|
||||
for rg in self.reg8s:
|
||||
self.dd.wr(rg, r(2**8))
|
||||
for rg in self.reg16s:
|
||||
self.dd.wr16(rg, r(2**16))
|
||||
for a,s in self.memories:
|
||||
self.dd.wrstr(a, randbytes(s))
|
||||
|
||||
def setup(self):
|
||||
self.scramble()
|
||||
|
||||
def cycle(self):
|
||||
for c in xrange(1000000):
|
||||
print "Cycle", c
|
||||
for (area,size) in random.sample(self.memories, r(len(self.memories))):
|
||||
dat = randbytes(1 + sling(6))
|
||||
if len(dat) == size:
|
||||
a = area
|
||||
else:
|
||||
a = area + random.randrange(0, size - len(dat))
|
||||
self.dd.wrstr(a, dat)
|
||||
if r(2) == 0 and self.reg16s:
|
||||
self.dd.wr16(random.choice(self.reg16s), random.getrandbits(16))
|
||||
if r(2) == 0 and self.reg8s:
|
||||
self.dd.wr(random.choice(self.reg8s), random.getrandbits(8))
|
||||
|
||||
for (y, (e,a)) in [(y, self.dd.linecrc(y)) for y in self.checklines()]:
|
||||
if e != a:
|
||||
print "mismatch at line", y, (e,a)
|
||||
e = gameduino.sim.screen([y])[y]
|
||||
a = read_y(y)
|
||||
print "expected", e
|
||||
print "actual", a
|
||||
print set([(ee != aa) for (ee,aa) in zip(e,a)])
|
||||
print 'y', self.dd.linecrc(y)
|
||||
sys.exit(1)
|
||||
a,s = random.choice(self.memories)
|
||||
if r(5) == 0:
|
||||
assert matches(self.dd.rd(a + r(s)))
|
||||
if r(5) == 0:
|
||||
assert matches(self.dd.memcrc(a, s))
|
||||
|
||||
if not matches(self.dd.collcrc()):
|
||||
def s9(vv):
|
||||
vv &= 0x1ff
|
||||
if vv > 400:
|
||||
vv -= 512;
|
||||
return vv
|
||||
|
||||
if 0:
|
||||
page = self.dd.spr_page()[0]
|
||||
for i in range(256):
|
||||
sprval = gdsim.rd32(page + 4 * i)
|
||||
sx = s9(sprval)
|
||||
sy = s9(sprval >> 16)
|
||||
simg = (sprval >> 25) & 63
|
||||
spal = (sprval >> 12) & 15
|
||||
srot = (sprval >> 9) & 7
|
||||
sjk = (sprval >> 31)
|
||||
print "%3d: x=%3d y=%3d img=%2d pal=%d rot=%d jk=%d" % (i, sx, sy, simg, spal, srot, sjk)
|
||||
(e,a) = self.dd.coll()
|
||||
print 'collcrc', self.dd.collcrc()
|
||||
import binascii
|
||||
print 'e crc', 0xffffffff & binascii.crc32(array.array('B', e).tostring())
|
||||
print 'a crc', 0xffffffff & binascii.crc32(array.array('B', a).tostring())
|
||||
for i in range(256):
|
||||
print "%3d: e=%3d a=%3d" % (i, e[i], a[i])
|
||||
sys.exit(1)
|
||||
# gdsim.im().save("p%04d.png" % c)
|
||||
|
||||
class FullchipRegime(TestRegime):
|
||||
reg16s = [gameduino.SCROLL_X, gameduino.SCROLL_Y, gameduino.BG_COLOR, gameduino.SAMPLE_L, gameduino.SAMPLE_R]
|
||||
reg8s = [gameduino.IDENT,
|
||||
gameduino.REV,
|
||||
gameduino.SPR_PAGE,
|
||||
gameduino.JK_MODE,
|
||||
gameduino.SPR_DISABLE,
|
||||
gameduino.IOMODE]
|
||||
memories = [
|
||||
(gameduino.RAM_PIC, 10 * 1024),
|
||||
(gameduino.RAM_SPR, 2048),
|
||||
(gameduino.RAM_SPRPAL, 2048),
|
||||
(gameduino.RAM_SPRIMG, 16384),
|
||||
(gameduino.PALETTE16A, 64),
|
||||
(gameduino.PALETTE4A, 64),
|
||||
(gameduino.VOICES, 256),
|
||||
# (gameduino.IDENT, 64), # register file
|
||||
]
|
||||
def checklines(self):
|
||||
return [0, 299] + random.sample(range(1, 299), 2)
|
||||
|
||||
class SpriteRegime(TestRegime):
|
||||
reg16s = []
|
||||
reg8s = [gameduino.JK_MODE,
|
||||
gameduino.SPR_PAGE]
|
||||
memories = [
|
||||
(gameduino.RAM_SPR, 2048),
|
||||
(gameduino.RAM_SPRPAL, 2048),
|
||||
# (gameduino.RAM_SPRIMG, 16384),
|
||||
(gameduino.PALETTE16A, 64),
|
||||
(gameduino.PALETTE4A, 64),
|
||||
(gameduino.VOICES, 256),
|
||||
]
|
||||
|
||||
def setup(self):
|
||||
self.scramble()
|
||||
patt = (
|
||||
"0101010101010101"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1222222222222220"
|
||||
"0222222222222221"
|
||||
"1010101010101010" )
|
||||
def expand(c):
|
||||
c = int(c)
|
||||
return c + 4 * c + 16 * c + 64 * c
|
||||
|
||||
image = array.array('B', [expand(c) for c in patt])
|
||||
for i in range(64):
|
||||
self.dd.wrstr(gameduino.RAM_SPRIMG + 256 * i, image);
|
||||
self.dd.microcode(open("../synth/sketches/j1firmware/thrasher.binle").read())
|
||||
|
||||
def checklines(self):
|
||||
return []
|
||||
|
||||
def main():
|
||||
gdsim = gameduino.sim.Gameduino()
|
||||
gd = gameduino.remote.Gameduino(sys.argv[1], 115200)
|
||||
dd = Duplicator((gdsim, gd))
|
||||
# rr = SpriteRegime(dd)
|
||||
rr = FullchipRegime(dd)
|
||||
rr.cycle()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
43
python/remoteunit.py
Normal file
43
python/remoteunit.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
import Image
|
||||
import StringIO
|
||||
|
||||
import gameduino.remote
|
||||
import gameduino.prep as gdprep
|
||||
|
||||
gd = gameduino.remote.Gameduino(sys.argv.pop(), 115200)
|
||||
|
||||
class TestGameduino(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_talk(self):
|
||||
self.assertEqual(gd.rd(gameduino.IDENT), 0x6d)
|
||||
|
||||
def test_sprites(self):
|
||||
ir = gdprep.ImageRAM(StringIO.StringIO())
|
||||
(rock0, rock1) = gdprep.palettize([Image.open("rock0r.png"), Image.open("rock1r.png")], 16)
|
||||
ir.addsprites("rock0", (16, 16), rock0, gdprep.PALETTE16A, center = (8,8))
|
||||
ir.addsprites("rock1", (32, 32), rock1, gdprep.PALETTE16A, center = (16,16))
|
||||
gd.wr16(gameduino.RAM_PAL, gameduino.RGB(0, 255, 0))
|
||||
gd.wrstr(gameduino.RAM_SPRIMG, ir.used())
|
||||
gd.wrstr(gameduino.PALETTE16A, gdprep.getpal(rock0))
|
||||
|
||||
for i in range(128):
|
||||
gd.sprite(i, 200 + 20 * (i & 7), 20 * (i / 8), i / 2, gdprep.PALETTE16A[i&1], 0)
|
||||
|
||||
(pic,chr,pal) = gdprep.encode(Image.open("platformer.png"))
|
||||
gd.wrstr(gameduino.RAM_CHR, chr)
|
||||
gd.wrstr(gameduino.RAM_PAL, pal)
|
||||
for y in range(32):
|
||||
gd.wrstr(gameduino.RAM_PIC + 64 * y, pic[16*y:16*y+16])
|
||||
|
||||
def test_ascii(self):
|
||||
gd.ascii()
|
||||
gd.putstr(10, 10, "THIS IS A!!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
BIN
python/rock0r-pal16.png
Normal file
BIN
python/rock0r-pal16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 583 B |
BIN
python/rock0r.png
Normal file
BIN
python/rock0r.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
python/rock1r.png
Normal file
BIN
python/rock1r.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
11
python/setup.py
Normal file
11
python/setup.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from distutils.core import setup
|
||||
setup(name='gameduino',
|
||||
version='1.0.3',
|
||||
author='James Bowman',
|
||||
author_email='jamesb@excamera.com',
|
||||
url='http://gameduino.com',
|
||||
description='Package of Gameduino development tools',
|
||||
long_description='Gameduino (http://gameduino.com) is an Arduino video games adapter. This package contains tools for developers: data preparation, simulation and remote control.',
|
||||
license='GPL',
|
||||
packages=['gameduino'],
|
||||
)
|
||||
71
python/unit.py
Normal file
71
python/unit.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import gameduino
|
||||
import unittest
|
||||
import Image
|
||||
import StringIO
|
||||
|
||||
import gameduino.prep as gdprep
|
||||
import gameduino.sim as gdsim
|
||||
import gameduino.compress as compress
|
||||
import gameduino
|
||||
|
||||
class TestGameduino(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.bg = Image.open("platformer.png")
|
||||
pass
|
||||
|
||||
def test_encode(self):
|
||||
im = self.bg.convert("RGB")
|
||||
w,h = im.size
|
||||
(pic,chr,pal) = gdprep.encode(im)
|
||||
self.assertEqual(len(chr.tostring()) / 16, len(pal.tostring()) / 8)
|
||||
self.assertEqual(len(pic), (w / 8) * (h / 8))
|
||||
|
||||
def test_imageRAM(self):
|
||||
hh = StringIO.StringIO()
|
||||
singles = [Image.open("rock0r-pal16.png"),
|
||||
gdprep.palettize(Image.open("rock0r.png"), 16),
|
||||
gdprep.palettize(Image.open("rock0r-pal16.png"), 16)]
|
||||
for r in singles:
|
||||
print r.mode
|
||||
ir = gdprep.ImageRAM(hh)
|
||||
ir.addsprites("rock0", (16, 16), r, gdprep.PALETTE16A, center = (8,8))
|
||||
self.assert_(len(hh.getvalue()) > 0)
|
||||
self.assertEqual(len(gdprep.getpal(r)), 16)
|
||||
ir = gdprep.ImageRAM(hh)
|
||||
(walk,) = gdprep.palettize([Image.open("walk.png")], 16)
|
||||
ir.addsprites("walk", (32, 32), walk, gdprep.PALETTE16A, center = (8,32))
|
||||
print len(ir.used())
|
||||
print hh.getvalue()
|
||||
|
||||
def test_compress(self):
|
||||
cc = compress.Codec(b_off = 9, b_len = 3)
|
||||
for plain in [ "00000111100000", "This is this" * 4]:
|
||||
compressed = cc.compress(plain)
|
||||
print "compressed to", len(compressed), "tokens"
|
||||
print compressed
|
||||
self.assertEqual(plain, cc.decompress(compressed))
|
||||
print len(cc.sched2bs(compressed))
|
||||
|
||||
def test_sim(self):
|
||||
gd = gdsim.Gameduino()
|
||||
self.assertEqual(gd.rd(gameduino.IDENT), 0x6d)
|
||||
i = gd.im()
|
||||
self.assertEqual(i.size, (400,300))
|
||||
|
||||
def test_prep_sim(self):
|
||||
im = self.bg.convert("RGB")
|
||||
(pic,chr,pal) = gdprep.encode(im)
|
||||
gd = gdsim.Gameduino()
|
||||
gd.wrstr(gameduino.RAM_PIC, pic)
|
||||
gd.wrstr(gameduino.RAM_CHR, chr)
|
||||
gd.wrstr(gameduino.RAM_PAL, pal)
|
||||
gd.im().save("preview.png")
|
||||
|
||||
def test_471fcf9e(self):
|
||||
im = Image.open("471fcf9e.png")
|
||||
imp = gdprep.palettize(im, 16)
|
||||
print gdprep.getpal(imp)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
BIN
python/walk.png
Normal file
BIN
python/walk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Reference in New Issue
Block a user