Skip to content

Commit

Permalink
✨ Games for E3V2 + MarlinUI (#27620)
Browse files Browse the repository at this point in the history
shadow578 authored Jan 9, 2025

Verified

This commit was signed with the committer’s verified signature. The key has expired.
nikolaybotev Nikolay Botev
1 parent de76f2f commit 22977c8
Showing 15 changed files with 808 additions and 94 deletions.
12 changes: 6 additions & 6 deletions Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
@@ -2035,17 +2035,17 @@
//#define STATUS_HEAT_PERCENT // Show heating in a progress bar
//#define STATUS_HEAT_POWER // Show heater output power as a vertical bar

// Frivolous Game Options
//#define MARLIN_BRICKOUT
//#define MARLIN_INVADERS
//#define MARLIN_SNAKE
//#define GAMES_EASTER_EGG // Add extra blank lines above the "Games" sub-menu

#endif // HAS_MARLINUI_U8GLIB

#if HAS_MARLINUI_U8GLIB || IS_DWIN_MARLINUI
#define MENU_HOLLOW_FRAME // Enable to save many cycles by drawing a hollow frame on Menu Screens
//#define OVERLAY_GFX_REVERSE // Swap the CW/CCW indicators in the graphics overlay

// Frivolous Game Options
//#define MARLIN_BRICKOUT
//#define MARLIN_INVADERS
//#define MARLIN_SNAKE
//#define GAMES_EASTER_EGG // Add extra blank lines above the "Games" sub-menu
#endif

//
80 changes: 80 additions & 0 deletions Marlin/src/lcd/dogm/game.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../../inc/MarlinConfigPre.h"

#if HAS_MARLINUI_U8GLIB && HAS_GAMES

#include "../menu/game/types.h" // includes dogm/game.h

void MarlinGame::frame_start() {
set_color(color::WHITE);
}

void MarlinGame::frame_end() {}

void MarlinGame::set_color(const color color) {
switch (color) {
default:
case color::WHITE: u8g.setColorIndex(1); break;
case color::BLACK: u8g.setColorIndex(0); break;
}
}

void MarlinGame::draw_hline(const game_dim_t x, const game_dim_t y, const game_dim_t w) {
u8g.drawHLine(x, y, w);
}

void MarlinGame::draw_vline(const game_dim_t x, const game_dim_t y, const game_dim_t h) {
u8g.drawVLine(x, y, h);
}

void MarlinGame::draw_frame(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
u8g.drawFrame(x, y, w, h);
}

void MarlinGame::draw_box(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
u8g.drawBox(x, y, w, h);
}

void MarlinGame::draw_pixel(const game_dim_t x, const game_dim_t y) {
u8g.drawPixel(x, y);
}

void MarlinGame::draw_bitmap(const game_dim_t x, const game_dim_t y, const game_dim_t bytes_per_row, const game_dim_t rows, const pgm_bitmap_t bitmap) {
u8g.drawBitmapP(x, y, bytes_per_row, rows, bitmap);
}

int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, const char* str) {
lcd_moveto(x, y);
return lcd_put_u8str_P(str);
}

int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, FSTR_P const fstr) {
lcd_moveto(x, y);
return lcd_put_u8str(fstr);
}

void MarlinGame::draw_int(const game_dim_t x, const game_dim_t y, const int value) {
lcd_put_int(x, y, value);
}

#endif // HAS_MARLINUI_U8GLIB && HAS_GAMES
33 changes: 33 additions & 0 deletions Marlin/src/lcd/dogm/game.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

#include "marlinui_DOGM.h"
#include "../lcdprint.h"

typedef uint8_t game_dim_t;
typedef const u8g_pgm_uint8_t* pgm_bitmap_t;

constexpr game_dim_t GAME_WIDTH = LCD_PIXEL_WIDTH;
constexpr game_dim_t GAME_HEIGHT = LCD_PIXEL_HEIGHT;
constexpr game_dim_t GAME_FONT_WIDTH = MENU_FONT_WIDTH;
constexpr game_dim_t GAME_FONT_ASCENT = MENU_FONT_ASCENT;
63 changes: 62 additions & 1 deletion Marlin/src/lcd/e3v2/common/dwin_api.cpp
Original file line number Diff line number Diff line change
@@ -158,6 +158,7 @@ void dwinFrameClear(const uint16_t color) {
}

#if DISABLED(TJC_DISPLAY)

// Draw a point
// color: point color
// width: point width 0x01-0x0F
@@ -173,7 +174,67 @@ void dwinFrameClear(const uint16_t color) {
dwinWord(i, y);
dwinSend(i);
}
#endif

// Draw a map of multiple points using minimal amount of point drawing commands
// color: point color
// point_width: point width 0x01-0x0F
// point_height: point height 0x01-0x0F
// x,y: upper left point
// map_columns: columns in theh point map. each column is a byte in the map and contains 8 points
// map_rows: rows in the point map
// map: point bitmap. 2D array of points, 1 bit per point
// Note: somewhat similar to U8G's drawBitmap() function, see https://github.com/olikraus/u8glib/wiki/userreference#drawbitmap
void dwinDrawPointMap(
const uint16_t color,
const uint8_t point_width, const uint8_t point_height,
const uint16_t x, const uint16_t y,
const uint16_t map_columns, const uint16_t map_rows,
const uint8_t *map_data
) {
// At how many bytes should we flush the send buffer?
// One byte is used (hidden) for F_HONE, and we need 4 bytes when appending a point.
// So we should flush the send buffer when we have less than 5 bytes left.
constexpr size_t flush_send_buffer_at = (COUNT(dwinSendBuf) - 1 - 4);

// How long is the header of each draw command?
// => 1B CMD, 2B COLOR, 1B WIDTH, 1B HEIGHT
constexpr size_t command_header_size = 5;

size_t i = 0;
for (uint16_t row = 0; row < map_rows; row++) {
for (uint16_t col = 0; col < map_columns; col++) {
const uint8_t map_byte = map_data[(row * map_columns) + col];
for (uint8_t bit = 0; bit < 8; bit++) {
// Draw a point at this position?
if (TEST(map_byte, bit)) {
// Flush the send buffer and prepare next draw if either
// a) The buffer reached the 'should flush' state, or
// b) This is the first point to draw
if (i >= flush_send_buffer_at || i == 0) {
// Dispatch the current draw command
if (i > command_header_size) dwinSend(i);

// Prepare the next draw command
i = 0;
dwinByte(i, 0x02); // cmd: draw point(s)
dwinWord(i, color);
dwinByte(i, point_width);
dwinByte(i, point_height);
}

// Append point coordinates to draw command
dwinWord(i, x + (point_width * ((8 * col) + (7 - bit)))); // x
dwinWord(i, y + (point_height * (row))); // y
}
}
}
}

// Dispatch final draw command if the buffer contains any points
if (i > command_header_size) dwinSend(i);
}

#endif // !TJC_DISPLAY

// Draw a line
// color: Line segment color
18 changes: 18 additions & 0 deletions Marlin/src/lcd/e3v2/common/dwin_api.h
Original file line number Diff line number Diff line change
@@ -164,6 +164,24 @@ inline void dwinDrawBox(uint8_t mode, uint16_t color, uint16_t xStart, uint16_t
void dwinDrawPoint(uint16_t color, uint8_t width, uint8_t height, uint16_t x, uint16_t y);
#endif

// Draw a map of multiple points using minimal amount of point drawing commands
// color: point color
// point_width: point width 0x01-0x0F
// point_height: point height 0x01-0x0F
// x,y: upper left point
// map_columns: columns in theh point map. each column is a byte in the map and contains 8 points
// map_rows: rows in the point map
// map: point bitmap. 2D array of points, 1 bit per point
#if DISABLED(TJC_DISPLAY)
void dwinDrawPointMap(
const uint16_t color,
const uint8_t point_width, const uint8_t point_height,
const uint16_t x, const uint16_t y,
const uint16_t map_columns, const uint16_t map_rows,
const uint8_t *map_data
);
#endif

// Move a screen area
// mode: 0, circle shift; 1, translation
// dir: 0=left, 1=right, 2=up, 3=down
254 changes: 254 additions & 0 deletions Marlin/src/lcd/e3v2/marlinui/game.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../../../inc/MarlinConfigPre.h"

#if IS_DWIN_MARLINUI && HAS_GAMES

// Enable performance counters (draw call count, frame timing) for debugging
//#define GAME_PERFORMANCE_COUNTERS

#include "../../menu/game/types.h" // includes e3v2/marlinui/game.h
#include "../../lcdprint.h"
#include "lcdprint_dwin.h"
#include "marlinui_dwin.h"

#if ENABLED(GAME_PERFORMANCE_COUNTERS)

typedef struct {
/**
* Number of draw calls sent to the LCD
*/
uint32_t draw_calls;

/**
* millis() value at the start of the current frame
*/
millis_t frame_draw_millis;

/**
* millis() value at the end of the previous frame (in frame_start)
* or time spend waiting for the next frame (in frame_end)
*/
millis_t frame_wait_millis;
} dwin_game_perf_t;

static dwin_game_perf_t dwin_game_perf;

#define COUNT_DRAW_CALLS(n) dwin_game_perf.draw_calls += n

#else // !GAME_PERFORMANCE_COUNTERS

#define COUNT_DRAW_CALLS(...) NOOP

#endif // !GAME_PERFORMANCE_COUNTERS

void MarlinGame::frame_start() {
// Clear the screen before each frame
//dwinFrameClear(CLEAR_COLOR);

// Instead of using dwinFrameClear, fill the play area with the background color
// This tends to be faster than clearing the whole screen
const uint16_t fg = dwin_font.fg;
dwin_font.fg = COLOR_BG_BLACK;
draw_box(0, 0, GAME_WIDTH, GAME_HEIGHT);
dwin_font.fg = fg;

// Ensure the correct font is selected
dwin_font.index = DWIN_FONT_MENU;

#if ENABLED(GAME_PERFORMANCE_COUNTERS)
// Reset draw call counters
dwin_game_perf.draw_calls = 0;

// Update timing information
const millis_t now = millis();
dwin_game_perf.frame_draw_millis = now;
dwin_game_perf.frame_wait_millis = now - dwin_game_perf.frame_wait_millis;
#endif
}

void MarlinGame::frame_end() {
#if ENABLED(GAME_PERFORMANCE_COUNTERS)
const millis_t now = millis();
const millis_t frame_wait_millis = dwin_game_perf.frame_wait_millis;
const millis_t frame_draw_millis = now - dwin_game_perf.frame_draw_millis;

dwin_game_perf.frame_wait_millis = now;

// Save previous font settings and set new ones
const uint16_t fg = dwin_font.fg;
const bool solid = dwin_font.solid;
set_color(color::YELLOW);
dwin_font.solid = true;

// Draw performance counters information
char perf_str[32];
sprintf_P(
perf_str,
PSTR("d%04lu w%04lu c%04lu"),
frame_draw_millis,
frame_wait_millis,
dwin_game_perf.draw_calls
);
lcd_moveto_xy(0, 0);
lcd_put_u8str(perf_str);

// Restore previous font settings
dwin_font.fg = fg;
dwin_font.solid = solid;
#endif // GAME_PERFORMANCE_COUNTERS
}

void MarlinGame::set_color(const color color) {
switch (color) {
default:
case color::WHITE: dwin_font.fg = COLOR_WHITE; break;
case color::BLACK: dwin_font.fg = COLOR_BG_BLACK; break;

// https://rgbcolorpicker.com/565/table
case color::RED: dwin_font.fg = RGB(0x1F, 0x00, 0x00); break;
case color::GREEN: dwin_font.fg = RGB(0x00, 0x3F, 0x00); break;
case color::BLUE: dwin_font.fg = RGB(0x00, 0x00, 0x1F); break;
case color::YELLOW: dwin_font.fg = RGB(0x1F, 0x3F, 0x00); break;
case color::CYAN: dwin_font.fg = RGB(0x00, 0x3F, 0x1F); break;
case color::MAGENTA:dwin_font.fg = RGB(0x1F, 0x00, 0x1F); break;
}
}

void MarlinGame::draw_hline(const game_dim_t x, const game_dim_t y, const game_dim_t w) {
// Draw lines as boxes, since DWIN lines are always 1px wide but we want to scale them
draw_box(x, y, w, 1);

COUNT_DRAW_CALLS(1);
}

void MarlinGame::draw_vline(const game_dim_t x, const game_dim_t y, const game_dim_t h) {
// Draw lines as boxes, since DWIN lines are always 1px wide but we want to scale them
draw_box(x, y, 1, h);

COUNT_DRAW_CALLS(1);
}

void MarlinGame::draw_frame(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
dwinDrawBox(
0, // mode = frame
dwin_font.fg, // color
dwin_game::game_to_screen(x) + dwin_game::x_offset,
dwin_game::game_to_screen(y) + dwin_game::y_offset,
dwin_game::game_to_screen(w),
dwin_game::game_to_screen(h)
);

COUNT_DRAW_CALLS(1);
}

void MarlinGame::draw_box(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
dwinDrawBox(
1, // mode = fill
dwin_font.fg, // color
dwin_game::game_to_screen(x) + dwin_game::x_offset,
dwin_game::game_to_screen(y) + dwin_game::y_offset,
dwin_game::game_to_screen(w),
dwin_game::game_to_screen(h)
);

COUNT_DRAW_CALLS(1);
}

void MarlinGame::draw_pixel(const game_dim_t x, const game_dim_t y) {
// Draw pixels using boxes.
// While DWIN protocol supports drawing points with different sizes, the
// 0x02 'draw point' command is slower per pixel than 0x05 'fill rectangle'
// (0.4 us vs 0.14 us per pixel)
draw_box(x, y, 1, 1);
}

void MarlinGame::draw_bitmap(const game_dim_t x, const game_dim_t y, const game_dim_t bytes_per_row, const game_dim_t rows, const pgm_bitmap_t bitmap) {
// DWIN theorethically supports bitmaps since kernel 2.1, but most screens don't support it
// (either because they use an older kernel version, or because they just (badly) emulate the DWIN protocol).
// So instead, we have to fall back to drawing points manually.

#if DISABLED(TJC_DISPLAY)

// DWIN T5UI actually supports drawing multiple points in one go using the 0x02 'draw point' command, ever since kernel 1.2.
// So we use that to draw the bitmap as a series of points, which is faster than drawing rectangles using draw_pixel.
dwinDrawPointMap(
dwin_font.fg,
dwin_game::game_to_screen(1),
dwin_game::game_to_screen(1),
dwin_game::game_to_screen(x) + dwin_game::x_offset,
dwin_game::game_to_screen(y) + dwin_game::y_offset,
bytes_per_row,
rows,
bitmap
);

COUNT_DRAW_CALLS(1);

#else // TJC_DISPLAY

// TJC displays don't seem to support the 0x02 'draw point' command, so instead we have to draw the bitmap
// as a series of rectangles using draw_pixel.
// This will absolutely suck for performance, but it's the best we can do on these screens.
for (game_dim_t row = 0; row < rows; row++) {
for (game_dim_t col = 0; col < bytes_per_row; col++) {
const uint8_t byte = bitmap[(row * bytes_per_row) + col];
for (uint8_t bit = 0; bit < 8; bit++) {
// Assuming that the drawing area was cleared before drawing
if (byte & (1 << bit)) {
draw_pixel(x + (col * 8) + (7 - bit + 1), y + row);
COUNT_DRAW_CALLS(1);
}
}
}
}

#endif // TJC_DISPLAY
}

int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, const char* str) {
COUNT_DRAW_CALLS(1);

lcd_moveto_xy(
dwin_game::game_to_screen(x) + dwin_game::x_offset,
dwin_game::game_to_screen(y) + dwin_game::y_offset
);

return lcd_put_u8str_max_P(str, PIXEL_LEN_NOLIMIT);
}

int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, FSTR_P const str) {
return draw_string(x, y, FTOP(str));
}

void MarlinGame::draw_int(const game_dim_t x, const game_dim_t y, const int value) {
COUNT_DRAW_CALLS(1);

lcd_moveto_xy(
dwin_game::game_to_screen(x) + dwin_game::x_offset,
dwin_game::game_to_screen(y) + dwin_game::y_offset
);

lcd_put_int(value);
}

#endif // IS_DWIN_MARLINUI && HAS_GAMES
85 changes: 85 additions & 0 deletions Marlin/src/lcd/e3v2/marlinui/game.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once

#include "../marlinui/marlinui_dwin.h"

typedef uint8_t game_dim_t;
typedef uint16_t screen_dim_t;
typedef const uint8_t* pgm_bitmap_t;

namespace dwin_game {
/**
* @brief Target the renderer at 128x64 pixels to match U8G screens
*/
constexpr screen_dim_t TARGET_WIDTH = 128;
constexpr screen_dim_t TARGET_HEIGHT = 64;

constexpr int calculate_scale() {
// Use whichever is smaller: the width or height scaling factor
float scaling_factor = _MIN(
static_cast<float>(DWIN_WIDTH) / static_cast<float>(TARGET_WIDTH),
static_cast<float>(DWIN_HEIGHT) / static_cast<float>(TARGET_HEIGHT)
);

// Round DOWN to closest integer
return static_cast<int>(scaling_factor);
}

/**
* @brief Game render scale.
*/
constexpr int scale = calculate_scale();

/**
* @brief Scale a game dimension to screen dimensions
*/
constexpr game_dim_t screen_to_game(const screen_dim_t x) {
return x / scale;
}

/**
* @brief Scale a screen dimension to game dimensions
*/
constexpr screen_dim_t game_to_screen(const game_dim_t x) {
return x * scale;
}

/**
* @brief Offset of the game window on the screen. Applied after scaling.
*/
constexpr screen_dim_t x_offset = (DWIN_WIDTH - game_to_screen(TARGET_WIDTH)) / 2;
constexpr screen_dim_t y_offset = (DWIN_HEIGHT - game_to_screen(TARGET_HEIGHT)) / 2;

static_assert(game_to_screen(TARGET_WIDTH) + (x_offset * 2) <= DWIN_WIDTH, "DWIN game renderer failed to auto-scale, is too wide");
static_assert(game_to_screen(TARGET_HEIGHT) + (y_offset * 2) <= DWIN_HEIGHT, "DWIN game renderer failed to auto-scale, is too high");
} // namespace dwin_game

constexpr game_dim_t GAME_WIDTH = dwin_game::screen_to_game(DWIN_WIDTH - (dwin_game::x_offset * 2));
constexpr game_dim_t GAME_HEIGHT = dwin_game::screen_to_game(DWIN_HEIGHT - (dwin_game::y_offset * 2));
constexpr game_dim_t GAME_FONT_WIDTH = dwin_game::screen_to_game(MENU_FONT_WIDTH);
constexpr game_dim_t GAME_FONT_ASCENT = dwin_game::screen_to_game(MENU_FONT_ASCENT);

// DWIN screens don't page, so these macros are always true
#define PAGE_OVER(ya) true
#define PAGE_UNDER(yb) true
#define PAGE_CONTAINS(ya, yb) true
4 changes: 3 additions & 1 deletion Marlin/src/lcd/e3v2/marlinui/lcdprint_dwin.cpp
Original file line number Diff line number Diff line change
@@ -52,7 +52,9 @@ void lcd_moveto(const lcd_uint_t col, const lcd_uint_t row) {
inline void lcd_advance_cursor(const uint8_t len=1) { cursor.x += len * dwin_font.width; }

void lcd_put_int(const int i) {
// TODO: Draw an int at the cursor position, advance the cursor
char buf[12]; // 10 digits + sign + null
itoa(i, buf, 10);
lcd_put_u8str_max(buf, PIXEL_LEN_NOLIMIT);
}

int lcd_put_dwin_string() {
66 changes: 43 additions & 23 deletions Marlin/src/lcd/menu/game/brickout.cpp
Original file line number Diff line number Diff line change
@@ -27,14 +27,14 @@
#include "game.h"

#define BRICK_H 5
#define BRICK_TOP MENU_FONT_ASCENT
#define BRICK_TOP GAME_FONT_ASCENT

#define PADDLE_H 2
#define PADDLE_VEL 3
#define PADDLE_W ((LCD_PIXEL_WIDTH) / 8)
#define PADDLE_Y (LCD_PIXEL_HEIGHT - 1 - PADDLE_H)
#define PADDLE_W ((GAME_WIDTH) / 8)
#define PADDLE_Y (GAME_HEIGHT - 1 - PADDLE_H)

#define BRICK_W ((LCD_PIXEL_WIDTH) / (BRICK_COLS))
#define BRICK_W ((GAME_WIDTH) / (BRICK_COLS))
#define BRICK_BOT (BRICK_TOP + BRICK_H * BRICK_ROWS - 1)

#define BRICK_COL(X) ((X) / (BRICK_W))
@@ -53,15 +53,15 @@ void reset_ball() {
bdat.ballv = FTOF(1.3f);
bdat.ballh = -FTOF(1.25f);
uint8_t bx = bdat.paddle_x + (PADDLE_W) / 2 + ball_dist;
if (bx >= LCD_PIXEL_WIDTH - 10) { bx -= ball_dist * 2; bdat.ballh = -bdat.ballh; }
if (bx >= GAME_WIDTH - 10) { bx -= ball_dist * 2; bdat.ballh = -bdat.ballh; }
bdat.ballx = BTOF(bx);
bdat.hit_dir = -1;
}

void BrickoutGame::game_screen() {
if (game_frame()) { // Run logic twice for finer resolution
// Update Paddle Position
bdat.paddle_x = constrain(int8_t(ui.encoderPosition), 0, (LCD_PIXEL_WIDTH - (PADDLE_W)) / (PADDLE_VEL));
bdat.paddle_x = constrain(int8_t(ui.encoderPosition), 0, (GAME_WIDTH - (PADDLE_W)) / (PADDLE_VEL));
ui.encoderPosition = bdat.paddle_x;
bdat.paddle_x *= (PADDLE_VEL);

@@ -70,15 +70,15 @@ void BrickoutGame::game_screen() {

// Provisionally update the ball position
const fixed_t newx = bdat.ballx + bdat.ballh, newy = bdat.bally + bdat.ballv; // current next position
if (!WITHIN(newx, 0, BTOF(LCD_PIXEL_WIDTH - 1))) { // out in x?
if (!WITHIN(newx, 0, BTOF(GAME_WIDTH - 1))) { // out in x?
bdat.ballh = -bdat.ballh; _BUZZ(5, 220); // bounce x
}
if (newy < 0) { // out in y?
bdat.ballv = -bdat.ballv; _BUZZ(5, 280); // bounce v
bdat.hit_dir = 1;
}
// Did the ball go below the bottom?
else if (newy > BTOF(LCD_PIXEL_HEIGHT)) {
else if (newy > BTOF(GAME_HEIGHT)) {
_BUZZ(500, 75);
if (--bdat.balls_left) reset_ball(); else game_state = 0;
break; // done
@@ -134,32 +134,51 @@ void BrickoutGame::game_screen() {
} while (false);
}

u8g.setColorIndex(1);
frame_start();

// Draw bricks
// Draw bricks, cycling through colors for each brick
#if IS_DWIN_MARLINUI
const color brick_colors[] = { color::RED, color::CYAN, color::GREEN, color::YELLOW, color::MAGENTA, color::BLUE };
int color_index = 0;
#endif
if (PAGE_CONTAINS(BRICK_TOP, BRICK_BOT)) {
for (uint8_t y = 0; y < BRICK_ROWS; ++y) {
const uint8_t yy = y * BRICK_H + BRICK_TOP;
if (PAGE_CONTAINS(yy, yy + BRICK_H - 1)) {
for (uint8_t x = 0; x < BRICK_COLS; ++x) {
#if IS_DWIN_MARLINUI
// Cycle through colors, even if the brick is gone.
// Otherwise, bricks would change color if their neighbor is hit
set_color(brick_colors[color_index++ % COUNT(brick_colors)]);
#endif

// Draw brick if it's still there
if (TEST(bdat.bricks[y], x)) {
const uint8_t xx = x * BRICK_W;
for (uint8_t v = 0; v < BRICK_H - 1; ++v)
if (PAGE_CONTAINS(yy + v, yy + v))
u8g.drawHLine(xx, yy + v, BRICK_W - 1);
#if IS_DWIN_MARLINUI
if (PAGE_CONTAINS(yy, yy + BRICK_H - 1))
draw_box(xx, yy, BRICK_W - 1, BRICK_H - 1);
#else
for (uint8_t v = 0; v < BRICK_H - 1; ++v)
if (PAGE_CONTAINS(yy + v, yy + v))
u8g.drawHLine(xx, yy + v, BRICK_W - 1);
#endif
}
}
}
}
}

// Everything else is white
TERN_(IS_DWIN_MARLINUI, set_color(color::WHITE));

// Draw paddle
if (PAGE_CONTAINS(PADDLE_Y-1, PADDLE_Y)) {
u8g.drawHLine(bdat.paddle_x, PADDLE_Y, PADDLE_W);
draw_hline(bdat.paddle_x, PADDLE_Y, PADDLE_W);
#if PADDLE_H > 1
u8g.drawHLine(bdat.paddle_x, PADDLE_Y-1, PADDLE_W);
draw_hline(bdat.paddle_x, PADDLE_Y-1, PADDLE_W);
#if PADDLE_H > 2
u8g.drawHLine(bdat.paddle_x, PADDLE_Y-2, PADDLE_W);
draw_hline(bdat.paddle_x, PADDLE_Y-2, PADDLE_W);
#endif
#endif
}
@@ -168,29 +187,30 @@ void BrickoutGame::game_screen() {
if (game_state) {
const uint8_t by = FTOB(bdat.bally);
if (PAGE_CONTAINS(by, by+1))
u8g.drawFrame(FTOB(bdat.ballx), by, 2, 2);
draw_frame(FTOB(bdat.ballx), by, 2, 2);
}
// Or draw GAME OVER
else
draw_game_over();

if (PAGE_UNDER(MENU_FONT_ASCENT)) {
if (PAGE_UNDER(GAME_FONT_ASCENT)) {
// Score Digits
//const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
//const uint8_t sx = (GAME_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * GAME_FONT_WIDTH) / 2;
constexpr uint8_t sx = 0;
lcd_put_int(sx, MENU_FONT_ASCENT - 1, score);
draw_int(sx, GAME_FONT_ASCENT - 1, score);

// Balls Left
lcd_moveto(LCD_PIXEL_WIDTH - MENU_FONT_WIDTH * 3, MENU_FONT_ASCENT - 1);
PGM_P const ohs = PSTR("ooo\0\0");
lcd_put_u8str_P(ohs + 3 - bdat.balls_left);
draw_string(GAME_WIDTH - GAME_FONT_WIDTH * 3, GAME_FONT_ASCENT - 1, ohs + 3 - bdat.balls_left);
}

frame_end();

// A click always exits this game
if (ui.use_click()) exit_game();
}

#define SCREEN_M ((LCD_PIXEL_WIDTH) / 2)
#define SCREEN_M ((GAME_WIDTH) / 2)

void BrickoutGame::enter_game() {
init_game(2, game_screen); // 2 = reset bricks on paddle hit
16 changes: 8 additions & 8 deletions Marlin/src/lcd/menu/game/game.cpp
Original file line number Diff line number Diff line change
@@ -40,15 +40,15 @@ bool MarlinGame::game_frame() {
}

void MarlinGame::draw_game_over() {
constexpr int8_t gowide = (MENU_FONT_WIDTH) * 9,
gohigh = MENU_FONT_ASCENT - 3,
lx = (LCD_PIXEL_WIDTH - gowide) / 2,
ly = (LCD_PIXEL_HEIGHT + gohigh) / 2;
constexpr int8_t gowide = (GAME_FONT_WIDTH) * 9,
gohigh = GAME_FONT_ASCENT - 3,
lx = (GAME_WIDTH - gowide) / 2,
ly = (GAME_HEIGHT + gohigh) / 2;
if (PAGE_CONTAINS(ly - gohigh - 1, ly + 1)) {
u8g.setColorIndex(0);
u8g.drawBox(lx - 1, ly - gohigh - 1, gowide + 2, gohigh + 2);
u8g.setColorIndex(1);
if (ui.get_blink()) lcd_put_u8str(lx, ly, F("GAME OVER"));
set_color(color::BLACK);
draw_box(lx - 1, ly - gohigh - 1, gowide + 2, gohigh + 2);
set_color(color::WHITE);
if (ui.get_blink()) draw_string(lx, ly, F("GAME OVER"));
}
}

3 changes: 1 addition & 2 deletions Marlin/src/lcd/menu/game/game.h
Original file line number Diff line number Diff line change
@@ -22,9 +22,8 @@
#pragma once

#include "../../../inc/MarlinConfigPre.h"
#include "../../dogm/marlinui_DOGM.h"
#include "../../lcdprint.h"
#include "../../marlinui.h"
#include "types.h"

//#define MUTE_GAMES

86 changes: 58 additions & 28 deletions Marlin/src/lcd/menu/game/invaders.cpp
Original file line number Diff line number Diff line change
@@ -29,11 +29,11 @@
#define CANNON_W 11
#define CANNON_H 8
#define CANNON_VEL 4
#define CANNON_Y (LCD_PIXEL_HEIGHT - 1 - CANNON_H)
#define CANNON_Y (GAME_HEIGHT - 1 - CANNON_H)

#define INVADER_VEL 3

#define INVADER_TOP MENU_FONT_ASCENT
#define INVADER_TOP GAME_FONT_ASCENT
#define INVADERS_WIDE ((INVADER_COL_W) * (INVADER_COLS))
#define INVADERS_HIGH ((INVADER_ROW_H) * (INVADER_ROWS))

@@ -48,6 +48,16 @@

#define INVADER_RIGHT ((INVADER_COLS) * (INVADER_COL_W))

#if IS_DWIN_MARLINUI
#define INVADER_COLOR { MarlinGame::color::GREEN, MarlinGame::color::CYAN, MarlinGame::color::YELLOW }
#define CANNON_COLOR MarlinGame::color::WHITE
#define LASER_COLOR MarlinGame::color::WHITE // Shot by player
#define BULLET_COLOR LASER_COLOR // Shot by invader
#define LIFE_COLOR CANNON_COLOR
#define UFO_COLOR MarlinGame::color::MAGENTA
#define EXPLOSION_COLOR MarlinGame::color::RED
#endif

// 11x8
const unsigned char invader[3][2][16] PROGMEM = {
{ { B00000110,B00000000,
@@ -175,7 +185,7 @@ inline void update_invader_data() {
}
idat.leftmost = 0;
for (uint8_t i = 0; i < INVADER_COLS; ++i) { if (TEST(inv_mask, i)) break; idat.leftmost -= INVADER_COL_W; }
idat.rightmost = LCD_PIXEL_WIDTH - (INVADERS_WIDE);
idat.rightmost = GAME_WIDTH - (INVADERS_WIDE);
for (uint8_t i = INVADER_COLS; i--;) { if (TEST(inv_mask, i)) break; idat.rightmost += INVADER_COL_W; }
if (idat.count == 2) idat.dir = idat.dir > 0 ? INVADER_VEL + 1 : -(INVADER_VEL + 1);
}
@@ -195,7 +205,7 @@ inline void reset_invaders() {

inline void spawn_ufo() {
idat.ufov = random(0, 2) ? 1 : -1;
idat.ufox = idat.ufov > 0 ? -(UFO_W) : LCD_PIXEL_WIDTH - 1;
idat.ufox = idat.ufov > 0 ? -(UFO_W) : GAME_WIDTH - 1;
}

inline void reset_player() {
@@ -205,7 +215,7 @@ inline void reset_player() {

inline void fire_cannon() {
idat.laser.x = idat.cannon_x + CANNON_W / 2;
idat.laser.y = LCD_PIXEL_HEIGHT - CANNON_H - (LASER_H);
idat.laser.y = GAME_HEIGHT - CANNON_H - (LASER_H);
idat.laser.v = -(LASER_H);
}

@@ -235,7 +245,7 @@ void InvadersGame::game_screen() {
if (ui.first_page) {

// Update Cannon Position
int16_t ep = constrain(int16_t(ui.encoderPosition), 0, (LCD_PIXEL_WIDTH - (CANNON_W)) / (CANNON_VEL));
int16_t ep = constrain(int16_t(ui.encoderPosition), 0, (GAME_WIDTH - (CANNON_W)) / (CANNON_VEL));
ui.encoderPosition = ep;

ep *= (CANNON_VEL);
@@ -246,7 +256,7 @@ void InvadersGame::game_screen() {
if (game_state) do {

// Move the UFO, if any
if (idat.ufov) { idat.ufox += idat.ufov; if (!WITHIN(idat.ufox, -(UFO_W), LCD_PIXEL_WIDTH - 1)) idat.ufov = 0; }
if (idat.ufov) { idat.ufox += idat.ufov; if (!WITHIN(idat.ufox, -(UFO_W), GAME_WIDTH - 1)) idat.ufov = 0; }

if (game_state > 1) { if (--game_state == 2) { reset_invaders(); } else if (game_state == 100) { game_state = 1; } break; }

@@ -326,7 +336,7 @@ void InvadersGame::game_screen() {
if (b->v) {
// Update alien bullet position
b->y += b->v;
if (b->y >= LCD_PIXEL_HEIGHT)
if (b->y >= GAME_HEIGHT)
b->v = 0; // Offscreen
else if (b->y >= CANNON_Y && WITHIN(b->x, idat.cannon_x, idat.cannon_x + CANNON_W - 1))
kill_cannon(game_state, 120); // Hit the cannon
@@ -365,7 +375,7 @@ void InvadersGame::game_screen() {

if (!idat.quit_count) exit_game();

u8g.setColorIndex(1);
frame_start();

// Draw invaders
if (PAGE_CONTAINS(idat.pos.y, idat.pos.y + idat.botmost * (INVADER_ROW_H) - 2 - 1)) {
@@ -375,8 +385,13 @@ void InvadersGame::game_screen() {
if (PAGE_CONTAINS(yy, yy + INVADER_H - 1)) {
int8_t xx = idat.pos.x;
for (uint8_t x = 0; x < INVADER_COLS; ++x) {
if (TEST(idat.bugs[y], x))
u8g.drawBitmapP(xx, yy, 2, INVADER_H, invader[type][idat.game_blink]);
if (TEST(idat.bugs[y], x)) {
#if IS_DWIN_MARLINUI
constexpr color invader_color[] = INVADER_COLOR;
set_color(invader_color[type]);
#endif
draw_bitmap(xx, yy, 2, INVADER_H, invader[type][idat.game_blink]);
}
xx += INVADER_COL_W;
}
}
@@ -385,44 +400,59 @@ void InvadersGame::game_screen() {
}

// Draw UFO
if (idat.ufov && PAGE_UNDER(UFO_H + 2))
u8g.drawBitmapP(idat.ufox, 2, 2, UFO_H, ufo);
if (idat.ufov && PAGE_UNDER(UFO_H + 2)) {
TERN_(IS_DWIN_MARLINUI, set_color(UFO_COLOR));
draw_bitmap(idat.ufox, 2, 2, UFO_H, ufo);
}

// Draw cannon
if (game_state && PAGE_CONTAINS(CANNON_Y, CANNON_Y + CANNON_H - 1) && (game_state < 2 || (game_state & 0x02)))
u8g.drawBitmapP(idat.cannon_x, CANNON_Y, 2, CANNON_H, cannon);
if (game_state && PAGE_CONTAINS(CANNON_Y, CANNON_Y + CANNON_H - 1) && (game_state < 2 || (game_state & 0x02))) {
TERN_(IS_DWIN_MARLINUI, set_color(CANNON_COLOR));
draw_bitmap(idat.cannon_x, CANNON_Y, 2, CANNON_H, cannon);
}

// Draw laser
if (idat.laser.v && PAGE_CONTAINS(idat.laser.y, idat.laser.y + LASER_H - 1))
u8g.drawVLine(idat.laser.x, idat.laser.y, LASER_H);
if (idat.laser.v && PAGE_CONTAINS(idat.laser.y, idat.laser.y + LASER_H - 1)) {
TERN_(IS_DWIN_MARLINUI, set_color(LASER_COLOR));
draw_vline(idat.laser.x, idat.laser.y, LASER_H);
}

// Draw invader bullets
for (uint8_t i = 0; i < COUNT(idat.bullet); ++i) {
if (idat.bullet[i].v && PAGE_CONTAINS(idat.bullet[i].y - (SHOT_H - 1), idat.bullet[i].y))
u8g.drawVLine(idat.bullet[i].x, idat.bullet[i].y - (SHOT_H - 1), SHOT_H);
if (idat.bullet[i].v && PAGE_CONTAINS(idat.bullet[i].y - (SHOT_H - 1), idat.bullet[i].y)) {
TERN_(IS_DWIN_MARLINUI, set_color(BULLET_COLOR));
draw_vline(idat.bullet[i].x, idat.bullet[i].y - (SHOT_H - 1), SHOT_H);
}
}

// Draw explosion
if (idat.explod.v && PAGE_CONTAINS(idat.explod.y, idat.explod.y + 7 - 1)) {
u8g.drawBitmapP(idat.explod.x, idat.explod.y, 2, 7, explosion);
TERN_(IS_DWIN_MARLINUI, set_color(EXPLOSION_COLOR));
draw_bitmap(idat.explod.x, idat.explod.y, 2, 7, explosion);
--idat.explod.v;
}

// Everything else is white
TERN_(IS_DWIN_MARLINUI, set_color(color::WHITE));

// Blink GAME OVER when game is over
if (!game_state) draw_game_over();

if (PAGE_UNDER(MENU_FONT_ASCENT - 1)) {
// Draw Score
//const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
constexpr uint8_t sx = 0;
lcd_put_int(sx, MENU_FONT_ASCENT - 1, score);

if (PAGE_UNDER(GAME_FONT_ASCENT - 1)) {
// Draw lives
if (idat.cannons_left)
for (uint8_t i = 1; i <= idat.cannons_left; ++i)
u8g.drawBitmapP(LCD_PIXEL_WIDTH - i * (LIFE_W), 6 - (LIFE_H), 1, LIFE_H, life);
for (uint8_t i = 1; i <= idat.cannons_left; ++i) {
TERN_(IS_DWIN_MARLINUI, set_color(LIFE_COLOR));
draw_bitmap(GAME_WIDTH - i * (LIFE_W), 6 - (LIFE_H), 1, LIFE_H, life);
}

// Draw Score
//const uint8_t sx = (GAME_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * GAME_FONT_WIDTH) / 2;
constexpr uint8_t sx = 0;
draw_int(sx, GAME_FONT_ASCENT - 1, score);
}

frame_end();
}

void InvadersGame::enter_game() {
49 changes: 27 additions & 22 deletions Marlin/src/lcd/menu/game/snake.cpp
Original file line number Diff line number Diff line change
@@ -28,13 +28,13 @@

#define SNAKE_BOX 4

#define HEADER_H (MENU_FONT_ASCENT - 2)
#define HEADER_H (GAME_FONT_ASCENT - 2)
#define SNAKE_WH (SNAKE_BOX + 1)

#define IDEAL_L 2
#define IDEAL_R (LCD_PIXEL_WIDTH - 1 - 2)
#define IDEAL_R (GAME_WIDTH - 1 - 2)
#define IDEAL_T (HEADER_H + 2)
#define IDEAL_B (LCD_PIXEL_HEIGHT - 1 - 2)
#define IDEAL_B (GAME_HEIGHT - 1 - 2)
#define IDEAL_W (IDEAL_R - (IDEAL_L) + 1)
#define IDEAL_H (IDEAL_B - (IDEAL_T) + 1)

@@ -43,9 +43,9 @@

#define BOARD_W ((SNAKE_WH) * (GAME_W) + 1)
#define BOARD_H ((SNAKE_WH) * (GAME_H) + 1)
#define BOARD_L ((LCD_PIXEL_WIDTH - (BOARD_W) + 1) / 2)
#define BOARD_L ((GAME_WIDTH - (BOARD_W) + 1) / 2)
#define BOARD_R (BOARD_L + BOARD_W - 1)
#define BOARD_T (((LCD_PIXEL_HEIGHT + IDEAL_T) - (BOARD_H)) / 2)
#define BOARD_T (((GAME_HEIGHT + IDEAL_T) - (BOARD_H)) / 2)
#define BOARD_B (BOARD_T + BOARD_H - 1)

#define GAMEX(X) (BOARD_L + ((X) * (SNAKE_WH)))
@@ -228,15 +228,10 @@ void SnakeGame::game_screen() {

} while(0);

u8g.setColorIndex(1);
frame_start();

// Draw Score
if (PAGE_UNDER(HEADER_H)) lcd_put_int(0, HEADER_H - 1, score);

// DRAW THE PLAYFIELD BORDER
u8g.drawFrame(BOARD_L - 2, BOARD_T - 2, BOARD_R - BOARD_L + 4, BOARD_B - BOARD_T + 4);

// Draw the snake (tail)
// Draw the snake (tail) in green
TERN_(IS_DWIN_MARLINUI, set_color(color::GREEN));
#if SNAKE_WH < 2

// At this scale just draw a line
@@ -245,11 +240,11 @@ void SnakeGame::game_screen() {
if (p.x == q.x) {
const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
if (PAGE_CONTAINS(y1, y2))
u8g.drawVLine(GAMEX(p.x), y1, y2 - y1 + 1);
draw_vline(GAMEX(p.x), y1, y2 - y1 + 1);
}
else if (PAGE_CONTAINS(GAMEY(p.y), GAMEY(p.y))) {
const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
u8g.drawHLine(x1, GAMEY(p.y), x2 - x1 + 1);
draw_hline(x1, GAMEY(p.y), x2 - x1 + 1);
}
}

@@ -261,13 +256,13 @@ void SnakeGame::game_screen() {
if (p.x == q.x) {
const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
if (PAGE_CONTAINS(y1, y2 + 1))
u8g.drawFrame(GAMEX(p.x), y1, 2, y2 - y1 + 1 + 1);
draw_frame(GAMEX(p.x), y1, 2, y2 - y1 + 1 + 1);
}
else {
const int8_t py = GAMEY(p.y);
if (PAGE_CONTAINS(py, py + 1)) {
const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
u8g.drawFrame(x1, py, x2 - x1 + 1 + 1, 2);
draw_frame(x1, py, x2 - x1 + 1 + 1, 2);
}
}
}
@@ -283,7 +278,7 @@ void SnakeGame::game_screen() {
for (int8_t i = y1; i <= y2; ++i) {
const int8_t y = GAMEY(i);
if (PAGE_CONTAINS(y, y + SNAKE_SIZ - 1))
u8g.drawBox(GAMEX(p.x), y, SNAKE_SIZ, SNAKE_SIZ);
draw_box(GAMEX(p.x), y, SNAKE_SIZ, SNAKE_SIZ);
}
}
}
@@ -292,26 +287,36 @@ void SnakeGame::game_screen() {
if (PAGE_CONTAINS(py, py + SNAKE_SIZ - 1)) {
const int8_t x1 = _MIN(p.x, q.x), x2 = _MAX(p.x, q.x);
for (int8_t i = x1; i <= x2; ++i)
u8g.drawBox(GAMEX(i), py, SNAKE_SIZ, SNAKE_SIZ);
draw_box(GAMEX(i), py, SNAKE_SIZ, SNAKE_SIZ);
}
}
}

#endif

// Draw food
// Draw food in red
TERN_(IS_DWIN_MARLINUI, set_color(color::RED));
const int8_t fy = GAMEY(sdat.foody);
if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
const int8_t fx = GAMEX(sdat.foodx);
u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
draw_frame(fx, fy, FOOD_WH, FOOD_WH);
if (FOOD_WH == 5) draw_pixel(fx + 2, fy + 2);
}

// Draw the playfield border
TERN_(IS_DWIN_MARLINUI, set_color(color::WHITE));
draw_frame(BOARD_L - 2, BOARD_T - 2, BOARD_R - BOARD_L + 4, BOARD_B - BOARD_T + 4);

// Draw Score
if (PAGE_UNDER(HEADER_H)) draw_int(0, HEADER_H - 1, score);

// Draw GAME OVER
if (!game_state) draw_game_over();

// A click always exits this game
if (ui.use_click()) exit_game();

frame_end();
}

void SnakeGame::enter_game() {
128 changes: 127 additions & 1 deletion Marlin/src/lcd/menu/game/types.h
Original file line number Diff line number Diff line change
@@ -21,7 +21,14 @@
*/
#pragma once

#include <stdint.h>
#include "../../../inc/MarlinConfigPre.h"
#include "../../marlinui.h"

#if HAS_MARLINUI_U8GLIB
#include "../../dogm/game.h"
#elif IS_DWIN_MARLINUI
#include "../../e3v2/marlinui/game.h"
#endif

typedef struct { int8_t x, y; } pos_t;

@@ -41,6 +48,125 @@ class MarlinGame {
static bool game_frame();
static void draw_game_over();
static void exit_game();

public:
static void init_game(const uint8_t init_state, const screenFunc_t screen);

// Game rendering API, based on U8glib's API.
// See the @see comments for the U8glib API documentation corresponding to each function.

/**
* @brief The colors available for drawing games.
* @note Renderer implementations will map these colors to the closest
* available color on the screen, as long as that color is not black.
* Thus, black is guranteed to be black on all screens, but other colors may differ.
* On black-and-white screens, all colors but black will be white.
*/
enum class color {
BLACK, WHITE
#if IS_DWIN_MARLINUI
, RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA
#endif
};

protected:
/**
* @brief Called before any draw calls in the current frame.
*/
static void frame_start();

/**
* @brief Called after all draw calls in the current frame.
*/
static void frame_end();

/**
* @brief Set the color for subsequent draw calls.
* @param color The color to use for subsequent draw calls.
*/
static void set_color(const color color);

/**
* @brief Draw a horizontal line.
* @param x The x-coordinate of the start of the line.
* @param y The y-coordinate of the line.
* @param l The length of the line.
* @see https://github.com/olikraus/u8glib/wiki/userreference#drawhline
*/
static void draw_hline(const game_dim_t x, const game_dim_t y, const game_dim_t l);

/**
* @brief Draw a vertical line.
* @param x The x-coordinate of the line.
* @param y The y-coordinate of the start of the line.
* @param l The length of the line.
* @see https://github.com/olikraus/u8glib/wiki/userreference#drawvline
*/
static void draw_vline(const game_dim_t x, const game_dim_t y, const game_dim_t l);

/**
* @brief Draw a outlined rectangle (frame).
* @param x The x-coordinate of the top-left corner of the frame.
* @param y The y-coordinate of the top-left corner of the frame.
* @param w The width of the frame.
* @param h The height of the frame.
* @see https://github.com/olikraus/u8glib/wiki/userreference#drawframe
*/
static void draw_frame(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h);

/**
* @brief Draw a filled rectangle (box).
* @param x The x-coordinate of the top-left corner of the box.
* @param y The y-coordinate of the top-left corner of the box.
* @param w The width of the box.
* @param h The height of the box.
* @see https://github.com/olikraus/u8glib/wiki/userreference#drawbox
*/
static void draw_box(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h);

/**
* @brief Draw a pixel.
* @param x The x-coordinate of the pixel.
* @param y The y-coordinate of the pixel.
* @see https://github.com/olikraus/u8glib/wiki/userreference#drawpixel
*/
static void draw_pixel(const game_dim_t x, const game_dim_t y);

/**
* @brief Draw a bitmap.
* @param x The x-coordinate of the top-left corner of the bitmap.
* @param y The y-coordinate of the top-left corner of the bitmap.
* @param bytes_per_row The number of bytes per row in the bitmap (Width = bytes_per_row * 8).
* @param rows The number of rows in the bitmap (= Height).
* @param bitmap The bitmap to draw.
* @see https://github.com/olikraus/u8glib/wiki/userreference#drawbitmap
*/
static void draw_bitmap(const game_dim_t x, const game_dim_t y, const game_dim_t bytes_per_row, const game_dim_t rows, const pgm_bitmap_t bitmap);

/**
* @brief Draw a string.
* @param x The x-coordinate of the string.
* @param y The y-coordinate of the string.
* @param str The string to draw.
* @see lcd_moveto + lcd_put_u8str
* @note The font size is available using the GAME_FONT_WIDTH and GAME_FONT_ASCENT constants.
*
* @note On the DWIN renderer, strings may flush the screen, which may cause flickering.
* Consider drawing strings after all other elements have been drawn.
*/
static int draw_string(const game_dim_t x, const game_dim_t y, const char *str);
static int draw_string(const game_dim_t x, const game_dim_t y, FSTR_P const str);

/**
* @brief Draw an integer.
* @param x The x-coordinate of the integer.
* @param y The y-coordinate of the integer.
* @param value The integer to draw.
* @see lcd_put_int
* @note The font size is available using the GAME_FONT_WIDTH and GAME_FONT_ASCENT constants.
*
* @note On the DWIN renderer, strings may flush the screen, which may cause flickering.
* Consider drawing strings after all other elements have been drawn.
*/
static void draw_int(const game_dim_t x, const game_dim_t y, const int value);
};
5 changes: 3 additions & 2 deletions buildroot/tests/STM32F103RE_creality
Original file line number Diff line number Diff line change
@@ -20,8 +20,9 @@ exec_test $1 $2 "Ender-3 V2 - JyersUI (ABL Bilinear/Manual)" "$3"

use_example_configs "Creality/Ender-3 V2/CrealityV422/CrealityUI"
opt_disable DWIN_CREALITY_LCD PIDTEMP
opt_enable DWIN_MARLINUI_LANDSCAPE LCD_ENDSTOP_TEST AUTO_BED_LEVELING_UBL BLTOUCH Z_SAFE_HOMING MPCTEMP MPC_AUTOTUNE
exec_test $1 $2 "Ender-3 V2 - MarlinUI (UBL+BLTOUCH, MPCTEMP, LCD_ENDSTOP_TEST)" "$3"
opt_enable DWIN_MARLINUI_LANDSCAPE LCD_ENDSTOP_TEST AUTO_BED_LEVELING_UBL BLTOUCH Z_SAFE_HOMING MPCTEMP MPC_AUTOTUNE \
MARLIN_BRICKOUT MARLIN_INVADERS MARLIN_SNAKE GAMES_EASTER_EGG
exec_test $1 $2 "Ender-3 V2 - MarlinUI (Games, UBL+BLTOUCH, MPCTEMP, LCD_ENDSTOP_TEST)" "$3"

use_example_configs "Creality/Ender-3 S1/STM32F1"
opt_disable DWIN_CREALITY_LCD Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN AUTO_BED_LEVELING_BILINEAR CANCEL_OBJECTS FWRETRACT EVENT_GCODE_SD_ABORT

0 comments on commit 22977c8

Please sign in to comment.