summaryrefslogtreecommitdiff
path: root/src/game.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/game.rs')
-rw-r--r--src/game.rs381
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
+ }
+}