├── .readthedocs.yaml ├── LICENSE ├── README.rst ├── docs ├── .buildinfo ├── .doctrees │ ├── README.doctree │ └── environment.pickle ├── Makefile ├── README.rst ├── _stage.py ├── audioio.py ├── board.py ├── busio.py ├── conf.py ├── digitalio.py ├── gamepad.py ├── index.rst └── ustruct.py ├── examples ├── ball │ ├── ball.bmp │ ├── ball.py │ └── main.py └── rpg │ ├── ground.bmp │ ├── main.py │ └── tiles.bmp ├── feather_m4_minitft_featherwing ├── stage.py └── ugame.py ├── font ├── font.bmp ├── font2.bmp ├── genfont.py └── genfont2.py ├── itsybitsy_m4_express ├── stage.py └── ugame.py ├── meowbit ├── stage.py └── ugame.py ├── pew.py ├── pewpew_m4 ├── pew.py ├── stage.py └── ugame.py ├── picosystem ├── stage.py └── ugame.py ├── png16.py ├── pybadge ├── stage.py └── ugame.py ├── pygamer ├── stage.py └── ugame.py ├── stage.py ├── ugame10 ├── stage.py └── ugame.py └── ugame22 ├── pew.py ├── stage.py └── ugame.py /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | # formats: 27 | # - pdf 28 | # - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | # python: 34 | # install: 35 | # - requirements: docs/requirements.txt 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Radomir Dopieralski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Stage – a Tile and Sprite Engine 2 | ******************************** 3 | 4 | Stage is a library that lets you display tile grids and sprites on SPI-based 5 | RGB displays in CircuitPython. It is mostly made with video games in mind, but 6 | it can be useful in making any kind of graphical user interface too. 7 | 8 | For performance reasons, a part of this library has been written in C and has 9 | to be compiled as part of the CircuitPython firmware as the ``_stage`` module. 10 | For memory saving reasons, it's best if this library is also included in the 11 | firmware, as a frozen module. 12 | 13 | 14 | API Reference 15 | ************* 16 | 17 | The API reference is available at ``_. 18 | 19 | stage 20 | ===== 21 | .. automodule:: stage 22 | :members: 23 | 24 | 25 | ugame 26 | ======= 27 | .. module:: ugame 28 | 29 | .. data:: display 30 | 31 | An initialized display, that can be used for creating Stage objects. 32 | 33 | .. data:: buttons 34 | 35 | An instance of ``GamePad`` or other similar class, that has a 36 | ``get_pressed`` method for retrieving a bit mask of pressed buttons. That 37 | value can be then checked with & operator against the constants: ``K_UP``, 38 | ``K_DOWN``, ``K_LEFT``, ``K_RIGHT``, ``K_X``, ``K_O`` and on some platforms 39 | also: ``K_START`` and ``K_SELECT``. 40 | 41 | .. data:: audio 42 | 43 | And instance of the ``Audio`` or other similar class, that has ``play``, 44 | ``stop`` and ``mute`` methods. 45 | -------------------------------------------------------------------------------- /docs/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: bbefe5630918a9d58757bbc0b225e223 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/.doctrees/README.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/docs/.doctrees/README.doctree -------------------------------------------------------------------------------- /docs/.doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/docs/.doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Stage 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/README.rst: -------------------------------------------------------------------------------- 1 | ../README.rst -------------------------------------------------------------------------------- /docs/_stage.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/docs/_stage.py -------------------------------------------------------------------------------- /docs/audioio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | -------------------------------------------------------------------------------- /docs/board.py: -------------------------------------------------------------------------------- 1 | X = None 2 | DOWN = None 3 | LEFT = None 4 | RIGHT = None 5 | UP = None 6 | O = None 7 | SCK = None 8 | MOSI = None 9 | DC = None 10 | SPEAKER = None 11 | -------------------------------------------------------------------------------- /docs/busio.py: -------------------------------------------------------------------------------- 1 | class SPI: 2 | def __init__(self, clock, MOSI): 3 | pass 4 | 5 | def try_lock(self): 6 | pass 7 | 8 | def write(self, buffer): 9 | pass 10 | 11 | def configure(self, baudrate=None, polarity=None, phase=None): 12 | pass 13 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | sys.path.insert(0, os.path.abspath('.')) 6 | sys.path.insert(0, os.path.abspath('..')) 7 | 8 | # -- General configuration ------------------------------------------------ 9 | 10 | # Add any Sphinx extension module names here, as strings. They can be 11 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 12 | # ones. 13 | extensions = [ 14 | 'sphinx.ext.autodoc', 15 | 'sphinx.ext.intersphinx', 16 | 'sphinx.ext.viewcode', 17 | ] 18 | 19 | intersphinx_mapping = { 20 | 'python': ('https://docs.python.org/3.4', None), 21 | 'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None), 22 | } 23 | 24 | # Add any paths that contain templates here, relative to this directory. 25 | templates_path = ['_templates'] 26 | 27 | source_suffix = '.rst' 28 | 29 | # The master toctree document. 30 | master_doc = 'index' 31 | 32 | # General information about the project. 33 | project = u'Stage' 34 | copyright = u'2017 Radomir Dopieralski' 35 | author = u'Radomir Dopieralski' 36 | 37 | # The version info for the project you're documenting, acts as replacement for 38 | # |version| and |release|, also used in various other places throughout the 39 | # built documents. 40 | # 41 | # The short X.Y version. 42 | version = u'1.0' 43 | # The full version, including alpha/beta/rc tags. 44 | release = u'1.0' 45 | 46 | # The language for content autogenerated by Sphinx. Refer to documentation 47 | # for a list of supported languages. 48 | # 49 | # This is also used if you do content translation via gettext catalogs. 50 | # Usually you set "language" from the command line for these cases. 51 | language = 'en' 52 | 53 | # List of patterns, relative to source directory, that match files and 54 | # directories to ignore when looking for source files. 55 | # This patterns also effect to html_static_path and html_extra_path 56 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 57 | 58 | # The reST default role (used for this markup: `text`) to use for all 59 | # documents. 60 | # 61 | default_role = "any" 62 | 63 | # If true, '()' will be appended to :func: etc. cross-reference text. 64 | # 65 | add_function_parentheses = True 66 | 67 | # The name of the Pygments (syntax highlighting) style to use. 68 | pygments_style = 'sphinx' 69 | 70 | # If true, `todo` and `todoList` produce output, else they produce nothing. 71 | todo_include_todos = False 72 | 73 | 74 | # -- Options for HTML output ---------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. See the documentation for 77 | # a list of builtin themes. 78 | # 79 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 80 | 81 | if not on_rtd: # only import and set the theme if we're building docs locally 82 | try: 83 | import sphinx_rtd_theme 84 | html_theme = 'sphinx_rtd_theme' 85 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] 86 | except: 87 | html_theme = 'default' 88 | html_theme_path = ['.'] 89 | else: 90 | html_theme_path = ['.'] 91 | 92 | # Add any paths that contain custom static files (such as style sheets) here, 93 | # relative to this directory. They are copied after the builtin static files, 94 | # so a file named "default.css" will overwrite the builtin "default.css". 95 | html_static_path = ['_static'] 96 | 97 | # Output file base name for HTML help builder. 98 | htmlhelp_basename = 'Stagedoc' 99 | 100 | # -- Options for LaTeX output --------------------------------------------- 101 | 102 | latex_elements = { 103 | # The paper size ('letterpaper' or 'a4paper'). 104 | # 105 | # 'papersize': 'letterpaper', 106 | 107 | # The font size ('10pt', '11pt' or '12pt'). 108 | # 109 | # 'pointsize': '10pt', 110 | 111 | # Additional stuff for the LaTeX preamble. 112 | # 113 | # 'preamble': '', 114 | 115 | # Latex figure (float) alignment 116 | # 117 | # 'figure_align': 'htbp', 118 | } 119 | 120 | # Grouping the document tree into LaTeX files. List of tuples 121 | # (source start file, target name, title, 122 | # author, documentclass [howto, manual, or own class]). 123 | latex_documents = [ 124 | (master_doc, 'Stage.tex', u'Stage Documentation', 125 | u'Radomir Dopieralski', 'manual'), 126 | ] 127 | 128 | # -- Options for manual page output --------------------------------------- 129 | 130 | # One entry per manual page. List of tuples 131 | # (source start file, name, description, authors, manual section). 132 | man_pages = [ 133 | (master_doc, 'stage', u'Stage Documentation', 134 | [author], 1) 135 | ] 136 | 137 | # -- Options for Texinfo output ------------------------------------------- 138 | 139 | # Grouping the document tree into Texinfo files. List of tuples 140 | # (source start file, target name, title, author, 141 | # dir menu entry, description, category) 142 | texinfo_documents = [ 143 | (master_doc, 'Stage', u'Stage Documentation', 144 | author, 'Stage', 'Tile and sprite engine.', 'Miscellaneous'), 145 | ] 146 | -------------------------------------------------------------------------------- /docs/digitalio.py: -------------------------------------------------------------------------------- 1 | class DigitalInOut: 2 | def __init__(self, pin): 3 | pass 4 | 5 | def switch_to_output(self, value=None): 6 | pass 7 | -------------------------------------------------------------------------------- /docs/gamepad.py: -------------------------------------------------------------------------------- 1 | class GamePad: 2 | def __init__(self, *args): 3 | pass 4 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ../README.rst -------------------------------------------------------------------------------- /docs/ustruct.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/docs/ustruct.py -------------------------------------------------------------------------------- /examples/ball/ball.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/examples/ball/ball.bmp -------------------------------------------------------------------------------- /examples/ball/ball.py: -------------------------------------------------------------------------------- 1 | import ugame 2 | import stage 3 | 4 | 5 | class Ball(stage.Sprite): 6 | def __init__(self, x, y): 7 | super().__init__(bank, 1, x, y) 8 | self.dx = 2 9 | self.dy = 1 10 | 11 | def update(self): 12 | super().update() 13 | self.set_frame(self.frame % 4 + 1) 14 | self.move(self.x + self.dx, self.y + self.dy) 15 | if not 0 < self.x < 112: 16 | self.dx = -self.dx 17 | if not 0 < self.y < 112: 18 | self.dy = -self.dy 19 | 20 | 21 | bank = stage.Bank.from_bmp16("ball.bmp") 22 | background = stage.Grid(bank) 23 | text = stage.Text(12, 1) 24 | text.move(16, 60) 25 | text.text("Hello world!") 26 | ball1 = Ball(64, 0) 27 | ball2 = Ball(0, 76) 28 | ball3 = Ball(111, 64) 29 | game = stage.Stage(ugame.display, 12) 30 | sprites = [ball1, ball2, ball3] 31 | game.layers = [text, ball1, ball2, ball3, background] 32 | game.render_block(0, 0, 128, 128) 33 | 34 | while True: 35 | for sprite in sprites: 36 | sprite.update() 37 | game.render_sprites(sprites) 38 | game.tick() 39 | -------------------------------------------------------------------------------- /examples/ball/main.py: -------------------------------------------------------------------------------- 1 | import ugame 2 | import stage 3 | 4 | 5 | class Ball(stage.Sprite): 6 | def __init__(self, x, y): 7 | super().__init__(bank, 1, x, y) 8 | self.dx = 2 9 | self.dy = 1 10 | 11 | def update(self): 12 | super().update() 13 | self.set_frame(self.frame % 4 + 1) 14 | self.move(self.x + self.dx, self.y + self.dy) 15 | if not 0 < self.x < 112: 16 | self.dx = -self.dx 17 | if not 0 < self.y < 112: 18 | self.dy = -self.dy 19 | 20 | 21 | bank = stage.Bank.from_bmp16("ball.bmp") 22 | background = stage.Grid(bank) 23 | text = stage.Text(12, 1) 24 | text.move(16, 60) 25 | text.text("Hello world!") 26 | ball1 = Ball(64, 0) 27 | ball2 = Ball(0, 76) 28 | ball3 = Ball(111, 64) 29 | game = stage.Stage(ugame.display, 12) 30 | sprites = [ball1, ball2, ball3] 31 | game.layers = [text, ball1, ball2, ball3, background] 32 | game.render_block(0, 0, 128, 128) 33 | 34 | while True: 35 | for sprite in sprites: 36 | sprite.update() 37 | game.render_sprites(sprites) 38 | game.tick() 39 | -------------------------------------------------------------------------------- /examples/rpg/ground.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/examples/rpg/ground.bmp -------------------------------------------------------------------------------- /examples/rpg/main.py: -------------------------------------------------------------------------------- 1 | import random 2 | import ugame 3 | import stage 4 | 5 | 6 | g = stage.Bank.from_bmp16("ground.bmp") 7 | b = stage.Bank.from_bmp16("tiles.bmp") 8 | l1 = stage.Grid(g) 9 | l0 = stage.Grid(b, 10, 9) 10 | l0.tile(0, 0, 13) 11 | l0.move(-8, -8) 12 | for y in range(8): 13 | for x in range(8): 14 | l1.tile(x, y, random.randint(0, 4)) 15 | for y in range(9): 16 | for x in range(9): 17 | t = 0 18 | bit = 1 19 | for dx in (0, -1): 20 | for dy in (-1, 0): 21 | if l1.tile(x + dx, y + dy) == 4: 22 | t |= bit 23 | bit <<= 1 24 | l0.tile(x, y, 15 - t) 25 | p = stage.Sprite(g, 15, 10, 10) 26 | t = stage.Text(14, 14) 27 | t.move(8, 8) 28 | t.text("Hello world!") 29 | 30 | game = stage.Stage(ugame.display, 12) 31 | sprites = [ 32 | stage.Sprite(g, 15, 60, 50), 33 | stage.Sprite(g, 15, 70, 60), 34 | stage.Sprite(g, 15, 80, 70), 35 | stage.Sprite(g, 15, 90, 80), 36 | stage.Sprite(g, 15, 100, 90), 37 | p, 38 | ] 39 | game.layers = [t, l0] + sprites + [l1] 40 | game.render(0, 0, 128, 128) 41 | 42 | frame = 0 43 | while True: 44 | frame = (frame + 1) % 8 45 | keys = ugame.buttons.get_pressed() 46 | if keys & ugame.K_RIGHT: 47 | p.move(p.x, p.y + 2) 48 | p.set_frame(12 + frame // 4, 0) 49 | elif keys & ugame.K_LEFT: 50 | p.move(p.x, p.y - 2) 51 | p.set_frame(12 + frame // 4, 4) 52 | elif keys & ugame.K_UP: 53 | p.move(p.x + 2, p.y) 54 | p.set_frame(14, (frame // 4) * 4) 55 | elif keys & ugame.K_DOWN: 56 | p.move(p.x - 2, p.y) 57 | p.set_frame(15, (frame // 4) * 4) 58 | else: 59 | p.set_frame(15, (frame // 4) * 4) 60 | for sprite in sprites: 61 | if sprite != p: 62 | sprite.set_frame(15, (frame // 4) * 4) 63 | x0 = min(sprite.px, sprite.x) 64 | y0 = min(sprite.py, sprite.y) 65 | x1 = max(sprite.px, sprite.x) + 16 66 | y1 = max(sprite.py, sprite.y) + 16 67 | game.render(x0, y0, x1, y1) 68 | game.tick() 69 | -------------------------------------------------------------------------------- /examples/rpg/tiles.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/examples/rpg/tiles.bmp -------------------------------------------------------------------------------- /feather_m4_minitft_featherwing/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /feather_m4_minitft_featherwing/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | from micropython import const 3 | from adafruit_seesaw.seesaw import Seesaw 4 | from adafruit_seesaw.pwmout import PWMOut 5 | import stage 6 | import displayio 7 | 8 | 9 | K_UP = const(4) 10 | K_LEFT = const(8) 11 | K_DOWN = const(16) 12 | K_RIGHT = const(128) 13 | K_X = const(512) 14 | K_O = const(1024) 15 | K_SELECT = const(2048) 16 | K_START = const(0) 17 | 18 | _INIT_SEQUENCE = ( 19 | b"\x01\x80\x96" # SWRESET and Delay 150ms 20 | b"\x11\x80\xff" # SLPOUT and Delay 21 | b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 22 | b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 23 | b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 24 | b"\xb4\x01\x07" # _INVCTR line inversion 25 | b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA 26 | b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V 27 | b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency 28 | b"\xc3\x02\x8a\x2a" 29 | b"\xc4\x02\x8a\xee" 30 | b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V 31 | b"\x20\x00" # _INVOFF 32 | b"\x36\x01\x60" # _MADCTL bottom to top refresh 33 | # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, 34 | # fix on VTL 35 | b"\x3a\x01\x05" # COLMOD - 16bit color 36 | b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma 37 | b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 38 | b"\x13\x80\x0a" # _NORON 39 | b"\x29\x80\x64" # _DISPON 40 | ) 41 | 42 | 43 | class GamePadSeesaw: 44 | mask = K_RIGHT | K_DOWN | K_LEFT | K_UP | K_SELECT | K_O | K_X 45 | 46 | def __init__(self, ss): 47 | ss.pin_mode_bulk(self.mask, ss.INPUT_PULLUP) 48 | self.ss = ss 49 | 50 | def get_pressed(self): 51 | return ~self.ss.digital_read_bulk(self.mask) 52 | 53 | 54 | class DummyAudio: 55 | def play(self, f, loop=False): 56 | pass 57 | 58 | def stop(self): 59 | pass 60 | 61 | def mute(self, mute): 62 | pass 63 | 64 | 65 | i2c = board.I2C() 66 | ss = Seesaw(i2c, 0x5E) 67 | spi = board.SPI() 68 | displayio.release_displays() 69 | while not spi.try_lock(): 70 | pass 71 | spi.configure(baudrate=24000000) 72 | spi.unlock() 73 | ss.pin_mode(8, ss.OUTPUT) 74 | ss.digital_write(8, True) # reset display 75 | display_bus = displayio.FourWire(spi, command=board.D6, chip_select=board.D5) 76 | display = displayio.Display(display_bus, _INIT_SEQUENCE, width=160, height=80, 77 | rowstart=24) 78 | del _INIT_SEQUENCE 79 | buttons = GamePadSeesaw(ss) 80 | audio = DummyAudio() 81 | backlight = PWMOut(ss, 5) 82 | backlight.duty_cycle = 0 83 | -------------------------------------------------------------------------------- /font/font.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/font/font.bmp -------------------------------------------------------------------------------- /font/font2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-ugame/circuitpython-stage/9e5c046201ef5da8b1bacfaf2ce828a8037eaccb/font/font2.bmp -------------------------------------------------------------------------------- /font/genfont.py: -------------------------------------------------------------------------------- 1 | import array 2 | import pprint 3 | 4 | 5 | def color565(r, g, b): 6 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 7 | 8 | 9 | class BMP16: 10 | """Read 16-color BMP files.""" 11 | 12 | def __init__(self, filename): 13 | self.filename = filename 14 | self.colors = 0 15 | 16 | def read_header(self): 17 | """Read the file's header information.""" 18 | 19 | if self.colors: 20 | return 21 | with open(self.filename, 'rb') as f: 22 | f.seek(10) 23 | self.data = int.from_bytes(f.read(4), 'little') 24 | f.seek(18) 25 | self.width = int.from_bytes(f.read(4), 'little') 26 | self.height = int.from_bytes(f.read(4), 'little') 27 | f.seek(46) 28 | self.colors = int.from_bytes(f.read(4), 'little') 29 | 30 | def read_palette(self): 31 | """Read the color palette information.""" 32 | 33 | palette = array.array('H', (0 for i in range(16))) 34 | with open(self.filename, 'rb') as f: 35 | f.seek(self.data - self.colors * 4) 36 | for color in range(self.colors): 37 | buffer = f.read(4) 38 | c = color565(buffer[2], buffer[1], buffer[0]) 39 | palette[color] = ((c & 0xff) << 8) | (c >> 8) 40 | return palette 41 | 42 | def read_data(self, offset=0, size=-1): 43 | """Read the image data.""" 44 | 45 | with open(self.filename, 'rb') as f: 46 | f.seek(self.data + offset) 47 | return f.read(size) 48 | 49 | 50 | class Font: 51 | def __init__(self, buffer): 52 | self.buffer = buffer 53 | 54 | @classmethod 55 | def from_bmp16(cls, filename): 56 | bmp = BMP16(filename) 57 | bmp.read_header() 58 | if bmp.width != 8 or bmp.height != 1024: 59 | raise ValueError("A 8x1024 16-color BMP expected!") 60 | data = bmp.read_data() 61 | self = cls(bytearray(2048)) 62 | c = 0 63 | x = 0 64 | y = 7 65 | for b in data: 66 | self.pixel(c, x, y, b >> 4) 67 | x += 1 68 | self.pixel(c, x, y, b & 0x0f) 69 | x += 1 70 | if x >= 8: 71 | x = 0 72 | y -= 1 73 | if y < 0: 74 | y = 7 75 | c += 1 76 | del data 77 | self.palette = bmp.read_palette() 78 | return self 79 | 80 | def pixel(self, c, x, y, color): 81 | index = (127 - c) * 16 + 2 * y + x // 4 82 | bit = (x % 4) * 2 83 | color = color & 0x03 84 | self.buffer[index] |= color << bit 85 | 86 | 87 | font = Font.from_bmp16("font.bmp") 88 | pprint.pprint(font.buffer) 89 | pprint.pprint(font.palette.tobytes()) 90 | -------------------------------------------------------------------------------- /font/genfont2.py: -------------------------------------------------------------------------------- 1 | import array 2 | import pprint 3 | 4 | 5 | def color565(r, g, b): 6 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 7 | 8 | 9 | class BMP16: 10 | """Read 16-color BMP files.""" 11 | 12 | def __init__(self, filename): 13 | self.filename = filename 14 | self.colors = 0 15 | 16 | def read_header(self): 17 | """Read the file's header information.""" 18 | 19 | if self.colors: 20 | return 21 | with open(self.filename, 'rb') as f: 22 | f.seek(10) 23 | self.data = int.from_bytes(f.read(4), 'little') 24 | f.seek(18) 25 | self.width = int.from_bytes(f.read(4), 'little') 26 | self.height = int.from_bytes(f.read(4), 'little') 27 | f.seek(46) 28 | self.colors = int.from_bytes(f.read(4), 'little') 29 | 30 | def read_palette(self): 31 | """Read the color palette information.""" 32 | 33 | palette = array.array('H', (0 for i in range(16))) 34 | with open(self.filename, 'rb') as f: 35 | f.seek(self.data - self.colors * 4) 36 | for color in range(self.colors): 37 | buffer = f.read(4) 38 | c = color565(buffer[2], buffer[1], buffer[0]) 39 | palette[color] = ((c & 0xff) << 8) | (c >> 8) 40 | return palette 41 | 42 | def read_data(self, offset=0, size=-1): 43 | """Read the image data.""" 44 | 45 | with open(self.filename, 'rb') as f: 46 | f.seek(self.data + offset) 47 | return f.read(size) 48 | 49 | 50 | class Font: 51 | def __init__(self, buffer): 52 | self.buffer = buffer 53 | 54 | @classmethod 55 | def from_bmp16(cls, filename): 56 | bmp = BMP16(filename) 57 | bmp.read_header() 58 | if bmp.width != 8 or bmp.height != 1024: 59 | raise ValueError("A 8x1024 16-color BMP expected!") 60 | data = bmp.read_data() 61 | self = cls(bytearray(2048)) 62 | c = 0 63 | x = 0 64 | y = 7 65 | for b in data: 66 | self.pixel(c, x, y, b >> 4) 67 | x += 1 68 | self.pixel(c, x, y, b & 0x0f) 69 | x += 1 70 | if x >= 8: 71 | x = 0 72 | y -= 1 73 | if y < 0: 74 | y = 7 75 | c += 1 76 | del data 77 | self.palette = bmp.read_palette() 78 | return self 79 | 80 | def pixel(self, c, x, y, color): 81 | index = (127 - c) * 16 + 2 * y + x // 4 82 | bit = (x % 4) * 2 83 | color = color & 0x03 84 | self.buffer[index] |= color << bit 85 | 86 | 87 | font = Font.from_bmp16("font2.bmp") 88 | pprint.pprint(font.buffer) 89 | pprint.pprint(font.palette.tobytes()) 90 | -------------------------------------------------------------------------------- /itsybitsy_m4_express/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /itsybitsy_m4_express/ugame.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper module that initializes the display and buttons for the uGame 3 | game console. See https://hackaday.io/project/27629-game 4 | """ 5 | 6 | import board 7 | import digitalio 8 | import gamepad 9 | import stage 10 | import displayio 11 | import busio 12 | 13 | 14 | K_X = 0x01 15 | K_DOWN = 0x02 16 | K_LEFT = 0x04 17 | K_RIGHT = 0x08 18 | K_UP = 0x10 19 | K_O = 0x20 20 | K_START = 0x40 21 | K_SELECT = 0x00 22 | 23 | 24 | _INIT_SEQUENCE = ( 25 | b"\x01\x80\x96" # SWRESET and Delay 150ms 26 | b"\x11\x80\xff" # SLPOUT and Delay 27 | b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 28 | b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 29 | b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 30 | b"\xb4\x01\x07" # _INVCTR line inversion 31 | b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA 32 | b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V 33 | b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency 34 | b"\xc3\x02\x8a\x2a" 35 | b"\xc4\x02\x8a\xee" 36 | b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V 37 | b"\x20\x00" # _INVOFF 38 | b"\x36\x01\x10" # _MADCTL bottom to top refresh 39 | # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, 40 | # fix on VTL 41 | b"\x3a\x01\x05" # COLMOD - 16bit color 42 | b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma 43 | b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 44 | b"\x13\x80\x0a" # _NORON 45 | b"\x29\x80\x64" # _DISPON 46 | ) 47 | 48 | 49 | class DummyAudio: 50 | def play(self, f, loop=False): 51 | pass 52 | 53 | def stop(self): 54 | pass 55 | 56 | def mute(self, mute): 57 | pass 58 | 59 | 60 | displayio.release_displays() 61 | _tft_spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI) 62 | _tft_spi.try_lock() 63 | _tft_spi.configure(baudrate=24000000) 64 | _tft_spi.unlock() 65 | _fourwire = displayio.FourWire(_tft_spi, command=board.A3, 66 | chip_select=board.A2, reset=board.A4) 67 | display = displayio.Display(_fourwire, _INIT_SEQUENCE, width=160, height=128, 68 | rotation=0, backlight_pin=board.A5) 69 | buttons = gamepad.GamePad( 70 | digitalio.DigitalInOut(board.SCL), 71 | digitalio.DigitalInOut(board.D12), 72 | digitalio.DigitalInOut(board.D11), 73 | digitalio.DigitalInOut(board.D9), 74 | digitalio.DigitalInOut(board.D10), 75 | digitalio.DigitalInOut(board.D7), 76 | digitalio.DigitalInOut(board.SDA), 77 | ) 78 | audio = DummyAudio() 79 | -------------------------------------------------------------------------------- /meowbit/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /meowbit/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | import stage 3 | import busio 4 | import time 5 | import keypad 6 | import audiocore 7 | 8 | 9 | K_X = 0x01 10 | K_O = 0x02 11 | K_DOWN = 0x04 12 | K_LEFT = 0x08 13 | K_RIGHT = 0x10 14 | K_UP = 0x20 15 | K_Z = 0x40 16 | 17 | display = board.DISPLAY 18 | display.auto_refresh = False 19 | 20 | 21 | class _Buttons: 22 | def __init__(self): 23 | self.keys = keypad.Keys((board.BTNA, board.BTNB, board.DOWN, 24 | board.LEFT, board.RIGHT, board.UP), 25 | value_when_pressed=False, interval=0.05) 26 | self.last_state = 0 27 | self.event = keypad.Event(0, False) 28 | self.last_z_press = None 29 | 30 | def get_pressed(self): 31 | buttons = self.last_state 32 | events = self.keys.events 33 | while events: 34 | if events.get_into(self.event): 35 | bit = 1 << self.event.key_number 36 | if self.event.pressed: 37 | buttons |= bit 38 | self.last_state |= bit 39 | else: 40 | self.last_state &= ~bit 41 | if buttons & K_Z: 42 | now = time.monotonic() 43 | if self.last_z_press: 44 | if now - self.last_z_press > 2: 45 | supervisor.set_next_code_file(None) 46 | supervisor.reload() 47 | else: 48 | self.last_z_press = now 49 | else: 50 | self.last_z_press = None 51 | return buttons 52 | 53 | 54 | class _Audio: 55 | last_audio = None 56 | 57 | def __init__(self): 58 | self.muted = True 59 | self.buffer = bytearray(128) 60 | self.audio = board.BUZZ 61 | 62 | def play(self, audio_file, loop=False): 63 | if self.muted: 64 | return 65 | self.stop() 66 | wave = audiocore.WaveFile(audio_file, self.buffer) 67 | self.audio.play(wave, loop=loop) 68 | 69 | def stop(self): 70 | self.audio.stop() 71 | 72 | def mute(self, value=True): 73 | self.muted = value 74 | 75 | 76 | audio = _Audio() 77 | buttons = _Buttons() 78 | -------------------------------------------------------------------------------- /pew.py: -------------------------------------------------------------------------------- 1 | from micropython import const 2 | import board 3 | import busio 4 | import digitalio 5 | import time 6 | import ugame 7 | import stage 8 | import array 9 | 10 | 11 | _FONT = ( 12 | b'{{{{{{wws{w{HY{{{{YDYDY{sUtGUsH[wyH{uHgHE{ws{{{{vyxyv{g[K[g{{]f]{{{wDw{{' 13 | b'{{{wy{{{D{{{{{{{w{K_w}x{VHLHe{wuwww{`KfyD{UKgKU{w}XDK{DxTKT{VxUHU{D[wyx{' 14 | b'UHfHU{UHEKe{{w{w{{{w{wy{KwxwK{{D{D{{xwKwx{eKg{w{VIHyB{fYH@H{dHdHd{FyxyF{' 15 | b'`XHX`{DxtxD{Dxtxx{FyxIF{HHDHH{wwwww{KKKHU{HXpXH{xxxxD{Y@DLH{IL@LX{fYHYf{' 16 | b'`HH`x{fYHIF{`HH`H{UxUKU{Dwwww{HHHIR{HHH]w{HHLD@{HYsYH{HYbww{D[wyD{txxxt{' 17 | b'x}w_K{GKKKG{wLY{{{{{{{{Dxs{{{{{BIIB{x`XX`{{ByyB{KBIIB{{WIpF{OwUwww{`YB[`' 18 | b'x`XHH{w{vwc{K{OKHUxHpXH{vwws_{{dD@H{{`XHH{{fYYf{{`XX`x{bYIBK{Ipxx{{F}_d{' 19 | b'wUws_{{HHIV{{HH]s{{HLD@{{HbbH{{HHV[a{D_}D{Cw|wC{wwwwwwpwOwp{WKfxu{@YYY@{' 20 | ) 21 | _SALT = const(132) 22 | 23 | _PALETTE = array.array('H', (0x0, 0x4a29, 0x6004, 0xf8, 0xfd, 0xf42, 0x825b, 24 | 0xf8, 0xfe, 0x125b, 0xcffb, 0xe0cf, 0xffff, 25 | 0x1ff8, 0xdbff, 0xffff)) 26 | 27 | K_X = ugame.K_X 28 | K_DOWN = ugame.K_DOWN 29 | K_LEFT = ugame.K_LEFT 30 | K_RIGHT = ugame.K_RIGHT 31 | K_UP = ugame.K_UP 32 | K_O = ugame.K_O 33 | 34 | _tick = None 35 | _display = None 36 | 37 | 38 | def brightness(level): 39 | pass 40 | 41 | 42 | def show(pix): 43 | for y in range(8): 44 | for x in range(8): 45 | _grid.tile(x + 1, y, 1 + (pix.pixel(x, y) & 0x03)) 46 | _game.render_block(16, 0, 144, 128) 47 | 48 | keys = ugame.buttons.get_pressed 49 | 50 | 51 | def tick(delay): 52 | global _tick 53 | 54 | now = time.monotonic() 55 | _tick += delay 56 | if _tick < now: 57 | _tick = now 58 | else: 59 | time.sleep(_tick - now) 60 | 61 | 62 | class GameOver(SystemExit): 63 | pass 64 | 65 | 66 | class Pix: 67 | __slots__ = ('buffer', 'width', 'height') 68 | 69 | def __init__(self, width=8, height=8, buffer=None): 70 | if buffer is None: 71 | buffer = bytearray(width * height) 72 | self.buffer = buffer 73 | self.width = width 74 | self.height = height 75 | 76 | @classmethod 77 | def from_text(cls, string, color=None, bgcolor=0, colors=None): 78 | pix = cls(4 * len(string), 6) 79 | font = memoryview(_FONT) 80 | if colors is None: 81 | if color is None: 82 | colors = (3, 2, bgcolor, bgcolor) 83 | else: 84 | colors = (color, color, bgcolor, bgcolor) 85 | x = 0 86 | for c in string: 87 | index = ord(c) - 0x20 88 | if not 0 <= index <= 95: 89 | continue 90 | row = 0 91 | for byte in font[index * 6:index * 6 + 6]: 92 | unsalted = byte ^ _SALT 93 | for col in range(4): 94 | pix.pixel(x + col, row, colors[unsalted & 0x03]) 95 | unsalted >>= 2 96 | row += 1 97 | x += 4 98 | return pix 99 | 100 | @classmethod 101 | def from_iter(cls, lines): 102 | pix = cls(len(lines[0]), len(lines)) 103 | y = 0 104 | for line in lines: 105 | x = 0 106 | for pixel in line: 107 | pix.pixel(x, y, pixel) 108 | x += 1 109 | y += 1 110 | return pix 111 | 112 | def pixel(self, x, y, color=None): 113 | if not 0 <= x < self.width or not 0 <= y < self.height: 114 | return 0 115 | if color is None: 116 | return self.buffer[x + y * self.width] 117 | self.buffer[x + y * self.width] = color 118 | 119 | def box(self, color, x=0, y=0, width=None, height=None): 120 | x = min(max(x, 0), self.width - 1) 121 | y = min(max(y, 0), self.height - 1) 122 | width = max(0, min(width or self.width, self.width - x)) 123 | height = max(0, min(height or self.height, self.height - y)) 124 | for y in range(y, y + height): 125 | xx = y * self.width + x 126 | for i in range(width): 127 | self.buffer[xx] = color 128 | xx += 1 129 | 130 | def blit(self, source, dx=0, dy=0, x=0, y=0, 131 | width=None, height=None, key=None): 132 | if dx < 0: 133 | x -= dx 134 | dx = 0 135 | if x < 0: 136 | dx -= x 137 | x = 0 138 | if dy < 0: 139 | y -= dy 140 | dy = 0 141 | if y < 0: 142 | dy -= y 143 | y = 0 144 | width = min(min(width or source.width, source.width - x), 145 | self.width - dx) 146 | height = min(min(height or source.height, source.height - y), 147 | self.height - dy) 148 | source_buffer = memoryview(source.buffer) 149 | self_buffer = self.buffer 150 | if key is None: 151 | for row in range(height): 152 | xx = y * source.width + x 153 | dxx = dy * self.width + dx 154 | self_buffer[dxx:dxx + width] = source_buffer[xx:xx + width] 155 | y += 1 156 | dy += 1 157 | else: 158 | for row in range(height): 159 | xx = y * source.width + x 160 | dxx = dy * self.width + dx 161 | for col in range(width): 162 | color = source_buffer[xx] 163 | if color != key: 164 | self_buffer[dxx] = color 165 | dxx += 1 166 | xx += 1 167 | y += 1 168 | dy += 1 169 | 170 | def __str__(self): 171 | return "\n".join( 172 | "".join( 173 | ('.', '+', '*', '@')[self.pixel(x, y)] 174 | for x in range(self.width) 175 | ) 176 | for y in range(self.height) 177 | ) 178 | 179 | 180 | def init(): 181 | global _tick, _display, _bitmap, _grid, _game 182 | 183 | if _tick is not None: 184 | return 185 | 186 | _tick = time.monotonic() 187 | 188 | _game = stage.Stage(ugame.display, 12) 189 | _bank = bytearray(2048) 190 | for c in range(16): 191 | for y in range(0, 15): 192 | for x in range(0, 7): 193 | _bank[c * 128 + y * 8 + x] = c | c << 4 194 | _bank[c * 128 + y * 8 + 7] = c << 4 195 | _bank[c * 128] = c 196 | _bank[c * 128 + 7] = 0 197 | _bank[c * 128 + 14 * 8] = c 198 | _bank[c * 128 + 14 * 8 + 7] = 0 199 | tiles = stage.Bank(_bank, _PALETTE) 200 | _grid = stage.Grid(tiles, 10, 8) 201 | _grid.move(0, 0) 202 | _game.layers = [_grid] 203 | _game.render_block() 204 | -------------------------------------------------------------------------------- /pewpew_m4/pew.py: -------------------------------------------------------------------------------- 1 | ../pew.py -------------------------------------------------------------------------------- /pewpew_m4/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /pewpew_m4/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | import stage 3 | import supervisor 4 | import time 5 | import keypad 6 | import audioio 7 | import audiocore 8 | 9 | 10 | K_X = 0x01 11 | K_DOWN = 0x02 12 | K_LEFT = 0x04 13 | K_RIGHT = 0x08 14 | K_UP = 0x10 15 | K_O = 0x20 16 | K_START = 0x40 17 | K_Z = 0x40 18 | K_SELECT = 0x80 19 | 20 | 21 | class _Buttons: 22 | def __init__(self): 23 | self.keys = keypad.Keys((board.BUTTON_X, board.BUTTON_DOWN, 24 | board.BUTTON_LEFT, board.BUTTON_RIGHT, board.BUTTON_UP, 25 | board.BUTTON_O, board.BUTTON_Z), value_when_pressed=False, 26 | interval=0.05) 27 | self.last_state = 0 28 | self.event = keypad.Event(0, False) 29 | self.last_z_press = None 30 | 31 | def get_pressed(self): 32 | buttons = self.last_state 33 | events = self.keys.events 34 | while events: 35 | if events.get_into(self.event): 36 | bit = 1 << self.event.key_number 37 | if self.event.pressed: 38 | buttons |= bit 39 | self.last_state |= bit 40 | else: 41 | self.last_state &= ~bit 42 | if buttons & K_Z: 43 | now = time.monotonic() 44 | if self.last_z_press: 45 | if now - self.last_z_press > 2: 46 | supervisor.set_next_code_file(None) 47 | supervisor.reload() 48 | else: 49 | self.last_z_press = now 50 | else: 51 | self.last_z_press = None 52 | return buttons 53 | 54 | 55 | class _Audio: 56 | last_audio = None 57 | 58 | def __init__(self, speaker_pin): 59 | self.muted = True 60 | self.buffer = bytearray(128) 61 | self.audio = audioio.AudioOut(speaker_pin) 62 | 63 | def play(self, audio_file, loop=False): 64 | if self.muted: 65 | return 66 | self.stop() 67 | wave = audiocore.WaveFile(audio_file, self.buffer) 68 | self.audio.play(wave, loop=loop) 69 | 70 | def stop(self): 71 | self.audio.stop() 72 | 73 | def mute(self, value=True): 74 | self.muted = value 75 | 76 | 77 | display = board.DISPLAY 78 | buttons = _Buttons() 79 | audio = _Audio(board.SPEAKER) 80 | -------------------------------------------------------------------------------- /picosystem/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /picosystem/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | import analogio 3 | import stage 4 | import keypad 5 | import audiocore 6 | import audiopwmio 7 | import time 8 | import supervisor 9 | 10 | 11 | K_O = 0x01 # A 12 | K_X = 0x02 # B 13 | K_SELECT = 0x04 # X 14 | K_START = 0x08 # Y 15 | K_Z = 0x08 # Y 16 | K_DOWN = 0x10 17 | K_LEFT = 0x20 18 | K_RIGHT = 0x40 19 | K_UP = 0x80 20 | 21 | 22 | class _Buttons: 23 | def __init__(self): 24 | self.keys = keypad.Keys(( 25 | board.SW_A, 26 | board.SW_B, 27 | board.SW_X, 28 | board.SW_Y, 29 | board.SW_DOWN, 30 | board.SW_LEFT, 31 | board.SW_RIGHT, 32 | board.SW_UP 33 | ), value_when_pressed=False, pull=True, interval=0.05) 34 | self.last_state = 0 35 | self.event = keypad.Event(0, False) 36 | self.last_z_press = None 37 | 38 | def get_pressed(self): 39 | buttons = self.last_state 40 | events = self.keys.events 41 | while events: 42 | if events.get_into(self.event): 43 | bit = 1 << self.event.key_number 44 | if self.event.pressed: 45 | buttons |= bit 46 | self.last_state |= bit 47 | else: 48 | self.last_state &= ~bit 49 | if buttons & K_Z: 50 | now = time.monotonic() 51 | if self.last_z_press: 52 | if now - self.last_z_press > 2: 53 | supervisor.set_next_code_file(None) 54 | supervisor.reload() 55 | else: 56 | self.last_z_press = now 57 | else: 58 | self.last_z_press = None 59 | return buttons 60 | 61 | class _Audio: 62 | last_audio = None 63 | 64 | def __init__(self): 65 | self.muted = True 66 | self.buffer = bytearray(128) 67 | self.audio = audiopwmio.PWMAudioOut(board.AUDIO) 68 | 69 | def play(self, audio_file, loop=False): 70 | if self.muted: 71 | return 72 | self.stop() 73 | wave = audiocore.WaveFile(audio_file, self.buffer) 74 | self.audio.play(wave, loop=loop) 75 | 76 | def stop(self): 77 | self.audio.stop() 78 | 79 | def mute(self, value=True): 80 | self.muted = value 81 | 82 | 83 | audio = _Audio() 84 | display = board.DISPLAY 85 | buttons = _Buttons() 86 | battery = analogio.AnalogIn(board.BAT_SENSE) 87 | -------------------------------------------------------------------------------- /png16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Converts images to the 4-bit PNG format required by Stage. 3 | """ 4 | 5 | import sys 6 | from PIL import Image 7 | 8 | 9 | filename = sys.argv[1] 10 | image = Image.open(filename) 11 | image = image.convert(mode='P', dither=Image.Dither.NONE, 12 | palette=Image.Palette.ADAPTIVE, colors=16) 13 | filename = filename.rsplit('.', 1)[0] + '.png' 14 | image.save(filename, 'png', bits=4) 15 | -------------------------------------------------------------------------------- /pybadge/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /pybadge/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | import stage 3 | import displayio 4 | import busio 5 | import time 6 | import keypad 7 | import audioio 8 | import audiocore 9 | import digitalio 10 | import supervisor 11 | 12 | 13 | K_O = 0x01 14 | K_X = 0x02 15 | K_DOWN = 0x20 16 | K_LEFT = 0x80 17 | K_RIGHT = 0x10 18 | K_UP = 0x40 19 | K_START = 0x04 20 | K_SELECT = 0x08 21 | 22 | # re-initialize the display for correct rotation and RGB mode 23 | 24 | _TFT_INIT = ( 25 | b"\x01\x80\x96" # SWRESET and Delay 150ms 26 | b"\x11\x80\xff" # SLPOUT and Delay 27 | b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 28 | b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 29 | b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 30 | b"\xb4\x01\x07" # _INVCTR line inversion 31 | b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA 32 | b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V 33 | b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency 34 | b"\xc3\x02\x8a\x2a" 35 | b"\xc4\x02\x8a\xee" 36 | b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V 37 | b"\x20\x00" # _INVOFF 38 | b"\x36\x01\xa0" # _MADCTL 39 | # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, 40 | # fix on VTL 41 | b"\x3a\x01\x05" # COLMOD - 16bit color 42 | b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma 43 | b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 44 | b"\x13\x80\x0a" # _NORON 45 | b"\x29\x80\x64" # _DISPON 46 | ) 47 | 48 | 49 | class _Buttons: 50 | def __init__(self): 51 | self.keys = keypad.ShiftRegisterKeys(clock=board.BUTTON_CLOCK, 52 | data=board.BUTTON_OUT, latch=board.BUTTON_LATCH, key_count=8, 53 | interval=0.05, value_when_pressed=True) 54 | self.last_state = 0 55 | self.event = keypad.Event(0, False) 56 | self.last_z_press = None 57 | 58 | def get_pressed(self): 59 | buttons = self.last_state 60 | events = self.keys.events 61 | while events: 62 | if events.get_into(self.event): 63 | bit = 1 << self.event.key_number 64 | if self.event.pressed: 65 | buttons |= bit 66 | self.last_state |= bit 67 | else: 68 | self.last_state &= ~bit 69 | if buttons & K_START: 70 | now = time.monotonic() 71 | if self.last_z_press: 72 | if now - self.last_z_press > 2: 73 | supervisor.set_next_code_file(None) 74 | supervisor.reload() 75 | else: 76 | self.last_z_press = now 77 | else: 78 | self.last_z_press = None 79 | return buttons 80 | 81 | 82 | class _Audio: 83 | last_audio = None 84 | 85 | def __init__(self, speaker_pin, mute_pin=None): 86 | self.muted = True 87 | self.buffer = bytearray(128) 88 | if mute_pin: 89 | self.mute_pin = digitalio.DigitalInOut(mute_pin) 90 | self.mute_pin.switch_to_output(value=not self.muted) 91 | else: 92 | self.mute_pin = None 93 | self.audio = audioio.AudioOut(speaker_pin) 94 | 95 | def play(self, audio_file, loop=False): 96 | if self.muted: 97 | return 98 | self.stop() 99 | wave = audiocore.WaveFile(audio_file, self.buffer) 100 | self.audio.play(wave, loop=loop) 101 | 102 | def stop(self): 103 | self.audio.stop() 104 | 105 | def mute(self, value=True): 106 | self.muted = value 107 | if self.mute_pin: 108 | self.mute_pin.value = not value 109 | 110 | 111 | displayio.release_displays() 112 | _tft_spi = busio.SPI(clock=board.TFT_SCK, MOSI=board.TFT_MOSI) 113 | _fourwire = displayio.FourWire(_tft_spi, command=board.TFT_DC, 114 | chip_select=board.TFT_CS, reset=board.TFT_RST) 115 | display = displayio.Display(_fourwire, _TFT_INIT, width=160, height=128, 116 | rotation=0, auto_refresh=False) 117 | # Work around broken backlight in CP 7.0 118 | _backlight = digitalio.DigitalInOut(board.TFT_LITE) 119 | _backlight.switch_to_output(value=1) 120 | del _TFT_INIT 121 | buttons = _Buttons() 122 | audio = _Audio(board.SPEAKER, board.SPEAKER_ENABLE) 123 | -------------------------------------------------------------------------------- /pygamer/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /pygamer/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | import analogio 3 | import stage 4 | import displayio 5 | import busio 6 | import time 7 | import keypad 8 | import audioio 9 | import audiocore 10 | import supervisor 11 | import digitalio 12 | 13 | 14 | K_X = 0x01 15 | K_O = 0x02 16 | K_START = 0x04 17 | K_SELECT = 0x08 18 | K_DOWN = 0x10 19 | K_LEFT = 0x20 20 | K_RIGHT = 0x40 21 | K_UP = 0x80 22 | 23 | # re-initialize the display for correct rotation and RGB mode 24 | 25 | _TFT_INIT = ( 26 | b"\x01\x80\x96" # SWRESET and Delay 150ms 27 | b"\x11\x80\xff" # SLPOUT and Delay 28 | b"\xb1\x03\x01\x2C\x2D" # _FRMCTR1 29 | b"\xb2\x03\x01\x2C\x2D" # _FRMCTR2 30 | b"\xb3\x06\x01\x2C\x2D\x01\x2C\x2D" # _FRMCTR3 31 | b"\xb4\x01\x07" # _INVCTR line inversion 32 | b"\xc0\x03\xa2\x02\x84" # _PWCTR1 GVDD = 4.7V, 1.0uA 33 | b"\xc1\x01\xc5" # _PWCTR2 VGH=14.7V, VGL=-7.35V 34 | b"\xc2\x02\x0a\x00" # _PWCTR3 Opamp current small, Boost frequency 35 | b"\xc3\x02\x8a\x2a" 36 | b"\xc4\x02\x8a\xee" 37 | b"\xc5\x01\x0e" # _VMCTR1 VCOMH = 4V, VOML = -1.1V 38 | b"\x20\x00" # _INVOFF 39 | b"\x36\x01\xa0" # _MADCTL 40 | # 1 clk cycle nonoverlap, 2 cycle gate rise, 3 sycle osc equalie, 41 | # fix on VTL 42 | b"\x3a\x01\x05" # COLMOD - 16bit color 43 | b"\xe0\x10\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10" # _GMCTRP1 Gamma 44 | b"\xe1\x10\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10" # _GMCTRN1 45 | b"\x13\x80\x0a" # _NORON 46 | b"\x29\x80\x64" # _DISPON 47 | ) 48 | 49 | 50 | class _Buttons: 51 | def __init__(self): 52 | self.keys = keypad.ShiftRegisterKeys(clock=board.BUTTON_CLOCK, 53 | data=board.BUTTON_OUT, latch=board.BUTTON_LATCH, key_count=4, 54 | interval=0.05, value_when_pressed=True) 55 | self.last_state = 0 56 | self.event = keypad.Event(0, False) 57 | self.last_z_press = None 58 | self.joy_x = analogio.AnalogIn(board.JOYSTICK_X) 59 | self.joy_y = analogio.AnalogIn(board.JOYSTICK_Y) 60 | 61 | def get_pressed(self): 62 | buttons = self.last_state 63 | events = self.keys.events 64 | while events: 65 | if events.get_into(self.event): 66 | bit = 1 << self.event.key_number 67 | if self.event.pressed: 68 | buttons |= bit 69 | self.last_state |= bit 70 | else: 71 | self.last_state &= ~bit 72 | if buttons & K_START: 73 | now = time.monotonic() 74 | if self.last_z_press: 75 | if now - self.last_z_press > 2: 76 | supervisor.set_next_code_file(None) 77 | supervisor.reload() 78 | else: 79 | self.last_z_press = now 80 | else: 81 | self.last_z_press = None 82 | dead = 15000 83 | x = self.joy_x.value - 32767 84 | if x < -dead: 85 | buttons |= K_LEFT 86 | elif x > dead: 87 | buttons |= K_RIGHT 88 | y = self.joy_y.value - 32767 89 | if y < -dead: 90 | buttons |= K_UP 91 | elif y > dead: 92 | buttons |= K_DOWN 93 | return buttons 94 | 95 | 96 | class _Audio: 97 | last_audio = None 98 | 99 | def __init__(self, speaker_pin, mute_pin=None): 100 | self.muted = True 101 | self.buffer = bytearray(128) 102 | if mute_pin: 103 | self.mute_pin = digitalio.DigitalInOut(mute_pin) 104 | self.mute_pin.switch_to_output(value=not self.muted) 105 | else: 106 | self.mute_pin = None 107 | self.audio = audioio.AudioOut(speaker_pin) 108 | 109 | def play(self, audio_file, loop=False): 110 | if self.muted: 111 | return 112 | self.stop() 113 | wave = audiocore.WaveFile(audio_file, self.buffer) 114 | self.audio.play(wave, loop=loop) 115 | 116 | def stop(self): 117 | self.audio.stop() 118 | 119 | def mute(self, value=True): 120 | self.muted = value 121 | if self.mute_pin: 122 | self.mute_pin.value = not value 123 | 124 | 125 | displayio.release_displays() 126 | _tft_spi = busio.SPI(clock=board.TFT_SCK, MOSI=board.TFT_MOSI) 127 | _fourwire = displayio.FourWire(_tft_spi, command=board.TFT_DC, 128 | chip_select=board.TFT_CS, reset=board.TFT_RST) 129 | display = displayio.Display(_fourwire, _TFT_INIT, width=160, height=128, 130 | rotation=0, auto_refresh=False) 131 | # Work around broken backlight in CP 7.0 132 | _backlight = digitalio.DigitalInOut(board.TFT_LITE) 133 | _backlight.switch_to_output(value=1) 134 | del _TFT_INIT 135 | buttons = _Buttons() 136 | audio = _Audio(board.SPEAKER, board.SPEAKER_ENABLE) 137 | -------------------------------------------------------------------------------- /stage.py: -------------------------------------------------------------------------------- 1 | import time 2 | import array 3 | import struct 4 | try: 5 | import zlib 6 | except ImportError: 7 | pass 8 | 9 | import _stage 10 | 11 | 12 | FONT = (b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 13 | b'P\x01\xd4\x05\xf5\x17\xed\x1e\xd5\x15\xd0\x01P\x01\x00\x00' 14 | b'P\x01\xd0\x01\xd5\x15\xed\x1e\xf5\x17\xd4\x05P\x01\x00\x00' 15 | b'P\x01\xd0\x05\x95\x17\xfd\x1f\x95\x17\xd0\x05P\x01\x00\x00' 16 | b'P\x01\xd4\x01\xb5\x15\xfd\x1f\xb5\x15\xd4\x01P\x01\x00\x00' 17 | b'T\x05\xf9\x1b\xdd\x1d}\x1f\xd9\x19\xa9\x1aT\x05\x00\x00' 18 | b'T\x05\xf9\x1b]\x1d\xdd\x1dY\x19\xa9\x1aT\x05\x00\x00P\x01\xd0\x01' 19 | b'\xe5\x16\xfd\x1f\xe4\x06t\x07\x14\x05\x00\x00P\x01\xd5\x15' 20 | b']\x1d\x95\x15\xf4\x07\xe4\x06T\x05\x00\x00\x14\x05y\x1b' 21 | b'\xfd\x1f\xf9\x1b\xe4\x06\xd0\x01@\x00\x00\x00P\x01\xf4\x06' 22 | b'\xad\x1b\xed\x1b\xf9\x1a\xa4\x06P\x01\x00\x00@U\xd0\xff' 23 | b'\xf4\xaa\xbdV\xad\x01m\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00' 24 | b'm\x00m\x00m\x00m\x00m\x00\xbd\x01\xf9V\xe4\xff\x90\xaa@UUU\xff\xff' 25 | b'\xaa\xaaUU\x00\x00\x00\x00\x00\x00\x00\x00U\x01\xff\x06' 26 | b'\xea\x1b\x95o@n\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00m\x00m' 27 | b'\x00m\x00m\x00m\x00m\x00m@o\xd5k\xff\x1a\xaa\x06U\x01' 28 | b'\x00\x00\x00\x00\x00\x00\x00\x00UU\xff\xff\xaa\xaaUU' 29 | b'\x00\x00\x00\x00\x00UE\xfe\xd9\xef\xdd\x9f\xad\x9f\xad\x9a' 30 | b'\x00\x00\x00\x00\x00\x00U\x15\xf7o\xa7jW\x15v\x00\xadu\xed\xda' 31 | b'\xddv\x99\xe6E\x9a\x00U\x00\x00\x00\x00m\x00W\x00n\x00\x15\x00' 32 | b'\x1b\x00\x05\x00\x00\x00\x00\x00\xaa\x00\xaa\x00\xaa\x00\xaa\x00' 33 | b'\x00\xaa\x00\xaa\x00\xaa\x00\xaaP\x05\x94\x16\xa4\x1b\xe4\x1b' 34 | b'\xe4\x1a\xa4\x1aT\x15\x00\x00P\x00\xd0\x01\xd0\x07\xd4\x19' 35 | b'\xf9\x1d\xbd\x05T\x00\x00\x00T\x05\xf5\x17\xdd\x1d\xdd\x1d' 36 | b'\xf5\x17\xe4\x06T\x05\x00\x00\x14\x05e\x16y\x1b\xd4\x05y\x1be\x16' 37 | b'\x14\x05\x00\x00T\x15\xf5\x1f\x9d\x19\xf5\x1d\xd4\x1d\xd0\x1d' 38 | b'P\x15\x00\x00\x00\x00P\x01\xe4\x06\xf4\x07\xe4\x06P\x01' 39 | b'\x00\x00\x00\x00U\x15\xdd\x1d\xdd\x1d\x99\x19U\x15\xdd\x1d' 40 | b'U\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\x15\xdd\x1d' 41 | b'U\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 42 | b'\x00\x00\x00\x00P\x01\xd0\x01\xd0\x01\x90\x01P\x01\xd0\x01' 43 | b'P\x01\x00\x00T\x05t\x07d\x06T\x05\x00\x00\x00\x00\x00\x00\x00\x00' 44 | b'\x14\x05u\x17\xed\x1et\x07\xed\x1eu\x17\x14\x05\x00\x00' 45 | b'T\x15\xf5\x1b\x99\x05\xf5\x17\x94\x19\xf9\x17U\x05\x00\x00' 46 | b'\x15\x14\x1d\x1dU\x07\xd0\x01t\x15\x1d\x1d\x05\x15\x00\x00' 47 | b'T\x01\xe4\x05u\x07\xdd\x01]\x17\xe5\x1dT\x14\x00\x00P\x01\xd0\x01' 48 | b'\x90\x01P\x01\x00\x00\x00\x00\x00\x00\x00\x00@\x05P\x06' 49 | b'\x90\x01\xd0\x01\x90\x01P\x06@\x05\x00\x00T\x00d\x01' 50 | b'\x90\x01\xd0\x01\x90\x01d\x01T\x00\x00\x00\x00\x00\x14\x05' 51 | b't\x07\xd0\x01t\x07\x14\x05\x00\x00\x00\x00P\x01\x90\x01' 52 | b'\xd5\x15\xf9\x1b\xd5\x15\x90\x01P\x01\x00\x00\x00\x00\x00\x00' 53 | b'\x00\x00P\x01\xd0\x01\x90\x01P\x01\x00\x00\x00\x00\x00\x00' 54 | b'U\x15\xf9\x1bU\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 55 | b'\x00\x00\x00\x00P\x01\xd0\x01P\x01\x00\x00\x00\x04\x00\x1d' 56 | b'@\x07\xd0\x01t\x00\x1d\x00\x04\x00\x00\x00T\x05\xe5\x16' 57 | b'Y\x1a\xdd\x1di\x19\xe5\x16T\x05\x00\x00@\x01\xd0\x01' 58 | b'\xe4\x01\xd0\x01\xd0\x01\xe4\x06T\x05\x00\x00T\x05\xf9\x17' 59 | b'U\x1d\xf4\x17Y\x05\xfd\x1fU\x15\x00\x00T\x05\xf5\x17]\x1d\x94\x07' 60 | b']\x1d\xf5\x17T\x05\x00\x00P\x00t\x00]\x05]\x17\xfd\x1fU\x17' 61 | b'@\x05\x00\x00U\x15\xfd\x1b]\x05\xfd\x1bU\x1d\xf9\x1bU\x05\x00\x00' 62 | b'T\x15\xf5\x1b]\x05\xfd\x1b]\x1d\xf9\x1bT\x05\x00\x00U\x15\xfd\x1f' 63 | b'U\x19\xd0\x06d\x01t\x00T\x00\x00\x00T\x05\xf5\x17]\x1d\xf5\x17' 64 | b']\x1d\xf5\x17T\x05\x00\x00T\x05\xf9\x1b]\x1d\xf9\x1fT\x1d\xf9\x17' 65 | b'U\x05\x00\x00\x00\x00P\x01\xd0\x01P\x01\xd0\x01P\x01' 66 | b'\x00\x00\x00\x00\x00\x00P\x01\xd0\x01P\x01\xd0\x01\x90\x01' 67 | b'P\x01\x00\x00\x00\x05@\x07\xd0\x01t\x00\xd0\x01@\x07' 68 | b'\x00\x05\x00\x00\x00\x00U\x15\xf9\x1bT\x05\xf9\x1bU\x15' 69 | b'\x00\x00\x00\x00\x14\x00t\x00\xd0\x01@\x07\xd0\x01t\x00' 70 | b'\x14\x00\x00\x00T\x05\xe5\x17]\x1d\xd5\x16P\x05\xd0\x01' 71 | b'P\x01\x00\x00T\x05\xb5\x17\xdd\x1d\x9d\x1bY\x15\xf5\x06' 72 | b'T\x05\x00\x00P\x00\xe4\x01Y\x07]\x1d\xed\x1e]\x1d\x15\x15\x00\x00' 73 | b'U\x01\xfd\x05]\x07\xed\x16]\x1d\xfd\x17U\x05\x00\x00T\x05\xf5\x06' 74 | b']\x01\x1d\x14]\x1d\xf5\x17T\x05\x00\x00U\x01\xbd\x05]\x17\x1d\x1d' 75 | b']\x1d\xfd\x16U\x05\x00\x00U\x05\xfd\x06]\x01\xfd\x01]\x15\xfd\x1b' 76 | b'U\x15\x00\x00U\x15\xfd\x1b]\x15]\x00\xbd\x01]\x01\x15\x00\x00\x00' 77 | b'T\x15\xf5\x1b]\x05\xdd\x1fY\x1d\xf5\x1bT\x15\x00\x00' 78 | b'\x15\x15\x1d\x1d]\x1d\xfd\x1f]\x1d\x1d\x1d\x15\x15\x00\x00' 79 | b'T\x05\xe4\x06\xd0\x01\xd0\x01\xd0\x01\xe4\x06T\x05\x00\x00' 80 | b'\x00\x15\x00\x1d\x00\x1d\x05\x1d]\x19\xf5\x17T\x05\x00\x00' 81 | b'\x15\x14\x1d\x1d]\x07\xfd\x01]\x07\x1d\x1d\x15\x14\x00\x00' 82 | b'\x15\x00\x1d\x00\x1d\x00\x1d\x00]\x15\xfd\x1fU\x15\x00\x00' 83 | b'\x05\x14\x1d\x1dm\x1e\xdd\x1d]\x1d\x1d\x1d\x15\x15\x00\x00' 84 | b'\x05\x15\x1d\x1dm\x1d\xdd\x1d]\x1e\x1d\x1d\x15\x14\x00\x00' 85 | b'T\x01\xb5\x05]\x17\x1d\x1d]\x1d\xe5\x17T\x05\x00\x00U\x05\xfd\x16' 86 | b']\x19]\x1d\xfd\x17]\x05\x15\x00\x00\x00T\x01\xb5\x05]\x17\x1d\x1d' 87 | b']\x1e\xe5\x07T\x1d\x00\x15U\x05\xfd\x16]\x19]\x1d\xfd\x07]\x1d' 88 | b'\x15\x15\x00\x00T\x05\xf5\x07]\x01\xe5\x06T\x1d\xf9\x17' 89 | b'U\x05\x00\x00U\x15\xf9\x1b\xd5\x15\xd0\x01\xd0\x01\xd0\x01' 90 | b'P\x01\x00\x00\x15\x15\x1d\x1d\x1d\x1d\x19\x1du\x19\xd4\x17' 91 | b'P\x05\x00\x00\x05\x14\x1d\x1d\x19\x19u\x17d\x06\xd0\x01' 92 | b'@\x00\x00\x00\x15\x15\x1d\x1d\x1d\x1d]\x1d\xd9\x19u\x17' 93 | b'\x14\x05\x00\x00\x05\x14\x1d\x1dt\x07\xd0\x01t\x07\x1d\x1d' 94 | b'\x05\x14\x00\x00\x15\x15\x1d\x1d\x19\x19u\x17\x94\x05\xd0\x01' 95 | b'P\x01\x00\x00U\x15\xf9\x1bU\x07\xd0\x01t\x15\xf9\x1bU\x15\x00\x00' 96 | b'T\x05\xf4\x06t\x01t\x00t\x01\xf4\x06T\x05\x00\x00\x05\x00\x1d\x00' 97 | b't\x00\xd0\x01@\x07\x00\x1d\x00\x14\x00\x00T\x05\xe4\x07P\x07@\x07' 98 | b'P\x07\xe4\x07T\x05\x00\x00@\x00\xd0\x01t\x07\x19\x19' 99 | b'\x04\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 100 | b'U\x15\xf9\x1bU\x15\x00\x00P\x00\xb4\x01\xd4\x06P\x07@\x01\x00\x00' 101 | b'\x00\x00\x00\x00\x00\x00T\x15\xe5\x1f]\x1d]\x1d\xf5\x1f' 102 | b'T\x15\x00\x00\x15\x00]\x05\xfd\x16]\x1d]\x1d\xfd\x17U\x05\x00\x00' 103 | b'\x00\x00T\x05\xe5\x07]\x05]\x1d\xf5\x16T\x05\x00\x00\x00\x15T\x1d' 104 | b'\xe5\x1f]\x1d]\x1d\xf5\x1fT\x15\x00\x00\x00\x00T\x05' 105 | b'\xf5\x17\xad\x1e]\x15\xf5\x07T\x05\x00\x00@\x15P\x1e' 106 | b'\xd4\x15\xf4\x07\xd4\x05\xd0\x01\xd0\x01P\x01\x00\x00T\x15' 107 | b'\xe5\x1f]\x1d\xf5\x1fT\x1d\xf9\x16U\x05\x15\x00]\x05\xfd\x16]\x1d' 108 | b'\x1d\x1d\x1d\x1d\x15\x15\x00\x00P\x01\xd0\x01P\x01\xd0\x01' 109 | b'\xd0\x01\xd0\x01P\x01\x00\x00@\x05@\x07@\x05@\x07E\x07]\x07' 110 | b'\xe5\x05T\x01\x15\x00\x1d\x14]\x1d\xfd\x06]\x19\x1d\x1d' 111 | b'\x15\x14\x00\x00T\x00t\x00t\x00t\x00d\x05\xd4\x07P\x05\x00\x00' 112 | b'\x00\x00U\x05\xfd\x17\xdd\x19\xdd\x1d]\x1d\x15\x15\x00\x00' 113 | b'\x00\x00U\x05\xfd\x17]\x19\x1d\x1d\x1d\x1d\x15\x15\x00\x00' 114 | b'\x00\x00T\x05\xe5\x17]\x1d]\x1d\xf5\x17T\x05\x00\x00\x00\x00U\x05' 115 | b'\xfd\x17]\x1d]\x1d\xfd\x17]\x05\x15\x00\x00\x00T\x15\xf5\x1f]\x1d' 116 | b']\x1d\xf5\x1fT\x1d\x00\x15\x00\x00U\x05\xdd\x16}\x1d]\x04\x1d\x00' 117 | b'\x15\x00\x00\x00\x00\x00T\x15\xe5\x1f\xad\x05\x94\x1e\xfd\x16' 118 | b'U\x05\x00\x00T\x00u\x05\xfd\x07t\x01t\x01\xd4\x07P\x05\x00\x00' 119 | b'\x00\x00\x15\x15\x1d\x1d\x1d\x1d]\x1d\xe5\x1fT\x15\x00\x00' 120 | b'\x00\x00\x05\x14\x1d\x1d\x19\x19u\x17\xd4\x05P\x01\x00\x00' 121 | b'\x00\x00\x15\x15]\x1d\xdd\x1d\xd9\x19u\x17T\x05\x00\x00' 122 | b'\x00\x00\x15\x15m\x1e\xd4\x05\xd4\x05m\x1e\x15\x15\x00\x00' 123 | b'\x00\x00\x15\x15\x1d\x1d]\x1d\xe5\x1fT\x1d\xfd\x17U\x05' 124 | b'\x00\x00U\x15\xfd\x1f\xa4\x15\x95\x06\xfd\x1fU\x15\x00\x00' 125 | b'@\x05\x90\x07\xd0\x01t\x01\xd0\x01\x90\x07@\x05\x00\x00' 126 | b'P\x01\x90\x01\xd0\x01\xd0\x01\xd0\x01\x90\x01P\x01\x00\x00' 127 | b'T\x00\xb4\x01\xd0\x01P\x07\xd0\x01\xb4\x01T\x00\x00\x00' 128 | b'\x00\x00T\x00u\x15\xd9\x19U\x17@\x05\x00\x00\x00\x00U\x15\xfd\x1f' 129 | b'\xed\x1e\xbd\x1f\xed\x1e\xfd\x1fU\x15\x00\x00') 130 | 131 | PALETTE = (b'\xf8\x1f\x00\x00\xcey\xff\xff\xf8\x1f\x00\x19\xfc\xe0\xfd\xe0' 132 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') 133 | 134 | 135 | def color565(r, g, b): 136 | """Convert 24-bit RGB color to 16-bit.""" 137 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 138 | 139 | 140 | def collide(ax0, ay0, ax1, ay1, bx0, by0, bx1=None, by1=None): 141 | """Return True if the two rectangles intersect.""" 142 | if bx1 is None: 143 | bx1 = bx0 144 | if by1 is None: 145 | by1 = by0 146 | return not (ax1 < bx0 or ay1 < by0 or ax0 > bx1 or ay0 > by1) 147 | 148 | 149 | class BMP16: 150 | """Read 16-color BMP files.""" 151 | 152 | def __init__(self, filename): 153 | self.filename = filename 154 | self.colors = 0 155 | 156 | def read_header(self): 157 | if self.colors: 158 | return 159 | with open(self.filename, 'rb') as f: 160 | f.seek(10) 161 | self.data = int.from_bytes(f.read(4), 'little') 162 | f.seek(18) 163 | self.width = int.from_bytes(f.read(4), 'little') 164 | self.height = int.from_bytes(f.read(4), 'little') 165 | f.seek(46) 166 | self.colors = int.from_bytes(f.read(4), 'little') 167 | 168 | def read_palette(self, palette=None): 169 | if palette is None: 170 | palette = array.array('H', (0 for i in range(16))) 171 | with open(self.filename, 'rb') as f: 172 | f.seek(self.data - self.colors * 4) 173 | for color in range(self.colors): 174 | buffer = f.read(4) 175 | c = color565(buffer[2], buffer[1], buffer[0]) 176 | palette[color] = ((c << 8) | (c >> 8)) & 0xffff 177 | return palette 178 | 179 | def read_data(self, buffer=None): 180 | line_size = (self.width + 1 ) >> 1 181 | if buffer is None: 182 | buffer = bytearray(line_size * self.height) 183 | 184 | with open(self.filename, 'rb') as f: 185 | f.seek(self.data) 186 | index = (self.height - 1) * line_size 187 | for line in range(self.height): 188 | chunk = f.read(line_size) 189 | buffer[index:index + line_size] = chunk 190 | index -= line_size 191 | return buffer 192 | 193 | 194 | class PNG16: 195 | """Read 16-color PNG files.""" 196 | 197 | def __init__(self, filename): 198 | self.filename = filename 199 | 200 | def read_header(self): 201 | with open(self.filename, 'rb') as f: 202 | magic = f.read(8) 203 | assert magic == b'\x89PNG\r\n\x1a\n' 204 | ( 205 | size, chunk, self.width, self.height, self.depth, self.mode, 206 | self.compression, self.filters, self.interlaced, crc 207 | ) = struct.unpack(">I4sIIBBBBB4s", f.read(25)) 208 | assert size == 13 # header length 209 | assert chunk == b'IHDR' 210 | if self.depth not in {4, 8} or self.mode != 3 or self.interlaced != 0: 211 | raise ValueError("16-color non-interaced PNG expected") 212 | 213 | def read_palette(self, palette=None): 214 | if palette is None: 215 | palette = array.array('H', (0 for i in range(16))) 216 | with open(self.filename, 'rb') as f: 217 | f.seek(8 + 25) 218 | while True: 219 | size, chunk = struct.unpack(">I4s", f.read(8)) 220 | if chunk == b'PLTE': 221 | break 222 | f.seek(size + 4, 1) 223 | colors = size // 3 224 | if colors > 16: 225 | raise ValueError("16-color PNG expected") 226 | for color in range(colors): 227 | c = color565(*struct.unpack("BBB", f.read(3))) 228 | palette[color] = ((c << 8) | (c >> 8)) & 0xffff 229 | return palette 230 | 231 | def read_data(self, buffer=None): 232 | data = bytearray() 233 | with open(self.filename, 'rb') as f: 234 | f.seek(8 + 25) 235 | while True: 236 | size, chunk = struct.unpack(">I4s", f.read(8)) 237 | if chunk == b'IEND': 238 | break 239 | elif chunk != b'IDAT': 240 | f.seek(size + 4, 1) 241 | continue 242 | data.extend(f.read(size)) 243 | f.seek(4, 1) # skip CRC 244 | data = zlib.decompress(data) 245 | line_size = (self.width + 1) >> 1 246 | if buffer is None: 247 | buffer = bytearray(line_size * self.height) 248 | if self.depth == 4: 249 | for line in range(self.height): 250 | a = line * line_size 251 | b = line * (line_size + 1) 252 | assert data[b] == 0 # no filter 253 | buffer[a:a + line_size] = data[b + 1:b + 1 + line_size] 254 | elif self.depth == 8: 255 | for line in range(self.height): 256 | a = line * line_size 257 | b = line * (self.width + 1) 258 | assert data[b] == 0 # no filter 259 | b += 1 260 | for col in range(line_size): 261 | buffer[a] = (data[b] & 0x0f) << 4 262 | b += 1 263 | try: 264 | buffer[a] |= data[b] & 0x0f 265 | except IndexError: 266 | pass 267 | b += 1 268 | a += 1 269 | return buffer 270 | 271 | 272 | class Bank: 273 | """ 274 | Store graphics for the tiles and sprites. 275 | 276 | A single bank stores exactly 16 tiles, each 16x16 pixels in 16 possible 277 | colors, and a 16-color palette. We just like the number 16. 278 | 279 | """ 280 | 281 | def __init__(self, buffer=None, palette=None): 282 | self.buffer = buffer 283 | self.palette = palette 284 | 285 | @classmethod 286 | def from_bmp16(cls, filename): 287 | """Read the bank from a BMP file.""" 288 | return cls.from_image(filename) 289 | 290 | 291 | @classmethod 292 | def from_image(cls, filename): 293 | """Read the bank from an image file.""" 294 | if filename.lower().endswith(".bmp"): 295 | image = BMP16(filename) 296 | elif filename.lower().endswith(".png"): 297 | image = PNG16(filename) 298 | else: 299 | raise ValueError("Unsupported format") 300 | image.read_header() 301 | if image.width != 16 or image.height != 256: 302 | raise ValueError("Image size not 16x256") 303 | palette = image.read_palette() 304 | buffer = image.read_data() 305 | return cls(buffer, palette) 306 | 307 | 308 | class Grid: 309 | """ 310 | A grid is a layer of tiles that can be displayed on the screen. Each square 311 | can contain any of the 16 tiles from the associated bank. 312 | """ 313 | 314 | def __init__(self, bank, width=8, height=8, palette=None, buffer=None): 315 | self.x = 0 316 | self.y = 0 317 | self.z = 0 318 | self.stride = (width + 1) & 0xfe 319 | self.width = width 320 | self.height = height 321 | self.bank = bank 322 | self.palette = palette or bank.palette 323 | self.buffer = buffer or bytearray((self.stride * height)>>1) 324 | self.layer = _stage.Layer(self.stride, self.height, self.bank.buffer, 325 | self.palette, self.buffer) 326 | 327 | def tile(self, x, y, tile=None): 328 | """Get or set what tile is displayed in the given place.""" 329 | 330 | if not 0 <= x < self.width or not 0 <= y < self.height: 331 | return 0 332 | index = (y * self.stride + x) >> 1 333 | b = self.buffer[index] 334 | if tile is None: 335 | return b & 0x0f if x & 0x01 else b >> 4 336 | if x & 0x01: 337 | b = b & 0xf0 | tile 338 | else: 339 | b = b & 0x0f | (tile << 4) 340 | self.buffer[index] = b 341 | 342 | def move(self, x, y, z=None): 343 | """Shift the whole layer respective to the screen.""" 344 | 345 | self.x = x 346 | self.y = y 347 | if z is not None: 348 | self.z = z 349 | self.layer.move(int(x), int(y)) 350 | 351 | 352 | class WallGrid(Grid): 353 | """ 354 | A special grid, shifted from its parents by half a tile, useful for making 355 | nice-looking corners of walls and similar structures. 356 | """ 357 | 358 | def __init__(self, grid, walls, bank, palette=None): 359 | super().__init__(bank, grid.width + 1, grid.height + 1, palette) 360 | self.grid = grid 361 | self.walls = walls 362 | self.update() 363 | self.move(self.x - 8, self.y - 8) 364 | 365 | def update(self): 366 | for y in range(self.height): 367 | for x in range(self.width): 368 | t = 0 369 | bit = 1 370 | for dy in (-1, 0): 371 | for dx in (-1, 0): 372 | if self.grid.tile(x + dx, y + dy) in self.walls: 373 | t |= bit 374 | bit <<= 1 375 | self.tile(x, y, t) 376 | 377 | 378 | class Sprite: 379 | """ 380 | A sprite is a layer containing just a single tile from the associated bank, 381 | that can be positioned anywhere on the screen. 382 | """ 383 | 384 | def __init__(self, bank, frame, x, y, z=0, rotation=0, palette=None): 385 | self.bank = bank 386 | self.palette = palette or bank.palette 387 | self.frame = frame 388 | self.rotation = rotation 389 | self.x = x 390 | self.y = y 391 | self.z = z 392 | self.layer = _stage.Layer(1, 1, self.bank.buffer, self.palette) 393 | self.layer.move(x, y) 394 | self.layer.frame(frame, rotation) 395 | self.px = x 396 | self.py = y 397 | 398 | def move(self, x, y, z=None): 399 | """Move the sprite to the given place.""" 400 | 401 | self.x = x 402 | self.y = y 403 | if z is not None: 404 | self.z = z 405 | self.layer.move(int(x), int(y)) 406 | 407 | def set_frame(self, frame=None, rotation=None): 408 | """ 409 | Set the current graphic and rotation of the sprite. 410 | 411 | The possible values for rotation are: 0 - none, 1 - 90 degrees 412 | clockwise, 2 - 180 degrees, 3 - 90 degrees counter-clockwise, 4 - 413 | mirrored, 5 - 90 degrees clockwise and mirrored, 6 - 180 degrees and 414 | mirrored, 7 - 90 degrees counter-clockwise and mirrored. """ 415 | 416 | if frame is not None: 417 | self.frame = frame 418 | if rotation is not None: 419 | self.rotation = rotation 420 | self.layer.frame(self.frame, self.rotation) 421 | 422 | def update(self): 423 | pass 424 | 425 | 426 | class Text: 427 | """Text layer. For displaying text.""" 428 | 429 | def __init__(self, width, height, font=None, palette=None, buffer=None): 430 | self.width = width 431 | self.height = height 432 | self.font = font or FONT 433 | self.palette = palette or PALETTE 434 | self.buffer = buffer or bytearray(width * height) 435 | self.layer = _stage.Text(width, height, self.font, 436 | self.palette, self.buffer) 437 | self.column = 0 438 | self.row = 0 439 | self.x = 0 440 | self.y = 0 441 | self.z = 0 442 | 443 | def char(self, x, y, c=None, hightlight=False): 444 | """Get or set the character at the given location.""" 445 | 446 | if not 0 <= x < self.width or not 0 <= y < self.height: 447 | return 448 | if c is None: 449 | return chr(self.buffer[y * self.width + x]) 450 | c = ord(c) 451 | if hightlight: 452 | c |= 0x80 453 | self.buffer[y * self.width + x] = c 454 | 455 | def move(self, x, y, z=None): 456 | """Shift the whole layer respective to the screen.""" 457 | 458 | self.x = x 459 | self.y = y 460 | if z is not None: 461 | self.z = z 462 | self.layer.move(int(x), int(y)) 463 | 464 | def cursor(self, x=None, y=None): 465 | """Move the text cursor to the specified row and column.""" 466 | 467 | if y is not None: 468 | self.row = min(max(0, y), self.width - 1) 469 | if x is not None: 470 | self.column = min(max(0, x), self.height - 1) 471 | 472 | def text(self, text, hightlight=False): 473 | """ 474 | Display text starting at the current cursor location. 475 | Return the dimensions of the rendered text. 476 | """ 477 | 478 | longest = 0 479 | tallest = 0 480 | for c in text: 481 | if c != '\n': 482 | self.char(self.column, self.row, c, hightlight) 483 | self.column += 1 484 | if self.column >= self.width or c == '\n': 485 | longest = max(longest, self.column) 486 | self.column = 0 487 | self.row += 1 488 | if self.row >= self.height: 489 | tallest = max(tallest, self.row) 490 | self.row = 0 491 | longest = max(longest, self.column) 492 | tallest = max(tallest, self.row) + (1 if self.column > 0 else 0) 493 | return longest * 8, tallest * 8 494 | 495 | def clear(self): 496 | """Clear all text from the layer.""" 497 | 498 | for i in range(self.width * self.height): 499 | self.buffer[i] = 0 500 | 501 | 502 | class Stage: 503 | """ 504 | Represents what is being displayed on the screen. 505 | 506 | The ``display`` parameter is displayio.Display representing an initialized 507 | display connected to the device. 508 | 509 | The ``fps`` specifies the maximum frame rate to be enforced. 510 | 511 | The ``scale`` specifies an optional scaling up of the display, to use 512 | 2x2 or 3x3, etc. pixels. If not specified, it is inferred from the display 513 | size (displays wider than 256 pixels will have scale=2, for example). 514 | """ 515 | buffer = bytearray(512) 516 | 517 | def __init__(self, display, fps=6, scale=None): 518 | if scale is None: 519 | self.scale = max(1, display.width // 128) 520 | else: 521 | self.scale = scale 522 | self.layers = [] 523 | self.display = display 524 | display.root_group = None 525 | self.width = display.width // self.scale 526 | self.height = display.height // self.scale 527 | self.last_tick = time.monotonic() 528 | self.tick_delay = 1 / fps 529 | self.vx = 0 530 | self.vy = 0 531 | 532 | def tick(self): 533 | """Wait for the start of the next frame.""" 534 | 535 | self.last_tick += self.tick_delay 536 | wait = max(0, self.last_tick - time.monotonic()) 537 | if wait: 538 | time.sleep(wait) 539 | else: 540 | self.last_tick = time.monotonic() 541 | 542 | def render_block(self, x0=None, y0=None, x1=None, y1=None): 543 | """Update a rectangle of the screen.""" 544 | 545 | if x0 is None: 546 | x0 = self.vx 547 | if y0 is None: 548 | y0 = self.vy 549 | if x1 is None: 550 | x1 = self.width + self.vx 551 | if y1 is None: 552 | y1 = self.height + self.vy 553 | x0 = min(max(0, x0 - self.vx), self.width - 1) 554 | y0 = min(max(0, y0 - self.vy), self.height - 1) 555 | x1 = min(max(1, x1 - self.vx), self.width) 556 | y1 = min(max(1, y1 - self.vy), self.height) 557 | if x0 >= x1 or y0 >= y1: 558 | return 559 | layers = [l.layer for l in self.layers] 560 | _stage.render(x0, y0, x1, y1, layers, self.buffer, 561 | self.display, self.scale, self.vx, self.vy) 562 | 563 | def render_sprites(self, sprites): 564 | """Update the spots taken by all the sprites in the list.""" 565 | 566 | layers = [l.layer for l in self.layers] 567 | for sprite in sprites: 568 | x = int(sprite.x) - self.vx 569 | y = int(sprite.y) - self.vy 570 | x0 = max(0, min(self.width - 1, min(sprite.px, x))) 571 | y0 = max(0, min(self.height - 1, min(sprite.py, y))) 572 | x1 = max(1, min(self.width, max(sprite.px, x) + 16)) 573 | y1 = max(1, min(self.height, max(sprite.py, y) + 16)) 574 | sprite.px = x 575 | sprite.py = y 576 | if x0 >= x1 or y0 >= y1: 577 | continue 578 | _stage.render(x0, y0, x1, y1, layers, self.buffer, 579 | self.display, self.scale, self.vx, self.vy) 580 | -------------------------------------------------------------------------------- /ugame10/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /ugame10/ugame.py: -------------------------------------------------------------------------------- 1 | import board 2 | import digitalio 3 | import analogio 4 | import keypad 5 | import stage 6 | import audioio 7 | import audiocore 8 | 9 | 10 | K_X = 0x01 11 | K_DOWN = 0x02 12 | K_LEFT = 0x04 13 | K_RIGHT = 0x08 14 | K_UP = 0x10 15 | K_O = 0x20 16 | K_START = 0x00 17 | K_SELECT = 0x00 18 | 19 | 20 | class _Buttons: 21 | def __init__(self): 22 | self.keys = keypad.Keys((board.X, board.DOWN, 23 | board.LEFT, board.RIGHT, board.UP, 24 | board.O), value_when_pressed=False, 25 | interval=0.05) 26 | self.last_state = 0 27 | self.event = keypad.Event(0, False) 28 | self.last_z_press = None 29 | 30 | def get_pressed(self): 31 | buttons = self.last_state 32 | events = self.keys.events 33 | while events: 34 | if events.get_into(self.event): 35 | bit = 1 << self.event.key_number 36 | if self.event.pressed: 37 | buttons |= bit 38 | self.last_state |= bit 39 | else: 40 | self.last_state &= ~bit 41 | return buttons 42 | 43 | 44 | class _Audio: 45 | last_audio = None 46 | 47 | def __init__(self, speaker_pin, mute_pin): 48 | self.muted = True 49 | self.buffer = bytearray(256) 50 | self.audio = audioio.AudioOut(speaker_pin) 51 | self.mute_pin = digitalio.DigitalInOut(mute_pin) 52 | self.mute_pin.switch_to_output(value=False) 53 | 54 | def play(self, audio_file, loop=False): 55 | if self.muted: 56 | return 57 | self.stop() 58 | wave = audiocore.WaveFile(audio_file, self.buffer) 59 | self.audio.play(wave, loop=loop) 60 | 61 | def stop(self): 62 | self.audio.stop() 63 | 64 | def mute(self, value=True): 65 | self.muted = value 66 | self.mute_pin.value = not value 67 | 68 | 69 | display = board.DISPLAY 70 | audio = _Audio(board.SPEAKER, board.MUTE) 71 | buttons = _Buttons() 72 | battery = analogio.AnalogIn(board.BATTERY) 73 | -------------------------------------------------------------------------------- /ugame22/pew.py: -------------------------------------------------------------------------------- 1 | ../pew.py -------------------------------------------------------------------------------- /ugame22/stage.py: -------------------------------------------------------------------------------- 1 | ../stage.py -------------------------------------------------------------------------------- /ugame22/ugame.py: -------------------------------------------------------------------------------- 1 | import audiobusio 2 | import audiocore 3 | import board 4 | import busio 5 | import digitalio 6 | import displayio 7 | import keypad 8 | import os 9 | import supervisor 10 | import time 11 | 12 | 13 | K_X = 0x01 14 | K_DOWN = 0x02 15 | K_LEFT = 0x04 16 | K_RIGHT = 0x08 17 | K_UP = 0x10 18 | K_O = 0x20 19 | K_START = 0x40 20 | K_Z = 0x40 21 | K_SELECT = 0x80 22 | 23 | 24 | class _Buttons: 25 | def __init__(self): 26 | self.keys = keypad.Keys((board.BUTTON_X, board.BUTTON_DOWN, 27 | board.BUTTON_LEFT, board.BUTTON_RIGHT, board.BUTTON_UP, 28 | board.BUTTON_O, board.BUTTON_Z), value_when_pressed=False, 29 | interval=0.05) 30 | self.last_state = 0 31 | self.event = keypad.Event(0, False) 32 | self.last_z_press = None 33 | 34 | def get_pressed(self): 35 | buttons = self.last_state 36 | events = self.keys.events 37 | while events: 38 | if events.get_into(self.event): 39 | bit = 1 << self.event.key_number 40 | if self.event.pressed: 41 | buttons |= bit 42 | self.last_state |= bit 43 | else: 44 | self.last_state &= ~bit 45 | if buttons & K_Z: 46 | now = time.monotonic() 47 | if self.last_z_press: 48 | if now - self.last_z_press > 2: 49 | os.chdir('/') 50 | supervisor.set_next_code_file(None) 51 | supervisor.reload() 52 | else: 53 | self.last_z_press = now 54 | else: 55 | self.last_z_press = None 56 | return buttons 57 | 58 | 59 | class _Audio: 60 | last_audio = None 61 | 62 | def __init__(self): 63 | self.muted = True 64 | self.buffer = bytearray(128) 65 | self.audio = audiobusio.I2SOut( 66 | board.I2S_BCLK, 67 | board.I2S_LRCLK, 68 | board.I2S_DIN, 69 | ) 70 | self.gain_pin= digitalio.DigitalInOut(board.GAIN) 71 | self.gain_pin.pull=digitalio.Pull.UP # 2dB gain 72 | #self.gain_pin.switch_to_output(value=True) # 6dB gain 73 | 74 | def play(self, audio_file, loop=False): 75 | if self.muted: 76 | return 77 | self.stop() 78 | wave = audiocore.WaveFile(audio_file, self.buffer) 79 | self.audio.play(wave, loop=loop) 80 | 81 | def stop(self): 82 | self.audio.stop() 83 | 84 | def mute(self, value=True): 85 | self.muted = value 86 | 87 | 88 | display = board.DISPLAY 89 | buttons = _Buttons() 90 | audio = _Audio() 91 | --------------------------------------------------------------------------------