diff options
Diffstat (limited to 'circuitpython/frozen/circuitpython-stage')
42 files changed, 2107 insertions, 0 deletions
diff --git a/circuitpython/frozen/circuitpython-stage/LICENSE b/circuitpython/frozen/circuitpython-stage/LICENSE new file mode 100644 index 0000000..acd9e28 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Radomir Dopieralski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/circuitpython/frozen/circuitpython-stage/README.rst b/circuitpython/frozen/circuitpython-stage/README.rst new file mode 100644 index 0000000..dfadce4 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/README.rst @@ -0,0 +1,44 @@ +Stage – a Tile and Sprite Engine +******************************** + +Stage is a library that lets you display tile grids and sprites on SPI-based +RGB displays in CircuitPython. It is mostly made with video games in mind, but +it can be useful in making any kind of graphical user interface too. + +For performance reasons, a part of this library has been written in C and has +to be compiled as part of the CircuitPython firmware as the ``_stage`` module. +For memory saving reasons, it's best if this library is also included in the +firmware, as a frozen module. + + +API Reference +************* + +The API reference is available at `<http://circuitpython-stage.readthedocs.io>`_. + +stage +===== +.. automodule:: stage + :members: + + +ugame +======= +.. module:: ugame + +.. data:: display + + An initialized display, that can be used for creating Stage objects. + +.. data:: buttons + + An instance of ``GamePad`` or other similar class, that has a + ``get_pressed`` method for retrieving a bit mask of pressed buttons. That + value can be then checked with & operator against the constants: ``K_UP``, + ``K_DOWN``, ``K_LEFT``, ``K_RIGHT``, ``K_X``, ``K_O`` and on some platforms + also: ``K_START`` and ``K_SELECT``. + +.. data:: audio + + And instance of the ``Audio`` or other similar class, that has ``play``, + ``stop`` and ``mute`` methods. diff --git a/circuitpython/frozen/circuitpython-stage/docs/.buildinfo b/circuitpython/frozen/circuitpython-stage/docs/.buildinfo new file mode 100644 index 0000000..30130b6 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: bbefe5630918a9d58757bbc0b225e223 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/circuitpython/frozen/circuitpython-stage/docs/.doctrees/README.doctree b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/README.doctree Binary files differnew file mode 100644 index 0000000..6b058e6 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/README.doctree diff --git a/circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.pickle b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.pickle Binary files differnew file mode 100644 index 0000000..d290392 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.pickle diff --git a/circuitpython/frozen/circuitpython-stage/docs/README.rst b/circuitpython/frozen/circuitpython-stage/docs/README.rst new file mode 120000 index 0000000..89a0106 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/README.rst @@ -0,0 +1 @@ +../README.rst
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/docs/_stage.py b/circuitpython/frozen/circuitpython-stage/docs/_stage.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/_stage.py diff --git a/circuitpython/frozen/circuitpython-stage/docs/audioio.py b/circuitpython/frozen/circuitpython-stage/docs/audioio.py new file mode 100644 index 0000000..3a1213c --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/audioio.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + diff --git a/circuitpython/frozen/circuitpython-stage/docs/board.py b/circuitpython/frozen/circuitpython-stage/docs/board.py new file mode 100644 index 0000000..ab4195e --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/board.py @@ -0,0 +1,10 @@ +X = None +DOWN = None +LEFT = None +RIGHT = None +UP = None +O = None +SCK = None +MOSI = None +DC = None +SPEAKER = None diff --git a/circuitpython/frozen/circuitpython-stage/docs/busio.py b/circuitpython/frozen/circuitpython-stage/docs/busio.py new file mode 100644 index 0000000..c419fea --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/busio.py @@ -0,0 +1,12 @@ +class SPI: + def __init__(self, clock, MOSI): + pass + + def try_lock(self): + pass + + def write(self, buffer): + pass + + def configure(self, baudrate=None, polarity=None, phase=None): + pass diff --git a/circuitpython/frozen/circuitpython-stage/docs/conf.py b/circuitpython/frozen/circuitpython-stage/docs/conf.py new file mode 100644 index 0000000..585fe6b --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/conf.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +import os +import sys +sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('..')) + +# -- General configuration ------------------------------------------------ + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', +] + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3.4', None), + 'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None), +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'README' + +# General information about the project. +project = u'Stage' +copyright = u'2017 Radomir Dopieralski' +author = u'Radomir Dopieralski' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'1.0' +# The full version, including alpha/beta/rc tags. +release = u'1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +default_role = "any" + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +add_function_parentheses = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + except: + html_theme = 'default' + html_theme_path = ['.'] +else: + html_theme_path = ['.'] + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Stagedoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Stage.tex', u'Stage Documentation', + u'Radomir Dopieralski', 'manual'), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'stage', u'Stage Documentation', + [author], 1) +] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Stage', u'Stage Documentation', + author, 'Stage', 'Tile and sprite engine.', 'Miscellaneous'), +] diff --git a/circuitpython/frozen/circuitpython-stage/docs/digitalio.py b/circuitpython/frozen/circuitpython-stage/docs/digitalio.py new file mode 100644 index 0000000..5b5ab80 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/digitalio.py @@ -0,0 +1,6 @@ +class DigitalInOut: + def __init__(self, pin): + pass + + def switch_to_output(self, value=None): + pass diff --git a/circuitpython/frozen/circuitpython-stage/docs/gamepad.py b/circuitpython/frozen/circuitpython-stage/docs/gamepad.py new file mode 100644 index 0000000..8cf5bb4 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/gamepad.py @@ -0,0 +1,3 @@ +class GamePad: + def __init__(self, *args): + pass diff --git a/circuitpython/frozen/circuitpython-stage/docs/ustruct.py b/circuitpython/frozen/circuitpython-stage/docs/ustruct.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/docs/ustruct.py diff --git a/circuitpython/frozen/circuitpython-stage/examples/ball/ball.bmp b/circuitpython/frozen/circuitpython-stage/examples/ball/ball.bmp Binary files differnew file mode 100644 index 0000000..67de048 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/examples/ball/ball.bmp diff --git a/circuitpython/frozen/circuitpython-stage/examples/ball/ball.py b/circuitpython/frozen/circuitpython-stage/examples/ball/ball.py new file mode 100644 index 0000000..66cc52c --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/examples/ball/ball.py @@ -0,0 +1,38 @@ +import ugame +import stage + + +class Ball(stage.Sprite): + def __init__(self, x, y): + super().__init__(bank, 1, x, y) + self.dx = 2 + self.dy = 1 + + def update(self): + super().update() + self.set_frame(self.frame % 4 + 1) + self.move(self.x + self.dx, self.y + self.dy) + if not 0 < self.x < 112: + self.dx = -self.dx + if not 0 < self.y < 112: + self.dy = -self.dy + + +bank = stage.Bank.from_bmp16("ball.bmp") +background = stage.Grid(bank) +text = stage.Text(12, 1) +text.move(16, 60) +text.text("Hello world!") +ball1 = Ball(64, 0) +ball2 = Ball(0, 76) +ball3 = Ball(111, 64) +game = stage.Stage(ugame.display, 12) +sprites = [ball1, ball2, ball3] +game.layers = [text, ball1, ball2, ball3, background] +game.render_block(0, 0, 128, 128) + +while True: + for sprite in sprites: + sprite.update() + game.render_sprites(sprites) + game.tick() diff --git a/circuitpython/frozen/circuitpython-stage/examples/ball/main.py b/circuitpython/frozen/circuitpython-stage/examples/ball/main.py new file mode 100644 index 0000000..66cc52c --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/examples/ball/main.py @@ -0,0 +1,38 @@ +import ugame +import stage + + +class Ball(stage.Sprite): + def __init__(self, x, y): + super().__init__(bank, 1, x, y) + self.dx = 2 + self.dy = 1 + + def update(self): + super().update() + self.set_frame(self.frame % 4 + 1) + self.move(self.x + self.dx, self.y + self.dy) + if not 0 < self.x < 112: + self.dx = -self.dx + if not 0 < self.y < 112: + self.dy = -self.dy + + +bank = stage.Bank.from_bmp16("ball.bmp") +background = stage.Grid(bank) +text = stage.Text(12, 1) +text.move(16, 60) +text.text("Hello world!") +ball1 = Ball(64, 0) +ball2 = Ball(0, 76) +ball3 = Ball(111, 64) +game = stage.Stage(ugame.display, 12) +sprites = [ball1, ball2, ball3] +game.layers = [text, ball1, ball2, ball3, background] +game.render_block(0, 0, 128, 128) + +while True: + for sprite in sprites: + sprite.update() + game.render_sprites(sprites) + game.tick() diff --git a/circuitpython/frozen/circuitpython-stage/examples/rpg/ground.bmp b/circuitpython/frozen/circuitpython-stage/examples/rpg/ground.bmp Binary files differnew file mode 100644 index 0000000..d7e5f16 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/examples/rpg/ground.bmp diff --git a/circuitpython/frozen/circuitpython-stage/examples/rpg/main.py b/circuitpython/frozen/circuitpython-stage/examples/rpg/main.py new file mode 100644 index 0000000..37dfd87 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/examples/rpg/main.py @@ -0,0 +1,68 @@ +import random +import ugame +import stage + + +g = stage.Bank.from_bmp16("ground.bmp") +b = stage.Bank.from_bmp16("tiles.bmp") +l1 = stage.Grid(g) +l0 = stage.Grid(b, 10, 9) +l0.tile(0, 0, 13) +l0.move(-8, -8) +for y in range(8): + for x in range(8): + l1.tile(x, y, random.randint(0, 4)) +for y in range(9): + for x in range(9): + t = 0 + bit = 1 + for dx in (0, -1): + for dy in (-1, 0): + if l1.tile(x + dx, y + dy) == 4: + t |= bit + bit <<= 1 + l0.tile(x, y, 15 - t) +p = stage.Sprite(g, 15, 10, 10) +t = stage.Text(14, 14) +t.move(8, 8) +t.text("Hello world!") + +game = stage.Stage(ugame.display, 12) +sprites = [ + stage.Sprite(g, 15, 60, 50), + stage.Sprite(g, 15, 70, 60), + stage.Sprite(g, 15, 80, 70), + stage.Sprite(g, 15, 90, 80), + stage.Sprite(g, 15, 100, 90), + p, +] +game.layers = [t, l0] + sprites + [l1] +game.render(0, 0, 128, 128) + +frame = 0 +while True: + frame = (frame + 1) % 8 + keys = ugame.buttons.get_pressed() + if keys & ugame.K_RIGHT: + p.move(p.x, p.y + 2) + p.set_frame(12 + frame // 4, 0) + elif keys & ugame.K_LEFT: + p.move(p.x, p.y - 2) + p.set_frame(12 + frame // 4, 4) + elif keys & ugame.K_UP: + p.move(p.x + 2, p.y) + p.set_frame(14, (frame // 4) * 4) + elif keys & ugame.K_DOWN: + p.move(p.x - 2, p.y) + p.set_frame(15, (frame // 4) * 4) + else: + p.set_frame(15, (frame // 4) * 4) + for sprite in sprites: + if sprite != p: + sprite.set_frame(15, (frame // 4) * 4) + x0 = min(sprite.px, sprite.x) + y0 = min(sprite.py, sprite.y) + x1 = max(sprite.px, sprite.x) + 16 + y1 = max(sprite.py, sprite.y) + 16 + game.render(x0, y0, x1, y1) + game.tick() diff --git a/circuitpython/frozen/circuitpython-stage/examples/rpg/tiles.bmp b/circuitpython/frozen/circuitpython-stage/examples/rpg/tiles.bmp Binary files differnew file mode 100644 index 0000000..0e50ae3 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/examples/rpg/tiles.bmp diff --git a/circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/stage.py b/circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/ugame.py b/circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/ugame.py new file mode 100644 index 0000000..728e014 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/ugame.py @@ -0,0 +1,82 @@ +import board +from micropython import const +from adafruit_seesaw.seesaw import Seesaw +from adafruit_seesaw.pwmout import PWMOut +import stage +import displayio + + +K_UP = const(4) +K_LEFT = const(8) +K_DOWN = const(16) +K_RIGHT = const(128) +K_X = const(512) +K_O = const(1024) +K_SELECT = const(2048) +K_START = const(0) + +_INIT_SEQUENCE = ( + b"\x01\x80\x96" # SWRESET and Delay 150ms + b"\x11\x80\xff" # SLPOUT and Delay + b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 + b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 + b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 + b"\xb4\x01\x07" # _INVCTR line inversion + b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA + b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V + b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency + b"\xc3\x02\x8a\x2a" + b"\xc4\x02\x8a\xee" + b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V + b"\x20\x00" # _INVOFF + b"\x36\x01\x60" # _MADCTL bottom to top refresh + # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, + # fix on VTL + b"\x3a\x01\x05" # COLMOD - 16bit color + b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma + b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 + b"\x13\x80\x0a" # _NORON + b"\x29\x80\x64" # _DISPON +) + + +class GamePadSeesaw: + mask = K_RIGHT | K_DOWN | K_LEFT | K_UP | K_SELECT | K_O | K_X + + def __init__(self, ss): + ss.pin_mode_bulk(self.mask, ss.INPUT_PULLUP) + self.ss = ss + + def get_pressed(self): + return ~self.ss.digital_read_bulk(self.mask) + + +class DummyAudio: + def play(self, f, loop=False): + pass + + def stop(self): + pass + + def mute(self, mute): + pass + + +i2c = board.I2C() +ss = Seesaw(i2c, 0x5E) +spi = board.SPI() +displayio.release_displays() +while not spi.try_lock(): + pass +spi.configure(baudrate=24000000) +spi.unlock() +ss.pin_mode(8, ss.OUTPUT) +ss.digital_write(8, True) # reset display +display_bus = displayio.FourWire(spi, command=board.D6, chip_select=board.D5) +display = displayio.Display(display_bus, _INIT_SEQUENCE, width=160, height=80, + rowstart=24) +del _INIT_SEQUENCE +buttons = GamePadSeesaw(ss) +audio = DummyAudio() +backlight = PWMOut(ss, 5) +backlight.duty_cycle = 0 diff --git a/circuitpython/frozen/circuitpython-stage/font/font.bmp b/circuitpython/frozen/circuitpython-stage/font/font.bmp Binary files differnew file mode 100644 index 0000000..c576e8b --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/font/font.bmp diff --git a/circuitpython/frozen/circuitpython-stage/font/font2.bmp b/circuitpython/frozen/circuitpython-stage/font/font2.bmp Binary files differnew file mode 100644 index 0000000..7d41ca6 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/font/font2.bmp diff --git a/circuitpython/frozen/circuitpython-stage/font/genfont.py b/circuitpython/frozen/circuitpython-stage/font/genfont.py new file mode 100644 index 0000000..74e0fcd --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/font/genfont.py @@ -0,0 +1,89 @@ +import array +import pprint + + +def color565(r, g, b): + return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 + + +class BMP16: + """Read 16-color BMP files.""" + + def __init__(self, filename): + self.filename = filename + self.colors = 0 + + def read_header(self): + """Read the file's header information.""" + + if self.colors: + return + with open(self.filename, 'rb') as f: + f.seek(10) + self.data = int.from_bytes(f.read(4), 'little') + f.seek(18) + self.width = int.from_bytes(f.read(4), 'little') + self.height = int.from_bytes(f.read(4), 'little') + f.seek(46) + self.colors = int.from_bytes(f.read(4), 'little') + + def read_palette(self): + """Read the color palette information.""" + + palette = array.array('H', (0 for i in range(16))) + with open(self.filename, 'rb') as f: + f.seek(self.data - self.colors * 4) + for color in range(self.colors): + buffer = f.read(4) + c = color565(buffer[2], buffer[1], buffer[0]) + palette[color] = ((c & 0xff) << 8) | (c >> 8) + return palette + + def read_data(self, offset=0, size=-1): + """Read the image data.""" + + with open(self.filename, 'rb') as f: + f.seek(self.data + offset) + return f.read(size) + + +class Font: + def __init__(self, buffer): + self.buffer = buffer + + @classmethod + def from_bmp16(cls, filename): + bmp = BMP16(filename) + bmp.read_header() + if bmp.width != 8 or bmp.height != 1024: + raise ValueError("A 8x1024 16-color BMP expected!") + data = bmp.read_data() + self = cls(bytearray(2048)) + c = 0 + x = 0 + y = 7 + for b in data: + self.pixel(c, x, y, b >> 4) + x += 1 + self.pixel(c, x, y, b & 0x0f) + x += 1 + if x >= 8: + x = 0 + y -= 1 + if y < 0: + y = 7 + c += 1 + del data + self.palette = bmp.read_palette() + return self + + def pixel(self, c, x, y, color): + index = (127 - c) * 16 + 2 * y + x // 4 + bit = (x % 4) * 2 + color = color & 0x03 + self.buffer[index] |= color << bit + + +font = Font.from_bmp16("font.bmp") +pprint.pprint(font.buffer) +pprint.pprint(font.palette.tobytes()) diff --git a/circuitpython/frozen/circuitpython-stage/font/genfont2.py b/circuitpython/frozen/circuitpython-stage/font/genfont2.py new file mode 100644 index 0000000..158d5e9 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/font/genfont2.py @@ -0,0 +1,89 @@ +import array +import pprint + + +def color565(r, g, b): + return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 + + +class BMP16: + """Read 16-color BMP files.""" + + def __init__(self, filename): + self.filename = filename + self.colors = 0 + + def read_header(self): + """Read the file's header information.""" + + if self.colors: + return + with open(self.filename, 'rb') as f: + f.seek(10) + self.data = int.from_bytes(f.read(4), 'little') + f.seek(18) + self.width = int.from_bytes(f.read(4), 'little') + self.height = int.from_bytes(f.read(4), 'little') + f.seek(46) + self.colors = int.from_bytes(f.read(4), 'little') + + def read_palette(self): + """Read the color palette information.""" + + palette = array.array('H', (0 for i in range(16))) + with open(self.filename, 'rb') as f: + f.seek(self.data - self.colors * 4) + for color in range(self.colors): + buffer = f.read(4) + c = color565(buffer[2], buffer[1], buffer[0]) + palette[color] = ((c & 0xff) << 8) | (c >> 8) + return palette + + def read_data(self, offset=0, size=-1): + """Read the image data.""" + + with open(self.filename, 'rb') as f: + f.seek(self.data + offset) + return f.read(size) + + +class Font: + def __init__(self, buffer): + self.buffer = buffer + + @classmethod + def from_bmp16(cls, filename): + bmp = BMP16(filename) + bmp.read_header() + if bmp.width != 8 or bmp.height != 1024: + raise ValueError("A 8x1024 16-color BMP expected!") + data = bmp.read_data() + self = cls(bytearray(2048)) + c = 0 + x = 0 + y = 7 + for b in data: + self.pixel(c, x, y, b >> 4) + x += 1 + self.pixel(c, x, y, b & 0x0f) + x += 1 + if x >= 8: + x = 0 + y -= 1 + if y < 0: + y = 7 + c += 1 + del data + self.palette = bmp.read_palette() + return self + + def pixel(self, c, x, y, color): + index = (127 - c) * 16 + 2 * y + x // 4 + bit = (x % 4) * 2 + color = color & 0x03 + self.buffer[index] |= color << bit + + +font = Font.from_bmp16("font2.bmp") +pprint.pprint(font.buffer) +pprint.pprint(font.palette.tobytes()) diff --git a/circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/stage.py b/circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/ugame.py b/circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/ugame.py new file mode 100644 index 0000000..20d4d05 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/ugame.py @@ -0,0 +1,79 @@ +""" +A helper module that initializes the display and buttons for the uGame +game console. See https://hackaday.io/project/27629-game +""" + +import board +import digitalio +import gamepad +import stage +import displayio +import busio + + +K_X = 0x01 +K_DOWN = 0x02 +K_LEFT = 0x04 +K_RIGHT = 0x08 +K_UP = 0x10 +K_O = 0x20 +K_START = 0x40 +K_SELECT = 0x00 + + +_INIT_SEQUENCE = ( + b"\x01\x80\x96" # SWRESET and Delay 150ms + b"\x11\x80\xff" # SLPOUT and Delay + b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 + b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 + b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 + b"\xb4\x01\x07" # _INVCTR line inversion + b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA + b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V + b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency + b"\xc3\x02\x8a\x2a" + b"\xc4\x02\x8a\xee" + b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V + b"\x20\x00" # _INVOFF + b"\x36\x01\x10" # _MADCTL bottom to top refresh + # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, + # fix on VTL + b"\x3a\x01\x05" # COLMOD - 16bit color + b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma + b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 + b"\x13\x80\x0a" # _NORON + b"\x29\x80\x64" # _DISPON +) + + +class DummyAudio: + def play(self, f, loop=False): + pass + + def stop(self): + pass + + def mute(self, mute): + pass + + +displayio.release_displays() +_tft_spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI) +_tft_spi.try_lock() +_tft_spi.configure(baudrate=24000000) +_tft_spi.unlock() +_fourwire = displayio.FourWire(_tft_spi, command=board.A3, + chip_select=board.A2, reset=board.A4) +display = displayio.Display(_fourwire, _INIT_SEQUENCE, width=160, height=128, + rotation=0, backlight_pin=board.A5) +display.auto_brightness = True +buttons = gamepad.GamePad( + digitalio.DigitalInOut(board.SCL), + digitalio.DigitalInOut(board.D12), + digitalio.DigitalInOut(board.D11), + digitalio.DigitalInOut(board.D9), + digitalio.DigitalInOut(board.D10), + digitalio.DigitalInOut(board.D7), + digitalio.DigitalInOut(board.SDA), +) +audio = DummyAudio() diff --git a/circuitpython/frozen/circuitpython-stage/meowbit/stage.py b/circuitpython/frozen/circuitpython-stage/meowbit/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/meowbit/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/meowbit/ugame.py b/circuitpython/frozen/circuitpython-stage/meowbit/ugame.py new file mode 100644 index 0000000..62dc378 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/meowbit/ugame.py @@ -0,0 +1,78 @@ +import board +import stage +import busio +import time +import keypad +import audiocore + + +K_X = 0x01 +K_O = 0x02 +K_DOWN = 0x04 +K_LEFT = 0x08 +K_RIGHT = 0x10 +K_UP = 0x20 +K_Z = 0x40 + +display = board.DISPLAY +display.auto_brightness = True +display.auto_refresh = False + + +class _Buttons: + def __init__(self): + self.keys = keypad.Keys((board.BTNA, board.BTNB, board.DOWN, + board.LEFT, board.RIGHT, board.UP), + value_when_pressed=False, interval=0.05) + self.last_state = 0 + self.event = keypad.Event(0, False) + self.last_z_press = None + + def get_pressed(self): + buttons = self.last_state + events = self.keys.events + while events: + if events.get_into(self.event): + bit = 1 << self.event.key_number + if self.event.pressed: + buttons |= bit + self.last_state |= bit + else: + self.last_state &= ~bit + if buttons & K_Z: + now = time.monotonic() + if self.last_z_press: + if now - self.last_z_press > 2: + supervisor.set_next_code_file(None) + supervisor.reload() + else: + self.last_z_press = now + else: + self.last_z_press = None + return buttons + + +class _Audio: + last_audio = None + + def __init__(self): + self.muted = True + self.buffer = bytearray(128) + self.audio = board.BUZZ + + def play(self, audio_file, loop=False): + if self.muted: + return + self.stop() + wave = audiocore.WaveFile(audio_file, self.buffer) + self.audio.play(wave, loop=loop) + + def stop(self): + self.audio.stop() + + def mute(self, value=True): + self.muted = value + + +audio = _Audio() +buttons = _Buttons() diff --git a/circuitpython/frozen/circuitpython-stage/pewpew_m4/pew.py b/circuitpython/frozen/circuitpython-stage/pewpew_m4/pew.py new file mode 100644 index 0000000..19e3937 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pewpew_m4/pew.py @@ -0,0 +1,203 @@ +from micropython import const +import board +import busio +import digitalio +import time +import ugame +import stage +import array + + +_FONT = ( + b'{{{{{{wws{w{HY{{{{YDYDY{sUtGUsH[wyH{uHgHE{ws{{{{vyxyv{g[K[g{{]f]{{{wDw{{' + b'{{{wy{{{D{{{{{{{w{K_w}x{VHLHe{wuwww{`KfyD{UKgKU{w}XDK{DxTKT{VxUHU{D[wyx{' + b'UHfHU{UHEKe{{w{w{{{w{wy{KwxwK{{D{D{{xwKwx{eKg{w{VIHyB{fYH@H{dHdHd{FyxyF{' + b'`XHX`{DxtxD{Dxtxx{FyxIF{HHDHH{wwwww{KKKHU{HXpXH{xxxxD{Y@DLH{IL@LX{fYHYf{' + b'`HH`x{fYHIF{`HH`H{UxUKU{Dwwww{HHHIR{HHH]w{HHLD@{HYsYH{HYbww{D[wyD{txxxt{' + b'x}w_K{GKKKG{wLY{{{{{{{{Dxs{{{{{BIIB{x`XX`{{ByyB{KBIIB{{WIpF{OwUwww{`YB[`' + b'x`XHH{w{vwc{K{OKHUxHpXH{vwws_{{dD@H{{`XHH{{fYYf{{`XX`x{bYIBK{Ipxx{{F}_d{' + b'wUws_{{HHIV{{HH]s{{HLD@{{HbbH{{HHV[a{D_}D{Cw|wC{wwwwwwpwOwp{WKfxu{@YYY@{' +) +_SALT = const(132) + +_PALETTE = array.array('H', (0x0, 0x4a29, 0x6004, 0xf8, 0xfd, 0xf42, 0x825b, + 0xf8, 0xfe, 0x125b, 0xcffb, 0xe0cf, 0xffff, + 0x1ff8, 0xdbff, 0xffff)) + +K_X = ugame.K_X +K_DOWN = ugame.K_DOWN +K_LEFT = ugame.K_LEFT +K_RIGHT = ugame.K_RIGHT +K_UP = ugame.K_UP +K_O = ugame.K_O + +_tick = None +_display = None + + +def brightness(level): + pass + + +def show(pix): + for y in range(8): + for x in range(8): + _grid.tile(x + 1, y, 1 + (pix.pixel(x, y) & 0x03)) + _game.render_block(16, 0, 144, 128) + +keys = ugame.buttons.get_pressed + + +def tick(delay): + global _tick + + now = time.monotonic() + _tick += delay + if _tick < now: + _tick = now + else: + time.sleep(_tick - now) + + +class GameOver(SystemExit): + pass + + +class Pix: + __slots__ = ('buffer', 'width', 'height') + + def __init__(self, width=8, height=8, buffer=None): + if buffer is None: + buffer = bytearray(width * height) + self.buffer = buffer + self.width = width + self.height = height + + @classmethod + def from_text(cls, string, color=None, bgcolor=0, colors=None): + pix = cls(4 * len(string), 6) + font = memoryview(_FONT) + if colors is None: + if color is None: + colors = (3, 2, bgcolor, bgcolor) + else: + colors = (color, color, bgcolor, bgcolor) + x = 0 + for c in string: + index = ord(c) - 0x20 + if not 0 <= index <= 95: + continue + row = 0 + for byte in font[index * 6:index * 6 + 6]: + unsalted = byte ^ _SALT + for col in range(4): + pix.pixel(x + col, row, colors[unsalted & 0x03]) + unsalted >>= 2 + row += 1 + x += 4 + return pix + + @classmethod + def from_iter(cls, lines): + pix = cls(len(lines[0]), len(lines)) + y = 0 + for line in lines: + x = 0 + for pixel in line: + pix.pixel(x, y, pixel) + x += 1 + y += 1 + return pix + + def pixel(self, x, y, color=None): + if not 0 <= x < self.width or not 0 <= y < self.height: + return 0 + if color is None: + return self.buffer[x + y * self.width] + self.buffer[x + y * self.width] = color + + def box(self, color, x=0, y=0, width=None, height=None): + x = min(max(x, 0), self.width - 1) + y = min(max(y, 0), self.height - 1) + width = max(0, min(width or self.width, self.width - x)) + height = max(0, min(height or self.height, self.height - y)) + for y in range(y, y + height): + xx = y * self.width + x + for i in range(width): + self.buffer[xx] = color + xx += 1 + + def blit(self, source, dx=0, dy=0, x=0, y=0, + width=None, height=None, key=None): + if dx < 0: + x -= dx + dx = 0 + if x < 0: + dx -= x + x = 0 + if dy < 0: + y -= dy + dy = 0 + if y < 0: + dy -= y + y = 0 + width = min(min(width or source.width, source.width - x), + self.width - dx) + height = min(min(height or source.height, source.height - y), + self.height - dy) + source_buffer = memoryview(source.buffer) + self_buffer = self.buffer + if key is None: + for row in range(height): + xx = y * source.width + x + dxx = dy * self.width + dx + self_buffer[dxx:dxx + width] = source_buffer[xx:xx + width] + y += 1 + dy += 1 + else: + for row in range(height): + xx = y * source.width + x + dxx = dy * self.width + dx + for col in range(width): + color = source_buffer[xx] + if color != key: + self_buffer[dxx] = color + dxx += 1 + xx += 1 + y += 1 + dy += 1 + + def __str__(self): + return "\n".join( + "".join( + ('.', '+', '*', '@')[self.pixel(x, y)] + for x in range(self.width) + ) + for y in range(self.height) + ) + + +def init(): + global _tick, _display, _bitmap, _grid, _game + + if _tick is not None: + return + + _tick = time.monotonic() + + _game = stage.Stage(ugame.display, 12) + _bank = bytearray(2048) + for c in range(16): + for y in range(0, 15): + for x in range(0, 7): + _bank[c * 128 + y * 8 + x] = c | c << 4 + _bank[c * 128 + y * 8 + 7] = c << 4 + _bank[c * 128] = c + _bank[c * 128 + 7] = 0 + _bank[c * 128 + 14 * 8] = c + _bank[c * 128 + 14 * 8 + 7] = 0 + tiles = stage.Bank(_bank, _PALETTE) + _grid = stage.Grid(tiles, 10, 8) + _grid.move(0, 0) + _game.layers = [_grid] + _game.render_block() diff --git a/circuitpython/frozen/circuitpython-stage/pewpew_m4/stage.py b/circuitpython/frozen/circuitpython-stage/pewpew_m4/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pewpew_m4/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/pewpew_m4/ugame.py b/circuitpython/frozen/circuitpython-stage/pewpew_m4/ugame.py new file mode 100644 index 0000000..f92a6e5 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pewpew_m4/ugame.py @@ -0,0 +1,79 @@ +import board +import stage +import supervisor +import time +import keypad +import audioio +import audiocore + + +K_X = 0x01 +K_DOWN = 0x02 +K_LEFT = 0x04 +K_RIGHT = 0x08 +K_UP = 0x10 +K_O = 0x20 +K_START = 0x40 +K_Z = 0x40 +K_SELECT = 0x80 + + +class _Buttons: + def __init__(self): + self.keys = keypad.Keys((board.BUTTON_X, board.BUTTON_DOWN, + board.BUTTON_LEFT, board.BUTTON_RIGHT, board.BUTTON_UP, + board.BUTTON_O, board.BUTTON_Z), value_when_pressed=False, + interval=0.05) + self.last_state = 0 + self.event = keypad.Event(0, False) + self.last_z_press = None + + def get_pressed(self): + buttons = self.last_state + events = self.keys.events + while events: + if events.get_into(self.event): + bit = 1 << self.event.key_number + if self.event.pressed: + buttons |= bit + self.last_state |= bit + else: + self.last_state &= ~bit + if buttons & K_Z: + now = time.monotonic() + if self.last_z_press: + if now - self.last_z_press > 2: + supervisor.set_next_code_file(None) + supervisor.reload() + else: + self.last_z_press = now + else: + self.last_z_press = None + return buttons + + +class _Audio: + last_audio = None + + def __init__(self, speaker_pin): + self.muted = True + self.buffer = bytearray(128) + self.audio = audioio.AudioOut(speaker_pin) + + def play(self, audio_file, loop=False): + if self.muted: + return + self.stop() + wave = audiocore.WaveFile(audio_file, self.buffer) + self.audio.play(wave, loop=loop) + + def stop(self): + self.audio.stop() + + def mute(self, value=True): + self.muted = value + + +display = board.DISPLAY +buttons = _Buttons() +audio = _Audio(board.SPEAKER) diff --git a/circuitpython/frozen/circuitpython-stage/picosystem/stage.py b/circuitpython/frozen/circuitpython-stage/picosystem/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/picosystem/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/picosystem/ugame.py b/circuitpython/frozen/circuitpython-stage/picosystem/ugame.py new file mode 100644 index 0000000..b0b2fd1 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/picosystem/ugame.py @@ -0,0 +1,86 @@ +import board +import analogio +import stage +import keypad +import audiocore +import audiopwmio +import time +import supervisor + + +K_O = 0x01 # A +K_X = 0x02 # B +K_SELECT = 0x04 # X +K_START = 0x08 # Y +K_Z = 0x08 # Y +K_DOWN = 0x10 +K_LEFT = 0x20 +K_RIGHT = 0x40 +K_UP = 0x80 + + +class _Buttons: + def __init__(self): + self.keys = keypad.Keys(( + board.SW_A, + board.SW_B, + board.SW_X, + board.SW_Y, + board.SW_DOWN, + board.SW_LEFT, + board.SW_RIGHT, + board.SW_UP + ), value_when_pressed=False, pull=True, interval=0.05) + self.last_state = 0 + self.event = keypad.Event(0, False) + self.last_z_press = None + + def get_pressed(self): + buttons = self.last_state + events = self.keys.events + while events: + if events.get_into(self.event): + bit = 1 << self.event.key_number + if self.event.pressed: + buttons |= bit + self.last_state |= bit + else: + self.last_state &= ~bit + if buttons & K_Z: + now = time.monotonic() + if self.last_z_press: + if now - self.last_z_press > 2: + supervisor.set_next_code_file(None) + supervisor.reload() + else: + self.last_z_press = now + else: + self.last_z_press = None + return buttons + +class _Audio: + last_audio = None + + def __init__(self): + self.muted = True + self.buffer = bytearray(128) + self.audio = audiopwmio.PWMAudioOut(board.AUDIO) + + def play(self, audio_file, loop=False): + if self.muted: + return + self.stop() + wave = audiocore.WaveFile(audio_file, self.buffer) + self.audio.play(wave, loop=loop) + + def stop(self): + self.audio.stop() + + def mute(self, value=True): + self.muted = value + + +audio = _Audio() +display = board.DISPLAY +buttons = _Buttons() +battery = analogio.AnalogIn(board.BAT_SENSE) diff --git a/circuitpython/frozen/circuitpython-stage/pybadge/stage.py b/circuitpython/frozen/circuitpython-stage/pybadge/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pybadge/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/pybadge/ugame.py b/circuitpython/frozen/circuitpython-stage/pybadge/ugame.py new file mode 100644 index 0000000..1384de0 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pybadge/ugame.py @@ -0,0 +1,122 @@ +import board +import stage +import displayio +import busio +import time +import keypad +import audioio +import audiocore +import digitalio +import supervisor + + +K_X = 0x01 +K_O = 0x02 +K_DOWN = 0x20 +K_LEFT = 0x80 +K_RIGHT = 0x10 +K_UP = 0x40 +K_START = 0x04 +K_SELECT = 0x08 + +# re-initialize the display for correct rotation and RGB mode + +_TFT_INIT = ( + b"\x01\x80\x96" # SWRESET and Delay 150ms + b"\x11\x80\xff" # SLPOUT and Delay + b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 + b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 + b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 + b"\xb4\x01\x07" # _INVCTR line inversion + b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA + b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V + b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency + b"\xc3\x02\x8a\x2a" + b"\xc4\x02\x8a\xee" + b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V + b"\x20\x00" # _INVOFF + b"\x36\x01\xa0" # _MADCTL + # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, + # fix on VTL + b"\x3a\x01\x05" # COLMOD - 16bit color + b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma + b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 + b"\x13\x80\x0a" # _NORON + b"\x29\x80\x64" # _DISPON +) + + +class _Buttons: + def __init__(self): + self.keys = keypad.ShiftRegisterKeys(clock=board.BUTTON_CLOCK, + data=board.BUTTON_OUT, latch=board.BUTTON_LATCH, key_count=8, + interval=0.05, value_when_pressed=True) + self.last_state = 0 + self.event = keypad.Event(0, False) + self.last_z_press = None + + def get_pressed(self): + buttons = self.last_state + events = self.keys.events + while events: + if events.get_into(self.event): + bit = 1 << self.event.key_number + if self.event.pressed: + buttons |= bit + self.last_state |= bit + else: + self.last_state &= ~bit + if buttons & K_START: + now = time.monotonic() + if self.last_z_press: + if now - self.last_z_press > 2: + supervisor.set_next_code_file(None) + supervisor.reload() + else: + self.last_z_press = now + else: + self.last_z_press = None + return buttons + + +class _Audio: + last_audio = None + + def __init__(self, speaker_pin, mute_pin=None): + self.muted = True + self.buffer = bytearray(128) + if mute_pin: + self.mute_pin = digitalio.DigitalInOut(mute_pin) + self.mute_pin.switch_to_output(value=not self.muted) + else: + self.mute_pin = None + self.audio = audioio.AudioOut(speaker_pin) + + def play(self, audio_file, loop=False): + if self.muted: + return + self.stop() + wave = audiocore.WaveFile(audio_file, self.buffer) + self.audio.play(wave, loop=loop) + + def stop(self): + self.audio.stop() + + def mute(self, value=True): + self.muted = value + if self.mute_pin: + self.mute_pin.value = not value + + +displayio.release_displays() +_tft_spi = busio.SPI(clock=board.TFT_SCK, MOSI=board.TFT_MOSI) +_fourwire = displayio.FourWire(_tft_spi, command=board.TFT_DC, + chip_select=board.TFT_CS, reset=board.TFT_RST) +display = displayio.Display(_fourwire, _TFT_INIT, width=160, height=128, + rotation=0, auto_refresh=False) +# Work around broken backlight in CP 7.0 +_backlight = digitalio.DigitalInOut(board.TFT_LITE) +_backlight.switch_to_output(value=1) +del _TFT_INIT +buttons = _Buttons() +audio = _Audio(board.SPEAKER, board.SPEAKER_ENABLE) diff --git a/circuitpython/frozen/circuitpython-stage/pygamer/stage.py b/circuitpython/frozen/circuitpython-stage/pygamer/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pygamer/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/pygamer/ugame.py b/circuitpython/frozen/circuitpython-stage/pygamer/ugame.py new file mode 100644 index 0000000..931f7f2 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/pygamer/ugame.py @@ -0,0 +1,136 @@ +import board +import analogio +import stage +import displayio +import busio +import time +import keypad +import audioio +import audiocore +import supervisor +import digitalio + + +K_X = 0x01 +K_O = 0x02 +K_START = 0x04 +K_SELECT = 0x08 +K_DOWN = 0x10 +K_LEFT = 0x20 +K_RIGHT = 0x40 +K_UP = 0x80 + +# re-initialize the display for correct rotation and RGB mode + +_TFT_INIT = ( + b"\x01\x80\x96" # SWRESET and Delay 150ms + b"\x11\x80\xff" # SLPOUT and Delay + b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 + b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 + b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 + b"\xb4\x01\x07" # _INVCTR line inversion + b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA + b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V + b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency + b"\xc3\x02\x8a\x2a" + b"\xc4\x02\x8a\xee" + b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V + b"\x20\x00" # _INVOFF + b"\x36\x01\xa0" # _MADCTL + # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, + # fix on VTL + b"\x3a\x01\x05" # COLMOD - 16bit color + b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma + b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 + b"\x13\x80\x0a" # _NORON + b"\x29\x80\x64" # _DISPON +) + + +class _Buttons: + def __init__(self): + self.keys = keypad.ShiftRegisterKeys(clock=board.BUTTON_CLOCK, + data=board.BUTTON_OUT, latch=board.BUTTON_LATCH, key_count=4, + interval=0.05, value_when_pressed=True) + self.last_state = 0 + self.event = keypad.Event(0, False) + self.last_z_press = None + self.joy_x = analogio.AnalogIn(board.JOYSTICK_X) + self.joy_y = analogio.AnalogIn(board.JOYSTICK_Y) + + def get_pressed(self): + buttons = self.last_state + events = self.keys.events + while events: + if events.get_into(self.event): + bit = 1 << self.event.key_number + if self.event.pressed: + buttons |= bit + self.last_state |= bit + else: + self.last_state &= ~bit + if buttons & K_START: + now = time.monotonic() + if self.last_z_press: + if now - self.last_z_press > 2: + supervisor.set_next_code_file(None) + supervisor.reload() + else: + self.last_z_press = now + else: + self.last_z_press = None + dead = 15000 + x = self.joy_x.value - 32767 + if x < -dead: + buttons |= K_LEFT + elif x > dead: + buttons |= K_RIGHT + y = self.joy_y.value - 32767 + if y < -dead: + buttons |= K_UP + elif y > dead: + buttons |= K_DOWN + return buttons + + +class _Audio: + last_audio = None + + def __init__(self, speaker_pin, mute_pin=None): + self.muted = True + self.buffer = bytearray(128) + if mute_pin: + self.mute_pin = digitalio.DigitalInOut(mute_pin) + self.mute_pin.switch_to_output(value=not self.muted) + else: + self.mute_pin = None + self.audio = audioio.AudioOut(speaker_pin) + + def play(self, audio_file, loop=False): + if self.muted: + return + self.stop() + wave = audiocore.WaveFile(audio_file, self.buffer) + self.audio.play(wave, loop=loop) + + def stop(self): + self.audio.stop() + + def mute(self, value=True): + self.muted = value + if self.mute_pin: + self.mute_pin.value = not value + + +displayio.release_displays() +_tft_spi = busio.SPI(clock=board.TFT_SCK, MOSI=board.TFT_MOSI) +_fourwire = displayio.FourWire(_tft_spi, command=board.TFT_DC, + chip_select=board.TFT_CS, reset=board.TFT_RST) +display = displayio.Display(_fourwire, _TFT_INIT, width=160, height=128, + rotation=0, auto_refresh=False) +# Work around broken backlight in CP 7.0 +_backlight = digitalio.DigitalInOut(board.TFT_LITE) +_backlight.switch_to_output(value=1) +del _TFT_INIT +buttons = _Buttons() +audio = _Audio(board.SPEAKER, board.SPEAKER_ENABLE) diff --git a/circuitpython/frozen/circuitpython-stage/stage.py b/circuitpython/frozen/circuitpython-stage/stage.py new file mode 100644 index 0000000..44ab69a --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/stage.py @@ -0,0 +1,630 @@ +import time +import array +import digitalio +import struct + +import _stage + + +FONT = (b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'P\x01\xd4\x05\xf5\x17\xed\x1e\xd5\x15\xd0\x01P\x01\x00\x00' + b'P\x01\xd0\x01\xd5\x15\xed\x1e\xf5\x17\xd4\x05P\x01\x00\x00' + b'P\x01\xd0\x05\x95\x17\xfd\x1f\x95\x17\xd0\x05P\x01\x00\x00' + b'P\x01\xd4\x01\xb5\x15\xfd\x1f\xb5\x15\xd4\x01P\x01\x00\x00' + b'T\x05\xf9\x1b\xdd\x1d}\x1f\xd9\x19\xa9\x1aT\x05\x00\x00' + b'T\x05\xf9\x1b]\x1d\xdd\x1dY\x19\xa9\x1aT\x05\x00\x00P\x01\xd0\x01' + b'\xe5\x16\xfd\x1f\xe4\x06t\x07\x14\x05\x00\x00P\x01\xd5\x15' + b']\x1d\x95\x15\xf4\x07\xe4\x06T\x05\x00\x00\x14\x05y\x1b' + b'\xfd\x1f\xf9\x1b\xe4\x06\xd0\x01@\x00\x00\x00P\x01\xf4\x06' + b'\xad\x1b\xed\x1b\xf9\x1a\xa4\x06P\x01\x00\x00@U\xd0\xff' + b'\xf4\xaa\xbdV\xad\x01m\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00' + b'm\x00m\x00m\x00m\x00m\x00\xbd\x01\xf9V\xe4\xff\x90\xaa@UUU\xff\xff' + b'\xaa\xaaUU\x00\x00\x00\x00\x00\x00\x00\x00U\x01\xff\x06' + b'\xea\x1b\x95o@n\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00m' + b'\x00m\x00m\x00m\x00m\x00m@o\xd5k\xff\x1a\xaa\x06U\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00UU\xff\xff\xaa\xaaUU' + b'\x00\x00\x00\x00\x00UE\xfe\xd9\xef\xdd\x9f\xad\x9f\xad\x9a' + b'\x00\x00\x00\x00\x00\x00U\x15\xf7o\xa7jW\x15v\x00\xadu\xed\xda' + b'\xddv\x99\xe6E\x9a\x00U\x00\x00\x00\x00m\x00W\x00n\x00\x15\x00' + b'\x1b\x00\x05\x00\x00\x00\x00\x00\xaa\x00\xaa\x00\xaa\x00\xaa\x00' + b'\x00\xaa\x00\xaa\x00\xaa\x00\xaaP\x05\x94\x16\xa4\x1b\xe4\x1b' + b'\xe4\x1a\xa4\x1aT\x15\x00\x00P\x00\xd0\x01\xd0\x07\xd4\x19' + b'\xf9\x1d\xbd\x05T\x00\x00\x00T\x05\xf5\x17\xdd\x1d\xdd\x1d' + b'\xf5\x17\xe4\x06T\x05\x00\x00\x14\x05e\x16y\x1b\xd4\x05y\x1be\x16' + b'\x14\x05\x00\x00T\x15\xf5\x1f\x9d\x19\xf5\x1d\xd4\x1d\xd0\x1d' + b'P\x15\x00\x00\x00\x00P\x01\xe4\x06\xf4\x07\xe4\x06P\x01' + b'\x00\x00\x00\x00U\x15\xdd\x1d\xdd\x1d\x99\x19U\x15\xdd\x1d' + b'U\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\x15\xdd\x1d' + b'U\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00P\x01\xd0\x01\xd0\x01\x90\x01P\x01\xd0\x01' + b'P\x01\x00\x00T\x05t\x07d\x06T\x05\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x14\x05u\x17\xed\x1et\x07\xed\x1eu\x17\x14\x05\x00\x00' + b'T\x15\xf5\x1b\x99\x05\xf5\x17\x94\x19\xf9\x17U\x05\x00\x00' + b'\x15\x14\x1d\x1dU\x07\xd0\x01t\x15\x1d\x1d\x05\x15\x00\x00' + b'T\x01\xe4\x05u\x07\xdd\x01]\x17\xe5\x1dT\x14\x00\x00P\x01\xd0\x01' + b'\x90\x01P\x01\x00\x00\x00\x00\x00\x00\x00\x00@\x05P\x06' + b'\x90\x01\xd0\x01\x90\x01P\x06@\x05\x00\x00T\x00d\x01' + b'\x90\x01\xd0\x01\x90\x01d\x01T\x00\x00\x00\x00\x00\x14\x05' + b't\x07\xd0\x01t\x07\x14\x05\x00\x00\x00\x00P\x01\x90\x01' + b'\xd5\x15\xf9\x1b\xd5\x15\x90\x01P\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00P\x01\xd0\x01\x90\x01P\x01\x00\x00\x00\x00\x00\x00' + b'U\x15\xf9\x1bU\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00P\x01\xd0\x01P\x01\x00\x00\x00\x04\x00\x1d' + b'@\x07\xd0\x01t\x00\x1d\x00\x04\x00\x00\x00T\x05\xe5\x16' + b'Y\x1a\xdd\x1di\x19\xe5\x16T\x05\x00\x00@\x01\xd0\x01' + b'\xe4\x01\xd0\x01\xd0\x01\xe4\x06T\x05\x00\x00T\x05\xf9\x17' + b'U\x1d\xf4\x17Y\x05\xfd\x1fU\x15\x00\x00T\x05\xf5\x17]\x1d\x94\x07' + b']\x1d\xf5\x17T\x05\x00\x00P\x00t\x00]\x05]\x17\xfd\x1fU\x17' + b'@\x05\x00\x00U\x15\xfd\x1b]\x05\xfd\x1bU\x1d\xf9\x1bU\x05\x00\x00' + b'T\x15\xf5\x1b]\x05\xfd\x1b]\x1d\xf9\x1bT\x05\x00\x00U\x15\xfd\x1f' + b'U\x19\xd0\x06d\x01t\x00T\x00\x00\x00T\x05\xf5\x17]\x1d\xf5\x17' + b']\x1d\xf5\x17T\x05\x00\x00T\x05\xf9\x1b]\x1d\xf9\x1fT\x1d\xf9\x17' + b'U\x05\x00\x00\x00\x00P\x01\xd0\x01P\x01\xd0\x01P\x01' + b'\x00\x00\x00\x00\x00\x00P\x01\xd0\x01P\x01\xd0\x01\x90\x01' + b'P\x01\x00\x00\x00\x05@\x07\xd0\x01t\x00\xd0\x01@\x07' + b'\x00\x05\x00\x00\x00\x00U\x15\xf9\x1bT\x05\xf9\x1bU\x15' + b'\x00\x00\x00\x00\x14\x00t\x00\xd0\x01@\x07\xd0\x01t\x00' + b'\x14\x00\x00\x00T\x05\xe5\x17]\x1d\xd5\x16P\x05\xd0\x01' + b'P\x01\x00\x00T\x05\xb5\x17\xdd\x1d\x9d\x1bY\x15\xf5\x06' + b'T\x05\x00\x00P\x00\xe4\x01Y\x07]\x1d\xed\x1e]\x1d\x15\x15\x00\x00' + b'U\x01\xfd\x05]\x07\xed\x16]\x1d\xfd\x17U\x05\x00\x00T\x05\xf5\x06' + b']\x01\x1d\x14]\x1d\xf5\x17T\x05\x00\x00U\x01\xbd\x05]\x17\x1d\x1d' + b']\x1d\xfd\x16U\x05\x00\x00U\x05\xfd\x06]\x01\xfd\x01]\x15\xfd\x1b' + b'U\x15\x00\x00U\x15\xfd\x1b]\x15]\x00\xbd\x01]\x01\x15\x00\x00\x00' + b'T\x15\xf5\x1b]\x05\xdd\x1fY\x1d\xf5\x1bT\x15\x00\x00' + b'\x15\x15\x1d\x1d]\x1d\xfd\x1f]\x1d\x1d\x1d\x15\x15\x00\x00' + b'T\x05\xe4\x06\xd0\x01\xd0\x01\xd0\x01\xe4\x06T\x05\x00\x00' + b'\x00\x15\x00\x1d\x00\x1d\x05\x1d]\x19\xf5\x17T\x05\x00\x00' + b'\x15\x14\x1d\x1d]\x07\xfd\x01]\x07\x1d\x1d\x15\x14\x00\x00' + b'\x15\x00\x1d\x00\x1d\x00\x1d\x00]\x15\xfd\x1fU\x15\x00\x00' + b'\x05\x14\x1d\x1dm\x1e\xdd\x1d]\x1d\x1d\x1d\x15\x15\x00\x00' + b'\x05\x15\x1d\x1dm\x1d\xdd\x1d]\x1e\x1d\x1d\x15\x14\x00\x00' + b'T\x01\xb5\x05]\x17\x1d\x1d]\x1d\xe5\x17T\x05\x00\x00U\x05\xfd\x16' + b']\x19]\x1d\xfd\x17]\x05\x15\x00\x00\x00T\x01\xb5\x05]\x17\x1d\x1d' + b']\x1e\xe5\x07T\x1d\x00\x15U\x05\xfd\x16]\x19]\x1d\xfd\x07]\x1d' + b'\x15\x15\x00\x00T\x05\xf5\x07]\x01\xe5\x06T\x1d\xf9\x17' + b'U\x05\x00\x00U\x15\xf9\x1b\xd5\x15\xd0\x01\xd0\x01\xd0\x01' + b'P\x01\x00\x00\x15\x15\x1d\x1d\x1d\x1d\x19\x1du\x19\xd4\x17' + b'P\x05\x00\x00\x05\x14\x1d\x1d\x19\x19u\x17d\x06\xd0\x01' + b'@\x00\x00\x00\x15\x15\x1d\x1d\x1d\x1d]\x1d\xd9\x19u\x17' + b'\x14\x05\x00\x00\x05\x14\x1d\x1dt\x07\xd0\x01t\x07\x1d\x1d' + b'\x05\x14\x00\x00\x15\x15\x1d\x1d\x19\x19u\x17\x94\x05\xd0\x01' + b'P\x01\x00\x00U\x15\xf9\x1bU\x07\xd0\x01t\x15\xf9\x1bU\x15\x00\x00' + b'T\x05\xf4\x06t\x01t\x00t\x01\xf4\x06T\x05\x00\x00\x05\x00\x1d\x00' + b't\x00\xd0\x01@\x07\x00\x1d\x00\x14\x00\x00T\x05\xe4\x07P\x07@\x07' + b'P\x07\xe4\x07T\x05\x00\x00@\x00\xd0\x01t\x07\x19\x19' + b'\x04\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'U\x15\xf9\x1bU\x15\x00\x00P\x00\xb4\x01\xd4\x06P\x07@\x01\x00\x00' + b'\x00\x00\x00\x00\x00\x00T\x15\xe5\x1f]\x1d]\x1d\xf5\x1f' + b'T\x15\x00\x00\x15\x00]\x05\xfd\x16]\x1d]\x1d\xfd\x17U\x05\x00\x00' + b'\x00\x00T\x05\xe5\x07]\x05]\x1d\xf5\x16T\x05\x00\x00\x00\x15T\x1d' + b'\xe5\x1f]\x1d]\x1d\xf5\x1fT\x15\x00\x00\x00\x00T\x05' + b'\xf5\x17\xad\x1e]\x15\xf5\x07T\x05\x00\x00@\x15P\x1e' + b'\xd4\x15\xf4\x07\xd4\x05\xd0\x01\xd0\x01P\x01\x00\x00T\x15' + b'\xe5\x1f]\x1d\xf5\x1fT\x1d\xf9\x16U\x05\x15\x00]\x05\xfd\x16]\x1d' + b'\x1d\x1d\x1d\x1d\x15\x15\x00\x00P\x01\xd0\x01P\x01\xd0\x01' + b'\xd0\x01\xd0\x01P\x01\x00\x00@\x05@\x07@\x05@\x07E\x07]\x07' + b'\xe5\x05T\x01\x15\x00\x1d\x14]\x1d\xfd\x06]\x19\x1d\x1d' + b'\x15\x14\x00\x00T\x00t\x00t\x00t\x00d\x05\xd4\x07P\x05\x00\x00' + b'\x00\x00U\x05\xfd\x17\xdd\x19\xdd\x1d]\x1d\x15\x15\x00\x00' + b'\x00\x00U\x05\xfd\x17]\x19\x1d\x1d\x1d\x1d\x15\x15\x00\x00' + b'\x00\x00T\x05\xe5\x17]\x1d]\x1d\xf5\x17T\x05\x00\x00\x00\x00U\x05' + b'\xfd\x17]\x1d]\x1d\xfd\x17]\x05\x15\x00\x00\x00T\x15\xf5\x1f]\x1d' + b']\x1d\xf5\x1fT\x1d\x00\x15\x00\x00U\x05\xdd\x16}\x1d]\x04\x1d\x00' + b'\x15\x00\x00\x00\x00\x00T\x15\xe5\x1f\xad\x05\x94\x1e\xfd\x16' + b'U\x05\x00\x00T\x00u\x05\xfd\x07t\x01t\x01\xd4\x07P\x05\x00\x00' + b'\x00\x00\x15\x15\x1d\x1d\x1d\x1d]\x1d\xe5\x1fT\x15\x00\x00' + b'\x00\x00\x05\x14\x1d\x1d\x19\x19u\x17\xd4\x05P\x01\x00\x00' + b'\x00\x00\x15\x15]\x1d\xdd\x1d\xd9\x19u\x17T\x05\x00\x00' + b'\x00\x00\x15\x15m\x1e\xd4\x05\xd4\x05m\x1e\x15\x15\x00\x00' + b'\x00\x00\x15\x15\x1d\x1d]\x1d\xe5\x1fT\x1d\xfd\x17U\x05' + b'\x00\x00U\x15\xfd\x1f\xa4\x15\x95\x06\xfd\x1fU\x15\x00\x00' + b'@\x05\x90\x07\xd0\x01t\x01\xd0\x01\x90\x07@\x05\x00\x00' + b'P\x01\x90\x01\xd0\x01\xd0\x01\xd0\x01\x90\x01P\x01\x00\x00' + b'T\x00\xb4\x01\xd0\x01P\x07\xd0\x01\xb4\x01T\x00\x00\x00' + b'\x00\x00T\x00u\x15\xd9\x19U\x17@\x05\x00\x00\x00\x00U\x15\xfd\x1f' + b'\xed\x1e\xbd\x1f\xed\x1e\xfd\x1fU\x15\x00\x00') + +PALETTE = (b'\xf8\x1f\x00\x00\xcey\xff\xff\xf8\x1f\x00\x19\xfc\xe0\xfd\xe0' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + +def color565(r, g, b): + """Convert 24-bit RGB color to 16-bit.""" + return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 + + +def collide(ax0, ay0, ax1, ay1, bx0, by0, bx1=None, by1=None): + """Return True if the two rectangles intersect.""" + if bx1 is None: + bx1 = bx0 + if by1 is None: + by1 = by0 + return not (ax1 < bx0 or ay1 < by0 or ax0 > bx1 or ay0 > by1) + + +class BMP16: + """Read 16-color BMP files.""" + + def __init__(self, filename): + self.filename = filename + self.colors = 0 + + def read_header(self): + """Read the file's header information.""" + + if self.colors: + return + with open(self.filename, 'rb') as f: + f.seek(10) + self.data = int.from_bytes(f.read(4), 'little') + f.seek(18) + self.width = int.from_bytes(f.read(4), 'little') + self.height = int.from_bytes(f.read(4), 'little') + f.seek(46) + self.colors = int.from_bytes(f.read(4), 'little') + + def read_palette(self): + """Read the color palette information.""" + + palette = array.array('H', (0 for i in range(16))) + with open(self.filename, 'rb') as f: + f.seek(self.data - self.colors * 4) + for color in range(self.colors): + buffer = f.read(4) + c = color565(buffer[2], buffer[1], buffer[0]) + palette[color] = ((c << 8) | (c >> 8)) & 0xffff + return palette + + def read_data(self, buffer=None): + """Read the image data.""" + line_size = self.width >> 1 + if buffer is None: + buffer = bytearray(line_size * self.height) + + with open(self.filename, 'rb') as f: + f.seek(self.data) + index = (self.height - 1) * line_size + for line in range(self.height): + chunk = f.read(line_size) + buffer[index:index + line_size] = chunk + index -= line_size + return buffer + + +def read_blockstream(f): + while True: + size = f.read(1)[0] + if size == 0: + break + for i in range(size): + yield f.read(1)[0] + + +class EndOfData(Exception): + pass + + +class LZWDict: + def __init__(self, code_size): + self.code_size = code_size + self.clear_code = 1 << code_size + self.end_code = self.clear_code + 1 + self.codes = [] + self.clear() + + def clear(self): + self.last = b'' + self.code_len = self.code_size + 1 + self.codes[:] = [] + + def decode(self, code): + if code == self.clear_code: + self.clear() + return b'' + elif code == self.end_code: + raise EndOfData() + elif code < self.clear_code: + value = bytes([code]) + elif code <= len(self.codes) + self.end_code: + value = self.codes[code - self.end_code - 1] + else: + value = self.last + self.last[0:1] + if self.last: + self.codes.append(self.last + value[0:1]) + if (len(self.codes) + self.end_code + 1 >= 1 << self.code_len and + self.code_len < 12): + self.code_len += 1 + self.last = value + return value + + +def lzw_decode(data, code_size): + dictionary = LZWDict(code_size) + bit = 0 + try: + byte = next(data) + try: + while True: + code = 0 + for i in range(dictionary.code_len): + code |= ((byte >> bit) & 0x01) << i + bit += 1 + if bit >= 8: + bit = 0 + byte = next(data) + yield dictionary.decode(code) + except EndOfData: + while True: + next(data) + except StopIteration: + return + + +class GIF16: + """Read 16-color GIF files.""" + + def __init__(self, filename): + self.filename = filename + + def read_header(self): + with open(self.filename, 'rb') as f: + header = f.read(6) + if header not in {b'GIF87a', b'GIF89a'}: + raise ValueError("Not GIF file") + self.width, self.height, flags, self.background, self.aspect = ( + struct.unpack('<HHBBB', f.read(7))) + self.palette_size = 1 << ((flags & 0x07) + 1) + if not flags & 0x80: + raise NotImplementedError() + if self.palette_size > 16: + raise ValueError("Too many colors (%d/16)." % self.palette_size) + + def read_palette(self): + palette = array.array('H', (0 for i in range(16))) + with open(self.filename, 'rb') as f: + f.seek(13) + for color in range(self.palette_size): + buffer = f.read(3) + c = color565(buffer[0], buffer[1], buffer[2]) + palette[color] = ((c << 8) | (c >> 8)) & 0xffff + return palette + + def read_data(self, buffer=None): + line_size = (self.width + 1) >> 1 + if buffer is None: + buffer = bytearray(line_size * self.height) + with open(self.filename, 'rb') as f: + f.seek(13 + self.palette_size * 3) + while True: # skip to first frame + block_type = f.read(1)[0] + if block_type == 0x2c: + break + elif block_type == 0x21: # skip extension + extension_type = f.read(1)[0] + while True: + size = f.read(1)[0] + if size == 0: + break + f.seek(1, size) + elif block_type == 0x3b: + raise NotImplementedError() + x, y, w, h, flags = struct.unpack('<HHHHB', f.read(9)) + if (flags & 0x80 or flags & 0x40 or + w != self.width or h != self.height or x != 0 or y != 0): + raise NotImplementedError() + min_code_size = f.read(1)[0] + x = 0 + y = 0 + for decoded in lzw_decode(read_blockstream(f), min_code_size): + for pixel in decoded: + if x & 0x01: + buffer[(x >> 1) + y * line_size] |= pixel + else: + buffer[(x >> 1) + y * line_size] = pixel << 4 + x += 1 + if (x >= self.width): + x = 0 + y += 1 + return buffer + + +class Bank: + """ + Store graphics for the tiles and sprites. + + A single bank stores exactly 16 tiles, each 16x16 pixels in 16 possible + colors, and a 16-color palette. We just like the number 16. + + """ + + def __init__(self, buffer=None, palette=None): + self.buffer = buffer + self.palette = palette + + @classmethod + def from_bmp16(cls, filename): + """Read the bank from a BMP file.""" + return cls.from_image(filename) + + + @classmethod + def from_image(cls, filename): + """Read the bank from an image file.""" + if filename.lower().endswith(".gif"): + image = GIF16(filename) + elif filename.lower().endswith(".bmp"): + image = BMP16(filename) + else: + raise ValueError("Unsupported format") + image.read_header() + if image.width != 16 or image.height != 256: + raise ValueError("Image size not 16x256") + palette = image.read_palette() + buffer = image.read_data() + return cls(buffer, palette) + + +class Grid: + """ + A grid is a layer of tiles that can be displayed on the screen. Each square + can contain any of the 16 tiles from the associated bank. + """ + + def __init__(self, bank, width=8, height=8, palette=None, buffer=None): + self.x = 0 + self.y = 0 + self.z = 0 + self.stride = (width + 1) & 0xfe + self.width = width + self.height = height + self.bank = bank + self.palette = palette or bank.palette + self.buffer = buffer or bytearray((self.stride * height)>>1) + self.layer = _stage.Layer(self.stride, self.height, self.bank.buffer, + self.palette, self.buffer) + + def tile(self, x, y, tile=None): + """Get or set what tile is displayed in the given place.""" + + if not 0 <= x < self.width or not 0 <= y < self.height: + return 0 + index = (y * self.stride + x) >> 1 + b = self.buffer[index] + if tile is None: + return b & 0x0f if x & 0x01 else b >> 4 + if x & 0x01: + b = b & 0xf0 | tile + else: + b = b & 0x0f | (tile << 4) + self.buffer[index] = b + + def move(self, x, y, z=None): + """Shift the whole layer respective to the screen.""" + + self.x = x + self.y = y + if z is not None: + self.z = z + self.layer.move(int(x), int(y)) + + +class WallGrid(Grid): + """ + A special grid, shifted from its parents by half a tile, useful for making + nice-looking corners of walls and similar structures. + """ + + def __init__(self, grid, walls, bank, palette=None): + super().__init__(bank, grid.width + 1, grid.height + 1, palette) + self.grid = grid + self.walls = walls + self.update() + self.move(self.x - 8, self.y - 8) + + def update(self): + for y in range(self.height): + for x in range(self.width): + t = 0 + bit = 1 + for dy in (-1, 0): + for dx in (-1, 0): + if self.grid.tile(x + dx, y + dy) in self.walls: + t |= bit + bit <<= 1 + self.tile(x, y, t) + + +class Sprite: + """ + A sprite is a layer containing just a single tile from the associated bank, + that can be positioned anywhere on the screen. + """ + + def __init__(self, bank, frame, x, y, z=0, rotation=0, palette=None): + self.bank = bank + self.palette = palette or bank.palette + self.frame = frame + self.rotation = rotation + self.x = x + self.y = y + self.z = z + self.layer = _stage.Layer(1, 1, self.bank.buffer, self.palette) + self.layer.move(x, y) + self.layer.frame(frame, rotation) + self.px = x + self.py = y + + def move(self, x, y, z=None): + """Move the sprite to the given place.""" + + self.x = x + self.y = y + if z is not None: + self.z = z + self.layer.move(int(x), int(y)) + + def set_frame(self, frame=None, rotation=None): + """ + Set the current graphic and rotation of the sprite. + + The possible values for rotation are: 0 - none, 1 - 90 degrees + clockwise, 2 - 180 degrees, 3 - 90 degrees counter-clockwise, 4 - + mirrored, 5 - 90 degrees clockwise and mirrored, 6 - 180 degrees and + mirrored, 7 - 90 degrees counter-clockwise and mirrored. """ + + if frame is not None: + self.frame = frame + if rotation is not None: + self.rotation = rotation + self.layer.frame(self.frame, self.rotation) + + def update(self): + pass + + +class Text: + """Text layer. For displaying text.""" + + def __init__(self, width, height, font=None, palette=None, buffer=None): + self.width = width + self.height = height + self.font = font or FONT + self.palette = palette or PALETTE + self.buffer = buffer or bytearray(width * height) + self.layer = _stage.Text(width, height, self.font, + self.palette, self.buffer) + self.column = 0 + self.row = 0 + self.x = 0 + self.y = 0 + self.z = 0 + + def char(self, x, y, c=None, hightlight=False): + """Get or set the character at the given location.""" + if not 0 <= x < self.width or not 0 <= y < self.height: + return + if c is None: + return chr(self.buffer[y * self.width + x]) + c = ord(c) + if hightlight: + c |= 0x80 + self.buffer[y * self.width + x] = c + + def move(self, x, y, z=None): + """Shift the whole layer respective to the screen.""" + self.x = x + self.y = y + if z is not None: + self.z = z + self.layer.move(int(x), int(y)) + + def cursor(self, x=None, y=None): + """Move the text cursor to the specified row and column.""" + if y is not None: + self.row = min(max(0, y), self.width - 1) + if x is not None: + self.column = min(max(0, x), self.height - 1) + + def text(self, text, hightlight=False): + """ + Display text starting at the current cursor location. + Return the dimensions of the rendered text. + """ + longest = 0 + tallest = 0 + for c in text: + if c != '\n': + self.char(self.column, self.row, c, hightlight) + self.column += 1 + if self.column >= self.width or c == '\n': + longest = max(longest, self.column) + self.column = 0 + self.row += 1 + if self.row >= self.height: + tallest = max(tallest, self.row) + self.row = 0 + longest = max(longest, self.column) + tallest = max(tallest, self.row) + (1 if self.column > 0 else 0) + return longest * 8, tallest * 8 + + def clear(self): + """Clear all text from the layer.""" + for i in range(self.width * self.height): + self.buffer[i] = 0 + + +class Stage: + """ + Represents what is being displayed on the screen. + + The ``display`` parameter is displayio.Display representing an initialized + display connected to the device. + + The ``fps`` specifies the maximum frame rate to be enforced. + + The ``scale`` specifies an optional scaling up of the display, to use + 2x2 or 3x3, etc. pixels. If not specified, it is inferred from the display + size (displays wider than 256 pixels will have scale=2, for example). + """ + buffer = bytearray(512) + + def __init__(self, display, fps=6, scale=None): + if scale is None: + self.scale = max(1, display.width // 128) + else: + self.scale = scale + self.layers = [] + self.display = display + self.width = display.width // self.scale + self.height = display.height // self.scale + self.last_tick = time.monotonic() + self.tick_delay = 1 / fps + self.vx = 0 + self.vy = 0 + + def tick(self): + """Wait for the start of the next frame.""" + self.last_tick += self.tick_delay + wait = max(0, self.last_tick - time.monotonic()) + if wait: + time.sleep(wait) + else: + self.last_tick = time.monotonic() + + def render_block(self, x0=None, y0=None, x1=None, y1=None): + """Update a rectangle of the screen.""" + if x0 is None: + x0 = self.vx + if y0 is None: + y0 = self.vy + if x1 is None: + x1 = self.width + self.vx + if y1 is None: + y1 = self.height + self.vy + x0 = min(max(0, x0 - self.vx), self.width - 1) + y0 = min(max(0, y0 - self.vy), self.height - 1) + x1 = min(max(1, x1 - self.vx), self.width) + y1 = min(max(1, y1 - self.vy), self.height) + if x0 >= x1 or y0 >= y1: + return + layers = [l.layer for l in self.layers] + _stage.render(x0, y0, x1, y1, layers, self.buffer, + self.display, self.scale, self.vx, self.vy) + + def render_sprites(self, sprites): + """Update the spots taken by all the sprites in the list.""" + layers = [l.layer for l in self.layers] + for sprite in sprites: + x = int(sprite.x) - self.vx + y = int(sprite.y) - self.vy + x0 = max(0, min(self.width - 1, min(sprite.px, x))) + y0 = max(0, min(self.height - 1, min(sprite.py, y))) + x1 = max(1, min(self.width, max(sprite.px, x) + 16)) + y1 = max(1, min(self.height, max(sprite.py, y) + 16)) + sprite.px = x + sprite.py = y + if x0 >= x1 or y0 >= y1: + continue + _stage.render(x0, y0, x1, y1, layers, self.buffer, + self.display, self.scale, self.vx, self.vy) diff --git a/circuitpython/frozen/circuitpython-stage/ugame10/stage.py b/circuitpython/frozen/circuitpython-stage/ugame10/stage.py new file mode 120000 index 0000000..2dedc93 --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/ugame10/stage.py @@ -0,0 +1 @@ +../stage.py
\ No newline at end of file diff --git a/circuitpython/frozen/circuitpython-stage/ugame10/ugame.py b/circuitpython/frozen/circuitpython-stage/ugame10/ugame.py new file mode 100644 index 0000000..d803dea --- /dev/null +++ b/circuitpython/frozen/circuitpython-stage/ugame10/ugame.py @@ -0,0 +1,33 @@ +""" +A helper module that initializes the display and buttons for the uGame +game console. See https://hackaday.io/project/27629-game +""" + +import board +import digitalio +import analogio +import gamepad +import stage + + +K_X = 0x01 +K_DOWN = 0x02 +K_LEFT = 0x04 +K_RIGHT = 0x08 +K_UP = 0x10 +K_O = 0x20 +K_START = 0x00 +K_SELECT = 0x00 + + +display = board.DISPLAY +buttons = gamepad.GamePad( + digitalio.DigitalInOut(board.X), + digitalio.DigitalInOut(board.DOWN), + digitalio.DigitalInOut(board.LEFT), + digitalio.DigitalInOut(board.RIGHT), + digitalio.DigitalInOut(board.UP), + digitalio.DigitalInOut(board.O), +) +audio = stage.Audio(board.SPEAKER, board.MUTE) +battery = analogio.AnalogIn(board.BATTERY) |