rust-flappy-dragon/src/main.rs

231 lines
6.0 KiB
Rust

#![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::<Vec<u16>>())
.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>,
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();
}