diff options
Diffstat (limited to 'src/game.rs')
-rw-r--r-- | src/game.rs | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..f6d0c51 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,381 @@ +use rand::{RngCore, SeedableRng, rngs::StdRng}; + +const PLAYER_X: u32 = 3; +const FLOOR_HEIGHT: u32 = 1; +const G: u32 = 2; +const PLAYER_BOOST: u32 = 3; +const PLAYER_BOOST_TIME: u32 = 6; +const PLAYER_BOOST_MIN_TIME: u32 = 4; + +#[derive(Clone, Debug, Default)] +pub struct Status { + pub player: [u32; 2], + pub player_v: i32, + pub fields: Vec<Field>, +} + +pub trait Game: Send { + fn size(&self) -> (u32, u32); + fn update(&mut self) -> Option<Status>; + fn jump(&mut self); + fn status(&self) -> Status; + fn get_points(&self) -> u64; +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum Field { + Air, Wall, Spike +} + +impl Field { + pub fn repr(&self) -> char { + match self { + Field::Air => '.', + Field::Wall => 'Z', + Field::Spike => 'x', + } + } +} + +#[derive(Clone, Debug)] +struct PlayerBoost { + freq: u32, + lifetime: u32, +} + +impl PlayerBoost { + fn new() -> Self { + Self { + freq: 0, + lifetime: 0 + } + } + + fn activate(&mut self) { + *self = Self { + freq: 1, + lifetime: PLAYER_BOOST_TIME + } + } + + fn is(&self) -> bool { + self.lifetime != 0 + } + + fn get_boost(&self) -> u32 { + if self.lifetime > 0 { + let m = self.lifetime % self.freq; + if m == 0 { + PLAYER_BOOST + G + } else if PLAYER_BOOST > m { + PLAYER_BOOST + G - m + } else { G as u32 } + } else { 0 } + } + + fn update(&mut self) { + if self.lifetime == 0 { return; } + if self.lifetime <= PLAYER_BOOST_MIN_TIME { + self.freq += 1; + } + self.lifetime -= 1; + } +} + +#[derive(Clone, Debug)] +pub struct JumpGame { + grid: Vec<Field>, + cols: u32, rows: u32, + player_boost: PlayerBoost, + jumping: bool, + gap_len: u32, + platforms: [(u32, u32); 2], + spike: u32, spike_down: bool, + player: u32, + status: Status, + rng: StdRng, + points: u64, +} + +impl JumpGame { + pub fn new(cols: u32, rows: u32, seed: u64) -> Self { + let grid: Vec<Field> = std::iter::repeat(Field::Air) + .enumerate() + .map(|(i, v)| if (i as u32 % rows) <= FLOOR_HEIGHT + { Field::Wall } else { v }) + .take((cols * rows) as usize) + .collect(); + Self { + grid: grid.clone(), + cols, rows, + player_boost: PlayerBoost::new(), + jumping: false, + gap_len: 0, + platforms: [(0, 0); 2], + spike: 0, spike_down: false, + player: 3, + status: Status { + player: [ PLAYER_X, 3 ], + player_v: 0, + fields: grid + }, + rng: StdRng::seed_from_u64(seed), + points: 0, + } + } + + pub fn get_field(&self, col: u32, row: u32) -> Option<&Field> { + self.grid.get((row + col * self.rows) as usize) + } + + pub fn mut_row(&mut self, col: u32) -> Option<&mut [Field]> { + if col >= self.cols { None } + else { + let pos1 = (col * self.rows) as usize; + let pos2 = pos1 + self.rows as usize; + Some(&mut self.grid[pos1..pos2]) + } + } + + pub fn shift(&mut self) { + self.grid.rotate_left(self.rows as usize) + } + + pub fn normal_col(&self, n: u32) -> Field { + if n <= FLOOR_HEIGHT || self.collides(n) { + Field::Wall + } else { Field::Air } + } + + pub fn spike_col(&self, n: u32) -> Field { + if n <= FLOOR_HEIGHT { + Field::Wall + } else if self.spike <= n + 1 && self.spike + 0 >= n { + Field::Spike + } else { Field::Air } + } + + pub fn gap_col(&self, n: u32) -> Field { + if self.collides(n) { + Field::Wall + } else { Field::Air } + } + + pub fn gen_col_with<F: Fn(&Self, u32) -> Field>(&mut self, f: &F) { + let vec: Vec<Field> = std::iter::repeat(Field::Air) + .enumerate() + .map(|(i, _)| f(self, i as u32)) + .take(self.rows as usize) + .collect(); + self.mut_row(self.cols - 1).unwrap() + .clone_from_slice(&vec); + } + + pub fn gen_col(&mut self) { + if self.gap_len > 0 { + self.gen_col_with(&Self::gap_col) + } else if self.chance((1, 15)) { + self.gen_col_with(&Self::spike_col) + } else { + self.gen_col_with(&Self::normal_col) + } + } + + fn update_platforms(&mut self) { + for i in 0..self.platforms.len() { + let (_, l) = self.platforms[i]; + if l <= 0 { + if self.chance((1, 29)) { + self.platforms[i] = ( + self.randint(FLOOR_HEIGHT + 2, self.rows - 1), + self.randint(4, 8)); + } else { + self.platforms[i].1 = 0; + } + } else { + self.platforms[i].1 -= 1; + } + } + } + + fn collides(&self, n: u32) -> bool { + n != 0 && self.platforms.iter().any(|&(h, _)| h == n) + } + + pub fn update_gap(&mut self) { + if self.gap_len > 0 { + self.gap_len -= 1; + } else if self.chance((1, 12)) { + self.gap_len = self.randint(2, 5); + } + } + + pub fn update_spike(&mut self) { + if self.spike_down { + if self.spike <= 0 { + self.spike_down = false; + self.spike += 1 + } else { self.spike -= 1; } + } else { + if self.spike >= self.rows - 1 { + self.spike_down = true; + self.spike -= 1; + } else { self.spike += 1; } + } + } + + pub fn check_dead(&self) -> Option<()> { + self.get_field(PLAYER_X, self.player) + .filter(|&&f| f != Field::Spike) + .map(|_|()) + } + + pub fn apply_phys(&mut self) -> Option<()> { + let v = self.player_boost.get_boost() as i32; + let v = v - G as i32; + self.status.player_v = v; + if v != 0 { + let (oldpos, newpos) = (self.player as i32, self.player as i32 + v); + if newpos < 0 { return None; } + let mut endpos = None; + let up = oldpos < newpos; + let (mut it1, mut it2) = (oldpos..(newpos+1), (newpos..oldpos).rev()); + let steps: &mut dyn Iterator<Item=i32> = + if up { &mut it1 } + else { &mut it2 }; + for h in steps.skip(0) { + match self.get_field(PLAYER_X, h as u32) { + Some(Field::Air) => endpos = Some(h), + Some(Field::Wall) | None => { + if !up { self.jumping = false; } + break + }, + Some(Field::Spike) => return None, + } + } + if let Some(pos) = endpos { + let mut pos = pos as u32; + if pos >= self.rows { pos = self.rows - 1; } + self.player = pos as u32; + } + } + Some(()) + } + + pub fn update_status(&mut self) { + self.status.player[1] = self.player; + self.status.fields = self.grid.clone(); + } + + fn randint(&mut self, a: u32, b: u32) -> u32 { + (self.rng.next_u32() % (b - a + 1)) + a + } + + fn chance(&mut self, (c, n): (u32, u32)) -> bool { + self.randint(0, n - 1) < c + } +} + +impl Game for JumpGame { + fn size(&self) -> (u32, u32) { + (self.cols, self.rows) + } + + fn update(&mut self) -> Option<Status> { + self.player_boost.update(); + self.update_gap(); + self.update_spike(); + self.update_platforms(); + self.apply_phys()?; + self.shift(); + self.check_dead()?; + self.gen_col(); + self.update_status(); + self.points += 1; + Some(self.status()) + } + + fn jump(&mut self) { + if !self.jumping { + self.jumping = true; + self.player_boost.activate(); + } + } + + fn status(&self) -> Status { + self.status.clone() + } + + fn get_points(&self) -> u64 { + self.points + } +} + +pub trait GameLogger { + type Output; + fn new_empty(cols: u32, rows: u32) -> Self; + fn append(&mut self, status: &Status); + fn extract(self) -> Self::Output; +} + +pub struct PointLogger { + points: u64 +} + +impl GameLogger for PointLogger { + type Output = u64; + + fn new_empty(_cols: u32, _rows: u32) -> Self { + Self { points: 0 } + } + fn append(&mut self, _status: &Status) { + self.points += 1; + } + fn extract(self) -> Self::Output { + self.points + } +} + +#[derive(Clone, Debug)] +pub struct GameLog { + status: Vec<Status>, + frame: u32, + size: (u32, u32) +} + +impl GameLogger for GameLog { + type Output = Self; + fn new_empty(cols: u32, rows: u32) -> Self { + Self { + status: vec![], + frame: 0, + size: (cols, rows) + } + } + fn extract(self) -> Self::Output { + self + } + fn append(&mut self, status: &Status) { + self.status.push(status.clone()) + } +} + +impl Game for GameLog { + fn size(&self) -> (u32, u32) { self.size } + fn update(&mut self) -> Option<Status> { + if self.frame >= self.status.len() as u32 { + None + } else { + let status = self.status(); + self.frame += 1; + Some(status) + } + } + fn jump(&mut self) {} + fn status(&self) -> Status { + self.status[self.frame as usize].clone() + } + fn get_points(&self) -> u64 { + self.status.len() as u64 + } +} |