Procházet zdrojové kódy

Split up games into separate files

Scott Lahteine před 6 roky
rodič
revize
240ea1bbb3

+ 1
- 1
Marlin/src/feature/runout.h Zobrazit soubor

@@ -49,7 +49,7 @@ class FilamentMonitorBase {
49 49
     #if ENABLED(HOST_ACTION_COMMANDS)
50 50
       static bool host_handling;
51 51
     #else
52
-      constexpr static bool host_handling = false;
52
+      static constexpr bool host_handling = false;
53 53
     #endif
54 54
 };
55 55
 

+ 5
- 3
Marlin/src/inc/Conditionals_LCD.h Zobrazit soubor

@@ -510,9 +510,11 @@
510 510
 #define HAS_FILAMENT_SENSOR   ENABLED(FILAMENT_RUNOUT_SENSOR)
511 511
 
512 512
 #define Z_MULTI_STEPPER_DRIVERS EITHER(Z_DUAL_STEPPER_DRIVERS, Z_TRIPLE_STEPPER_DRIVERS)
513
-#define Z_MULTI_ENDSTOPS EITHER(Z_DUAL_ENDSTOPS, Z_TRIPLE_ENDSTOPS)
514
-#define HAS_EXTRA_ENDSTOPS (EITHER(X_DUAL_ENDSTOPS, Y_DUAL_ENDSTOPS) || Z_MULTI_ENDSTOPS)
515
-#define HAS_GAME_MENU (1 < ENABLED(MARLIN_BRICKOUT) + ENABLED(MARLIN_INVADERS) + ENABLED(MARLIN_SNAKE))
513
+#define Z_MULTI_ENDSTOPS        EITHER(Z_DUAL_ENDSTOPS, Z_TRIPLE_ENDSTOPS)
514
+#define HAS_EXTRA_ENDSTOPS     (EITHER(X_DUAL_ENDSTOPS, Y_DUAL_ENDSTOPS) || Z_MULTI_ENDSTOPS)
515
+
516
+#define HAS_GAMES     ANY(MARLIN_BRICKOUT, MARLIN_INVADERS, MARLIN_SNAKE, MARLIN_MAZE)
517
+#define HAS_GAME_MENU (1 < ENABLED(MARLIN_BRICKOUT) + ENABLED(MARLIN_INVADERS) + ENABLED(MARLIN_SNAKE) + ENABLED(MARLIN_MAZE))
516 518
 
517 519
 #define IS_SCARA     EITHER(MORGAN_SCARA, MAKERARM_SCARA)
518 520
 #define IS_KINEMATIC (ENABLED(DELTA) || IS_SCARA)

+ 213
- 0
Marlin/src/lcd/menu/game/brickout.cpp Zobrazit soubor

@@ -0,0 +1,213 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+#include "../../../inc/MarlinConfigPre.h"
24
+
25
+#if ENABLED(MARLIN_BRICKOUT)
26
+
27
+#include "game.h"
28
+
29
+#define BRICK_H      5
30
+#define BRICK_TOP    MENU_FONT_ASCENT
31
+#define BRICK_ROWS   4
32
+#define BRICK_COLS  16
33
+
34
+#define PADDLE_H     2
35
+#define PADDLE_VEL   3
36
+#define PADDLE_W    ((LCD_PIXEL_WIDTH) / 8)
37
+#define PADDLE_Y    (LCD_PIXEL_HEIGHT - 1 - PADDLE_H)
38
+
39
+#define BRICK_W     ((LCD_PIXEL_WIDTH) / (BRICK_COLS))
40
+#define BRICK_BOT   (BRICK_TOP + BRICK_H * BRICK_ROWS - 1)
41
+
42
+#define BRICK_COL(X) ((X) / (BRICK_W))
43
+#define BRICK_ROW(Y) ((Y - (BRICK_TOP)) / (BRICK_H))
44
+
45
+uint8_t balls_left, brick_count;
46
+uint16_t bricks[BRICK_ROWS];
47
+
48
+inline void reset_bricks(const uint16_t v) {
49
+  brick_count = (BRICK_COLS) * (BRICK_ROWS);
50
+  LOOP_L_N(i, BRICK_ROWS) bricks[i] = v;
51
+}
52
+
53
+int8_t paddle_x, hit_dir;
54
+fixed_t ballx, bally, ballh, ballv;
55
+
56
+void reset_ball() {
57
+  constexpr uint8_t ball_dist = 24;
58
+  bally = BTOF(PADDLE_Y - ball_dist);
59
+  ballv = FTOP(1.3f);
60
+  ballh = -FTOP(1.25f);
61
+  uint8_t bx = paddle_x + (PADDLE_W) / 2 + ball_dist;
62
+  if (bx >= LCD_PIXEL_WIDTH - 10) { bx -= ball_dist * 2; ballh = -ballh; }
63
+  ballx = BTOF(bx);
64
+  hit_dir = -1;
65
+}
66
+
67
+void BrickoutGame::game_screen() {
68
+  if (game_frame()) do {     // Run logic twice for finer resolution
69
+    // Update Paddle Position
70
+    paddle_x = (int8_t)ui.encoderPosition;
71
+    paddle_x = constrain(paddle_x, 0, (LCD_PIXEL_WIDTH - (PADDLE_W)) / (PADDLE_VEL));
72
+    ui.encoderPosition = paddle_x;
73
+    paddle_x *= (PADDLE_VEL);
74
+
75
+    // Run the ball logic
76
+    if (game_state) do {
77
+
78
+      // Provisionally update the position
79
+      const fixed_t newx = ballx + ballh, newy = bally + ballv;  // current next position
80
+      if (!WITHIN(newx, 0, BTOF(LCD_PIXEL_WIDTH - 1))) {    // out in x?
81
+        ballh = -ballh; _BUZZ(5, 220);                      // bounce x
82
+      }
83
+      if (newy < 0) {                                       // out in y?
84
+        ballv = -ballv; _BUZZ(5, 280);                      // bounce v
85
+        hit_dir = 1;
86
+      }
87
+      // Did the ball go below the bottom?
88
+      else if (newy > BTOF(LCD_PIXEL_HEIGHT)) {
89
+        BUZZ(500, 75);
90
+        if (--balls_left) reset_ball(); else game_state = 0;
91
+        break; // done
92
+      }
93
+
94
+      // Is the ball colliding with a brick?
95
+      if (WITHIN(newy, BTOF(BRICK_TOP), BTOF(BRICK_BOT))) {
96
+        const int8_t bit = BRICK_COL(FTOB(newx)), row = BRICK_ROW(FTOB(newy));
97
+        const uint16_t mask = _BV(bit);
98
+        if (bricks[row] & mask) {
99
+          // Yes. Remove it!
100
+          bricks[row] &= ~mask;
101
+          // Score!
102
+          score += BRICK_ROWS - row;
103
+          // If bricks are gone, go to reset state
104
+          if (!--brick_count) game_state = 2;
105
+          // Bounce the ball cleverly
106
+          if ((ballv < 0) == (hit_dir < 0)) { ballv = -ballv; ballh += fixed_t(random(-16, 16)); _BUZZ(5, 880); }
107
+                                       else { ballh = -ballh; ballv += fixed_t(random(-16, 16)); _BUZZ(5, 640); }
108
+        }
109
+      }
110
+      // Is the ball moving down and in paddle range?
111
+      else if (ballv > 0 && WITHIN(newy, BTOF(PADDLE_Y), BTOF(PADDLE_Y + PADDLE_H))) {
112
+        // Ball actually hitting paddle
113
+        const int8_t diff = FTOB(newx) - paddle_x;
114
+        if (WITHIN(diff, 0, PADDLE_W - 1)) {
115
+
116
+          // Reverse Y direction
117
+          ballv = -ballv; _BUZZ(3, 880);
118
+          hit_dir = -1;
119
+
120
+          // Near edges affects X velocity
121
+          const bool is_left_edge = (diff <= 1);
122
+          if (is_left_edge || diff >= PADDLE_W-1 - 1) {
123
+            if ((ballh > 0) == is_left_edge) ballh = -ballh;
124
+          }
125
+          else if (diff <= 3) {
126
+            ballh += fixed_t(random(-64, 0));
127
+            NOLESS(ballh, BTOF(-2));
128
+            NOMORE(ballh, BTOF(2));
129
+          }
130
+          else if (diff >= PADDLE_W-1 - 3) {
131
+            ballh += fixed_t(random( 0, 64));
132
+            NOLESS(ballh, BTOF(-2));
133
+            NOMORE(ballh, BTOF(2));
134
+          }
135
+
136
+          // Paddle hit after clearing the board? Reset the board.
137
+          if (game_state == 2) { reset_bricks(0xFFFF); game_state = 1; }
138
+        }
139
+      }
140
+
141
+      ballx += ballh; bally += ballv;   // update with new velocity
142
+
143
+    } while (false);
144
+  }
145
+
146
+  u8g.setColorIndex(1);
147
+
148
+  // Draw bricks
149
+  if (PAGE_CONTAINS(BRICK_TOP, BRICK_BOT)) {
150
+    for (uint8_t y = 0; y < BRICK_ROWS; ++y) {
151
+      const uint8_t yy = y * BRICK_H + BRICK_TOP;
152
+      if (PAGE_CONTAINS(yy, yy + BRICK_H - 1)) {
153
+        for (uint8_t x = 0; x < BRICK_COLS; ++x) {
154
+          if (TEST(bricks[y], x)) {
155
+            const uint8_t xx = x * BRICK_W;
156
+            for (uint8_t v = 0; v < BRICK_H - 1; ++v)
157
+              if (PAGE_CONTAINS(yy + v, yy + v))
158
+                u8g.drawHLine(xx, yy + v, BRICK_W - 1);
159
+          }
160
+        }
161
+      }
162
+    }
163
+  }
164
+
165
+  // Draw paddle
166
+  if (PAGE_CONTAINS(PADDLE_Y-1, PADDLE_Y)) {
167
+    u8g.drawHLine(paddle_x, PADDLE_Y, PADDLE_W);
168
+    #if PADDLE_H > 1
169
+      u8g.drawHLine(paddle_x, PADDLE_Y-1, PADDLE_W);
170
+      #if PADDLE_H > 2
171
+        u8g.drawHLine(paddle_x, PADDLE_Y-2, PADDLE_W);
172
+      #endif
173
+    #endif
174
+  }
175
+
176
+  // Draw ball while game is running
177
+  if (game_state) {
178
+    const uint8_t by = FTOB(bally);
179
+    if (PAGE_CONTAINS(by, by+1))
180
+      u8g.drawFrame(FTOB(ballx), by, 2, 2);
181
+  }
182
+  // Or draw GAME OVER
183
+  else
184
+    draw_game_over();
185
+
186
+  if (PAGE_UNDER(MENU_FONT_ASCENT)) {
187
+    // Score Digits
188
+    //const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
189
+    constexpr uint8_t sx = 0;
190
+    lcd_moveto(sx, MENU_FONT_ASCENT - 1);
191
+    lcd_put_int(score);
192
+
193
+    // Balls Left
194
+    lcd_moveto(LCD_PIXEL_WIDTH - MENU_FONT_WIDTH * 3, MENU_FONT_ASCENT - 1);
195
+    PGM_P const ohs = PSTR("ooo\0\0");
196
+    lcd_put_u8str_P(ohs + 3 - balls_left);
197
+  }
198
+
199
+  // A click always exits this game
200
+  if (ui.use_click()) ui.goto_previous_screen();
201
+}
202
+
203
+void BrickoutGame::enter_game() {
204
+  init_game(2, game_screen); // 2 = reset bricks on paddle hit
205
+  constexpr uint8_t paddle_start = SCREEN_M - (PADDLE_W) / 2;
206
+  paddle_x = paddle_start;
207
+  balls_left = 3;
208
+  reset_bricks(0x0000);
209
+  reset_ball();
210
+  ui.encoderPosition = paddle_start / (PADDLE_VEL);
211
+}
212
+
213
+#endif // MARLIN_BRICKOUT

+ 66
- 0
Marlin/src/lcd/menu/game/game.cpp Zobrazit soubor

@@ -0,0 +1,66 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+#include "../../../inc/MarlinConfigPre.h"
24
+
25
+#if HAS_GAMES
26
+
27
+#include "game.h"
28
+
29
+int MarlinGame::score;
30
+uint8_t MarlinGame::game_state;
31
+millis_t MarlinGame::next_frame;
32
+
33
+bool MarlinGame::game_frame() {
34
+  static int8_t slew;
35
+  if (ui.first_page) slew = 2;
36
+  ui.refresh(LCDVIEW_CALL_NO_REDRAW); // Refresh as often as possible
37
+  return (game_state && slew-- > 0);
38
+}
39
+
40
+void MarlinGame::draw_game_over() {
41
+  constexpr int8_t gowide = (MENU_FONT_WIDTH) * 9,
42
+                   gohigh = MENU_FONT_ASCENT - 3,
43
+                       lx = (LCD_PIXEL_WIDTH - gowide) / 2,
44
+                       ly = (LCD_PIXEL_HEIGHT + gohigh) / 2;
45
+  if (PAGE_CONTAINS(ly - gohigh - 1, ly + 1)) {
46
+    u8g.setColorIndex(0);
47
+    u8g.drawBox(lx - 1, ly - gohigh - 1, gowide + 2, gohigh + 2);
48
+    u8g.setColorIndex(1);
49
+    if (ui.get_blink()) {
50
+      lcd_moveto(lx, ly);
51
+      lcd_put_u8str_P(PSTR("GAME OVER"));
52
+    }
53
+  }
54
+}
55
+
56
+void MarlinGame::init_game(const uint8_t init_state, const screenFunc_t screen) {
57
+  score = 0;
58
+  game_state = init_state;
59
+  ui.encoder_direction_normal();
60
+  ui.goto_screen(screen);
61
+  ui.defer_status_screen();
62
+}
63
+
64
+//void MarlinGame::exit_game() { ui.goto_previous_screen(); }
65
+
66
+#endif // HAS_GAMES

+ 78
- 0
Marlin/src/lcd/menu/game/game.h Zobrazit soubor

@@ -0,0 +1,78 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+#pragma once
23
+
24
+#include "../../../inc/MarlinConfigPre.h"
25
+#include "../../dogm/ultralcd_DOGM.h"
26
+#include "../../lcdprint.h"
27
+#include "../../ultralcd.h"
28
+
29
+//#define MUTE_GAMES
30
+
31
+#ifdef MUTE_GAMES
32
+  #define _BUZZ(D,F) NOOP
33
+#else
34
+  #define _BUZZ(D,F) BUZZ(D,F)
35
+#endif
36
+
37
+// Simple 8:8 fixed-point
38
+typedef int16_t fixed_t;
39
+#define FTOP(F) fixed_t((F)*256.0f)
40
+#define PTOF(P) (float(P)*(1.0f/256.0f))
41
+#define BTOF(X) (fixed_t(X)<<8)
42
+#define FTOB(X) int8_t(fixed_t(X)>>8)
43
+
44
+#define SCREEN_M ((LCD_PIXEL_WIDTH) / 2)
45
+
46
+#if HAS_GAME_MENU
47
+  void menu_game();
48
+#endif
49
+
50
+class MarlinGame {
51
+protected:
52
+  static int score;
53
+  static uint8_t game_state;
54
+  static millis_t next_frame;
55
+
56
+  static bool game_frame();
57
+  static void draw_game_over();
58
+public:
59
+  MarlinGame() {}
60
+  static void init_game(const uint8_t init_state, const screenFunc_t screen);
61
+};
62
+
63
+#if ENABLED(MARLIN_BRICKOUT)
64
+  class BrickoutGame : MarlinGame { public: static void enter_game(); static void game_screen(); };
65
+  extern BrickoutGame brickout;
66
+#endif
67
+#if ENABLED(MARLIN_INVADERS)
68
+  class InvadersGame : MarlinGame { public: static void enter_game(); static void game_screen(); };
69
+  extern InvadersGame invaders;
70
+#endif
71
+#if ENABLED(MARLIN_SNAKE)
72
+  class SnakeGame : MarlinGame { public: static void enter_game(); static void game_screen(); };
73
+  extern SnakeGame snake;
74
+#endif
75
+#if ENABLED(MARLIN_MAZE)
76
+  class MazeGame : MarlinGame { public: static void enter_game(); static void game_screen(); };
77
+  extern MazeGame maze;
78
+#endif

+ 460
- 0
Marlin/src/lcd/menu/game/invaders.cpp Zobrazit soubor

@@ -0,0 +1,460 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+#include "../../../inc/MarlinConfigPre.h"
24
+
25
+#if ENABLED(MARLIN_INVADERS)
26
+
27
+#include "game.h"
28
+
29
+// 11x8
30
+const unsigned char invader[3][2][16] PROGMEM = {
31
+  { { B00000110,B00000000,
32
+      B00001111,B00000000,
33
+      B00011111,B10000000,
34
+      B00110110,B11000000,
35
+      B00111111,B11000000,
36
+      B00001001,B00000000,
37
+      B00010110,B10000000,
38
+      B00101001,B01000000
39
+    }, {
40
+      B00000110,B00000000,
41
+      B00001111,B00000000,
42
+      B00011111,B10000000,
43
+      B00110110,B11000000,
44
+      B00111111,B11000000,
45
+      B00010110,B10000000,
46
+      B00100000,B01000000,
47
+      B00010000,B10000000
48
+    }
49
+  }, {
50
+    { B00010000,B01000000,
51
+      B00001000,B10000000,
52
+      B00011111,B11000000,
53
+      B00110111,B01100000,
54
+      B01111111,B11110000,
55
+      B01011111,B11010000,
56
+      B01010000,B01010000,
57
+      B00001101,B10000000
58
+    }, {
59
+      B00010000,B01000000,
60
+      B01001000,B10010000,
61
+      B01011111,B11010000,
62
+      B01110111,B01110000,
63
+      B01111111,B11110000,
64
+      B00011111,B11000000,
65
+      B00010000,B01000000,
66
+      B00100000,B00100000 
67
+    }
68
+  }, {
69
+    { B00001111,B00000000,
70
+      B01111111,B11100000,
71
+      B11111111,B11110000,
72
+      B11100110,B01110000,
73
+      B11111111,B11110000,
74
+      B00011001,B10000000,
75
+      B00110110,B11000000,
76
+      B11000000,B00110000
77
+    }, {
78
+      B00001111,B00000000,
79
+      B01111111,B11100000,
80
+      B11111111,B11110000,
81
+      B11100110,B01110000,
82
+      B11111111,B11110000,
83
+      B00011001,B10000000,
84
+      B00110110,B11000000,
85
+      B00011001,B10000000
86
+    }
87
+  }
88
+};
89
+const unsigned char cannon[] PROGMEM = {
90
+  B00000100,B00000000,
91
+  B00001110,B00000000,
92
+  B00001110,B00000000,
93
+  B01111111,B11000000,
94
+  B11111111,B11100000,
95
+  B11111111,B11100000,
96
+  B11111111,B11100000,
97
+  B11111111,B11100000
98
+};
99
+const unsigned char life[] PROGMEM = {
100
+  B00010000,
101
+  B01111100,
102
+  B11111110,
103
+  B11111110,
104
+  B11111110
105
+};
106
+const unsigned char explosion[] PROGMEM = {
107
+  B01000100,B01000000,
108
+  B00100100,B10000000,
109
+  B00000000,B00000000,
110
+  B00110001,B10000000,
111
+  B00000000,B00000000,
112
+  B00100100,B10000000,
113
+  B01000100,B01000000
114
+};
115
+const unsigned char ufo[] PROGMEM = {
116
+  B00011111,B11000000,
117
+  B01111111,B11110000,
118
+  B11011101,B11011000,
119
+  B11111111,B11111000,
120
+  B01111111,B11110000
121
+};
122
+
123
+#define INVASION_SIZE 3
124
+
125
+#if INVASION_SIZE == 3
126
+  #define INVADER_COLS   5
127
+#elif INVASION_SIZE == 4
128
+  #define INVADER_COLS   6
129
+#else
130
+  #define INVADER_COLS   8
131
+  #undef INVASION_SIZE
132
+  #define INVASION_SIZE  5
133
+#endif
134
+
135
+#define INVADER_ROWS INVASION_SIZE
136
+
137
+constexpr uint8_t inv_type[] = {
138
+  #if INVADER_ROWS == 5
139
+    0, 1, 1, 2, 2
140
+  #elif INVADER_ROWS == 4
141
+    0, 1, 1, 2
142
+  #elif INVADER_ROWS == 3
143
+    0, 1, 2
144
+  #else
145
+    #error "INVASION_SIZE must be 3, 4, or 5."
146
+  #endif
147
+};
148
+
149
+#define INVADER_RIGHT ((INVADER_COLS) * (COL_W))
150
+
151
+#define CANNON_W      11
152
+#define CANNON_H       8
153
+#define CANNON_VEL     4
154
+#define CANNON_Y      (LCD_PIXEL_HEIGHT - 1 - CANNON_H)
155
+
156
+#define COL_W         14
157
+#define INVADER_H      8
158
+#define ROW_H         (INVADER_H + 2)
159
+#define INVADER_VEL    3
160
+
161
+#define INVADER_TOP   MENU_FONT_ASCENT
162
+#define INVADERS_WIDE ((COL_W) * (INVADER_COLS))
163
+#define INVADERS_HIGH ((ROW_H) * (INVADER_ROWS))
164
+
165
+#define UFO_H          5
166
+#define UFO_W         13
167
+
168
+#define LASER_H        4
169
+#define SHOT_H         3
170
+#define EXPL_W        11
171
+#define LIFE_W         8
172
+#define LIFE_H         5
173
+
174
+#define INVADER_COL(X) ((X - invaders_x) / (COL_W))
175
+#define INVADER_ROW(Y) ((Y - invaders_y + 2) / (ROW_H))
176
+
177
+#define INV_X_LEFT(C,T) (invaders_x + (C) * (COL_W) + inv_off[T])
178
+#define INV_X_CTR(C,T)  (INV_X_LEFT(C,T) + inv_wide[T] / 2)
179
+#define INV_Y_BOT(R)    (invaders_y + (R + 1) * (ROW_H) - 2)
180
+
181
+typedef struct { int8_t x, y, v; } laser_t;
182
+
183
+uint8_t cannons_left;
184
+int8_t cannon_x;
185
+laser_t laser, expl, bullet[10];
186
+constexpr uint8_t inv_off[] = { 2, 1, 0 }, inv_wide[] = { 8, 11, 12 };
187
+int8_t invaders_x, invaders_y, invaders_dir, leftmost, rightmost, botmost;
188
+uint8_t invader_count, bugs[INVADER_ROWS], shooters[(INVADER_ROWS) * (INVADER_COLS)];
189
+
190
+inline void update_invader_data() {
191
+  uint8_t inv_mask = 0;
192
+  // Get a list of all active invaders
193
+  uint8_t sc = 0;
194
+  LOOP_L_N(y, INVADER_ROWS) {
195
+    uint8_t m = bugs[y];
196
+    if (m) botmost = y + 1;
197
+    inv_mask |= m;
198
+    for (uint8_t x = 0; x < INVADER_COLS; ++x)
199
+      if (TEST(m, x)) shooters[sc++] = (y << 4) | x;
200
+  }
201
+  leftmost = 0;
202
+  LOOP_L_N(i, INVADER_COLS)            { if (TEST(inv_mask, i)) break; leftmost -= COL_W; }
203
+  rightmost = LCD_PIXEL_WIDTH - (INVADERS_WIDE);
204
+  for (uint8_t i = INVADER_COLS; i--;) { if (TEST(inv_mask, i)) break; rightmost += COL_W; }
205
+  if (invader_count == 2) invaders_dir = invaders_dir > 0 ? INVADER_VEL + 1 : -(INVADER_VEL + 1);
206
+}
207
+
208
+inline void reset_bullets() {
209
+  LOOP_L_N(i, COUNT(bullet)) bullet[i].v = 0;
210
+}
211
+
212
+inline void reset_invaders() {
213
+  invaders_x = 0; invaders_y = INVADER_TOP;
214
+  invaders_dir = INVADER_VEL;
215
+  invader_count = (INVADER_COLS) * (INVADER_ROWS);
216
+  LOOP_L_N(i, INVADER_ROWS) bugs[i] = _BV(INVADER_COLS) - 1;
217
+  update_invader_data();
218
+  reset_bullets();
219
+}
220
+
221
+int8_t ufox, ufov;
222
+inline void spawn_ufo() {
223
+  ufov = random(0, 2) ? 1 : -1;
224
+  ufox = ufov > 0 ? -(UFO_W) : LCD_PIXEL_WIDTH - 1;
225
+}
226
+
227
+inline void reset_player() {
228
+  cannon_x = 0;
229
+  ui.encoderPosition = 0;
230
+}
231
+
232
+inline void fire_cannon() {
233
+  laser.x = cannon_x + CANNON_W / 2;
234
+  laser.y = LCD_PIXEL_HEIGHT - CANNON_H - (LASER_H);
235
+  laser.v = -(LASER_H);
236
+}
237
+
238
+inline void explode(const int8_t x, const int8_t y, const int8_t v=4) {
239
+  expl.x = x - (EXPL_W) / 2; expl.y = y; expl.v = v;
240
+}
241
+
242
+inline void kill_cannon(uint8_t &game_state, const uint8_t st) {
243
+  reset_bullets();
244
+  explode(cannon_x + (CANNON_W) / 2, CANNON_Y, 6);
245
+  _BUZZ(1000, 10);
246
+  if (--cannons_left) {
247
+    laser.v = 0;
248
+    game_state = st;
249
+    reset_player();
250
+  }
251
+  else
252
+    game_state = 0;
253
+}
254
+
255
+void InvadersGame::game_screen() {
256
+  static bool game_blink;
257
+
258
+  ui.refresh(LCDVIEW_CALL_NO_REDRAW); // Call as often as possible
259
+
260
+  // Run game logic once per full screen
261
+  if (ui.first_page) {
262
+
263
+    // Update Cannon Position
264
+    int32_t ep = (int32_t)ui.encoderPosition;
265
+    ep = constrain(ep, 0, (LCD_PIXEL_WIDTH - (CANNON_W)) / (CANNON_VEL));
266
+    ui.encoderPosition = ep;
267
+
268
+    ep *= (CANNON_VEL);
269
+    if (ep > cannon_x) { cannon_x += CANNON_VEL - 1; if (ep - cannon_x < 2) cannon_x = ep; }
270
+    if (ep < cannon_x) { cannon_x -= CANNON_VEL - 1; if (cannon_x - ep < 2) cannon_x = ep; }
271
+
272
+    // Run the game logic
273
+    if (game_state) do {
274
+
275
+      // Move the UFO, if any
276
+      if (ufov) { ufox += ufov; if (!WITHIN(ufox, -(UFO_W), LCD_PIXEL_WIDTH - 1)) ufov = 0; }
277
+
278
+      if (game_state > 1) { if (--game_state == 2) { reset_invaders(); } else if (game_state == 100) { game_state = 1; } break; }
279
+
280
+      static uint8_t blink_count;
281
+      const bool did_blink = (++blink_count > invader_count >> 1);
282
+      if (did_blink) {
283
+        game_blink = !game_blink;
284
+        blink_count = 0;
285
+      }
286
+
287
+      if (invader_count && did_blink) {
288
+        const int8_t newx = invaders_x + invaders_dir;
289
+        if (!WITHIN(newx, leftmost, rightmost)) {             // Invaders reached the edge?
290
+          invaders_dir *= -1;                                 // Invaders change direction
291
+          invaders_y += (ROW_H) / 2;                          // Invaders move down
292
+          invaders_x -= invaders_dir;                         // ...and only move down this time.
293
+          if (invaders_y + botmost * (ROW_H) - 2 >= CANNON_Y) // Invaders reached the bottom?
294
+            kill_cannon(game_state, 20);                      // Kill the cannon. Reset invaders.
295
+        }
296
+
297
+        invaders_x += invaders_dir;               // Invaders take one step left/right
298
+
299
+        // Randomly shoot if invaders are listed
300
+        if (invader_count && !random(0, 20)) {
301
+
302
+          // Find a free bullet
303
+          laser_t *b = NULL;
304
+          LOOP_L_N(i, COUNT(bullet)) if (!bullet[i].v) { b = &bullet[i]; break; }
305
+          if (b) {
306
+            // Pick a random shooter and update the bullet
307
+            //SERIAL_ECHOLNPGM("free bullet found");
308
+            const uint8_t inv = shooters[random(0, invader_count + 1)], col = inv & 0x0F, row = inv >> 4, type = inv_type[row];
309
+            b->x = INV_X_CTR(col, type);
310
+            b->y = INV_Y_BOT(row);
311
+            b->v = 2 + random(0, 2);
312
+          }
313
+        }
314
+      }
315
+
316
+      // Update the laser position
317
+      if (laser.v) {
318
+        laser.y += laser.v;
319
+        if (laser.y < 0) laser.v = 0;
320
+      }
321
+
322
+      // Did the laser collide with an invader?
323
+      if (laser.v && WITHIN(laser.y, invaders_y, invaders_y + INVADERS_HIGH - 1)) {
324
+        const int8_t col = INVADER_COL(laser.x);
325
+        if (WITHIN(col, 0, INVADER_COLS - 1)) {
326
+          const int8_t row = INVADER_ROW(laser.y);
327
+          if (WITHIN(row, 0, INVADER_ROWS - 1)) {
328
+            const uint8_t mask = _BV(col);
329
+            if (bugs[row] & mask) {
330
+              const uint8_t type = inv_type[row];
331
+              const int8_t invx = INV_X_LEFT(col, type);
332
+              if (WITHIN(laser.x, invx, invx + inv_wide[type] - 1)) {
333
+                // Turn off laser
334
+                laser.v = 0;
335
+                // Remove the invader!
336
+                bugs[row] &= ~mask;
337
+                // Score!
338
+                score += INVADER_ROWS - row;
339
+                // Explode sound!
340
+                _BUZZ(40, 10);
341
+                // Explosion bitmap!
342
+                explode(invx + inv_wide[type] / 2, invaders_y + row * (ROW_H));
343
+                // If invaders are gone, go to reset invaders state
344
+                if (--invader_count) update_invader_data(); else { game_state = 20; reset_bullets(); }
345
+              } // laser x hit
346
+            } // invader exists
347
+          } // good row
348
+        } // good col
349
+      } // laser in invader zone
350
+
351
+      // Handle alien bullets
352
+      LOOP_L_N(s, COUNT(bullet)) {
353
+        laser_t *b = &bullet[s];
354
+        if (b->v) {
355
+          // Update alien bullet position
356
+          b->y += b->v;
357
+          if (b->y >= LCD_PIXEL_HEIGHT)
358
+            b->v = 0; // Offscreen
359
+          else if (b->y >= CANNON_Y && WITHIN(b->x, cannon_x, cannon_x + CANNON_W - 1))
360
+            kill_cannon(game_state, 120); // Hit the cannon
361
+        }
362
+      }
363
+
364
+      // Randomly spawn a UFO
365
+      if (!ufov && !random(0,500)) spawn_ufo();
366
+
367
+      // Did the laser hit a ufo?
368
+      if (laser.v && ufov && laser.y < UFO_H + 2 && WITHIN(laser.x, ufox, ufox + UFO_W - 1)) {
369
+        // Turn off laser and UFO
370
+        laser.v = ufov = 0;
371
+        // Score!
372
+        score += 10;
373
+        // Explode!
374
+        _BUZZ(40, 10);
375
+        // Explosion bitmap
376
+        explode(ufox + (UFO_W) / 2, 1);
377
+      }
378
+
379
+    } while (false);
380
+
381
+  }
382
+
383
+  // Click to fire or exit
384
+  if (ui.use_click()) {
385
+    if (!game_state)
386
+      ui.goto_previous_screen();
387
+    else if (game_state == 1 && !laser.v)
388
+      fire_cannon();
389
+  }
390
+
391
+  u8g.setColorIndex(1);
392
+
393
+  // Draw invaders
394
+  if (PAGE_CONTAINS(invaders_y, invaders_y + botmost * (ROW_H) - 2 - 1)) {
395
+    int8_t yy = invaders_y;
396
+    for (uint8_t y = 0; y < INVADER_ROWS; ++y) {
397
+      const uint8_t type = inv_type[y];
398
+      if (PAGE_CONTAINS(yy, yy + INVADER_H - 1)) {
399
+        int8_t xx = invaders_x;
400
+        for (uint8_t x = 0; x < INVADER_COLS; ++x) {
401
+          if (TEST(bugs[y], x))
402
+            u8g.drawBitmapP(xx, yy, 2, INVADER_H, invader[type][game_blink]);
403
+          xx += COL_W;
404
+        }
405
+      }
406
+      yy += ROW_H;
407
+    }
408
+  }
409
+
410
+  // Draw UFO
411
+  if (ufov && PAGE_UNDER(UFO_H + 2))
412
+    u8g.drawBitmapP(ufox, 2, 2, UFO_H, ufo);
413
+
414
+  // Draw cannon
415
+  if (game_state && PAGE_CONTAINS(CANNON_Y, CANNON_Y + CANNON_H - 1) && (game_state < 2 || (game_state & 0x02)))
416
+    u8g.drawBitmapP(cannon_x, CANNON_Y, 2, CANNON_H, cannon);
417
+
418
+  // Draw laser
419
+  if (laser.v && PAGE_CONTAINS(laser.y, laser.y + LASER_H - 1))
420
+    u8g.drawVLine(laser.x, laser.y, LASER_H);
421
+
422
+  // Draw invader bullets
423
+  LOOP_L_N (i, COUNT(bullet)) {
424
+    if (bullet[i].v && PAGE_CONTAINS(bullet[i].y - (SHOT_H - 1), bullet[i].y))
425
+      u8g.drawVLine(bullet[i].x, bullet[i].y - (SHOT_H - 1), SHOT_H);
426
+  }
427
+
428
+  // Draw explosion
429
+  if (expl.v && PAGE_CONTAINS(expl.y, expl.y + 7 - 1)) {
430
+    u8g.drawBitmapP(expl.x, expl.y, 2, 7, explosion);
431
+    --expl.v;
432
+  }
433
+
434
+  // Blink GAME OVER when game is over
435
+  if (!game_state) draw_game_over();
436
+
437
+  if (PAGE_UNDER(MENU_FONT_ASCENT - 1)) {
438
+    // Draw Score
439
+    //const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
440
+    constexpr uint8_t sx = 0;
441
+    lcd_moveto(sx, MENU_FONT_ASCENT - 1);
442
+    lcd_put_int(score);
443
+
444
+    // Draw lives
445
+    if (cannons_left)
446
+      for (uint8_t i = 1; i <= cannons_left; ++i)
447
+        u8g.drawBitmapP(LCD_PIXEL_WIDTH - i * (LIFE_W), 6 - (LIFE_H), 1, LIFE_H, life);
448
+  }
449
+
450
+}
451
+
452
+void InvadersGame::enter_game() {
453
+  init_game(20, game_screen); // countdown to reset invaders
454
+  cannons_left = 3;
455
+  laser.v = 0;
456
+  reset_invaders();
457
+  reset_player();
458
+}
459
+
460
+#endif // MARLIN_INVADERS

+ 137
- 0
Marlin/src/lcd/menu/game/maze.cpp Zobrazit soubor

@@ -0,0 +1,137 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+#include "../../../inc/MarlinConfigPre.h"
24
+
25
+#if ENABLED(MARLIN_MAZE)
26
+
27
+#include "game.h"
28
+
29
+int8_t move_dir, last_move_dir, // NESW0
30
+       prizex, prizey, prize_cnt, old_encoder;
31
+fixed_t playerx, playery;
32
+
33
+// Up to 50 lines, then you win!
34
+typedef struct { int8_t x, y; } pos_t;
35
+uint8_t head_ind;
36
+pos_t maze_walls[50] = {
37
+  { 0, 0 }
38
+};
39
+
40
+// Turn the player cw or ccw
41
+inline void turn_player(const bool cw) {
42
+  if (move_dir == 4) move_dir = last_move_dir;
43
+  move_dir += cw ? 1 : -1;
44
+  move_dir &= 0x03;
45
+  last_move_dir = move_dir;
46
+}
47
+
48
+// Reset the player for a new game
49
+void player_reset() {
50
+  // Init position
51
+  playerx = BTOF(1);
52
+  playery = BTOF(GAME_H / 2);
53
+
54
+  // Init motion with a ccw turn
55
+  move_dir = 0;
56
+  turn_player(false);
57
+
58
+  // Clear prize flag
59
+  prize_cnt = 255;
60
+
61
+  // Clear the controls
62
+  ui.encoderPosition = 0;
63
+  old_encoder = 0;
64
+}
65
+
66
+void MazeGame::game_screen() {
67
+  // Run the sprite logic
68
+  if (game_frame()) do {     // Run logic twice for finer resolution
69
+
70
+    // Move the man one unit in the current direction
71
+    // Direction index 4 is for the stopped man
72
+    const int8_t oldx = FTOB(playerx), oldy = FTOB(playery);
73
+    pos_t dir_add[] = { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, 0 } };
74
+    playerx += dir_add[move_dir].x;
75
+    playery += dir_add[move_dir].y;
76
+    const int8_t x = FTOB(playerx), y = FTOB(playery);
77
+
78
+  } while(0);
79
+
80
+  u8g.setColorIndex(1);
81
+
82
+  // Draw Score
83
+  if (PAGE_UNDER(HEADER_H)) {
84
+    lcd_moveto(0, HEADER_H - 1);
85
+    lcd_put_int(score);
86
+  }
87
+
88
+  // Draw the maze
89
+  // for (uint8_t n = 0; n < head_ind; ++n) {
90
+  //   const pos_t &p = maze_walls[n], &q = maze_walls[n + 1];
91
+  //   if (p.x == q.x) {
92
+  //     const int8_t y1 = GAMEY(MIN(p.y, q.y)), y2 = GAMEY(MAX(p.y, q.y));
93
+  //     if (PAGE_CONTAINS(y1, y2))
94
+  //       u8g.drawVLine(GAMEX(p.x), y1, y2 - y1 + 1);
95
+  //   }
96
+  //   else if (PAGE_CONTAINS(GAMEY(p.y), GAMEY(p.y))) {
97
+  //     const int8_t x1 = GAMEX(MIN(p.x, q.x)), x2 = GAMEX(MAX(p.x, q.x));
98
+  //     u8g.drawHLine(x1, GAMEY(p.y), x2 - x1 + 1);
99
+  //   }
100
+  // }
101
+
102
+  // Draw Man
103
+  // const int8_t fy = GAMEY(foody);
104
+  // if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
105
+  //   const int8_t fx = GAMEX(foodx);
106
+  //   u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
107
+  //   if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
108
+  // }
109
+
110
+  // Draw Ghosts
111
+  // const int8_t fy = GAMEY(foody);
112
+  // if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
113
+  //   const int8_t fx = GAMEX(foodx);
114
+  //   u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
115
+  //   if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
116
+  // }
117
+
118
+  // Draw Prize
119
+  // if (PAGE_CONTAINS(prizey, prizey + PRIZE_WH - 1)) {
120
+  //   u8g.drawFrame(prizex, prizey, PRIZE_WH, PRIZE_WH);
121
+  //   if (PRIZE_WH == 5) u8g.drawPixel(prizex + 2, prizey + 2);
122
+  // }
123
+
124
+  // Draw GAME OVER
125
+  if (!game_state) draw_game_over();
126
+
127
+  // A click always exits this game
128
+  if (ui.use_click()) ui.goto_previous_screen();
129
+}
130
+
131
+void MazeGame::enter_game() {
132
+  init_game(1, game_screen); // Game running
133
+  reset_player();
134
+  reset_enemies();
135
+}
136
+
137
+#endif // MARLIN_MAZE

+ 334
- 0
Marlin/src/lcd/menu/game/snake.cpp Zobrazit soubor

@@ -0,0 +1,334 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+#include "../../../inc/MarlinConfigPre.h"
24
+
25
+#if ENABLED(MARLIN_SNAKE)
26
+
27
+#include "game.h"
28
+
29
+#define SNAKE_BOX 4
30
+
31
+#define HEADER_H  (MENU_FONT_ASCENT - 2)
32
+#define SNAKE_WH  (SNAKE_BOX + 1)
33
+
34
+#define IDEAL_L   2
35
+#define IDEAL_R   (LCD_PIXEL_WIDTH - 1 - 2)
36
+#define IDEAL_T   (HEADER_H + 2)
37
+#define IDEAL_B   (LCD_PIXEL_HEIGHT - 1 - 2)
38
+#define IDEAL_W   (IDEAL_R - (IDEAL_L) + 1)
39
+#define IDEAL_H   (IDEAL_B - (IDEAL_T) + 1)
40
+
41
+#define GAME_W    int((IDEAL_W) / (SNAKE_WH))
42
+#define GAME_H    int((IDEAL_H) / (SNAKE_WH))
43
+
44
+#define BOARD_W   ((SNAKE_WH) * (GAME_W) + 1)
45
+#define BOARD_H   ((SNAKE_WH) * (GAME_H) + 1)
46
+#define BOARD_L   ((LCD_PIXEL_WIDTH - (BOARD_W) + 1) / 2)
47
+#define BOARD_R   (BOARD_L + BOARD_W - 1)
48
+#define BOARD_T   (((LCD_PIXEL_HEIGHT + IDEAL_T) - (BOARD_H)) / 2)
49
+#define BOARD_B   (BOARD_T + BOARD_H - 1)
50
+
51
+#define GAMEX(X)  (BOARD_L + ((X) * (SNAKE_WH)))
52
+#define GAMEY(Y)  (BOARD_T + ((Y) * (SNAKE_WH)))
53
+
54
+#if SNAKE_BOX > 2
55
+  #define FOOD_WH SNAKE_BOX
56
+#else
57
+  #define FOOD_WH 2
58
+#endif
59
+
60
+#if SNAKE_BOX < 1
61
+  #define SNAKE_SIZ 1
62
+#else
63
+  #define SNAKE_SIZ SNAKE_BOX
64
+#endif
65
+
66
+constexpr fixed_t snakev = FTOP(0.20);
67
+
68
+int8_t snake_dir, // NESW
69
+       foodx, foody, food_cnt,
70
+       old_encoder;
71
+fixed_t snakex, snakey;
72
+
73
+// Up to 50 lines, then you win!
74
+typedef struct { int8_t x, y; } pos_t;
75
+uint8_t head_ind;
76
+pos_t snake_tail[50];
77
+
78
+// Remove the first pixel from the tail.
79
+// If needed, shift out the first segment.
80
+void shorten_tail() {
81
+  pos_t &p = snake_tail[0], &q = snake_tail[1];
82
+  bool shift = false;
83
+  if (p.x == q.x) {
84
+    // Vertical line
85
+    p.y += (q.y > p.y) ? 1 : -1;
86
+    shift = p.y == q.y;
87
+  }
88
+  else {
89
+    // Horizontal line
90
+    p.x += (q.x > p.x) ? 1 : -1;
91
+    shift = p.x == q.x;
92
+  }
93
+  if (shift) {
94
+    head_ind--;
95
+    for (uint8_t i = 0; i <= head_ind; ++i)
96
+      snake_tail[i] = snake_tail[i + 1];
97
+  }
98
+}
99
+
100
+// The food is on a line
101
+inline bool food_on_line() {
102
+  for (uint8_t n = 0; n < head_ind; ++n) {
103
+    pos_t &p = snake_tail[n], &q = snake_tail[n + 1];
104
+    if (p.x == q.x) {
105
+      if ((foodx == p.x - 1 || foodx == p.x) && WITHIN(foody, MIN(p.y, q.y), MAX(p.y, q.y)))
106
+        return true;
107
+    }
108
+    else if ((foody == p.y - 1 || foody == p.y) && WITHIN(foodx, MIN(p.x, q.x), MAX(p.x, q.x)))
109
+      return true;
110
+  }
111
+  return false;
112
+}
113
+
114
+// Add a new food blob
115
+void food_reset() {
116
+  do {
117
+    foodx = random(0, GAME_W);
118
+    foody = random(0, GAME_H);
119
+  } while (food_on_line());
120
+}
121
+
122
+// Turn the snake cw or ccw
123
+inline void turn_snake(const bool cw) {
124
+  snake_dir += cw ? 1 : -1;
125
+  snake_dir &= 0x03;
126
+  head_ind++;
127
+  snake_tail[head_ind].x = FTOB(snakex);
128
+  snake_tail[head_ind].y = FTOB(snakey);
129
+}
130
+
131
+// Reset the snake for a new game
132
+void snake_reset() {
133
+  // Init the head and velocity
134
+  snakex = BTOF(1);
135
+  snakey = BTOF(GAME_H / 2);
136
+  //snakev = FTOP(0.25);
137
+
138
+  // Init the tail with a cw turn
139
+  snake_dir = 0;
140
+  head_ind = 0;
141
+  snake_tail[0].x = 0;
142
+  snake_tail[0].y = GAME_H / 2;
143
+  turn_snake(true);
144
+
145
+  // Clear food flag
146
+  food_cnt = 5;
147
+
148
+  // Clear the controls
149
+  ui.encoderPosition = 0;
150
+  old_encoder = 0;
151
+}
152
+
153
+// Check if head segment overlaps another
154
+bool snake_overlap() {
155
+  // 4 lines must exist before a collision is possible
156
+  if (head_ind < 4) return false;
157
+  // Is the last segment crossing any others?
158
+  const pos_t &h1 = snake_tail[head_ind - 1], &h2 = snake_tail[head_ind];
159
+  // VERTICAL head segment?
160
+  if (h1.x == h2.x) {
161
+    // Loop from oldest to segment two away from head
162
+    for (uint8_t n = 0; n < head_ind - 2; ++n) {
163
+      // Segment p to q
164
+      const pos_t &p = snake_tail[n], &q = snake_tail[n + 1];
165
+      if (p.x != q.x) {
166
+        // Crossing horizontal segment
167
+        if (WITHIN(h1.x, MIN(p.x, q.x), MAX(p.x, q.x)) && (h1.y <= p.y) == (h2.y >= p.y)) return true;
168
+      } // Overlapping vertical segment
169
+      else if (h1.x == p.x && MIN(h1.y, h2.y) <= MAX(p.y, q.y) && MAX(h1.y, h2.y) >= MIN(p.y, q.y)) return true;
170
+    }
171
+  }
172
+  else {
173
+    // Loop from oldest to segment two away from head
174
+    for (uint8_t n = 0; n < head_ind - 2; ++n) {
175
+      // Segment p to q
176
+      const pos_t &p = snake_tail[n], &q = snake_tail[n + 1];
177
+      if (p.y != q.y) {
178
+        // Crossing vertical segment
179
+        if (WITHIN(h1.y, MIN(p.y, q.y), MAX(p.y, q.y)) && (h1.x <= p.x) == (h2.x >= p.x)) return true;
180
+      } // Overlapping horizontal segment
181
+      else if (h1.y == p.y && MIN(h1.x, h2.x) <= MAX(p.x, q.x) && MAX(h1.x, h2.x) >= MIN(p.x, q.x)) return true;
182
+    }
183
+  }
184
+  return false;
185
+}
186
+
187
+void SnakeGame::game_screen() {
188
+  // Run the snake logic
189
+  if (game_frame()) do {    // Run logic twice for finer resolution
190
+
191
+    // Move the snake's head one unit in the current direction
192
+    const int8_t oldx = FTOB(snakex), oldy = FTOB(snakey);
193
+    switch (snake_dir) {
194
+      case 0: snakey -= snakev; break;
195
+      case 1: snakex += snakev; break;
196
+      case 2: snakey += snakev; break;
197
+      case 3: snakex -= snakev; break;
198
+    }
199
+    const int8_t x = FTOB(snakex), y = FTOB(snakey);
200
+
201
+    // If movement took place...
202
+    if (oldx != x || oldy != y) {
203
+
204
+      if (!WITHIN(x, 0, GAME_W - 1) || !WITHIN(y, 0, GAME_H - 1)) {
205
+        game_state = 0; // Game Over
206
+        _BUZZ(400, 40); // Bzzzt!
207
+        break;          // ...out of do-while
208
+      }
209
+
210
+      snake_tail[head_ind].x = x;
211
+      snake_tail[head_ind].y = y;
212
+
213
+      // Change snake direction if set
214
+      const int8_t enc = int8_t(ui.encoderPosition), diff = enc - old_encoder;
215
+      if (diff) {
216
+        old_encoder = enc;
217
+        turn_snake(diff > 0);
218
+      }
219
+
220
+      if (food_cnt) --food_cnt; else shorten_tail();
221
+
222
+      // Did the snake collide with itself or go out of bounds?
223
+      if (snake_overlap()) {
224
+        game_state = 0; // Game Over
225
+        _BUZZ(400, 40); // Bzzzt!
226
+      }
227
+      // Is the snake at the food?
228
+      else if (x == foodx && y == foody) {
229
+        _BUZZ(5, 220);
230
+        _BUZZ(5, 280);
231
+        score++;
232
+        food_cnt = 2;
233
+        food_reset();
234
+      }
235
+    }
236
+
237
+  } while(0);
238
+
239
+  u8g.setColorIndex(1);
240
+
241
+  // Draw Score
242
+  if (PAGE_UNDER(HEADER_H)) {
243
+    lcd_moveto(0, HEADER_H - 1);
244
+    lcd_put_int(score);
245
+  }
246
+
247
+  // DRAW THE PLAYFIELD BORDER
248
+  u8g.drawFrame(BOARD_L - 2, BOARD_T - 2, BOARD_R - BOARD_L + 4, BOARD_B - BOARD_T + 4);
249
+
250
+  // Draw the snake (tail)
251
+  #if SNAKE_WH < 2
252
+
253
+    // At this scale just draw a line
254
+    for (uint8_t n = 0; n < head_ind; ++n) {
255
+      const pos_t &p = snake_tail[n], &q = snake_tail[n + 1];
256
+      if (p.x == q.x) {
257
+        const int8_t y1 = GAMEY(MIN(p.y, q.y)), y2 = GAMEY(MAX(p.y, q.y));
258
+        if (PAGE_CONTAINS(y1, y2))
259
+          u8g.drawVLine(GAMEX(p.x), y1, y2 - y1 + 1);
260
+      }
261
+      else if (PAGE_CONTAINS(GAMEY(p.y), GAMEY(p.y))) {
262
+        const int8_t x1 = GAMEX(MIN(p.x, q.x)), x2 = GAMEX(MAX(p.x, q.x));
263
+        u8g.drawHLine(x1, GAMEY(p.y), x2 - x1 + 1);
264
+      }
265
+    }
266
+
267
+  #elif SNAKE_WH == 2
268
+
269
+    // At this scale draw two lines
270
+    for (uint8_t n = 0; n < head_ind; ++n) {
271
+      const pos_t &p = snake_tail[n], &q = snake_tail[n + 1];
272
+      if (p.x == q.x) {
273
+        const int8_t y1 = GAMEY(MIN(p.y, q.y)), y2 = GAMEY(MAX(p.y, q.y));
274
+        if (PAGE_CONTAINS(y1, y2 + 1))
275
+          u8g.drawFrame(GAMEX(p.x), y1, 2, y2 - y1 + 1 + 1);
276
+      }
277
+      else {
278
+        const int8_t py = GAMEY(p.y);
279
+        if (PAGE_CONTAINS(py, py + 1)) {
280
+          const int8_t x1 = GAMEX(MIN(p.x, q.x)), x2 = GAMEX(MAX(p.x, q.x));
281
+          u8g.drawFrame(x1, py, x2 - x1 + 1 + 1, 2);
282
+        }
283
+      }
284
+    }
285
+
286
+  #else
287
+
288
+    // Draw a series of boxes
289
+    for (uint8_t n = 0; n < head_ind; ++n) {
290
+      const pos_t &p = snake_tail[n], &q = snake_tail[n + 1];
291
+      if (p.x == q.x) {
292
+        const int8_t y1 = MIN(p.y, q.y), y2 = MAX(p.y, q.y);
293
+        if (PAGE_CONTAINS(GAMEY(y1), GAMEY(y2) + SNAKE_SIZ - 1)) {
294
+          for (int8_t i = y1; i <= y2; ++i) {
295
+            const int8_t y = GAMEY(i);
296
+            if (PAGE_CONTAINS(y, y + SNAKE_SIZ - 1))
297
+              u8g.drawBox(GAMEX(p.x), y, SNAKE_SIZ, SNAKE_SIZ);
298
+          }
299
+        }
300
+      }
301
+      else {
302
+        const int8_t py = GAMEY(p.y);
303
+        if (PAGE_CONTAINS(py, py + SNAKE_SIZ - 1)) {
304
+          const int8_t x1 = MIN(p.x, q.x), x2 = MAX(p.x, q.x);
305
+          for (int8_t i = x1; i <= x2; ++i)
306
+            u8g.drawBox(GAMEX(i), py, SNAKE_SIZ, SNAKE_SIZ);
307
+        }
308
+      }
309
+    }
310
+
311
+  #endif
312
+
313
+  // Draw food
314
+  const int8_t fy = GAMEY(foody);
315
+  if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
316
+    const int8_t fx = GAMEX(foodx);
317
+    u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
318
+    if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
319
+  }
320
+
321
+  // Draw GAME OVER
322
+  if (!game_state) draw_game_over();
323
+
324
+  // A click always exits this game
325
+  if (ui.use_click()) ui.goto_previous_screen();
326
+}
327
+
328
+void SnakeGame::enter_game() {
329
+  init_game(1, game_screen); // 1 = Game running
330
+  snake_reset();
331
+  food_reset();
332
+}
333
+
334
+#endif // MARLIN_SNAKE

+ 17
- 997
Marlin/src/lcd/menu/menu_game.cpp
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 10
- 14
Marlin/src/lcd/menu/menu_main.cpp Zobrazit soubor

@@ -46,6 +46,10 @@
46 46
   #include "../../feature/host_actions.h"
47 47
 #endif
48 48
 
49
+#if HAS_GAMES
50
+  #include "game/game.h"
51
+#endif
52
+
49 53
 #define MACHINE_CAN_STOP (EITHER(SDSUPPORT, HOST_PROMPT_SUPPORT) || defined(ACTION_ON_CANCEL))
50 54
 #define MACHINE_CAN_PAUSE (ANY(SDSUPPORT, HOST_PROMPT_SUPPORT, PARK_HEAD_ON_PAUSE) || defined(ACTION_ON_PAUSE))
51 55
 
@@ -138,16 +142,6 @@ void menu_led();
138 142
   #endif
139 143
 #endif
140 144
 
141
-#if HAS_GAME_MENU
142
-  void menu_game();
143
-#elif ENABLED(MARLIN_BRICKOUT)
144
-  void lcd_goto_brickout();
145
-#elif ENABLED(MARLIN_INVADERS)
146
-  void lcd_goto_invaders();
147
-#elif ENABLED(MARLIN_SNAKE)
148
-  void lcd_goto_snake();
149
-#endif
150
-
151 145
 void menu_main() {
152 146
   START_MENU();
153 147
   MENU_BACK(MSG_WATCH);
@@ -286,16 +280,18 @@ void menu_main() {
286 280
     #endif
287 281
   #endif
288 282
 
289
-  #if ANY(MARLIN_BRICKOUT, MARLIN_INVADERS, MARLIN_SNAKE)
283
+  #if ANY(MARLIN_BRICKOUT, MARLIN_INVADERS, MARLIN_SNAKE, MARLIN_MAZE)
290 284
     MENU_ITEM(submenu, "Game", (
291 285
       #if HAS_GAME_MENU
292 286
         menu_game
293 287
       #elif ENABLED(MARLIN_BRICKOUT)
294
-        lcd_goto_brickout
288
+        brickout.enter_game
295 289
       #elif ENABLED(MARLIN_INVADERS)
296
-        lcd_goto_invaders
290
+        invaders.enter_game
297 291
       #elif ENABLED(MARLIN_SNAKE)
298
-        lcd_goto_snake
292
+        snake.enter_game
293
+      #elif ENABLED(MARLIN_MAZE)
294
+        maze.enter_game
299 295
       #endif
300 296
     ));
301 297
   #endif

+ 1
- 1
Marlin/src/module/temperature.h Zobrazit soubor

@@ -673,7 +673,7 @@ class Temperature {
673 673
       #if ENABLED(NO_FAN_SLOWING_IN_PID_TUNING)
674 674
         static bool adaptive_fan_slowing;
675 675
       #elif ENABLED(ADAPTIVE_FAN_SLOWING)
676
-        constexpr static bool adaptive_fan_slowing = true;
676
+        static constexpr bool adaptive_fan_slowing = true;
677 677
       #endif
678 678
 
679 679
       /**

Loading…
Zrušit
Uložit