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:
Godzil
2017-05-31 12:44:42 +01:00
parent 2b0c1e60f3
commit f622dafc8a
107 changed files with 12183 additions and 0 deletions

BIN
python/471fcf9e.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

8
python/duplicator.py Normal file
View 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

View File

@@ -0,0 +1 @@
from gameduino.registers import *

207
python/gameduino/base.py Normal file
View 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()

View 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
View 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", ]

View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

190
python/randoms.py Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

BIN
python/rock0r.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
python/rock1r.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

11
python/setup.py Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB