My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

snake.cpp 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (c) 2020 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. #include "../../../inc/MarlinConfigPre.h"
  23. #if ENABLED(MARLIN_SNAKE)
  24. #include "game.h"
  25. #define SNAKE_BOX 4
  26. #define HEADER_H (MENU_FONT_ASCENT - 2)
  27. #define SNAKE_WH (SNAKE_BOX + 1)
  28. #define IDEAL_L 2
  29. #define IDEAL_R (LCD_PIXEL_WIDTH - 1 - 2)
  30. #define IDEAL_T (HEADER_H + 2)
  31. #define IDEAL_B (LCD_PIXEL_HEIGHT - 1 - 2)
  32. #define IDEAL_W (IDEAL_R - (IDEAL_L) + 1)
  33. #define IDEAL_H (IDEAL_B - (IDEAL_T) + 1)
  34. #define GAME_W int((IDEAL_W) / (SNAKE_WH))
  35. #define GAME_H int((IDEAL_H) / (SNAKE_WH))
  36. #define BOARD_W ((SNAKE_WH) * (GAME_W) + 1)
  37. #define BOARD_H ((SNAKE_WH) * (GAME_H) + 1)
  38. #define BOARD_L ((LCD_PIXEL_WIDTH - (BOARD_W) + 1) / 2)
  39. #define BOARD_R (BOARD_L + BOARD_W - 1)
  40. #define BOARD_T (((LCD_PIXEL_HEIGHT + IDEAL_T) - (BOARD_H)) / 2)
  41. #define BOARD_B (BOARD_T + BOARD_H - 1)
  42. #define GAMEX(X) (BOARD_L + ((X) * (SNAKE_WH)))
  43. #define GAMEY(Y) (BOARD_T + ((Y) * (SNAKE_WH)))
  44. #if SNAKE_BOX > 2
  45. #define FOOD_WH SNAKE_BOX
  46. #else
  47. #define FOOD_WH 2
  48. #endif
  49. #if SNAKE_BOX < 1
  50. #define SNAKE_SIZ 1
  51. #else
  52. #define SNAKE_SIZ SNAKE_BOX
  53. #endif
  54. constexpr fixed_t snakev = FTOP(0.20);
  55. snake_data_t &sdat = marlin_game_data.snake;
  56. // Remove the first pixel from the tail.
  57. // If needed, shift out the first segment.
  58. void shorten_tail() {
  59. pos_t &p = sdat.snake_tail[0], &q = sdat.snake_tail[1];
  60. bool shift = false;
  61. if (p.x == q.x) {
  62. // Vertical line
  63. p.y += (q.y > p.y) ? 1 : -1;
  64. shift = p.y == q.y;
  65. }
  66. else {
  67. // Horizontal line
  68. p.x += (q.x > p.x) ? 1 : -1;
  69. shift = p.x == q.x;
  70. }
  71. if (shift) {
  72. sdat.head_ind--;
  73. LOOP_LE_N(i, sdat.head_ind)
  74. sdat.snake_tail[i] = sdat.snake_tail[i + 1];
  75. }
  76. }
  77. // The food is on a line
  78. inline bool food_on_line() {
  79. LOOP_L_N(n, sdat.head_ind) {
  80. pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
  81. if (p.x == q.x) {
  82. if ((sdat.foodx == p.x - 1 || sdat.foodx == p.x) && WITHIN(sdat.foody, _MIN(p.y, q.y), _MAX(p.y, q.y)))
  83. return true;
  84. }
  85. else if ((sdat.foody == p.y - 1 || sdat.foody == p.y) && WITHIN(sdat.foodx, _MIN(p.x, q.x), _MAX(p.x, q.x)))
  86. return true;
  87. }
  88. return false;
  89. }
  90. // Add a new food blob
  91. void food_reset() {
  92. do {
  93. sdat.foodx = random(0, GAME_W);
  94. sdat.foody = random(0, GAME_H);
  95. } while (food_on_line());
  96. }
  97. // Turn the snake cw or ccw
  98. inline void turn_snake(const bool cw) {
  99. sdat.snake_dir += cw ? 1 : -1;
  100. sdat.snake_dir &= 0x03;
  101. sdat.head_ind++;
  102. sdat.snake_tail[sdat.head_ind].x = FTOB(sdat.snakex);
  103. sdat.snake_tail[sdat.head_ind].y = FTOB(sdat.snakey);
  104. }
  105. // Reset the snake for a new game
  106. void snake_reset() {
  107. // Init the head and velocity
  108. sdat.snakex = BTOF(1);
  109. sdat.snakey = BTOF(GAME_H / 2);
  110. //snakev = FTOP(0.25);
  111. // Init the tail with a cw turn
  112. sdat.snake_dir = 0;
  113. sdat.head_ind = 0;
  114. sdat.snake_tail[0].x = 0;
  115. sdat.snake_tail[0].y = GAME_H / 2;
  116. turn_snake(true);
  117. // Clear food flag
  118. sdat.food_cnt = 5;
  119. // Clear the controls
  120. ui.encoderPosition = 0;
  121. sdat.old_encoder = 0;
  122. }
  123. // Check if head segment overlaps another
  124. bool snake_overlap() {
  125. // 4 lines must exist before a collision is possible
  126. if (sdat.head_ind < 4) return false;
  127. // Is the last segment crossing any others?
  128. const pos_t &h1 = sdat.snake_tail[sdat.head_ind - 1], &h2 = sdat.snake_tail[sdat.head_ind];
  129. // VERTICAL head segment?
  130. if (h1.x == h2.x) {
  131. // Loop from oldest to segment two away from head
  132. LOOP_L_N(n, sdat.head_ind - 2) {
  133. // Segment p to q
  134. const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
  135. if (p.x != q.x) {
  136. // Crossing horizontal segment
  137. if (WITHIN(h1.x, _MIN(p.x, q.x), _MAX(p.x, q.x)) && (h1.y <= p.y) == (h2.y >= p.y)) return true;
  138. } // Overlapping vertical segment
  139. 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;
  140. }
  141. }
  142. else {
  143. // Loop from oldest to segment two away from head
  144. LOOP_L_N(n, sdat.head_ind - 2) {
  145. // Segment p to q
  146. const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
  147. if (p.y != q.y) {
  148. // Crossing vertical segment
  149. if (WITHIN(h1.y, _MIN(p.y, q.y), _MAX(p.y, q.y)) && (h1.x <= p.x) == (h2.x >= p.x)) return true;
  150. } // Overlapping horizontal segment
  151. 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;
  152. }
  153. }
  154. return false;
  155. }
  156. void SnakeGame::game_screen() {
  157. // Run the snake logic
  158. if (game_frame()) do { // Run logic twice for finer resolution
  159. // Move the snake's head one unit in the current direction
  160. const int8_t oldx = FTOB(sdat.snakex), oldy = FTOB(sdat.snakey);
  161. switch (sdat.snake_dir) {
  162. case 0: sdat.snakey -= snakev; break;
  163. case 1: sdat.snakex += snakev; break;
  164. case 2: sdat.snakey += snakev; break;
  165. case 3: sdat.snakex -= snakev; break;
  166. }
  167. const int8_t x = FTOB(sdat.snakex), y = FTOB(sdat.snakey);
  168. // If movement took place...
  169. if (oldx != x || oldy != y) {
  170. if (!WITHIN(x, 0, GAME_W - 1) || !WITHIN(y, 0, GAME_H - 1)) {
  171. game_state = 0; // Game Over
  172. _BUZZ(400, 40); // Bzzzt!
  173. break; // ...out of do-while
  174. }
  175. sdat.snake_tail[sdat.head_ind].x = x;
  176. sdat.snake_tail[sdat.head_ind].y = y;
  177. // Change snake direction if set
  178. const int8_t enc = int8_t(ui.encoderPosition), diff = enc - sdat.old_encoder;
  179. if (diff) {
  180. sdat.old_encoder = enc;
  181. turn_snake(diff > 0);
  182. }
  183. if (sdat.food_cnt) --sdat.food_cnt; else shorten_tail();
  184. // Did the snake collide with itself or go out of bounds?
  185. if (snake_overlap()) {
  186. game_state = 0; // Game Over
  187. _BUZZ(400, 40); // Bzzzt!
  188. }
  189. // Is the snake at the food?
  190. else if (x == sdat.foodx && y == sdat.foody) {
  191. _BUZZ(5, 220);
  192. _BUZZ(5, 280);
  193. score++;
  194. sdat.food_cnt = 2;
  195. food_reset();
  196. }
  197. }
  198. } while(0);
  199. u8g.setColorIndex(1);
  200. // Draw Score
  201. if (PAGE_UNDER(HEADER_H)) lcd_put_int(0, HEADER_H - 1, score);
  202. // DRAW THE PLAYFIELD BORDER
  203. u8g.drawFrame(BOARD_L - 2, BOARD_T - 2, BOARD_R - BOARD_L + 4, BOARD_B - BOARD_T + 4);
  204. // Draw the snake (tail)
  205. #if SNAKE_WH < 2
  206. // At this scale just draw a line
  207. LOOP_L_N(n, sdat.head_ind) {
  208. const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
  209. if (p.x == q.x) {
  210. const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
  211. if (PAGE_CONTAINS(y1, y2))
  212. u8g.drawVLine(GAMEX(p.x), y1, y2 - y1 + 1);
  213. }
  214. else if (PAGE_CONTAINS(GAMEY(p.y), GAMEY(p.y))) {
  215. const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
  216. u8g.drawHLine(x1, GAMEY(p.y), x2 - x1 + 1);
  217. }
  218. }
  219. #elif SNAKE_WH == 2
  220. // At this scale draw two lines
  221. LOOP_L_N(n, sdat.head_ind) {
  222. const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
  223. if (p.x == q.x) {
  224. const int8_t y1 = GAMEY(_MIN(p.y, q.y)), y2 = GAMEY(_MAX(p.y, q.y));
  225. if (PAGE_CONTAINS(y1, y2 + 1))
  226. u8g.drawFrame(GAMEX(p.x), y1, 2, y2 - y1 + 1 + 1);
  227. }
  228. else {
  229. const int8_t py = GAMEY(p.y);
  230. if (PAGE_CONTAINS(py, py + 1)) {
  231. const int8_t x1 = GAMEX(_MIN(p.x, q.x)), x2 = GAMEX(_MAX(p.x, q.x));
  232. u8g.drawFrame(x1, py, x2 - x1 + 1 + 1, 2);
  233. }
  234. }
  235. }
  236. #else
  237. // Draw a series of boxes
  238. LOOP_L_N(n, sdat.head_ind) {
  239. const pos_t &p = sdat.snake_tail[n], &q = sdat.snake_tail[n + 1];
  240. if (p.x == q.x) {
  241. const int8_t y1 = _MIN(p.y, q.y), y2 = _MAX(p.y, q.y);
  242. if (PAGE_CONTAINS(GAMEY(y1), GAMEY(y2) + SNAKE_SIZ - 1)) {
  243. for (int8_t i = y1; i <= y2; ++i) {
  244. const int8_t y = GAMEY(i);
  245. if (PAGE_CONTAINS(y, y + SNAKE_SIZ - 1))
  246. u8g.drawBox(GAMEX(p.x), y, SNAKE_SIZ, SNAKE_SIZ);
  247. }
  248. }
  249. }
  250. else {
  251. const int8_t py = GAMEY(p.y);
  252. if (PAGE_CONTAINS(py, py + SNAKE_SIZ - 1)) {
  253. const int8_t x1 = _MIN(p.x, q.x), x2 = _MAX(p.x, q.x);
  254. for (int8_t i = x1; i <= x2; ++i)
  255. u8g.drawBox(GAMEX(i), py, SNAKE_SIZ, SNAKE_SIZ);
  256. }
  257. }
  258. }
  259. #endif
  260. // Draw food
  261. const int8_t fy = GAMEY(sdat.foody);
  262. if (PAGE_CONTAINS(fy, fy + FOOD_WH - 1)) {
  263. const int8_t fx = GAMEX(sdat.foodx);
  264. u8g.drawFrame(fx, fy, FOOD_WH, FOOD_WH);
  265. if (FOOD_WH == 5) u8g.drawPixel(fx + 2, fy + 2);
  266. }
  267. // Draw GAME OVER
  268. if (!game_state) draw_game_over();
  269. // A click always exits this game
  270. if (ui.use_click()) exit_game();
  271. }
  272. void SnakeGame::enter_game() {
  273. init_game(1, game_screen); // 1 = Game running
  274. snake_reset();
  275. food_reset();
  276. }
  277. #endif // MARLIN_SNAKE