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 }