From 6f65e6c52dd6da92df14d4bd7299dfe1f435c0c2 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Mon, 9 Mar 2026 19:00:07 +0100 Subject: [PATCH] Turn mpong into Breakout clone --- c_mpos/mpong/mpong.c | 184 +++++++++++++++--- .../com.micropythonos.mpong/assets/mpong.py | 2 +- 2 files changed, 163 insertions(+), 23 deletions(-) diff --git a/c_mpos/mpong/mpong.c b/c_mpos/mpong/mpong.c index 543bfd5f..5fcc0a15 100644 --- a/c_mpos/mpong/mpong.c +++ b/c_mpos/mpong/mpong.c @@ -2,12 +2,23 @@ #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; +int g_paddle_x; +int g_paddle_width; +int g_paddle_height; +int g_ball_x; +int g_ball_y; +int g_ball_vx; +int g_ball_vy; + +#define BRICK_ROWS 4 +#define BRICK_COLS 8 +uint8_t g_bricks[BRICK_ROWS][BRICK_COLS]; + // 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); @@ -26,6 +37,63 @@ static mp_obj_t readfile(mp_obj_t filename_obj) { } static MP_DEFINE_CONST_FUN_OBJ_1(readfile_obj, readfile); +static inline int clamp_int(int value, int min_value, int max_value) { + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +static inline size_t framebuffer_max_pixels(void) { + const size_t max_pixels = g_framebuffer_len / sizeof(uint16_t); + const size_t total_pixels = g_framebuffer_width * g_framebuffer_height; + return (max_pixels < total_pixels) ? max_pixels : total_pixels; +} + +static void draw_pixel(int x, int y, uint16_t color) { + if (x < 0 || y < 0) { + return; + } + if ((size_t)x >= g_framebuffer_width || (size_t)y >= g_framebuffer_height) { + return; + } + const size_t idx = (size_t)y * g_framebuffer_width + (size_t)x; + const size_t max_pixels = framebuffer_max_pixels(); + if (idx >= max_pixels) { + return; + } + g_framebuffer[idx] = color; +} + +static void draw_rect(int x, int y, int w, int h, uint16_t color) { + if (w <= 0 || h <= 0) { + return; + } + for (int yy = 0; yy < h; yy++) { + for (int xx = 0; xx < w; xx++) { + draw_pixel(x + xx, y + yy, color); + } + } +} + +static void reset_ball(void) { + g_ball_x = (int)g_framebuffer_width / 2; + g_ball_y = (int)g_framebuffer_height / 2; + g_ball_vx = 1; + g_ball_vy = -1; +} + +static void reset_bricks(void) { + for (int row = 0; row < BRICK_ROWS; row++) { + for (int col = 0; col < BRICK_COLS; col++) { + g_bricks[row][col] = 1; + } + } +} + // 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; @@ -36,53 +104,125 @@ static mp_obj_t init(mp_obj_t framebuffer_obj, mp_obj_t width_obj, mp_obj_t heig g_framebuffer_width = (size_t)mp_obj_get_int(width_obj); g_framebuffer_height = (size_t)mp_obj_get_int(height_obj); + g_paddle_width = (int)g_framebuffer_width / 5; + g_paddle_height = 4; + g_paddle_x = ((int)g_framebuffer_width - g_paddle_width) / 2; + + reset_ball(); + reset_bricks(); + 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. +// render(): draw a simple Breakout-style frame and advance the game simulation. 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; + const size_t fill_pixels = framebuffer_max_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 + // Clear to black. + for (size_t i = 0; i < fill_pixels; i++) { g_framebuffer[i] = 0x0000; } // RGB565 black - // 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; + // Update ball position. + g_ball_x += g_ball_vx; + g_ball_y += g_ball_vy; + + // Wall collisions. + if (g_ball_x <= 0) { + g_ball_x = 0; + g_ball_vx = 1; + } else if (g_ball_x >= (int)width - 1) { + g_ball_x = (int)width - 1; + g_ball_vx = -1; + } + + if (g_ball_y <= 0) { + g_ball_y = 0; + g_ball_vy = 1; + } + + // Brick layout. + const int brick_gap = 2; + const int brick_rows = BRICK_ROWS; + const int brick_cols = BRICK_COLS; + const int brick_height = 6; + const int brick_area_width = (int)width - (brick_gap * (brick_cols + 1)); + const int brick_width = (brick_area_width > 0) ? (brick_area_width / brick_cols) : 0; + const int brick_offset_y = 8; + + // Brick collision. + if (brick_width > 0 && g_ball_y <= brick_offset_y + brick_rows * (brick_height + brick_gap)) { + for (int row = 0; row < brick_rows; row++) { + for (int col = 0; col < brick_cols; col++) { + if (!g_bricks[row][col]) { + continue; + } + const int bx = brick_gap + col * (brick_width + brick_gap); + const int by = brick_offset_y + row * (brick_height + brick_gap); + if (g_ball_x >= bx && g_ball_x < bx + brick_width && g_ball_y >= by && g_ball_y < by + brick_height) { + g_bricks[row][col] = 0; + g_ball_vy = -g_ball_vy; + row = brick_rows; + 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; + // Paddle collision. + const int paddle_y = (int)height - g_paddle_height - 4; + if (g_ball_y >= paddle_y - 1 && g_ball_y <= paddle_y + g_paddle_height) { + if (g_ball_x >= g_paddle_x && g_ball_x <= g_paddle_x + g_paddle_width) { + g_ball_y = paddle_y - 1; + g_ball_vy = -1; + const int paddle_center = g_paddle_x + g_paddle_width / 2; + if (g_ball_x < paddle_center) { + g_ball_vx = -1; + } else if (g_ball_x > paddle_center) { + g_ball_vx = 1; + } + } } + // Ball fell below paddle: reset. + if (g_ball_y >= (int)height - 1) { + reset_ball(); + } + + // Draw bricks. + if (brick_width > 0) { + for (int row = 0; row < brick_rows; row++) { + for (int col = 0; col < brick_cols; col++) { + if (!g_bricks[row][col]) { + continue; + } + const int bx = brick_gap + col * (brick_width + brick_gap); + const int by = brick_offset_y + row * (brick_height + brick_gap); + draw_rect(bx, by, brick_width, brick_height, 0xF800); // RGB565 red + } + } + } + + // Draw paddle and ball. + draw_rect(g_paddle_x, paddle_y, g_paddle_width, g_paddle_height, 0xFFFF); // RGB565 white + draw_pixel(g_ball_x, g_ball_y, 0xFFFF); + return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_0(render_obj, render); -// move_paddle(delta): print delta for debugging. +// move_paddle(delta): move the paddle horizontally by delta. 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); + if (g_framebuffer_width > 0) { + g_paddle_x = clamp_int(g_paddle_x + delta, 0, (int)g_framebuffer_width - g_paddle_width); + } return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(move_paddle_obj, move_paddle); diff --git a/internal_filesystem/apps/com.micropythonos.mpong/assets/mpong.py b/internal_filesystem/apps/com.micropythonos.mpong/assets/mpong.py index f4581d4f..f589134c 100644 --- a/internal_filesystem/apps/com.micropythonos.mpong/assets/mpong.py +++ b/internal_filesystem/apps/com.micropythonos.mpong/assets/mpong.py @@ -10,7 +10,7 @@ import sys if sys.platform == "esp32": import mpong_xtensawin as mpong else: - import mpong_amd64 as mpong + import mpong_x64 as mpong class MPong(Activity):