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.

555 lines
21 KiB

  1. /*
  2. * Copyright 2021 Tyler Thrailkill (@snowe/@snowe2010) <tyler.b.thrailkill@gmail.com>
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include "ocean_dream.h"
  18. #include "quantum.h"
  19. #include "print.h"
  20. // Calculated Parameters
  21. #define TWINKLE_PROBABILITY_MODULATOR 100 / TWINKLE_PROBABILITY // CALCULATED: Don't Touch
  22. #define TOTAL_STARS STARS_PER_LINE *NUMBER_OF_STAR_LINES // CALCULATED: Don't Touch
  23. #define OCEAN_ANIMATION_MODULATOR NUMBER_OF_FRAMES / OCEAN_ANIMATION_SPEED // CALCULATED: Don't Touch
  24. #define SHOOTING_STAR_ANIMATION_MODULATOR NUMBER_OF_FRAMES / SHOOTING_STAR_ANIMATION_SPEED // CALCULATED: Don't Touch
  25. #define STAR_ANIMATION_MODULATOR NUMBER_OF_FRAMES / STAR_ANIMATION_SPEED // CALCULATED: Don't Touch
  26. uint8_t animation_counter = 0; // global animation counter.
  27. bool is_calm = false;
  28. uint32_t starry_night_anim_timer = 0;
  29. uint32_t starry_night_anim_sleep = 0;
  30. static int current_wpm = 0;
  31. static uint8_t increment_counter(uint8_t counter, uint8_t max) {
  32. counter++;
  33. if (counter >= max) {
  34. return 0;
  35. } else {
  36. return counter;
  37. }
  38. }
  39. #ifdef ENABLE_WAVE
  40. static uint8_t decrement_counter(uint8_t counter, uint8_t max) {
  41. counter--;
  42. if (counter < 0 || counter > max) {
  43. return max;
  44. } else {
  45. return counter;
  46. }
  47. }
  48. #endif
  49. #ifdef ENABLE_MOON // region
  50. # ifndef STATIC_MOON
  51. uint8_t moon_animation_frame = 0; // keeps track of current moon frame
  52. uint16_t moon_animation_counter = 0; // counts how many frames to wait before animating moon to next frame
  53. # endif
  54. # ifdef STATIC_MOON
  55. static const char PROGMEM moon[6] = {
  56. 0x18, 0x7E, 0xFF, 0xC3, 0x81, 0x81,
  57. };
  58. # endif
  59. # ifndef STATIC_MOON
  60. static const char PROGMEM moon_animation[14][8] = {
  61. // clang-format off
  62. { 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, },
  63. { 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x42, 0x00, },
  64. { 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xC3, 0x00, 0x00, },
  65. { 0x3C, 0x7E, 0xFF, 0xFF, 0xC3, 0x81, 0x00, 0x00, },
  66. { 0x3C, 0x7E, 0xFF, 0xC3, 0x81, 0x00, 0x00, 0x00, },
  67. { 0x3C, 0x7E, 0xC3, 0x81, 0x81, 0x00, 0x00, 0x00, },
  68. { 0x3C, 0x42, 0x81, 0x81, 0x00, 0x00, 0x00, 0x00, },
  69. { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
  70. { 0x00, 0x00, 0x00, 0x00, 0x81, 0x81, 0x42, 0x3C, },
  71. { 0x00, 0x00, 0x00, 0x81, 0x81, 0xC3, 0x7E, 0x3C, },
  72. { 0x00, 0x00, 0x00, 0x81, 0xC3, 0xFF, 0x7E, 0x3C, },
  73. { 0x00, 0x00, 0x81, 0xC3, 0xFF, 0xFF, 0x7E, 0x3C, },
  74. { 0x00, 0x00, 0xC3, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, },
  75. { 0x00, 0x42, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, },
  76. // clang-format on
  77. };
  78. # endif
  79. static void draw_moon(void) {
  80. # ifdef STATIC_MOON
  81. oled_set_cursor(MOON_COLUMN, MOON_LINE);
  82. oled_write_raw_P(moon, 6);
  83. # endif
  84. # ifndef STATIC_MOON
  85. moon_animation_counter = increment_counter(moon_animation_counter, ANIMATE_MOON_EVERY_N_FRAMES);
  86. if (moon_animation_counter == 0) {
  87. moon_animation_frame = increment_counter(moon_animation_frame, 14);
  88. oled_set_cursor(MOON_COLUMN, MOON_LINE);
  89. oled_write_raw_P(moon_animation[moon_animation_frame], 8);
  90. }
  91. # endif
  92. }
  93. #endif // endregion
  94. #ifdef ENABLE_WAVE // region
  95. uint8_t starry_night_wave_frame_width_counter = 31;
  96. uint8_t rough_waves_frame_counter = 0;
  97. // clang-format off
  98. static const char PROGMEM ocean_top[8][32] = {
  99. // still ocean
  100. {
  101. 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  102. 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  103. 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  104. 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  105. },
  106. // small ripples
  107. {
  108. 0x20, 0x60, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  109. 0x20, 0x60, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  110. 0x20, 0x60, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  111. 0x20, 0x60, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  112. },
  113. // level 2 ripples
  114. {
  115. 0x20, 0x60, 0x40, 0x40, 0x20, 0x60, 0x40, 0x40,
  116. 0x20, 0x60, 0x40, 0x40, 0x20, 0x60, 0x40, 0x40,
  117. 0x20, 0x60, 0x40, 0x40, 0x20, 0x60, 0x40, 0x40,
  118. 0x20, 0x60, 0x40, 0x40, 0x20, 0x60, 0x40, 0x40,
  119. },
  120. // level 3 waves
  121. {
  122. 0x40, 0x20, 0x10, 0x20, 0x40, 0x40, 0x40, 0x40,
  123. 0x40, 0x20, 0x10, 0x20, 0x40, 0x40, 0x40, 0x40,
  124. 0x40, 0x20, 0x10, 0x20, 0x40, 0x40, 0x40, 0x40,
  125. 0x40, 0x20, 0x10, 0x20, 0x40, 0x40, 0x40, 0x40,
  126. },
  127. {
  128. 0x40, 0x40, 0x20, 0x10, 0x28, 0x50, 0x40, 0x40,
  129. 0x40, 0x40, 0x20, 0x10, 0x28, 0x50, 0x40, 0x40,
  130. 0x40, 0x40, 0x20, 0x10, 0x28, 0x50, 0x40, 0x40,
  131. 0x40, 0x40, 0x20, 0x10, 0x28, 0x50, 0x40, 0x40,
  132. },
  133. {
  134. 0x40, 0x40, 0x40, 0x20, 0x10, 0x30, 0x70, 0x60,
  135. 0x40, 0x40, 0x40, 0x20, 0x10, 0x30, 0x70, 0x60,
  136. 0x40, 0x40, 0x40, 0x20, 0x10, 0x30, 0x70, 0x60,
  137. 0x40, 0x40, 0x40, 0x20, 0x10, 0x30, 0x70, 0x60,
  138. },
  139. };
  140. static const char PROGMEM ocean_bottom[8][32] = {
  141. // still ocean
  142. {
  143. 0x00, 0x40, 0x40, 0x41, 0x01, 0x01, 0x01, 0x21,
  144. 0x20, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x44,
  145. 0x44, 0x40, 0x40, 0x00, 0x00, 0x08, 0x08, 0x00,
  146. 0x01, 0x01, 0x01, 0x00, 0x40, 0x40, 0x00, 0x00,
  147. },
  148. // small ripples
  149. {
  150. 0x00, 0x00, 0x40, 0x40, 0x01, 0x01, 0x01, 0x20,
  151. 0x20, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
  152. 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
  153. 0x00, 0x01, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00,
  154. },
  155. // level 2 ripples
  156. {
  157. 0x00, 0x00, 0x40, 0x40, 0x01, 0x01, 0x01, 0x20,
  158. 0x20, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04,
  159. 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
  160. 0x00, 0x01, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00,
  161. },
  162. // level 3 waves
  163. {
  164. 0x00, 0x40, 0x40, 0x42, 0x42, 0x03, 0x11, 0x11,
  165. 0x20, 0x20, 0x00, 0x00, 0x08, 0x0C, 0x0C, 0x04,
  166. 0x05, 0x41, 0x41, 0x21, 0x20, 0x00, 0x00, 0x08,
  167. 0x0A, 0x0A, 0x0B, 0x41, 0x41, 0x41, 0x41, 0x00,
  168. },
  169. {
  170. 0x10, 0x10, 0x00, 0x80, 0x84, 0xC4, 0x02, 0x06,
  171. 0x84, 0x44, 0xC0, 0x80, 0x80, 0x20, 0x20, 0x10,
  172. 0x08, 0x12, 0x91, 0x81, 0x42, 0x40, 0x00, 0x00,
  173. 0x10, 0x12, 0x22, 0x22, 0x24, 0x04, 0x84, 0x80,
  174. },
  175. {
  176. 0x08, 0x80, 0x80, 0x82, 0x82, 0x03, 0x21, 0x21,
  177. 0x10, 0x10, 0x00, 0x00, 0x04, 0x04, 0x0C, 0x08,
  178. 0x09, 0x41, 0x42, 0x22, 0x20, 0x00, 0x00, 0x08,
  179. 0x0A, 0x0A, 0x0B, 0x41, 0x43, 0x42, 0x42, 0x00,
  180. },
  181. };
  182. // clang-format on
  183. static void animate_waves(void) {
  184. starry_night_wave_frame_width_counter = decrement_counter(starry_night_wave_frame_width_counter, WIDTH - 1); // only 3 frames for last wave type
  185. rough_waves_frame_counter = increment_counter(rough_waves_frame_counter, 3); // only 3 frames for last wave type
  186. void draw_ocean(uint8_t frame, uint16_t offset, uint8_t byte_index) {
  187. oled_write_raw_byte(pgm_read_byte(ocean_top[frame] + byte_index), offset);
  188. oled_write_raw_byte(pgm_read_byte(ocean_bottom[frame] + byte_index), offset + WIDTH);
  189. }
  190. for (int i = 0; i < WIDTH; ++i) {
  191. uint16_t offset = OCEAN_LINE * WIDTH + i;
  192. uint8_t byte_index = starry_night_wave_frame_width_counter + i;
  193. if (byte_index >= WIDTH) {
  194. byte_index = byte_index - WIDTH;
  195. }
  196. if (is_calm || current_wpm <= WAVE_CALM) {
  197. draw_ocean(0, offset, byte_index);
  198. } else if (current_wpm <= WAVE_HEAVY_STORM) {
  199. draw_ocean(1, offset, byte_index);
  200. } else if (current_wpm <= WAVE_HURRICANE) {
  201. draw_ocean(2, offset, byte_index);
  202. } else {
  203. draw_ocean(3 + rough_waves_frame_counter, offset, byte_index);
  204. }
  205. }
  206. }
  207. #endif // endregion
  208. #ifdef ENABLE_ISLAND // region
  209. uint8_t island_frame_1 = 0;
  210. // clang-format off
  211. // only use 46 bytes (first 18 are blank, so we don't write them, makes it smaller and we can see the shooting stars properly!)
  212. // To save space and allow the shooting stars to be seen, only draw the tree on every frame.
  213. // Tree is only 14bytes wide so we save 108 bytes on just the first row. Second row, the
  214. // first 18 bytes is always the same piece of land, so only store that once, which saves 90 bytes
  215. static const char PROGMEM islandRightTop[6][14] = {
  216. {0x84, 0xEC, 0x6C, 0x3C, 0xF8, 0xFE, 0x3F, 0x6B, 0xDB, 0xB9, 0x30, 0x40, 0x00, 0x00,},
  217. {0x80, 0xC3, 0xEE, 0x7C, 0xB8, 0xFC, 0xFE, 0x6F, 0xDB, 0x9B, 0xB2, 0x30, 0x00, 0x00,},
  218. {0x00, 0xC0, 0xEE, 0x7F, 0x3D, 0xF8, 0xFC, 0x7E, 0x57, 0xDB, 0xDB, 0x8A, 0x00, 0x00,},
  219. {0x00, 0xC0, 0xE6, 0x7F, 0x3B, 0xF9, 0xFC, 0xFC, 0xB6, 0xB3, 0x33, 0x61, 0x00, 0x00,},
  220. {0x00, 0x00, 0x00, 0x00, 0x80, 0xEE, 0xFF, 0xFB, 0xF9, 0xFC, 0xDE, 0xB6, 0xB6, 0x24,},
  221. {0x00, 0x00, 0x00, 0x00, 0xC0, 0xEE, 0xFE, 0xFF, 0xFB, 0xFD, 0xEE, 0xB6, 0xB6, 0x92,},
  222. };
  223. static const char PROGMEM islandRightBottom[6][14] = {
  224. {0x41, 0x40, 0x60, 0x3E, 0x3F, 0x23, 0x20, 0x60, 0x41, 0x43, 0x40, 0x40, 0x40, 0x80,},
  225. {0x40, 0x41, 0x60, 0x3E, 0x3F, 0x23, 0x20, 0x60, 0x40, 0x40, 0x41, 0x41, 0x40, 0x80,},
  226. {0x40, 0x40, 0x61, 0x3D, 0x3F, 0x27, 0x21, 0x60, 0x40, 0x40, 0x40, 0x40, 0x40, 0x80,},
  227. {0x40, 0x43, 0x61, 0x3C, 0x3F, 0x27, 0x21, 0x60, 0x41, 0x43, 0x43, 0x42, 0x40, 0x80,},
  228. {0x40, 0x40, 0x60, 0x3C, 0x3F, 0x27, 0x23, 0x63, 0x44, 0x40, 0x41, 0x41, 0x41, 0x81,},
  229. {0x40, 0x40, 0x60, 0x3C, 0x3F, 0x27, 0x23, 0x63, 0x42, 0x42, 0x41, 0x41, 0x41, 0x80,},
  230. };
  231. static const char PROGMEM islandLeft[18] = {
  232. 0x80, 0x40, 0x40, 0x40, 0x40, 0x60,
  233. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
  234. 0x20, 0x20, 0x20, 0x60, 0x40, 0x40,
  235. };
  236. // clang-format on
  237. static void animate_island(void) {
  238. if (animation_counter == 0) {
  239. island_frame_1 = increment_counter(island_frame_1, 2);
  240. }
  241. void draw_island_parts(uint8_t frame) {
  242. oled_set_cursor(ISLAND_COLUMN + 3, ISLAND_LINE);
  243. oled_write_raw_P(islandRightTop[frame], 14);
  244. oled_set_cursor(ISLAND_COLUMN + 0, ISLAND_LINE + 1);
  245. oled_write_raw_P(islandLeft, 18);
  246. oled_set_cursor(ISLAND_COLUMN + 3, ISLAND_LINE + 1);
  247. oled_write_raw_P(islandRightBottom[frame], 14);
  248. }
  249. if (is_calm || current_wpm < ISLAND_CALM) {
  250. draw_island_parts(0);
  251. } else if (current_wpm >= ISLAND_CALM && current_wpm < ISLAND_HEAVY_STORM) {
  252. draw_island_parts(island_frame_1 + 1);
  253. } else if (current_wpm >= ISLAND_HEAVY_STORM && current_wpm < ISLAND_HURRICANE) {
  254. draw_island_parts(island_frame_1 + 2);
  255. } else {
  256. draw_island_parts(island_frame_1 + 4);
  257. }
  258. }
  259. #endif // endregion
  260. #ifdef ENABLE_STARS // region
  261. bool stars_setup = false; // only setup stars once, then we just twinkle them
  262. struct Coordinate {
  263. int x;
  264. int y;
  265. bool exists;
  266. };
  267. struct Coordinate stars[TOTAL_STARS]; // tracks all stars/coordinates
  268. /**
  269. * Setup all the initial stars on the screen
  270. * This function divides the screen into regions based on STARS_PER_LINE and NUMBER_OF_STAR_LINES
  271. * where each line is made up of 8x8 pixel groups, that are populated by a single star.
  272. *
  273. * Not sure how this function will work with larger or smaller screens.
  274. * It should be fine, as long as the screen width is a multiple of 8
  275. */
  276. static void setup_stars(void) {
  277. // For every line, split the line into STARS_PER_LINE, find a random point in that region, and turn the pixel on
  278. // 36% probability it will not be added
  279. // (said another way, 80% chance it will start out lit in the x direction, then 80% chance it will start out lit in the y direction = 64% probability it will start out lit at all)
  280. for (int line = 0; line < NUMBER_OF_STAR_LINES; ++line) {
  281. for (int column_group = 0; column_group < STARS_PER_LINE; ++column_group) {
  282. uint8_t rand_column = rand() % 10;
  283. uint8_t rand_row = rand() % 10;
  284. if (rand_column < 8 && rand_row < 8) {
  285. int column_adder = column_group * 8;
  286. int line_adder = line * 8;
  287. int x = rand_column + column_adder;
  288. int y = rand_row + line_adder;
  289. oled_write_pixel(x, y, true);
  290. stars[column_group + (line * STARS_PER_LINE)].x = x;
  291. stars[column_group + (line * STARS_PER_LINE)].y = y;
  292. stars[column_group + (line * STARS_PER_LINE)].exists = true;
  293. } else {
  294. stars[column_group + (line * STARS_PER_LINE)].exists = false;
  295. }
  296. }
  297. }
  298. stars_setup = true;
  299. }
  300. /**
  301. * Twinkle the stars (move them one pixel in any direction) with a probability of 50% to twinkle any given star
  302. */
  303. static void twinkle_stars(void) {
  304. for (int line = 0; line < NUMBER_OF_STAR_LINES; ++line) {
  305. for (int column_group = 0; column_group < STARS_PER_LINE; ++column_group) {
  306. struct Coordinate star = stars[column_group + (line * STARS_PER_LINE)];
  307. // skip stars that were never added
  308. if (!star.exists) {
  309. continue;
  310. }
  311. if (rand() % TWINKLE_PROBABILITY_MODULATOR == 0) {
  312. oled_write_pixel(star.x, star.y, false); // black out pixel
  313. // don't allow stars to leave their own region
  314. if (star.x == (column_group * 8)) { // star is the farthest left it can go in its region
  315. star.x++; // move it right immediately
  316. } else if (star.x == (((column_group + 1) * 8) - 1)) { // star is farthest right it can go in its region
  317. star.x--; // move it left immediately
  318. }
  319. if (star.y == (line * 8)) { // star is the farthest up it can go in its region
  320. star.y++; // move it down immediately
  321. } else if (star.y == (((line + 1) * 8) - 1)) { // star is farthest down it can go in its region
  322. star.y--; // move it up immediately
  323. }
  324. // now decide direction
  325. int new_x;
  326. int x_choice = rand() % 3;
  327. if (x_choice == 0) {
  328. new_x = star.x - 1;
  329. } else if (x_choice == 1) {
  330. new_x = star.x + 1;
  331. } else {
  332. new_x = star.x;
  333. }
  334. int new_y;
  335. int y_choice = rand() % 3;
  336. if (y_choice == 0) {
  337. new_y = star.y - 1;
  338. } else if (y_choice == 1) {
  339. new_y = star.y + 1;
  340. } else {
  341. new_y = star.y;
  342. }
  343. star.x = new_x;
  344. star.y = new_y;
  345. oled_write_pixel(new_x, new_y, true);
  346. }
  347. stars[column_group + (line * STARS_PER_LINE)] = star;
  348. }
  349. }
  350. }
  351. /**
  352. * Setup the stars and then animate them on subsequent frames
  353. */
  354. static void animate_stars(void) {
  355. if (!stars_setup) {
  356. setup_stars();
  357. } else {
  358. twinkle_stars();
  359. }
  360. }
  361. #endif // endregion
  362. #ifdef ENABLE_SHOOTING_STARS // region
  363. bool shooting_stars_setup = false; // only setup shooting stars array once with defaults
  364. struct ShootingStar {
  365. int x_1;
  366. int y_1;
  367. int x_2;
  368. int y_2;
  369. bool running;
  370. int frame;
  371. int delay;
  372. };
  373. struct ShootingStar shooting_stars[MAX_NUMBER_OF_SHOOTING_STARS]; // tracks all the shooting stars
  374. static void setup_shooting_star(struct ShootingStar *shooting_star) {
  375. int column_to_start = rand() % (WIDTH / 2);
  376. int row_to_start = rand() % (HEIGHT - 48); // shooting_stars travel diagonally 1 down, 1 across. So the lowest a shooting_star can start and not 'hit' the ocean is 32 above the ocean.
  377. shooting_star->x_1 = column_to_start;
  378. shooting_star->y_1 = row_to_start;
  379. shooting_star->x_2 = column_to_start + 1;
  380. shooting_star->y_2 = row_to_start + 1;
  381. shooting_star->running = true;
  382. shooting_star->frame++;
  383. shooting_star->delay = rand() % SHOOTING_STAR_DELAY;
  384. }
  385. static void move_shooting_star(struct ShootingStar *shooting_star) {
  386. oled_write_pixel(shooting_star->x_1, shooting_star->y_1, false);
  387. oled_write_pixel(shooting_star->x_2, shooting_star->y_2, false);
  388. shooting_star->x_1++;
  389. shooting_star->y_1++;
  390. shooting_star->x_2++;
  391. shooting_star->y_2++;
  392. shooting_star->frame++;
  393. oled_write_pixel(shooting_star->x_1, shooting_star->y_1, true);
  394. oled_write_pixel(shooting_star->x_2, shooting_star->y_2, true);
  395. }
  396. static void finish_shooting_star(struct ShootingStar *shooting_star) {
  397. oled_write_pixel(shooting_star->x_1, shooting_star->y_1, false);
  398. oled_write_pixel(shooting_star->x_2, shooting_star->y_2, false);
  399. shooting_star->running = false;
  400. shooting_star->frame = 0;
  401. }
  402. static void animate_shooting_star(struct ShootingStar *shooting_star) {
  403. if (shooting_star->frame > SHOOTING_STAR_FRAMES) {
  404. finish_shooting_star(shooting_star);
  405. return;
  406. } else if (!shooting_star->running) {
  407. setup_shooting_star(shooting_star);
  408. } else {
  409. if (shooting_star->delay == 0) {
  410. move_shooting_star(shooting_star);
  411. } else {
  412. shooting_star->delay--;
  413. }
  414. }
  415. }
  416. static void animate_shooting_stars(void) {
  417. if (is_calm) {
  418. return;
  419. }
  420. if (!shooting_stars_setup) {
  421. for (int i = 0; i < MAX_NUMBER_OF_SHOOTING_STARS; ++i) {
  422. shooting_stars[i].running = false;
  423. }
  424. shooting_stars_setup = true;
  425. }
  426. /**
  427. * Fixes issue with stars that were falling _while_ the
  428. * wpm dropped below the condition for them to keep falling
  429. */
  430. void end_extra_stars(uint8_t starting_index) {
  431. for (int shooting_star_index = starting_index; shooting_star_index < MAX_NUMBER_OF_SHOOTING_STARS; ++shooting_star_index) {
  432. struct ShootingStar shooting_star = shooting_stars[shooting_star_index];
  433. if (shooting_star.running) {
  434. finish_shooting_star(&shooting_star);
  435. shooting_stars[shooting_star_index] = shooting_star;
  436. }
  437. }
  438. }
  439. int number_of_shooting_stars = current_wpm / SHOOTING_STAR_WPM_INCREMENT;
  440. number_of_shooting_stars = (number_of_shooting_stars > MAX_NUMBER_OF_SHOOTING_STARS) ? MAX_NUMBER_OF_SHOOTING_STARS : number_of_shooting_stars;
  441. if (number_of_shooting_stars == 0) {
  442. // make sure all shooting_stars are ended
  443. end_extra_stars(0);
  444. } else {
  445. for (int shooting_star_index = 0; shooting_star_index < number_of_shooting_stars; ++shooting_star_index) {
  446. struct ShootingStar shooting_star = shooting_stars[shooting_star_index];
  447. animate_shooting_star(&shooting_star);
  448. shooting_stars[shooting_star_index] = shooting_star;
  449. }
  450. end_extra_stars(number_of_shooting_stars);
  451. }
  452. }
  453. #endif // endregion
  454. /**
  455. * Main rendering function
  456. *
  457. * Calls all different animations at different rates
  458. */
  459. void render_stars(void) {
  460. // // animation timer
  461. if (timer_elapsed32(starry_night_anim_timer) > STARRY_NIGHT_ANIM_FRAME_DURATION) {
  462. starry_night_anim_timer = timer_read32();
  463. current_wpm = get_current_wpm();
  464. #ifdef ENABLE_ISLAND
  465. animate_island();
  466. #endif
  467. #ifdef ENABLE_SHOOTING_STARS
  468. if (animation_counter % SHOOTING_STAR_ANIMATION_MODULATOR == 0) {
  469. animate_shooting_stars();
  470. }
  471. #endif
  472. #ifdef ENABLE_STARS
  473. // TODO offsetting the star animation from the wave animation would look better,
  474. // but if I do that, then the stars appear in the water because
  475. // the ocean animation has to wait a bunch of frames to overwrite it.
  476. // Possible solutions:
  477. // 1. Only draw stars to the top of the island/ocean.
  478. // 2. Draw ocean every frame, only move ocean on frames matching modulus
  479. // Problems:
  480. // 1. What if someone wants to move the island up a bit, or they want to have the stars reflect in the water?
  481. // 2. More cpu intensive. And I'm already running out of cpu as it is...
  482. if (animation_counter % STAR_ANIMATION_MODULATOR == 0) {
  483. animate_stars();
  484. }
  485. #endif
  486. #ifdef ENABLE_WAVE
  487. if (animation_counter % OCEAN_ANIMATION_MODULATOR == 0) {
  488. animate_waves();
  489. }
  490. #endif
  491. #ifdef ENABLE_MOON
  492. draw_moon();
  493. #endif
  494. animation_counter = increment_counter(animation_counter, NUMBER_OF_FRAMES);
  495. }
  496. // this fixes the screen on and off bug
  497. if (current_wpm > 0) {
  498. oled_on();
  499. starry_night_anim_sleep = timer_read32();
  500. } else if (timer_elapsed32(starry_night_anim_sleep) > OLED_TIMEOUT) {
  501. oled_off();
  502. }
  503. }