/ src / grid.rs
grid.rs
  1  //! Bubble grid.
  2  
  3  use super::*;
  4  
  5  pub const NUM_COLS: u32 = 6;
  6  pub const NUM_ROWS: u32 = 10;
  7  
  8  pub struct GameState {
  9      pub stats: PlayStats,
 10      pub rng: RandGenerator,
 11      pub grid: Grid,
 12      pub input: Input,
 13  }
 14  
 15  impl GameState {
 16      pub fn restart(&mut self) {
 17          self.stats.clear();
 18          self.stats.seed(&mut self.rng);
 19          self.grid = Grid::new(&mut self.rng);
 20      }
 21  }
 22  
 23  impl Default for GameState {
 24      fn default() -> Self {
 25          let mut rng = RandGenerator::new();
 26          Self {
 27              stats: PlayStats::default(),
 28              grid: Grid::new(&mut rng),
 29              input: Input::default(),
 30              rng,
 31          }
 32      }
 33  }
 34  
 35  pub struct PlayStats {
 36      /// Level name.
 37      pub level_name: String,
 38      /// Score.
 39      score: u32,
 40      /// Play phase.
 41      phase: u32,
 42      /// Gameplay speed.
 43      rate: f32,
 44      /// Total playtime (seconds).
 45      playtime: f32,
 46      /// Number of lifes (life preservers).
 47      lifes: u8,
 48      /// Last check was a success.
 49      last_check_success: bool,
 50      /// Total successes.
 51      total_successes: u32,
 52      /// Remaining in phase.
 53      remaining_in_phase: u32,
 54  }
 55  
 56  impl PlayStats {
 57      fn game_over(&self) -> bool {
 58          self.lifes == 0
 59      }
 60  
 61      fn clear(&mut self) {
 62          self.score = 0;
 63          self.phase = 1;
 64          self.rate = 1.6;
 65          self.lifes = 3;
 66          self.playtime = 0.0;
 67          self.last_check_success = true;
 68          self.total_successes = 0;
 69          self.remaining_in_phase = 12;
 70      }
 71  
 72      pub fn seed(&self, rng: &mut RandGenerator) {
 73          use std::hash::Hasher;
 74  
 75          let mut hasher = std::hash::DefaultHasher::new();
 76          self.level_name.hash(&mut hasher);
 77          rng.srand(hasher.finish());
 78      }
 79  }
 80  
 81  impl Default for PlayStats {
 82      fn default() -> Self {
 83          Self {
 84              level_name: "DEBUG".into(),
 85              score: 0,
 86              phase: 1,
 87              rate: 0.6,
 88              playtime: 0.0,
 89              lifes: 3,
 90              last_check_success: true,
 91              total_successes: 0,
 92              remaining_in_phase: 12,
 93          }
 94      }
 95  }
 96  
 97  pub struct Grid {
 98      pub rows: Vec<GridRow>,
 99      pub rules: Vec<GridRowRule>,
100      pub y_offset_percent: f32,
101  }
102  
103  impl Grid {
104      fn new(rng: &mut RandGenerator) -> Self {
105          let mut rows = Vec::with_capacity(10);
106          let mut rules = Vec::new();
107  
108          // Build initial rules.
109          for _ in 0..4 {
110              rules.push(GridRow::new(rng).into_rule());
111          }
112  
113          // Add ocean rows.
114          for _ in 0..NUM_ROWS {
115              let mut row = GridRow::new_ocean();
116              row.set_remaining_wraps(0);
117              rows.push(row);
118          }
119  
120          // Add new row for the next set.
121          let row = rules[rng.gen_range(0, rules.len())].generate_row(rng, 0);
122          rows.push(row);
123  
124          Self {
125              rows,
126              rules,
127              y_offset_percent: 0.0,
128          }
129      }
130  
131      pub fn tick(
132          &mut self,
133          dt: f32,
134          stats: &mut PlayStats,
135          rng: &mut RandGenerator,
136          input: &mut Input,
137      ) {
138          let rate = match input.fast {
139              true => 7.5f32.max(stats.rate),
140              false => stats.rate,
141          };
142          stats.playtime += dt * rate;
143  
144          if stats.game_over() {
145              return;
146          }
147  
148          for event in &input.events {
149              match *event {
150                  InputEvent::SwapRight(pos) => {
151                      if self.rows.len() > pos.y as usize {
152                          self.rows[pos.y as usize].swap(pos.x, pos.x + 1);
153                      }
154                  }
155                  InputEvent::Alert(pos) => {
156                      if self.rows.len() > pos.y as usize {
157                          self.rows[pos.y as usize].mixed[pos.x as usize].marked ^= true;
158                      }
159                  }
160                  InputEvent::Restart => {}
161              }
162          }
163  
164          let next_offset = (self.y_offset_percent + ((dt / 5.0) * rate)).rem_euclid(1.0);
165          let wrapped = next_offset < self.y_offset_percent;
166  
167          if wrapped {
168              if !self.rows[0].is_ocean() {
169                  stats.score += self.passing_score(&self.rows[0]);
170              }
171  
172              match self.rows[0].flags {
173                  GridRowFlags::PhaseStart => {
174                      self.wrap_row(stats, rng);
175                      if stats.phase <= 5 {
176                          let new_row = GridRow::new(rng);
177                          if !new_row.is_ocean() {
178                              self.rules.push(new_row.into_rule());
179                          }
180                      }
181                      stats.phase += 1;
182                      stats.remaining_in_phase = match stats.phase {
183                          0..2 => 12,
184                          2..4 => 24,
185                          4..6 => 36,
186                          _ => 48,
187                      };
188                      stats.rate += 0.1;
189                      stats.lifes += 1;
190                  }
191                  _ => {
192                      self.wrap_row(stats, rng);
193                  }
194              }
195  
196              input.hover_pos.y = input.hover_pos.y.saturating_sub(1);
197          }
198  
199          self.y_offset_percent = next_offset;
200      }
201  
202      pub fn new_random_row(&mut self, stats: &mut PlayStats, rng: &mut RandGenerator) -> GridRow {
203          if stats.remaining_in_phase == 0 {
204              return GridRow::new_ocean();
205          }
206          if self.rules.len() == 0 {
207              return GridRow::new_ocean();
208          }
209  
210          let r = rng.gen_range(0.0, 1.0);
211          let ocean = match (stats.phase, r) {
212              (0..2, 0.0..0.5) => true,
213              (2..4, 0.0..0.25) => true,
214              (4..6, 0.0..0.1) => true,
215              _ => false,
216          };
217          if ocean {
218              return GridRow::new_ocean();
219          }
220  
221          let r = rng.gen_range(0.0, 1.0);
222          let num_variants = match (stats.phase, r) {
223              (0..2, 0.0..0.8) => 0,
224              (0..2, 0.8..1.0) => 1,
225              (2..4, 0.0..0.5) => 0,
226              (2..4, 0.5..0.8) => 1,
227              (2..4, 0.8..1.0) => 2,
228              (4..8, 0.0..0.2) => 0,
229              (4..8, 0.2..0.5) => 1,
230              (4..8, 0.5..0.8) => 2,
231              (4..8, 0.8..1.0) => 3,
232              _ => 4,
233          };
234  
235          stats.remaining_in_phase = stats.remaining_in_phase.saturating_sub(1);
236          let mut new =
237              self.rules[rng.gen_range(0, self.rules.len())].generate_row(rng, num_variants);
238          if stats.remaining_in_phase == 0 {
239              new.flags = GridRowFlags::PhaseStart;
240          }
241  
242          new
243      }
244  
245      pub fn passing_score(&self, row: &GridRow) -> u32 {
246          self.rules
247              .iter()
248              .map(|rule| rule.row_score(row))
249              .max()
250              .unwrap_or(0)
251      }
252  
253      pub fn wrap_row(&mut self, stats: &mut PlayStats, rng: &mut RandGenerator) -> bool {
254          let mut row = self.rows.remove(0);
255          let is_wrapping = row.should_wrap();
256          if let Some(remaining) = &mut row.remaining_loops {
257              *remaining = remaining.saturating_sub(1);
258          }
259  
260          let score = self.passing_score(&row);
261          if (row.is_ocean() || score > 0) && !(row.flags == GridRowFlags::PhaseStart) {
262              stats.last_check_success = true;
263              let new_row = self.new_random_row(stats, rng);
264              self.rows.push(new_row);
265              true
266          } else {
267              if !row.is_ocean() && score == 0 {
268                  stats.last_check_success = false;
269                  stats.lifes = stats.lifes.saturating_sub(1);
270              }
271              row.to_correct();
272              if is_wrapping {
273                  row.total_loops += 1;
274                  self.rows.push(row);
275              } else {
276                  let new_row = self.new_random_row(stats, rng);
277                  self.rows.push(new_row);
278              }
279              false
280          }
281      }
282  
283      /// Draw the grid.
284      /// The grid is drawn between <0, 0> & <1000,600>.
285      pub fn draw(&self, input: &Input, stats: &PlayStats) {
286          // Draw ocean.
287          draw_rectangle(0.0, 0.0, 600.0, 1000.0, colors::OCEAN);
288  
289          // Draw foam.
290          let cycle = stats.playtime.rem_euclid(20.0) / 20.0;
291          for i in 0..10 {
292              let x = -750.0 + (i as f32) * 200.0 + cycle * 200.0;
293              for j in 0..10 {
294                  let y = -400.0 + (j as f32) * 200.0 + cycle * 200.0;
295                  let offset = wave_offset(x, y, stats.playtime).scaled(0.5);
296                  draw_rectangle(x + offset.x, y + offset.y, 100.0, 100.0, colors::FOAM);
297              }
298          }
299  
300          // Draw bubbles.
301          for i in 0..self.rows.len().min(11) {
302              let row = &self.rows[i];
303              let y = (1000.0 - (i as f32) * 100.0) - 50.0 + self.y_offset_percent * 100.0;
304              for j in 0..row.mixed.len() {
305                  let bubble = &row.mixed[j];
306                  let x = (j as f32) * 100.0 + 50.0;
307                  let pos = wave_pos(x, y, stats.playtime);
308                  bubble.draw(pos.x, pos.y, 1.0);
309              }
310          }
311  
312          // Draw hover.
313          {
314              let x = (input.hover_pos.x as f32) * 100.0 + 50.0;
315              let y = (1000.0 - (input.hover_pos.y as f32) * 100.0) - 50.0
316                  + self.y_offset_percent * 100.0;
317              let offset = wave_offset(x, y, stats.playtime).scaled(0.5);
318  
319              draw_circle_lines(x + offset.x, y + offset.y, 50.0, 5.0, colors::SELECT_BOX);
320  
321              if input.hover_side == HoverSide::Left {
322                  draw_rectangle_lines(
323                      x - 150.0 + offset.x,
324                      y - 50.0 + offset.y,
325                      200.0,
326                      100.0,
327                      5.0,
328                      colors::SELECT_BUBBLE,
329                  );
330              }
331              if input.hover_side == HoverSide::Right {
332                  draw_rectangle_lines(
333                      x - 50.0 + offset.x,
334                      y - 50.0 + offset.y,
335                      200.0,
336                      100.0,
337                      5.0,
338                      colors::SELECT_BUBBLE,
339                  );
340              }
341          }
342  
343          // Draw indicator.
344          for i in 0..self.rows.len().min(11) {
345              let row = &self.rows[i];
346  
347              // Only show for first phase.
348              if stats.phase >= 2 {
349                  continue;
350              }
351  
352              // Indicator phases out after the second loop.
353              if row.total_loops > 2 {
354                  continue;
355              }
356  
357              let x = 650.0 - 12.5;
358              let y = (1000.0 - (i as f32) * 100.0) - 50.0 + self.y_offset_percent * 100.0;
359              let offset = wave_offset(x, y - 12.5, stats.playtime).scaled(0.25);
360              draw_rectangle(
361                  x + offset.x,
362                  y + offset.y - 12.5,
363                  25.0,
364                  25.0,
365                  match row.is_ocean() || self.passing_score(row) > 0 {
366                      true => colors::GREEN.with_alpha(ocean_alpha(300.0, y)),
367                      false => colors::RED.with_alpha(ocean_alpha(300.0, y)),
368                  },
369              );
370          }
371  
372          // Cover top and bottom.
373          // draw_rectangle(-100.0, -100.0, 700.0 + 200.0, 100.0, colors::DEEP_OCEAN);
374          // draw_rectangle(-100.0, 1000.0, 700.0 + 200.0, 100.0, colors::DEEP_OCEAN);
375  
376          // Draw base.
377          draw_rectangle(
378              0.0,
379              1000.0,
380              600.0,
381              100.0,
382              match stats.last_check_success {
383                  true => colors::SAND,
384                  false => colors::RED,
385              },
386          );
387  
388          // Draw phase & lifes.
389          let phase_offset = wave_offset(100.0, 25.0, stats.playtime).scaled(0.5);
390          draw_text(
391              &format!("PHASEx{}", stats.phase),
392              0.0 + phase_offset.x,
393              -50.0 + phase_offset.y,
394              50.0,
395              colors::TEXT,
396          );
397          let phase_offset = wave_offset(450.0, 25.0, stats.playtime).scaled(0.5);
398          draw_text(
399              &format!("FLOATSx{}", stats.lifes),
400              425.0 + phase_offset.x,
401              -50.0 + phase_offset.y,
402              50.0,
403              colors::RED,
404          );
405  
406          // Draw score & level.
407          let phase_offset = wave_offset(100.0, -25.0, stats.playtime).scaled(0.5);
408          draw_text(
409              &format!("{}", stats.score),
410              0.0 + phase_offset.x,
411              -100.0 + phase_offset.y,
412              50.0,
413              colors::TEXT,
414          );
415          let phase_offset = wave_offset(450.0, -25.0, stats.playtime).scaled(0.5);
416          draw_text(
417              &stats.level_name,
418              600.0 - ((stats.level_name.len() as f32) * 22.0) + phase_offset.x,
419              -100.0 + phase_offset.y,
420              50.0,
421              colors::TEXT,
422          );
423  
424          // Draw rules.
425          let phase_offset = wave_offset(800.0, 200.0, stats.playtime).scaled(0.5);
426          draw_text(
427              &format!("Pool's Rules"),
428              700.0 + phase_offset.x,
429              100.0 + phase_offset.y,
430              47.5,
431              colors::TEXT,
432          );
433          draw_line(
434              700.0 + phase_offset.x,
435              110.0 + phase_offset.y,
436              950.0 + phase_offset.x,
437              110.0 + phase_offset.y,
438              5.0,
439              colors::TEXT,
440          );
441          for (i, rule) in self.rules.iter().enumerate() {
442              let y = 150.0 + 40.0 * (i as f32);
443              rule.draw_rule(725.0 + phase_offset.x, y + phase_offset.y, 0.4);
444          }
445      }
446  }
447  
448  #[derive(Clone)]
449  pub struct GridRow {
450      original: Vec<Bubble>,
451      mixed: Vec<Bubble>,
452      flags: GridRowFlags,
453      total_loops: u32,
454      remaining_loops: Option<u8>,
455      events: Vec<GridRowEvent>,
456  }
457  
458  impl GridRow {
459      /// Randomly generate a new bubble row.
460      fn new(rng: &mut RandGenerator) -> Self {
461          let mut original = Vec::new();
462          for _ in 0..6 {
463              original.push(Bubble::new(rng));
464          }
465          let mixed = original.clone();
466          Self {
467              original,
468              mixed,
469              flags: GridRowFlags::None,
470              ..Default::default()
471          }
472      }
473  
474      /// Create a row of ocean.
475      fn new_ocean() -> Self {
476          Self::default()
477      }
478  
479      /// Randomly mix the mixed row.
480      fn mix(&mut self, rng: &mut RandGenerator, mixes: u8) {
481          // Reset events.
482          self.events.clear();
483  
484          // Re-mix.
485          self.mixed = self.original.clone();
486          for _ in 0..mixes {
487              let bubble_idx = rng.gen_range(0, 6);
488              let dir = MixDirection::new(rng);
489              if (dir == MixDirection::Left && bubble_idx > 0) || bubble_idx == 5 {
490                  self.mixed.swap(bubble_idx, bubble_idx - 1);
491              }
492              if (dir == MixDirection::Right && bubble_idx < 5) || bubble_idx == 0 {
493                  self.mixed.swap(bubble_idx, bubble_idx + 1);
494              }
495          }
496  
497          // Random chance for events.
498          let event_rng = rng.gen_range(0.0, 1.0);
499          match event_rng {
500              // Missing swimmer.
501              0.0..0.2 => {
502                  for bubble in &mut self.mixed {
503                      let event_rng = rng.gen_range(0.0, 1.0);
504                      if bubble.color != BubbleType::Ocean && event_rng < 0.5 {
505                          bubble.color = BubbleType::Ocean;
506                          self.events.push(GridRowEvent::Missing);
507                          break;
508                      }
509                  }
510              }
511              // Imposter.
512              0.2..0.4 => {
513                  for bubble in &mut self.mixed {
514                      let event_rng = rng.gen_range(0.0, 1.0);
515                      if bubble.color == BubbleType::Ocean && event_rng < 0.5 {
516                          *bubble = Bubble::new_swimmer(rng);
517                          self.events.push(GridRowEvent::Imposter(bubble.color));
518                          break;
519                      }
520                  }
521              }
522              // Do nothing.
523              _ => {}
524          }
525      }
526  
527      // fn is_correct(&self) -> bool {
528      //     if self.events.len() == 0 {
529      //         if self.original == self.mixed {
530      //             for bubble in &self.mixed {
531      //                 if bubble.marked {
532      //                     return false;
533      //                 }
534      //             }
535      //             return true;
536      //         }
537      //         return false;
538      //     }
539  
540      //     'event_check: for event in &self.events {
541      //         match event {
542      //             GridRowEvent::Missing => {
543      //                 for bubble in &self.mixed {
544      //                     if bubble.color == BubbleType::Ocean && bubble.marked {
545      //                         continue 'event_check;
546      //                     }
547      //                 }
548      //                 return false;
549      //             }
550      //             GridRowEvent::Imposter(color) => {
551      //                 for bubble in &self.mixed {
552      //                     if bubble.color == *color && bubble.marked {
553      //                         continue 'event_check;
554      //                     }
555      //                 }
556      //                 return false;
557      //             }
558      //         }
559      //     }
560  
561      //     true
562      // }
563  
564      fn to_correct(&mut self) {
565          self.mixed = self.original.clone();
566      }
567  
568      // fn score(&self, rules: &Vec<GridRowRule>) -> u32 {
569      //     if !self.is_correct() {
570      //         return 0;
571      //     }
572      //     if self.is_ocean() && self.events.len() == 0 {
573      //         return 0;
574      //     }
575  
576      //     let mut total = 100u32;
577      //     let total_markings = self.mixed.iter().filter(|b| b.marked).count();
578      //     if total_markings <= self.events.len() {
579      //         total += self.events.len() as u32 * 100;
580      //     }
581  
582      //     total
583      // }
584  
585      /// Check if row is ocean.
586      fn is_ocean(&self) -> bool {
587          self.original
588              .iter()
589              .filter(|b| b.color == BubbleType::Ocean)
590              .count()
591              == NUM_COLS as usize
592      }
593  
594      fn swap(&mut self, left: u32, right: u32) {
595          if left.max(right) < self.original.len() as u32 {
596              self.mixed.swap(left as usize, right as usize);
597          }
598      }
599  
600      fn should_wrap(&self) -> bool {
601          if self.flags == GridRowFlags::DoNotLoop {
602              return false;
603          }
604          if let Some(remaining) = &self.remaining_loops {
605              if *remaining == 0 {
606                  return false;
607              }
608          }
609  
610          true
611      }
612  
613      fn set_remaining_wraps(&mut self, remaining: u8) {
614          self.remaining_loops = Some(remaining);
615      }
616  
617      fn into_rule(&self) -> GridRowRule {
618          GridRowRule {
619              rule: self.original.clone(),
620          }
621      }
622  }
623  
624  impl Default for GridRow {
625      fn default() -> Self {
626          let mut original = Vec::new();
627          for _ in 0..6 {
628              original.push(Bubble::new_ocean());
629          }
630          Self {
631              mixed: original.clone(),
632              original,
633              flags: GridRowFlags::None,
634              total_loops: 0,
635              remaining_loops: None,
636              events: Vec::new(),
637          }
638      }
639  }
640  
641  #[derive(Clone)]
642  pub struct GridRowRule {
643      rule: Vec<Bubble>,
644  }
645  
646  impl GridRowRule {
647      fn generate_row(&self, rng: &mut RandGenerator, num_variants: u8) -> GridRow {
648          let mut row = GridRow::new_ocean();
649          row.original = self.rule.clone();
650          row.mixed = self.rule.clone();
651  
652          row.mix(rng, num_variants);
653  
654          row
655      }
656  
657      fn row_score(&self, row: &GridRow) -> u32 {
658          let mut score = 0u32;
659          for i in 0..self.rule.len() {
660              let rule_bubble = &self.rule[i];
661              let row_bubble = &row.mixed[i];
662  
663              if row_bubble.color == rule_bubble.color {
664                  score += 20;
665                  continue;
666              }
667              if row_bubble.marked {
668                  score += 120;
669                  continue;
670              }
671  
672              return 0;
673          }
674  
675          score
676      }
677  
678      fn draw_rule(&self, x: f32, y: f32, scale: f32) {
679          for (i, bubble) in self.rule.iter().enumerate() {
680              bubble.draw(x + (i as f32) * 100.0 * scale, y, scale);
681          }
682      }
683  }
684  
685  /// Event that can happen on a row.
686  #[derive(Copy, Clone, PartialEq)]
687  enum GridRowEvent {
688      /// A bubble is missing.
689      Missing,
690      /// There is an extra bubble on the row.
691      Imposter(BubbleType),
692  }
693  
694  /// Row modifiers.
695  #[derive(Copy, Clone, PartialEq)]
696  enum GridRowFlags {
697      None,
698      PhaseStart,
699      DoNotLoop,
700  }
701  
702  #[derive(Clone)]
703  struct Bubble {
704      color: BubbleType,
705      marked: bool,
706  }
707  
708  impl Bubble {
709      fn new(rng: &mut RandGenerator) -> Self {
710          if rng.gen_range(0.0, 1.0) < 0.5 {
711              return Self::default();
712          }
713  
714          const BUBBLES: &[BubbleType] = &[
715              BubbleType::Ocean,
716              BubbleType::SwimmerOrange,
717              BubbleType::SwimmerPurple,
718              BubbleType::SwimmerPink,
719          ];
720          let color = BUBBLES[rng.gen_range(0, BUBBLES.len())];
721          Self {
722              color: color,
723              ..Default::default()
724          }
725      }
726  
727      fn new_swimmer(rng: &mut RandGenerator) -> Self {
728          const BUBBLES: &[BubbleType] = &[
729              BubbleType::SwimmerOrange,
730              BubbleType::SwimmerPurple,
731              BubbleType::SwimmerPink,
732          ];
733          let color = BUBBLES[rng.gen_range(0, BUBBLES.len())];
734          Self {
735              color: color,
736              ..Default::default()
737          }
738      }
739  
740      fn new_ocean() -> Self {
741          Self {
742              color: BubbleType::Ocean,
743              ..Default::default()
744          }
745      }
746  
747      fn draw(&self, x: f32, y: f32, scale: f32) {
748          let alpha = match scale {
749              1.0 => ocean_alpha(x, y),
750              _ => 1.0,
751          };
752          if self.marked {
753              draw_ellipse(
754                  x,
755                  y,
756                  45.0 * scale,
757                  35.0 * scale,
758                  0.0,
759                  colors::MARKER.with_alpha(alpha),
760              );
761              draw_ellipse(
762                  x,
763                  y,
764                  35.0 * scale,
765                  25.0 * scale,
766                  0.0,
767                  colors::OCEAN.with_alpha(alpha),
768              );
769          }
770  
771          match self.color {
772              BubbleType::Ocean => {
773                  // Draw nothing.
774              }
775              BubbleType::SwimmerOrange => {
776                  draw_ellipse(
777                      x,
778                      y,
779                      35.0 * scale,
780                      25.0 * scale,
781                      0.0,
782                      colors::SWIMMER_ORANGE_FLOAT.with_alpha(alpha),
783                  );
784                  draw_ellipse(
785                      x,
786                      y,
787                      28.0 * scale,
788                      15.0 * scale,
789                      0.0,
790                      colors::OCEAN.with_alpha(alpha),
791                  );
792                  draw_rectangle(
793                      x - 12.5 * scale,
794                      y + 8.0 * scale,
795                      25.0 * scale,
796                      -40.0 * scale,
797                      colors::SWIMMER_ORANGE_SWIMMER.with_alpha(alpha),
798                  );
799              }
800              BubbleType::SwimmerPurple => {
801                  draw_rectangle(
802                      x - 15.0 * scale,
803                      y + 25.0 * scale,
804                      30.0 * scale,
805                      -50.0 * scale,
806                      colors::SWIMMER_PURPLE_SWIMMER.with_alpha(alpha),
807                  );
808                  draw_circle(
809                      x - 18.0 * scale,
810                      y + 13.0 * scale,
811                      12.0 * scale,
812                      colors::SWIMMER_PURPLE_FLOAT.with_alpha(alpha),
813                  );
814                  draw_circle(
815                      x + 18.0 * scale,
816                      y + 13.0 * scale,
817                      12.0 * scale,
818                      colors::SWIMMER_PURPLE_FLOAT.with_alpha(alpha),
819                  );
820                  draw_circle(
821                      x - 8.0 * scale,
822                      y + 17.0 * scale,
823                      12.0 * scale,
824                      colors::SWIMMER_PURPLE_FLOAT.with_alpha(alpha),
825                  );
826                  draw_circle(
827                      x + 8.0 * scale,
828                      y + 17.0 * scale,
829                      12.0 * scale,
830                      colors::SWIMMER_PURPLE_FLOAT.with_alpha(alpha),
831                  );
832              }
833              BubbleType::SwimmerPink => {
834                  draw_ellipse(
835                      x,
836                      y,
837                      35.0 * scale,
838                      25.0 * scale,
839                      0.0,
840                      colors::SWIMMER_PINK_FLOAT.with_alpha(alpha),
841                  );
842                  draw_ellipse(
843                      x,
844                      y,
845                      28.0 * scale,
846                      15.0 * scale,
847                      0.0,
848                      colors::OCEAN.with_alpha(alpha),
849                  );
850                  draw_rectangle(
851                      x - 40.0 * scale,
852                      y + 10.0 * scale,
853                      80.0 * scale,
854                      -30.0 * scale,
855                      colors::SWIMMER_PINK_SWIMMER.with_alpha(alpha),
856                  );
857              }
858          };
859      }
860  }
861  
862  impl Default for Bubble {
863      fn default() -> Self {
864          Self {
865              color: BubbleType::Ocean,
866              marked: false,
867          }
868      }
869  }
870  
871  impl PartialEq for Bubble {
872      fn eq(&self, other: &Self) -> bool {
873          self.color == other.color
874      }
875  }
876  
877  #[derive(Clone, Copy, PartialEq)]
878  enum BubbleType {
879      Ocean,
880      SwimmerOrange,
881      SwimmerPurple,
882      SwimmerPink,
883  }
884  
885  #[derive(Clone, Copy, PartialEq)]
886  enum MixDirection {
887      Left,
888      Right,
889  }
890  
891  impl MixDirection {
892      fn new(rng: &mut RandGenerator) -> Self {
893          let val = rng.gen_range(0, 1);
894          if val == 0 {
895              return MixDirection::Left;
896          }
897          return MixDirection::Right;
898      }
899  }