#![warn(clippy::all, clippy::pedantic)] #![feature(stmt_expr_attributes)] use bracket_lib::prelude::*; use std::collections::VecDeque; // const VERSION: &str = "0.3.0"; const SCREEN_WIDTH: u16 = 80; const SCREEN_HEIGHT: u16 = 50; const FRAME_DURATION: f32 = 35.0; const X_OFFSET: i32 = 10; const Y_START: i32 = 25; #[derive(Debug)] struct Obstacle { x: i32, gap_y: u16, gap_height: u16, } impl Obstacle { fn new(x: i32, score: u32) -> Self { let mut random = RandomNumberGenerator::new(); Obstacle { x, gap_y: random.range(10, 40), gap_height: u16::max(5, 20 - (score as u16 / 3)), } } fn render(&mut self, ctx: &mut BTerm, current_x: i32) { let x = self.x - current_x + X_OFFSET; let (y_top_of_bottom, y_bottom_of_top) = self.y_bounds(); let y_positions = [0..y_bottom_of_top, y_top_of_bottom..SCREEN_HEIGHT] .map(|r| r.collect::>()) .concat(); for y in y_positions { ctx.set(x, y as i32, RED, BLACK, to_cp437('|')); } } fn y_bounds(&mut self) -> (u16, u16) { let half_gap_height = self.gap_height / 2; let y_bottom_of_top = self.gap_y - half_gap_height; let y_top_of_bottom = self.gap_y + half_gap_height; (y_top_of_bottom, y_bottom_of_top) } } struct Player { x: i32, y: i32, velocity: f32, } impl Player { fn new(y: i32) -> Self { Player { x: 0, y, velocity: -2.0, } } fn render(&mut self, ctx: &mut BTerm) { ctx.set(X_OFFSET, self.y, YELLOW, BLACK, to_cp437('@')); } fn update(&mut self) { if self.velocity < 2.0 { self.velocity += 0.2; // self.velocity = self.velocity.clamp(-2.0 } #[allow(clippy::cast_possible_truncation)] self.y += self.velocity.round() as i32; self.x += 1; if self.y < 0 { self.y = 0; } } fn flap(&mut self) { self.velocity = -2.0; } } struct State { player: Player, time: f32, mode: GameMode, obstacles: VecDeque, obstacle_spawn_time: f32, score: u32, } enum GameMode { Menu, Playing, Dead, } impl State { fn new() -> Self { State { player: Player::new(Y_START), time: 0.0, mode: GameMode::Menu, obstacles: VecDeque::new(), obstacle_spawn_time: 0.0, score: 0u32, } } fn menu(&mut self, ctx: &mut BTerm) { ctx.cls(); ctx.print_centered(5, "Welcome to Flappy Dragon"); ctx.print_centered(8, "(P) Play"); ctx.print_centered(9, "(Q) Quit"); if let Some(key) = ctx.key { match key { VirtualKeyCode::P => self.restart(), VirtualKeyCode::Q => ctx.quitting = true, _ => {} } } } fn restart(&mut self) { self.player = Player::new(25); self.time = 0.0; self.mode = GameMode::Playing; self.obstacles = VecDeque::new(); self.score = 0u32; } fn play(&mut self, ctx: &mut BTerm) { ctx.cls_bg(BLACK); self.time += ctx.frame_time_ms; self.obstacle_spawn_time -= ctx.frame_time_ms; ctx.print(1, 1, "Press Space to flap!"); ctx.print(1, 2, &format!("Score: {}", self.score)); // ctx.print(1, 3, &format!("Pos: {}, {}", self.player.x, self.player.y)); // ctx.print(1, 4, &format!("Obstables: (Spawn: {}) #{} {:#?}", self.obstacle_spawn_time, self.obstacles.len(), self.obstacles)); // update if self.time > FRAME_DURATION { self.player.update(); self.time %= FRAME_DURATION; } if self.obstacle_spawn_time < 0.0 { let mut random = RandomNumberGenerator::new(); self.obstacle_spawn_time += random.range(400.0, 1200.0) - self.score as f32; let o = Obstacle::new(self.player.x + SCREEN_WIDTH as i32, self.score); self.obstacles.push_back(o); } if let Some(o) = self.obstacles.front() { if o.x < self.player.x - X_OFFSET { self.obstacles.pop_front(); self.score += 1; } } if let Some(VirtualKeyCode::Space) = ctx.key { self.player.flap(); } self.player.render(ctx); let mut did_collide = false; self.obstacles.iter_mut().for_each(|o| { if o.x == self.player.x { let (y_top_of_bottom, y_bottom_of_top) = o.y_bounds(); if (self.player.y as u16) >= y_top_of_bottom || (self.player.y as u16) <= y_bottom_of_top { did_collide = true; } } o.render(ctx, self.player.x); }); if did_collide || self.player.y > (SCREEN_HEIGHT as i32) { self.die(); } // self.mode = GameMode::Dead; } fn die(&mut self) { self.mode = GameMode::Dead; } fn dead(&mut self, ctx: &mut BTerm) { ctx.cls(); ctx.print_centered(5, "You are dead. =("); ctx.print_centered(6, &format!("Final Score: {}", self.score)); ctx.print_centered(8, "(P) Play Again"); ctx.print_centered(9, "(Q) Quit"); if let Some(key) = ctx.key { match key { VirtualKeyCode::P => self.restart(), VirtualKeyCode::Q => ctx.quitting = true, _ => {} } } } } impl GameState for State { fn tick(&mut self, ctx: &mut BTerm) { match self.mode { GameMode::Menu => self.menu(ctx), GameMode::Playing => self.play(ctx), GameMode::Dead => self.dead(ctx), } } } fn main() { let ctx = BTermBuilder::simple80x50() .with_title("Flappy Dragon") .build() .unwrap(); main_loop(ctx, State::new()).unwrap(); }