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, } pub trait Game: Send { fn size(&self) -> (u32, u32); fn update(&mut self) -> Option; 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, 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 = 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 Field>(&mut self, f: &F) { let vec: Vec = 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 = 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 { 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, 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 { 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 } }