aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/frozen/circuitpython-stage
diff options
context:
space:
mode:
Diffstat (limited to 'circuitpython/frozen/circuitpython-stage')
-rw-r--r--circuitpython/frozen/circuitpython-stage/LICENSE21
-rw-r--r--circuitpython/frozen/circuitpython-stage/README.rst44
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/.buildinfo4
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/.doctrees/README.doctreebin0 -> 8048 bytes
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.picklebin0 -> 1971693 bytes
l---------circuitpython/frozen/circuitpython-stage/docs/README.rst1
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/_stage.py0
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/audioio.py3
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/board.py10
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/busio.py12
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/conf.py145
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/digitalio.py6
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/gamepad.py3
-rw-r--r--circuitpython/frozen/circuitpython-stage/docs/ustruct.py0
-rw-r--r--circuitpython/frozen/circuitpython-stage/examples/ball/ball.bmpbin0 -> 2234 bytes
-rw-r--r--circuitpython/frozen/circuitpython-stage/examples/ball/ball.py38
-rw-r--r--circuitpython/frozen/circuitpython-stage/examples/ball/main.py38
-rw-r--r--circuitpython/frozen/circuitpython-stage/examples/rpg/ground.bmpbin0 -> 2234 bytes
-rw-r--r--circuitpython/frozen/circuitpython-stage/examples/rpg/main.py68
-rw-r--r--circuitpython/frozen/circuitpython-stage/examples/rpg/tiles.bmpbin0 -> 2218 bytes
l---------circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/feather_m4_minitft_featherwing/ugame.py82
-rw-r--r--circuitpython/frozen/circuitpython-stage/font/font.bmpbin0 -> 4250 bytes
-rw-r--r--circuitpython/frozen/circuitpython-stage/font/font2.bmpbin0 -> 4250 bytes
-rw-r--r--circuitpython/frozen/circuitpython-stage/font/genfont.py89
-rw-r--r--circuitpython/frozen/circuitpython-stage/font/genfont2.py89
l---------circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/itsybitsy_m4_express/ugame.py79
l---------circuitpython/frozen/circuitpython-stage/meowbit/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/meowbit/ugame.py78
-rw-r--r--circuitpython/frozen/circuitpython-stage/pewpew_m4/pew.py203
l---------circuitpython/frozen/circuitpython-stage/pewpew_m4/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/pewpew_m4/ugame.py79
l---------circuitpython/frozen/circuitpython-stage/picosystem/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/picosystem/ugame.py86
l---------circuitpython/frozen/circuitpython-stage/pybadge/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/pybadge/ugame.py122
l---------circuitpython/frozen/circuitpython-stage/pygamer/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/pygamer/ugame.py136
-rw-r--r--circuitpython/frozen/circuitpython-stage/stage.py630
l---------circuitpython/frozen/circuitpython-stage/ugame10/stage.py1
-rw-r--r--circuitpython/frozen/circuitpython-stage/ugame10/ugame.py33
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
new file mode 100644
index 0000000..6b058e6
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/README.doctree
Binary files differ
diff --git a/circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.pickle b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.pickle
new file mode 100644
index 0000000..d290392
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/docs/.doctrees/environment.pickle
Binary files differ
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
new file mode 100644
index 0000000..67de048
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/examples/ball/ball.bmp
Binary files differ
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
new file mode 100644
index 0000000..d7e5f16
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/examples/rpg/ground.bmp
Binary files differ
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
new file mode 100644
index 0000000..0e50ae3
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/examples/rpg/tiles.bmp
Binary files differ
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
new file mode 100644
index 0000000..c576e8b
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/font/font.bmp
Binary files differ
diff --git a/circuitpython/frozen/circuitpython-stage/font/font2.bmp b/circuitpython/frozen/circuitpython-stage/font/font2.bmp
new file mode 100644
index 0000000..7d41ca6
--- /dev/null
+++ b/circuitpython/frozen/circuitpython-stage/font/font2.bmp
Binary files differ
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)