You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Initial MPong game commit
This demonstrates native machine code in .mpy files as documented in https://docs.micropython.org/en/latest/develop/natmod.html The idea is to make a simple Pong game to demonstrate input from buttons and touchscreen and output to framebuffer and audio.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
MPY_DIR = ../../lvgl_micropython/lib/micropython/
|
||||
|
||||
# Name of module
|
||||
MOD = mpong
|
||||
|
||||
# Source files (.c or .py)
|
||||
SRC = mpong.c
|
||||
|
||||
# Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
|
||||
ARCH = x64
|
||||
|
||||
# Include to get the rules for compiling and linking the module
|
||||
include $(MPY_DIR)/py/dynruntime.mk
|
||||
@@ -0,0 +1,13 @@
|
||||
MPY_DIR = ../../lvgl_micropython/lib/micropython/
|
||||
|
||||
# Name of module
|
||||
MOD = mpong
|
||||
|
||||
# Source files (.c or .py)
|
||||
SRC = mpong.c
|
||||
|
||||
# Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
|
||||
ARCH = xtensawin
|
||||
|
||||
# Include to get the rules for compiling and linking the module
|
||||
include $(MPY_DIR)/py/dynruntime.mk
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
mydir=$(readlink -f "$0")
|
||||
mydir=$(dirname "$mydir")
|
||||
|
||||
cd "$mydir"
|
||||
rm -rf build
|
||||
|
||||
make -f Makefile_amd64 </dev/null
|
||||
result=$?
|
||||
[ $result -ne 0 ] && exit $result
|
||||
|
||||
mv mpong.mpy "$mydir"/../../internal_filesystem/apps/com.micropythonos.mpong/assets/mpong_amd64.mpy
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#. /home/user/projects/MicroPythonOS/claude/.espressif/python_env/idf5.4_py3.11_env/bin/activate
|
||||
|
||||
mydir=$(readlink -f "$0")
|
||||
mydir=$(dirname "$mydir")
|
||||
|
||||
cd "$mydir"
|
||||
|
||||
rm -rf build
|
||||
|
||||
|
||||
PATH=/home/user/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/:$PATH make -f Makefile_esp32 </dev/null
|
||||
|
||||
mv mpong.mpy ../../internal_filesystem/mpong_esp32.mpy
|
||||
@@ -0,0 +1,103 @@
|
||||
// Include the header file to get access to the MicroPython API
|
||||
#include "py/dynruntime.h"
|
||||
|
||||
// Global BSS (non-static) state is required for native modules.
|
||||
size_t g_line_y;
|
||||
uint16_t *g_framebuffer;
|
||||
size_t g_framebuffer_len;
|
||||
size_t g_framebuffer_width;
|
||||
size_t g_framebuffer_height;
|
||||
|
||||
// readfile(filename): return first 10 bytes of a file as bytes
|
||||
static mp_obj_t readfile(mp_obj_t filename_obj) {
|
||||
mp_obj_t open_fun = mp_load_global(MP_QSTR_open);
|
||||
mp_obj_t open_args[2] = { filename_obj, mp_obj_new_str("rb", 2) };
|
||||
mp_obj_t file_obj = mp_call_function_n_kw(open_fun, 2, 0, open_args);
|
||||
|
||||
mp_obj_t read_fun = mp_load_attr(file_obj, MP_QSTR_read);
|
||||
mp_obj_t read_args[1] = { mp_obj_new_int(10) };
|
||||
mp_obj_t data_obj = mp_call_function_n_kw(read_fun, 1, 0, read_args);
|
||||
|
||||
mp_obj_t close_fun = mp_load_attr(file_obj, MP_QSTR_close);
|
||||
mp_obj_t close_args[1];
|
||||
mp_call_function_n_kw(close_fun, 0, 0, close_args);
|
||||
|
||||
return data_obj;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(readfile_obj, readfile);
|
||||
|
||||
// init(framebuffer, width, height): store a reference to the framebuffer and dimensions.
|
||||
static mp_obj_t init(mp_obj_t framebuffer_obj, mp_obj_t width_obj, mp_obj_t height_obj) {
|
||||
mp_buffer_info_t bufinfo;
|
||||
mp_get_buffer_raise(framebuffer_obj, &bufinfo, MP_BUFFER_WRITE);
|
||||
|
||||
g_framebuffer = (uint16_t *)bufinfo.buf;
|
||||
g_framebuffer_len = bufinfo.len;
|
||||
g_framebuffer_width = (size_t)mp_obj_get_int(width_obj);
|
||||
g_framebuffer_height = (size_t)mp_obj_get_int(height_obj);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_3(init_obj, init);
|
||||
|
||||
// render(): draw a moving black line on the RGB565 framebuffer as a deterministic test pattern.
|
||||
static mp_obj_t render(void) {
|
||||
if (g_framebuffer == NULL || g_framebuffer_width == 0 || g_framebuffer_height == 0) {
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
uint16_t *pixels = g_framebuffer;
|
||||
const size_t len = g_framebuffer_len;
|
||||
const size_t width = g_framebuffer_width;
|
||||
const size_t height = g_framebuffer_height;
|
||||
const size_t max_pixels = len / sizeof(uint16_t);
|
||||
const size_t total_pixels = width * height;
|
||||
const size_t fill_pixels = (max_pixels < total_pixels) ? max_pixels : total_pixels;
|
||||
|
||||
// Fill the framebuffer with white so the black line is visible.
|
||||
for (size_t i = 0; i < fill_pixels; i++) { pixels[i] = 0xFFFF; } // RGB565 white
|
||||
|
||||
// Draw a horizontal black line across the current row.
|
||||
if (g_line_y < height) {
|
||||
const size_t base = g_line_y * width;
|
||||
for (size_t x = 0; x < width; x++) {
|
||||
const size_t idx = base + x;
|
||||
if (idx >= fill_pixels) {
|
||||
break;
|
||||
}
|
||||
pixels[idx] = 0x0000; // RGB565 black
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the line for the next call, wrapping at the bottom.
|
||||
g_line_y++;
|
||||
if (g_line_y >= height) {
|
||||
g_line_y = 0;
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_0(render_obj, render);
|
||||
|
||||
// move_paddle(delta): print delta for debugging.
|
||||
static mp_obj_t move_paddle(mp_obj_t delta_obj) {
|
||||
int delta = mp_obj_get_int(delta_obj);
|
||||
mp_printf(&mp_plat_print, "delta: %d\n", delta);
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(move_paddle_obj, move_paddle);
|
||||
|
||||
// This is the entry point and is called when the module is imported
|
||||
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
|
||||
// This must be first, it sets up the globals dict and other things
|
||||
MP_DYNRUNTIME_INIT_ENTRY
|
||||
|
||||
// Make the function available in the module's namespace
|
||||
mp_store_global(MP_QSTR_init, MP_OBJ_FROM_PTR(&init_obj));
|
||||
mp_store_global(MP_QSTR_render, MP_OBJ_FROM_PTR(&render_obj));
|
||||
mp_store_global(MP_QSTR_move_paddle, MP_OBJ_FROM_PTR(&move_paddle_obj));
|
||||
mp_store_global(MP_QSTR_readfile, MP_OBJ_FROM_PTR(&readfile_obj));
|
||||
|
||||
// This must be last, it restores the globals dict
|
||||
MP_DYNRUNTIME_INIT_EXIT
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "mPong",
|
||||
"publisher": "MicroPythonOS",
|
||||
"short_description": "PingPong game",
|
||||
"long_description": "Demonstrates native machinecode in .mpy files on both AMD64 and ESP32",
|
||||
"icon_url": "https://apps.micropythonos.com/apps/com.micropythonos.mpong/icons/com.micropythonos.mpong_0.1.0_64x64.png",
|
||||
"download_url": "https://apps.micropythonos.com/apps/com.micropythonos.mpong/mpks/com.micropythonos.mpong_0.1.0.mpk",
|
||||
"fullname": "com.micropythonos.mpong",
|
||||
"version": "0.1.0",
|
||||
"category": "games",
|
||||
"activities": [
|
||||
{
|
||||
"entrypoint": "assets/mpong.py",
|
||||
"classname": "MPong",
|
||||
"intent_filters": [
|
||||
{
|
||||
"action": "main",
|
||||
"category": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import lvgl as lv
|
||||
from mpos import Activity, DisplayMetrics, InputManager
|
||||
|
||||
indev_error_x = 160
|
||||
indev_error_y = 120
|
||||
|
||||
DARKPINK = lv.color_hex(0xEC048C)
|
||||
|
||||
import sys
|
||||
if sys.platform == "esp32":
|
||||
import mpong_esp32 as mpong
|
||||
else:
|
||||
import mpong_amd64 as mpong
|
||||
|
||||
class MPong(Activity):
|
||||
|
||||
hor_res = 0
|
||||
ver_res = 0
|
||||
layer = None
|
||||
buffer = None
|
||||
|
||||
# Widgets:
|
||||
screen = None
|
||||
canvas = None
|
||||
|
||||
def onCreate(self):
|
||||
self.screen = lv.obj()
|
||||
self.canvas = lv.canvas(self.screen)
|
||||
d = lv.display_get_default()
|
||||
self.hor_res = d.get_horizontal_resolution()
|
||||
self.ver_res = d.get_vertical_resolution()
|
||||
self.canvas.set_size(self.hor_res, self.ver_res)
|
||||
#self.canvas.set_style_bg_color(lv.color_white(), lv.PART.MAIN)
|
||||
self.buffer = bytearray(self.hor_res * self.ver_res * 2)
|
||||
self.canvas.set_buffer(self.buffer, self.hor_res, self.ver_res, lv.COLOR_FORMAT.NATIVE)
|
||||
#self.canvas.fill_bg(lv.color_white(), lv.OPA.COVER)
|
||||
self.canvas.add_flag(lv.obj.FLAG.CLICKABLE)
|
||||
self.canvas.add_event_cb(self.touch_cb, lv.EVENT.ALL, None)
|
||||
self.layer = lv.layer_t()
|
||||
self.canvas.init_layer(self.layer)
|
||||
self.setContentView(self.screen)
|
||||
|
||||
def onResume(self, screen):
|
||||
mpong.init(self.buffer, self.hor_res, self.ver_res)
|
||||
self.refresh_timer = lv.timer_create(self.run_mpong, 10, None)
|
||||
|
||||
def onPause(self, screen):
|
||||
print("stopping it!")
|
||||
if self.refresh_timer:
|
||||
self.refresh_timer.delete()
|
||||
|
||||
def run_mpong(self, timer=None):
|
||||
mpong.render()
|
||||
self.canvas.invalidate() # force redraw
|
||||
|
||||
def touch_cb(self, event):
|
||||
event_code=event.get_code()
|
||||
if event_code not in [19,23,25,26,27,28,29,30,49]:
|
||||
if event_code == lv.EVENT.PRESSING: # this is probably enough
|
||||
x, y = InputManager.pointer_xy()
|
||||
mpong.move_paddle(-10)
|
||||
return
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
Reference in New Issue
Block a user