Compare commits
10 commits
f02b72c57e
...
b31c7c22e7
Author | SHA1 | Date | |
---|---|---|---|
Stephen Seo | b31c7c22e7 | ||
Stephen Seo | c6751bea39 | ||
Stephen Seo | d9cc21f247 | ||
Stephen Seo | ce5cedb2c9 | ||
Stephen Seo | 62adede449 | ||
Stephen Seo | 74a72629e3 | ||
Stephen Seo | d555655690 | ||
Stephen Seo | 087d3213a6 | ||
Stephen Seo | cfaf2e9970 | ||
Stephen Seo | 94f3cdfa6e |
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
|
@ -1,4 +1,4 @@
|
|||
# ld53
|
||||
# Ludum Dare 53: House Delivery
|
||||
|
||||
A game written in Rust for the [WASM-4](https://wasm4.org) fantasy console.
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ mod wasm4;
|
|||
use wasm4::*;
|
||||
|
||||
mod helpers;
|
||||
mod music;
|
||||
mod sprites;
|
||||
mod world;
|
||||
|
||||
|
|
470
src/music.rs
Normal file
470
src/music.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
];
|
||||
|
|
340
src/world.rs
340
src/world.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue