Compare commits

...

10 commits

Author SHA1 Message Date
Stephen Seo b31c7c22e7 Add LICENSE 2023-04-30 19:46:51 +09:00
Stephen Seo c6751bea39 Update README.md 2023-04-30 19:44:00 +09:00
Stephen Seo d9cc21f247 Add HP, tweaks (just about done) 2023-04-30 19:42:16 +09:00
Stephen Seo ce5cedb2c9 Fixes and tweaks 2023-04-30 18:14:08 +09:00
Stephen Seo 62adede449 Add "Wrong House" 2023-04-30 17:24:39 +09:00
Stephen Seo 74a72629e3 Add speed up sfx 2023-04-30 17:14:58 +09:00
Stephen Seo d555655690 Rewrite music module (needs tweaks)
Music now slows down on miss or slowdown. Support for music speed-up is
in, but sounds not good when used, so it's currently disabled.
2023-04-30 15:08:06 +09:00
Stephen Seo 087d3213a6 Minor fix to miss-house-message 2023-04-29 21:51:15 +09:00
Stephen Seo cfaf2e9970 Add speedup/slowdown, refactorings 2023-04-29 20:35:44 +09:00
Stephen Seo 94f3cdfa6e WIP music 2023-04-29 17:46:58 +09:00
6 changed files with 788 additions and 75 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Stephen Seo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,4 +1,4 @@
# ld53
# Ludum Dare 53: House Delivery
A game written in Rust for the [WASM-4](https://wasm4.org) fantasy console.

View file

@ -5,6 +5,7 @@ mod wasm4;
use wasm4::*;
mod helpers;
mod music;
mod sprites;
mod world;

470
src/music.rs Normal file
View file

@ -0,0 +1,470 @@
use crate::wasm4::*;
const FRAMES_PER_SIXTEENTH: f32 = 6.0f32;
const SLOWDOWN_RATE: f32 = 0.001f32;
const SLOWDOWN_REVERT_RATE: f32 = 0.002f32;
const SLOWDOWN_MIN: f32 = 0.6f32;
const SPEEDUP_RATE: f32 = 1.00f32;
const SPEEDUP_REVERT_RATE: f32 = 0.1f32;
const SPEEDUP_MAX: f32 = 1.6f32;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Pitch {
C0,
D0,
E0,
F0,
G0,
A0,
B0,
C1,
D1,
E1,
F1,
G1,
A1,
B1,
C2,
D2,
E2,
F2,
G2,
A2,
B2,
C3,
D3,
E3,
F3,
G3,
A3,
B3,
C4,
D4,
E4,
F4,
G4,
A4,
B4,
C5,
D5,
E5,
F5,
G5,
A5,
B5,
C6,
D6,
E6,
F6,
G6,
A6,
B6,
C7,
D7,
E7,
F7,
G7,
A7,
B7,
C8,
D8,
E8,
F8,
G8,
A8,
B8,
}
impl Into<f32> for Pitch {
fn into(self) -> f32 {
match self {
Pitch::C0 => 16.35,
Pitch::D0 => 17.32,
Pitch::E0 => 20.6,
Pitch::F0 => 21.83,
Pitch::G0 => 24.5,
Pitch::A0 => 27.5,
Pitch::B0 => 30.87,
Pitch::C1 => 32.70,
Pitch::D1 => 36.71,
Pitch::E1 => 41.2,
Pitch::F1 => 43.65,
Pitch::G1 => 49.0,
Pitch::A1 => 55.0,
Pitch::B1 => 61.74,
Pitch::C2 => 65.41,
Pitch::D2 => 73.42,
Pitch::E2 => 82.41,
Pitch::F2 => 87.31,
Pitch::G2 => 98.0,
Pitch::A2 => 110.0,
Pitch::B2 => 123.47,
Pitch::C3 => 130.81,
Pitch::D3 => 146.83,
Pitch::E3 => 164.81,
Pitch::F3 => 174.61,
Pitch::G3 => 196.0,
Pitch::A3 => 220.0,
Pitch::B3 => 246.94,
Pitch::C4 => 261.63,
Pitch::D4 => 293.66,
Pitch::E4 => 329.63,
Pitch::F4 => 349.23,
Pitch::G4 => 392.0,
Pitch::A4 => 440.0,
Pitch::B4 => 493.88,
Pitch::C5 => 523.25,
Pitch::D5 => 587.33,
Pitch::E5 => 659.25,
Pitch::F5 => 698.46,
Pitch::G5 => 783.99,
Pitch::A5 => 880.0,
Pitch::B5 => 987.77,
Pitch::C6 => 1046.5,
Pitch::D6 => 1174.66,
Pitch::E6 => 1318.51,
Pitch::F6 => 1396.91,
Pitch::G6 => 1567.98,
Pitch::A6 => 1760.00,
Pitch::B6 => 1975.53,
Pitch::C7 => 2093.0,
Pitch::D7 => 2349.32,
Pitch::E7 => 2637.02,
Pitch::F7 => 2793.83,
Pitch::G7 => 3135.96,
Pitch::A7 => 3520.00,
Pitch::B7 => 3951.07,
Pitch::C8 => 4186.01,
Pitch::D8 => 4698.63,
Pitch::E8 => 5274.04,
Pitch::F8 => 5587.65,
Pitch::G8 => 6271.93,
Pitch::A8 => 7040.0,
Pitch::B8 => 7902.13,
}
}
}
impl Pitch {
pub fn to_u32(self) -> u32 {
(Into::<f32>::into(self) + 0.5f32) as u32
}
pub fn to_u32_mult(self, mult: f32) -> u32 {
(Into::<f32>::into(self) * mult + 0.5f32) as u32
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
enum Duration {
SIXTEENTH,
EIGHTH,
QUARTER,
HALF,
FULL,
}
impl Into<f32> for Duration {
fn into(self) -> f32 {
match self {
Duration::SIXTEENTH => 1f32,
Duration::EIGHTH => 2f32,
Duration::QUARTER => 4f32,
Duration::HALF => 8f32,
Duration::FULL => 16f32,
}
}
}
const PULSE1_NOTES: [(Pitch, Duration, u8); 63] = [
// m1
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::E4, Duration::EIGHTH, 40),
(Pitch::D4, Duration::EIGHTH, 40),
(Pitch::F4, Duration::EIGHTH, 40),
(Pitch::E4, Duration::EIGHTH, 40),
(Pitch::C6, Duration::EIGHTH, 40),
(Pitch::B5, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
// m2
(Pitch::D5, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
(Pitch::A5, Duration::EIGHTH, 40),
(Pitch::A4, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::G4, Duration::QUARTER, 40),
// m3
(Pitch::A4, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::F4, Duration::EIGHTH, 40),
(Pitch::E4, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::F4, Duration::EIGHTH, 40),
(Pitch::E4, Duration::EIGHTH, 40),
(Pitch::D4, Duration::EIGHTH, 40),
// m4
(Pitch::C4, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::D5, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
(Pitch::C4, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
(Pitch::C6, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
// m5
(Pitch::A4, Duration::EIGHTH, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::E5, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::A4, Duration::EIGHTH, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::D5, Duration::EIGHTH, 40),
// m6
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::A4, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::B5, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
// m7
(Pitch::A5, Duration::EIGHTH, 40),
(Pitch::G5, Duration::EIGHTH, 40),
(Pitch::F5, Duration::EIGHTH, 40),
(Pitch::E5, Duration::EIGHTH, 40),
(Pitch::D5, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::A4, Duration::EIGHTH, 40),
// m8
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::F4, Duration::EIGHTH, 40),
(Pitch::E4, Duration::EIGHTH, 40),
(Pitch::D4, Duration::EIGHTH, 40),
(Pitch::C4, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::C4, Duration::EIGHTH, 40),
];
const TRI_NOTES: [(Pitch, Duration, u8); 37] = [
// m1
(Pitch::E4, Duration::QUARTER, 40),
(Pitch::G4, Duration::QUARTER, 40),
(Pitch::C4, Duration::QUARTER, 40),
(Pitch::G4, Duration::QUARTER, 40),
// m2
(Pitch::F5, Duration::QUARTER, 40),
(Pitch::A4, Duration::QUARTER, 40),
(Pitch::A4, Duration::QUARTER, 40),
(Pitch::G5, Duration::QUARTER, 40),
// m3
(Pitch::F5, Duration::QUARTER, 40),
(Pitch::G5, Duration::QUARTER, 40),
(Pitch::C5, Duration::QUARTER, 40),
(Pitch::G5, Duration::QUARTER, 40),
// m4
(Pitch::B4, Duration::QUARTER, 40),
(Pitch::G5, Duration::QUARTER, 40),
(Pitch::C4, Duration::EIGHTH, 40),
(Pitch::G4, Duration::EIGHTH, 40),
(Pitch::C4, Duration::QUARTER, 40),
// m5
(Pitch::A4, Duration::QUARTER, 40),
(Pitch::A4, Duration::QUARTER, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::A4, Duration::EIGHTH, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::E5, Duration::EIGHTH, 40),
// m6
(Pitch::C5, Duration::QUARTER, 40),
(Pitch::E5, Duration::QUARTER, 40),
(Pitch::D5, Duration::QUARTER, 40),
(Pitch::C5, Duration::QUARTER, 40),
// m7
(Pitch::A5, Duration::QUARTER, 40),
(Pitch::E5, Duration::QUARTER, 40),
(Pitch::A5, Duration::QUARTER, 40),
(Pitch::E5, Duration::QUARTER, 40),
// m8
(Pitch::G5, Duration::QUARTER, 40),
(Pitch::D5, Duration::QUARTER, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::B4, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
(Pitch::C5, Duration::EIGHTH, 40),
];
pub struct Music {
started: bool,
pulse1_time: f32,
pulse1_idx: usize,
tri_time: f32,
tri_idx: usize,
factor: f32,
factor_slowing: Option<bool>,
factor_speeding: Option<bool>,
game_over: bool,
}
impl Music {
pub fn new() -> Self {
Self {
started: false,
pulse1_time: 0f32,
pulse1_idx: 0,
tri_time: 0f32,
tri_idx: 0,
factor: 1f32,
factor_slowing: None,
factor_speeding: None,
game_over: false,
}
}
pub fn reset(&mut self) {
self.started = false;
self.pulse1_time = 0f32;
self.pulse1_idx = 0;
self.tri_time = 0f32;
self.tri_idx = 0;
self.factor = 1f32;
self.factor_slowing = None;
self.factor_speeding = None;
self.game_over = false;
}
pub fn gameover(&mut self) {
self.game_over = true;
}
pub fn speed_up(&mut self) {
self.factor_slowing = None;
//self.factor_speeding = Some(true);
self.factor_speeding = None;
self.factor = 1f32;
crate::tone(
Pitch::C2.to_u32() | (Pitch::C7.to_u32() << 16),
30 | (30 << 8),
40,
TONE_PULSE2,
);
}
pub fn slow_down(&mut self) {
self.factor_slowing = Some(true);
self.factor_speeding = None;
self.factor = 1f32;
}
pub fn damaged(&self) {
crate::tone(
Pitch::D5.to_u32() | (Pitch::D1.to_u32() << 16),
30 << 8,
60,
TONE_NOISE,
)
}
fn get_factor(&self) -> f32 {
self.factor
}
pub fn start(&mut self) {
self.started = true;
}
pub fn update(&mut self) {
if !self.started {
return;
}
if self.game_over {
self.factor -= SLOWDOWN_RATE;
if self.factor <= 0f32 {
self.reset();
}
} else {
if let Some(not_reverting) = &mut self.factor_slowing {
if *not_reverting {
self.factor -= SLOWDOWN_RATE;
if self.factor <= SLOWDOWN_MIN {
*not_reverting = false;
self.factor = SLOWDOWN_MIN;
}
} else {
self.factor += SLOWDOWN_REVERT_RATE;
if self.factor >= 1f32 {
self.factor = 1f32;
self.factor_slowing.take();
}
}
} else if let Some(not_reverting) = &mut self.factor_speeding {
if *not_reverting {
self.factor += SPEEDUP_RATE;
if self.factor >= SPEEDUP_MAX {
*not_reverting = false;
self.factor = SPEEDUP_MAX;
}
} else {
self.factor -= SPEEDUP_REVERT_RATE;
if self.factor <= 1f32 {
self.factor = 1f32;
self.factor_speeding.take();
}
}
}
}
if self.pulse1_idx < PULSE1_NOTES.len() {
if self.pulse1_time <= 0f32 {
let frames =
Into::<f32>::into(PULSE1_NOTES[self.pulse1_idx].1) * FRAMES_PER_SIXTEENTH;
// / self.get_factor();
crate::tone(
PULSE1_NOTES[self.pulse1_idx]
.0
.to_u32_mult(self.get_factor()),
((frames + 0.5f32) as u32) << 8,
PULSE1_NOTES[self.pulse1_idx].2 as u32,
TONE_PULSE1,
);
self.pulse1_time += frames;
self.pulse1_idx += 1;
}
}
self.pulse1_time -= self.get_factor();
//self.pulse1_time -= 1f32;
if self.tri_idx < TRI_NOTES.len() {
if self.tri_time <= 0f32 {
let frames = Into::<f32>::into(TRI_NOTES[self.tri_idx].1) * FRAMES_PER_SIXTEENTH;
// / self.get_factor();
crate::tone(
TRI_NOTES[self.tri_idx].0.to_u32_mult(self.get_factor()),
((frames + 0.5f32) as u32) << 8,
TRI_NOTES[self.tri_idx].2 as u32,
TONE_TRIANGLE,
);
self.tri_time += frames;
self.tri_idx += 1;
}
}
self.tri_time -= self.get_factor();
//self.tri_time -= 1f32;
if self.pulse1_idx >= PULSE1_NOTES.len()
&& self.tri_idx >= TRI_NOTES.len()
&& self.pulse1_time <= 0f32
&& self.tri_time <= 0f32
{
self.pulse1_idx = 0;
self.tri_idx = 0;
}
}
}

View file

@ -130,3 +130,32 @@ pub const PLANT: [u8; 30] = [
0x7f, 0xff, 0xf7, 0x13, 0xff, 0x03, 0xf0, 0xbc, 0x3f, 0xfc, 0x3c, 0x81, 0xff, 0x30, 0x0f, 0xff,
0x82, 0xef, 0x43, 0x0b, 0xff, 0xf0, 0x0b, 0xff, 0xf8, 0x2f, 0xff, 0xff, 0x2f, 0xff,
];
// speedup
pub const SPEEDUP_WIDTH: u32 = 32;
pub const SPEEDUP_HEIGHT: u32 = 16;
pub const SPEEDUP_FLAGS: u32 = 1; // BLIT_2BPP
pub const SPEEDUP: [u8; 128] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8,
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8, 0x25, 0x65, 0xa5, 0x96, 0x5a, 0xa6, 0x99, 0x68,
0x26, 0xa6, 0x66, 0x9a, 0x66, 0xa6, 0x99, 0x98, 0x26, 0xa6, 0x66, 0x9a, 0x66, 0xa6, 0x99, 0x98,
0x25, 0x65, 0xa5, 0x96, 0x66, 0xa6, 0x99, 0x68, 0x2a, 0x66, 0xa6, 0x9a, 0x66, 0xa6, 0x99, 0xa8,
0x2a, 0x66, 0xa6, 0x9a, 0x66, 0xa6, 0x99, 0xa8, 0x25, 0x66, 0xa5, 0x96, 0x5a, 0xa9, 0x69, 0xa8,
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff,
];
// slowdown
pub const SLOWDOWN_WIDTH: u32 = 32;
pub const SLOWDOWN_HEIGHT: u32 = 16;
pub const SLOWDOWN_FLAGS: u32 = 1; // BLIT_2BPP
pub const SLOWDOWN: [u8; 128] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8,
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8, 0x25, 0x66, 0xa9, 0x69, 0x99, 0xaa, 0x9a, 0xa8,
0x26, 0xa6, 0xa6, 0x99, 0x99, 0xaa, 0x9a, 0xa8, 0x26, 0xa6, 0xa6, 0x99, 0x99, 0xaa, 0x9a, 0xa8,
0x25, 0x66, 0xa6, 0x99, 0x99, 0xa6, 0x9a, 0x68, 0x2a, 0x66, 0xa6, 0x99, 0x99, 0xa9, 0x99, 0xa8,
0x2a, 0x66, 0xa6, 0x9a, 0x66, 0xaa, 0x56, 0xa8, 0x25, 0x65, 0x69, 0x6a, 0x66, 0xaa, 0x9a, 0xa8,
0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xff,
];

View file

@ -1,12 +1,37 @@
use crate::helpers::round_f32_to_i32;
use crate::music::Music;
use crate::sprites::*;
use tinyrand::{Rand, StdRand};
use tinyrand::{Rand, Seeded, StdRand};
const CAR_ANIM_FRAMES: u8 = 10;
const MOVE_RATE: f32 = 1f32;
const MULTIPLIER_INC_RATE: f32 = 0.13f32;
const HOUSE_FRAMES: u32 = 100;
const HOUSE_RANGE: f32 = 90f32;
const MULTIPLIER_INC_RATE: f32 = 0.25f32;
const SPEEDUP_INC: f32 = 0.47f32;
const SLOWDOWN_DIV: f32 = 1.3f32;
const BUILDING_FRAMES: u32 = 100;
const BUILDING_RANGE: f32 = 90f32;
const DAMAGED_FRAMES_MAX: u16 = 120;
const GAMEOVER_SLOWDOWN_RATE: f32 = 0.01f32;
#[derive(Copy, Clone, Debug, PartialEq)]
enum Building {
House,
SpeedUp,
SlowDown,
WrongHouse,
}
impl Building {
pub fn random(rand: &mut StdRand) -> Self {
match rand.next_u16() % 30 {
0..=9 => Building::House,
10..=14 => Building::SpeedUp,
15..=19 => Building::SlowDown,
20..=29 => Building::WrongHouse,
_ => unreachable!(),
}
}
}
pub struct World {
car_state: bool,
@ -14,18 +39,21 @@ pub struct World {
rand: StdRand,
street_offset: f32,
shrubs: [Option<(f32, f32)>; 8],
// x, state
// x, type, state
// state:
// - 0 broken
// - 1 fixed
house: Option<(f32, u8)>,
house_frames: u32,
house_frames_max: u32,
building: Option<(f32, Building, u8)>,
building_frames: u32,
building_frames_max: u32,
is_in_range: bool,
score: u64,
rate_multiplier: f32,
status_text: Option<(&'static str, u32)>,
str_buf: [u8; 16],
score_buf: [u8; 16],
music: Music,
lives: u8,
damaged_frames: u16,
}
impl World {
@ -33,20 +61,55 @@ impl World {
World {
car_state: false,
car_frames: 0,
rand: StdRand::default(),
rand: StdRand::seed(3),
street_offset: 0.0f32,
shrubs: [None, None, None, None, None, None, None, None],
house: None,
house_frames: 0,
house_frames_max: HOUSE_FRAMES,
building: None,
building_frames: 0,
building_frames_max: BUILDING_FRAMES,
is_in_range: false,
score: 0,
rate_multiplier: 1f32,
status_text: None,
str_buf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
status_text: Some(("Ludum Dare 53:\nHouse Delivery!\n\nBy: BurnedKirby", 300)),
score_buf: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
music: Music::new(),
lives: 3,
damaged_frames: 0,
}
}
fn reset(&mut self) {
self.car_state = false;
self.car_frames = 0;
self.street_offset = 0f32;
for shrub in &mut self.shrubs {
shrub.take();
}
self.building = None;
self.building_frames = 0;
self.building_frames_max = BUILDING_FRAMES;
self.is_in_range = false;
self.score = 0;
self.rate_multiplier = 1f32;
self.status_text = Some(("Ludum Dare 53:\nHouse Delivery!\n\nBy: BurnedKirby", 300));
for score in &mut self.score_buf {
*score = 0;
}
self.music.reset();
self.lives = 3;
self.damaged_frames = 0;
}
pub fn decrement_lives(&mut self) -> bool {
if self.lives > 0 {
self.lives -= 1;
if self.lives == 0 {
self.music.gameover();
}
}
self.lives != 0
}
pub fn get_move_rate(&self) -> f32 {
MOVE_RATE * self.rate_multiplier
}
@ -65,11 +128,28 @@ impl World {
}
}
if self.rate_multiplier <= 0f32 {
self.car_state = true;
}
self.street_offset -= self.get_move_rate();
if self.street_offset <= -45f32 {
self.street_offset += 45f32;
}
self.music.update();
if let Some((_, t)) = &mut self.status_text {
*t -= 1;
if *t == 0 {
self.status_text.take();
}
}
if self.damaged_frames != 0 {
self.damaged_frames -= 1;
}
let mut empty_shrub_exists: Option<usize> = None;
let move_rate = self.get_move_rate();
for (idx, shrub) in self.shrubs.iter_mut().enumerate() {
@ -84,60 +164,114 @@ impl World {
}
}
{
let move_rate = self.get_move_rate();
if let Some((x, _, _)) = &mut self.building {
*x -= move_rate;
}
}
if self.lives == 0 {
self.rate_multiplier -= GAMEOVER_SLOWDOWN_RATE;
if self.rate_multiplier < 0f32 {
self.rate_multiplier = 0f32;
}
if (gamepad & crate::BUTTON_2) != 0 {
self.reset();
}
return;
}
if empty_shrub_exists.is_some() && self.rand.next_u16() % 32 == 0 {
self.shrubs[empty_shrub_exists.unwrap()] =
Some((180f32, (self.rand.next_u16() % 80 + 60) as f32));
}
if self.house.is_none() {
self.house_frames += 1;
if self.house_frames > self.house_frames_max {
self.house_frames = 0;
self.house = Some((170f32, 0));
self.house_frames_max = HOUSE_FRAMES + (self.rand.next_u16() % 100) as u32;
if self.building.is_none() {
self.building_frames += 1;
if self.building_frames > self.building_frames_max {
self.building_frames = 0;
self.building = Some((170f32, Building::random(&mut self.rand), 0));
self.building_frames_max = BUILDING_FRAMES + (self.rand.next_u16() % 100) as u32;
}
} else {
self.house.as_mut().unwrap().0 -= self.get_move_rate();
let pos_ref: &f32 = &self.house.as_ref().unwrap().0;
let state_ref: &u8 = &self.house.as_ref().unwrap().1;
if *state_ref == 0 && *pos_ref < HOUSE_RANGE && *pos_ref >= -20f32 {
let pos_ref: &f32 = &self.building.as_ref().unwrap().0;
let building_type = self.building.as_ref().unwrap().1;
let state_ref: &u8 = &self.building.as_ref().unwrap().2;
if *state_ref == 0 && *pos_ref < BUILDING_RANGE && *pos_ref >= -20f32 {
self.is_in_range = true;
} else if *pos_ref < -(HOUSE0_WIDTH as f32) {
} else if building_type == Building::House && *pos_ref < -(HOUSE0_WIDTH as f32) {
if self.is_in_range {
self.rate_multiplier /= 2f32;
if self.rate_multiplier < 1f32 {
self.rate_multiplier = 1f32;
if self.decrement_lives() {
self.rate_multiplier /= 2f32;
if self.rate_multiplier < 1f32 {
self.rate_multiplier = 1f32;
}
self.status_text = Some(("Miss!\nSlow down!", 120));
self.music.slow_down();
self.damaged_frames = DAMAGED_FRAMES_MAX;
}
self.status_text = Some(("Slow down!", 80));
self.music.damaged();
}
self.house.take();
self.building.take();
self.is_in_range = false;
} else {
} else if (building_type == Building::SpeedUp || building_type == Building::SlowDown)
&& *pos_ref < -(SPEEDUP_WIDTH as f32)
{
self.building.take();
self.status_text = Some(("It's OK!\nKeep delivering!", 120));
self.is_in_range = false;
} else if building_type == Building::WrongHouse && *pos_ref < -(HOUSE0_WIDTH as f32) {
if self.is_in_range {
self.rate_multiplier /= 2f32;
if self.rate_multiplier < 1f32 {
self.rate_multiplier = 1f32;
}
self.status_text = Some(("Slow down!", 80));
self.status_text = Some(("OK!\nKeep going!", 120));
}
self.building.take();
self.is_in_range = false;
}
}
if self.is_in_range
&& ((gamepad & crate::BUTTON_1) != 0 || (mouse & crate::MOUSE_LEFT) != 0)
{
self.is_in_range = false;
self.house.as_mut().unwrap().1 = 1;
self.score += 1;
self.rate_multiplier += MULTIPLIER_INC_RATE;
self.status_text = Some(("Speed up!", 80));
}
if let Some((_, t)) = &mut self.status_text {
*t -= 1;
if *t == 0 {
self.status_text.take();
if (gamepad & crate::BUTTON_1) != 0 || (mouse & crate::MOUSE_LEFT) != 0 {
self.rand.next_u16();
if self.is_in_range {
self.is_in_range = false;
match self.building.as_ref().unwrap().1 {
Building::House => {
self.building.as_mut().unwrap().2 = 1;
self.score += 1;
self.rate_multiplier += MULTIPLIER_INC_RATE;
self.status_text = Some(("Nice delivery!\nSpeed up!", 120));
self.music.speed_up();
}
Building::SpeedUp => {
self.rate_multiplier += SPEEDUP_INC;
self.status_text = Some(("Speed up!", 120));
self.building.take();
self.music.speed_up();
}
Building::SlowDown => {
self.rate_multiplier /= SLOWDOWN_DIV;
if self.rate_multiplier < 1f32 {
self.rate_multiplier = 1f32;
}
self.status_text = Some(("Slow down!", 120));
self.building.take();
self.music.slow_down();
}
Building::WrongHouse => {
self.building.as_mut().unwrap().2 = 1;
if self.decrement_lives() {
self.rate_multiplier /= SLOWDOWN_DIV;
if self.rate_multiplier < 1f32 {
self.rate_multiplier = 1f32;
}
self.status_text = Some(("Oh no!\nSlow down!", 120));
self.music.slow_down();
self.damaged_frames = DAMAGED_FRAMES_MAX;
}
self.music.damaged();
}
}
self.music.start();
}
}
}
@ -172,47 +306,91 @@ impl World {
}
}
if let Some((x, state)) = self.house {
match state {
0 => crate::blit(
&HOUSE1,
round_f32_to_i32(x),
30 + HOUSE0_HEIGHT as i32 - HOUSE1_HEIGHT as i32,
HOUSE1_WIDTH,
HOUSE1_HEIGHT,
HOUSE1_FLAGS,
),
1 => crate::blit(
if let Some((x, building_type, state)) = &self.building {
match building_type {
Building::WrongHouse => crate::blit(
&HOUSE0,
round_f32_to_i32(x),
round_f32_to_i32(*x),
30,
HOUSE0_WIDTH,
HOUSE0_HEIGHT,
HOUSE0_FLAGS,
),
_ => (),
Building::House => match state {
0 => crate::blit(
&HOUSE1,
round_f32_to_i32(*x),
30 + HOUSE0_HEIGHT as i32 - HOUSE1_HEIGHT as i32,
HOUSE1_WIDTH,
HOUSE1_HEIGHT,
HOUSE1_FLAGS,
),
1 => crate::blit(
&HOUSE0,
round_f32_to_i32(*x),
30,
HOUSE0_WIDTH,
HOUSE0_HEIGHT,
HOUSE0_FLAGS,
),
_ => (),
},
Building::SpeedUp => crate::blit(
&SPEEDUP,
round_f32_to_i32(*x),
50,
SPEEDUP_WIDTH,
SPEEDUP_HEIGHT,
SPEEDUP_FLAGS,
),
Building::SlowDown => crate::blit(
&SLOWDOWN,
round_f32_to_i32(*x),
50,
SLOWDOWN_WIDTH,
SLOWDOWN_HEIGHT,
SLOWDOWN_FLAGS,
),
}
}
if self.car_state {
crate::blit(&CAR0, 10, 103, CAR0_WIDTH, CAR0_HEIGHT, CAR0_FLAGS);
} else {
crate::blit(&CAR1, 10, 103, CAR1_WIDTH, CAR1_HEIGHT, CAR1_FLAGS);
if self.damaged_frames % 4 < 2 || self.lives == 0 {
if self.car_state {
crate::blit(&CAR0, 10, 103, CAR0_WIDTH, CAR0_HEIGHT, CAR0_FLAGS);
} else {
crate::blit(&CAR1, 10, 103, CAR1_WIDTH, CAR1_HEIGHT, CAR1_FLAGS);
}
}
if self.is_in_range {
unsafe {
*crate::DRAW_COLORS = 0x1;
}
crate::text("Tap or Press X!", 5, 5);
if let Some((_, Building::WrongHouse, _)) = self.building {
crate::text("Don't Press!", 5, 10);
} else if let Some((_, Building::SpeedUp, _)) = self.building {
crate::text("Tap or Press X?", 5, 10);
} else if let Some((_, Building::SlowDown, _)) = self.building {
crate::text("Tap or Press X?", 5, 10);
} else {
crate::text("Tap or Press X!", 5, 10);
}
}
unsafe {
*crate::DRAW_COLORS = 0x1;
}
match self.lives {
3 => crate::text("3 HP", 0, 0),
2 => crate::text("2 HP", 0, 0),
1 => crate::text("1 HP", 0, 0),
0 => crate::text("0 HP", 0, 0),
_ => unreachable!(),
}
if let Some((s, t)) = self.status_text {
if (t / 10) % 2 == 0 {
if (t / 10) % 4 <= 1 {
crate::text(s, 20, 30);
}
}
@ -229,15 +407,29 @@ impl World {
temp = self.score;
if width < 15 {
for i in 0..width {
self.str_buf[width - 1 - i] = '0' as u8 + (temp % 10) as u8;
self.score_buf[width - 1 - i] = '0' as u8 + (temp % 10) as u8;
temp /= 10;
}
for i in width..16 {
self.str_buf[i] = 0;
self.score_buf[i] = 0;
}
if self.lives > 0 {
crate::custom_text(self.score_buf, width, 160 - width as i32 * 8, 0);
} else {
crate::text("GAME OVER", 40, 40);
crate::text("Score:", 50, 50);
crate::custom_text(self.score_buf, width, 70 - (width / 2) as i32 * 8, 60);
crate::text("Press Z to restart", 10, 70);
}
crate::custom_text(self.str_buf, width, 160 - width as i32 * 8, 0);
} else {
crate::text("99999999999999", 160 - 10 * 8, 0);
if self.lives > 0 {
crate::text("99999999999999", 160 - 10 * 8, 0);
} else {
crate::text("GAME OVER", 40, 40);
crate::text("Score:", 50, 50);
crate::text("99999999999999", 70 - 7 * 8, 60);
crate::text("Press Z to restart", 10, 70);
}
}
}
}