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