I while ago I was trying to make a game shield for the micro:bit, #Micro:Boy, but trying to write a simple game for it in MicroPython I quickly ran out of memory – not for the game itself, but for compiling it source code. Minifying the code helped somewhat, but not enough to get something playable,
But this time, with CitcuitPython, I have orders of magnitude more memory, and can even split the code into multiple files if I have to. It's also somewhat faster, using SPI instead of I2C for communication. So I ported the code, and got something like this:
import board
import busdisplay
import busio
import displayio
import fourwire
import keypad
import time
import keypad
import supervisor
_TICKS_PERIOD = const(1<<29)
_TICKS_MAX = const(_TICKS_PERIOD-1)
_TICKS_HALFPERIOD = const(_TICKS_PERIOD//2)
class Blitty:
UP = 0x01
DOWN = 0x02
LEFT = 0x04
RIGHT = 0x08
BUTTON_O = 0x10
BUTTON_X = 0x20
def __init__(self, delay_ms=100):
self.buffer = bytearray(1024)
self.dmin = bytearray(8)
self.dmax = bytearray(128 for i in range(8))
displayio.release_displays()
self.bus = fourwire.FourWire(busio.SPI(board.SCK, board.MOSI),
command=board.D7, chip_select=board.D9, baudrate=10_000_000)
self.display = busdisplay.BusDisplay(
self.bus,
(b"\xae\x00\xd5\x01\x80\xa8\x01\x3f\xd3\x01\x00\x40\x00\xad\x01"
b"\x8b\xa1\x00\xc8\x00\xda\x01\x12\x81\x01\xff\xd9\x01\x1f\xdb"
b"\x01\x40\x20\x01\x20\x33\x00\xa6\x00\xa4\x00\xaf\x00"),
width=128,
height=64,
colstart=2,
rowstart=0,
color_depth=1,
grayscale=True,
pixels_in_byte_share_row=False,
data_as_commands=True,
brightness_command=0x81,
SH1107_addressing=True,
auto_refresh=False,
)
self.delay = delay_ms
self.next_tick = (supervisor.ticks_ms() + self.delay) % _TICKS_PERIOD
self.keypad = keypad.Keys((board.D5, board.D1, board.D2, board.D3,
board.D4, board.D0), value_when_pressed=False, interval=0.01)
self.last_buttons = 0
self.event = keypad.Event(0, False)
def buttons(self):
buttons = self.last_buttons
events = self.keypad.events
while events:
if events.get_into(self.event):
bit = 1 << self.event.key_number
if self.event.pressed:
buttons |= bit
self.last_buttons |= bit
else:
self.last_buttons &= ~bit
return buttons
def tick(self):
diff = (self.next_tick - supervisor.ticks_ms()) & _TICKS_MAX
diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
time.sleep(diff / 1000)
self.next_tick = (self.next_tick + self.delay) % _TICKS_PERIOD
def update(self):
b = memoryview(self.buffer)
addr = 0
for page in range(8):
dmin = self.dmin[page]
dmax = self.dmax[page]
if dmax:
self.bus.send(0xb0 | page, b'')
self.bus.send(0x00 | ((2 + dmin) & 0x0f), b'')
self.bus.send(0x10 | ((2 + dmin) >> 4) & 0x0f, b'')
self.bus.send(0x40, b[addr + dmin:addr + dmax])
addr += 128
self.dmax[page] = 0
self.dmin[page] = 127
def blit(self, x, y, data, mask=b''):
if not 0 <= x <= 120:
return
b = memoryview(self.buffer)
page = y // 8
shift = y % 8
if 0 <= page <= 7:
addr = x + 128 * page
for byte in mask:
b[addr] &= ~(byte << shift)
addr += 1
addr = x + 128 * page
for byte in data:
b[addr] ^= (byte << shift) & 0xff
addr += 1
self.dmin[page] = min(self.dmin[page], x)
self.dmax[page] = max(self.dmax[page], x + 7)
page += 1
if 0 <= page <= 7 and shift:
shift = 8 - shift
addr = x + 128 * page
for byte in mask:
b[addr] &= ~(byte >> shift)
addr += 1
addr = x + 128 * page
for byte in data:
b[addr] ^= byte >> shift
addr += 1
self.dmin[page] = min(self.dmin[page], x)
self.dmax[page] = max(self.dmax[page], x + 7)
MAN_MASK = bytes((
0b00111100,
0b11111110,
0b11111111,
0b11111111,
0b11111111,
0b11111111,
0b11111110,
0b00111000,
))
MAN = (bytes((
0b00000000,
0b00010000,
0b01111010,
0b00111110,
0b00111010,
0b01111110,
0b00100000,
0b00000000,
)), bytes((
0b00000000,
0b00100000,
0b00011010,
0b01111110,
0b01111010,
0b00111110,
0b00010000,
0b00000000,
)))
DIRT = bytes((
0b10001010,
0b00100000,
0b00000101,
0b01010000,
0b00000101,
0b00100000,
0b10001010,
0b00100000,
))
DARK = bytes((
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
))
ROCK_MASK = bytes((
0b00111100,
0b01111110,
0b11111111,
0b11111111,
0b11111111,
0b11111111,
0b01111110,
0b00111100,
))
ROCK = bytes((
0b00000000,
0b00110100,
0b01011010,
0b00111110,
0b01010110,
0b00101010,
0b00010100,
0b00000000,
))
blitty = Blitty()
dirt = bytearray(16)
for y in range(8):
for x in range(16):
blitty.blit(x * 8, y * 8, DIRT)
rocks = {(0, 0), (3, 3), (3, 4), (5, 2), (10, 1)}
for x, y in rocks:
blitty.blit(x * 8, y * 8, ROCK, ROCK_MASK)
x, y = 0, 8
dx = dy = steps = 0
while True:
for rx, ry in rocks:
if dirt[rx] & (1 << (ry + 1)) and not (
(y // 8 == ry + 1) and
(rx * 8 - 6 <= x <= rx * 8 + 6)
) and (rx, ry + 1) not in rocks:
dirt[rx] |= 1 << ry
blitty.blit(rx * 8, ry * 8, DARK, ROCK_MASK)
blitty.blit(rx * 8, ry * 8 + 8, ROCK, ROCK_MASK)
rocks.remove((rx, ry))
rocks.add((rx, ry + 1))
key = blitty.buttons()
if steps == 0:
dirt[x // 8] |= 1 << (y // 8)
dx = dy = 0
if key & blitty.RIGHT and x < 120:
dx = 1
elif key & blitty.LEFT and x > 0:
dx = -1
elif key & blitty.DOWN and y < 56:
dy = 1
elif key & blitty.UP and y > 0:
dy = -1
if (x // 8 + dx, y // 8 + dy) in rocks:
if dx:
rx = x // 8 + dx
ry = y // 8
if (rx + dx, ry) in rocks or not dirt[rx + dx] & (1 << ry):
dx = 0
else:
blitty.blit(rx * 8, ry * 8, DARK, ROCK_MASK)
blitty.blit(rx * 8 + dx * 8, ry * 8, ROCK, ROCK_MASK)
rocks.remove((rx, ry))
rocks.add((rx + dx, ry))
dy = 0
if dx or dy:
steps = 7
else:
steps -= 1
x += dx
y += dy
man = MAN[(steps // 2) % 2]
if dx + dy > 0:
man = bytes(reversed(man))
blitty.blit(x, y, man, MAN_MASK)
blitty.update()
blitty.blit(x, y, DARK, MAN_MASK)
blitty.tick()
It's not a complete game now, but you can dig around, push the boulders, and they fall down when not supported. All I need is to make them slip sideways when on top of another boulder, and add some diamonds for collecting, then design a few levels.
But testing this game also made me make a small change in the hardware. While it ran the game menu and the snake game on battery without problems, as soon as a large portion of the screen was filled, it refused to run on the battery anymore. A quick measurement showed that the voltage of the battery simply drops too much for the boost converter to cope. The whole thing is using too much current. The solution turned out to be simple: replace the 330k IREF resistor with a 680k one. Of course that lowers the brightness of the display, but it also makes it possible to use it with the battery, so I will take it.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.