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 created on GitHub.com and signed with GitHub’s verified signature.
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.