Created
July 31, 2022 15:59
-
-
Save nicolaracco/18d6ed34571f8f4cf4a42b39457f38a1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![warn(clippy::all, clippy::pedantic)] | |
use bracket_lib::prelude::*; | |
const SCREEN_WIDTH: i32 = 80; | |
const SCREEN_HEIGHT: i32 = 50; | |
const FRAME_DURATION: f32 = 75.0; | |
const DRAGON_FRAMES: [u16; 6] = [64, 1, 2, 3, 2, 1]; | |
struct Obstacle { | |
x: i32, | |
gap_y: i32, | |
size: i32, | |
} | |
impl Obstacle { | |
fn new(x: i32, score: i32) -> Self { | |
let mut random = RandomNumberGenerator::new(); | |
Obstacle { | |
x, | |
gap_y: random.range(10, 40), | |
size: i32::max(2, 20 - score), | |
} | |
} | |
fn render(&mut self, ctx: &mut BTerm, player_x: i32) { | |
let screen_x = self.x - player_x; | |
let half_size = self.size / 2; | |
for y in 0..self.gap_y - half_size { | |
ctx.set(screen_x, y, RED, BLACK, to_cp437('|')); | |
} | |
for y in self.gap_y + half_size..SCREEN_HEIGHT { | |
ctx.set(screen_x, y, RED, BLACK, to_cp437('|')); | |
} | |
} | |
fn hit_obstacle(&self, player: &Player) -> bool { | |
let half_size = self.size / 2; | |
let does_x_match = player.x == self.x; | |
let player_above_gap = (player.y as i32) < self.gap_y - half_size; | |
let player_below_gap = (player.y as i32) > self.gap_y + half_size; | |
does_x_match && (player_above_gap || player_below_gap) | |
} | |
} | |
struct Player { | |
x: i32, | |
y: f32, | |
velocity: f32, | |
frame: usize, | |
} | |
impl Player { | |
fn new(x: i32, y: i32) -> Self { | |
Self { | |
x, | |
y: y as f32, | |
velocity: 0.0, | |
frame: 0, | |
} | |
} | |
fn render(&self, ctx: &mut BTerm) { | |
ctx.set_active_console(1); | |
ctx.cls(); | |
ctx.set_fancy( | |
PointF::new(0.0, self.y), | |
1, | |
Degrees::new(0.0), | |
PointF::new(2.0, 2.0), | |
WHITE, | |
NAVY, | |
DRAGON_FRAMES[self.frame], | |
); | |
ctx.set_active_console(0); | |
} | |
fn gravity_and_move(&mut self) { | |
if self.velocity < 2.0 { | |
self.velocity += 0.2; | |
} | |
self.y += self.velocity; | |
if self.y < 0.0 { | |
self.y = 0.0; | |
} | |
self.x += 1; | |
self.frame = (self.frame + 1) % 6; | |
} | |
fn flap(&mut self) { | |
self.velocity = -2.0; | |
} | |
} | |
enum GameMode { | |
Menu, | |
Playing, | |
End, | |
} | |
struct State { | |
mode: GameMode, | |
player: Player, | |
frame_time: f32, | |
obstacle: Obstacle, | |
score: i32, | |
} | |
impl State { | |
fn new() -> Self { | |
State { | |
mode: GameMode::Menu, | |
player: Player::new(5, 25), | |
frame_time: 0.0, | |
obstacle: Obstacle::new(SCREEN_WIDTH, 0), | |
score: 0, | |
} | |
} | |
fn main_menu(&mut self, ctx: &mut BTerm) { | |
ctx.cls(); | |
ctx.print_centered(5, "Welcome to Flappy Dragon"); | |
ctx.print_centered(8, "(P) Play Game"); | |
ctx.print_centered(9, "(Q) Quit Game"); | |
if let Some(key) = ctx.key { | |
match key { | |
VirtualKeyCode::P => self.restart(), | |
VirtualKeyCode::Q => ctx.quitting = true, | |
_ => {} | |
} | |
} | |
} | |
fn dead(&mut self, ctx: &mut BTerm) { | |
ctx.set_active_console(1); | |
ctx.cls(); | |
ctx.set_active_console(0); | |
ctx.cls(); | |
ctx.print_centered(5, "You are dead!"); | |
ctx.print_centered(6, &format!("You earned {} points", self.score)); | |
ctx.print_centered(8, "(P) Play Again"); | |
ctx.print_centered(9, "(Q) Quit Game"); | |
if let Some(key) = ctx.key { | |
match key { | |
VirtualKeyCode::P => self.restart(), | |
VirtualKeyCode::Q => ctx.quitting = true, | |
_ => {} | |
} | |
} | |
} | |
fn play(&mut self, ctx: &mut BTerm) { | |
ctx.cls_bg(NAVY); | |
self.frame_time += ctx.frame_time_ms; | |
if self.frame_time > FRAME_DURATION { | |
self.frame_time = 0.0; | |
self.player.gravity_and_move(); | |
} | |
if let Some(VirtualKeyCode::Space) = ctx.key { | |
self.player.flap(); | |
} | |
self.player.render(ctx); | |
ctx.print(0, 0, "Press SPACE to flap."); | |
ctx.print(0, 1, &format!("Score: {}", self.score)); | |
self.obstacle.render(ctx, self.player.x); | |
if self.player.x > self.obstacle.x { | |
self.score += 1; | |
self.obstacle = Obstacle::new(self.player.x + SCREEN_WIDTH, self.score); | |
} | |
if (self.player.y as i32) > SCREEN_HEIGHT || self.obstacle.hit_obstacle(&self.player) { | |
self.mode = GameMode::End; | |
} | |
} | |
fn restart(&mut self) { | |
self.mode = GameMode::Playing; | |
self.player = Player::new(5, 25); | |
self.score = 0; | |
self.obstacle = Obstacle::new(SCREEN_WIDTH, 0); | |
self.frame_time = 0.0; | |
} | |
} | |
impl GameState for State { | |
fn tick(&mut self, ctx: &mut BTerm) { | |
match self.mode { | |
GameMode::Menu => self.main_menu(ctx), | |
GameMode::End => self.dead(ctx), | |
GameMode::Playing => self.play(ctx), | |
} | |
} | |
} | |
fn main() -> BError { | |
let context = BTermBuilder::new() | |
.with_font("../flappy32.png", 32, 32) | |
.with_simple_console(32, 32, "../flappy32.png") | |
.with_fancy_console(32, 32, "../flappy32.png") | |
.with_title("Flappy Dragon") | |
.with_tile_dimensions(16, 16) | |
.build()?; | |
main_loop(context, State::new()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment