asteroids game rewrite

This commit is contained in:
FantasyPvP
2023-12-04 10:42:17 +00:00
parent f400a1cf22
commit b0da71942a
17 changed files with 569 additions and 265 deletions
+324
View File
@@ -0,0 +1,324 @@
use crate::std::application::Error;
use crate::std::application::Error::ApplicationError;
use crate::std::frame::{ColouredChar, Dimensions, Frame, Position, RenderError};
use crate::std::io::{Color, ColorCode, KeyStroke, Screen, Stdin};
use crate::std::random::Random;
use crate::system::std::application::Application;
use crate::user::lib::libgui::cg_core::{CgComponent, Widget};
use crate::user::lib::libgui::cg_widgets::{CgContainer, CgLabel};
use crate::{serial_println, std};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use async_trait::async_trait;
use core::any::Any;
#[derive(Clone)]
pub struct Player {
pub health: i32,
pub position: Position,
}
impl Player {
pub fn new() -> Player {
Player {
health: 5,
position: Position::new(10, 12),
}
}
}
#[derive(Clone)]
pub struct Game {
pub player: Player,
pub enemies: Vec<Enemy>,
pub score: u32,
pub hit: bool,
pub difficulty_idx: u8,
pub gamespeed: f64,
pub timer: GameTimer,
}
#[async_trait]
impl Application for Game {
fn new() -> Self {
Self {
player: Player::new(),
enemies: Vec::new(),
score: 0,
hit: false,
difficulty_idx: 1,
gamespeed: 1.0,
timer: GameTimer::new(),
}
}
async fn run(&mut self, args: Vec<String>) -> Result<(), Error> {
Screen::Application.set_mode().unwrap();
let mut container_data =
CgContainer::new(Position::new(0, 0), Dimensions::new(80, 25), true);
let score_label =
Widget::insert(CgLabel::new(String::new(), Position::new(1, 1), 78, true));
let self_ref = Widget::insert(self.clone());
container_data.insert("app", self_ref);
container_data.insert("score_label", score_label);
let self_ref = container_data.fetch("app").unwrap();
let score_ref = container_data.fetch("score_label").unwrap();
loop {
std::time::wait(0.01);
if self.gameloop_iteration() {
break;
}
self_ref.update(self.clone());
score_ref.update(CgLabel::new(
format!("< Score: {} >", self.score),
Position::new(1, 1),
78,
true,
));
if let Ok(frame) = container_data.render() {
frame.write_to_screen().unwrap();
}
self.hit = false;
// check if player has lost
if self.player.health <= 0 {
break;
}
}
self.render_end_screen().await?;
Ok(())
}
}
impl Game {
fn new_enemy(&mut self, speed: u8) {
self.enemies.push(Enemy::new(Random::int(1, 21), speed));
}
fn check_difficulty(&mut self) {
if self.difficulty_idx > 6 {
self.new_enemy(2);
self.new_enemy(1);
} else if self.difficulty_idx > 4 {
self.new_enemy(2);
} else {
self.new_enemy(1);
}
}
// checks if an enemy should be spawned based on a random chance
fn random_spawn_enemy(&mut self, chance: f64) {
let mut randomdecimal = Random::int(0, 1000) as f64 / 1000.0;
while randomdecimal > 1.0 {
randomdecimal -= 1.0;
self.check_difficulty();
}
// if the given chance is greater than the random number, spawn an enemy
if chance > randomdecimal {
self.check_difficulty();
}
}
fn gameloop_iteration(&mut self) -> bool {
// triggers roughly every 10ms
match self.score {
0..=9 => { self.gamespeed = 1.0;self.difficulty_idx = 1 },
10..=24 => { self.gamespeed = 2.0;self.difficulty_idx = 2 },
25..=49 => { self.gamespeed = 2.0;self.difficulty_idx = 3 },
50..=99 => { self.gamespeed = 3.0;self.difficulty_idx = 4 } ,
100..=199 => { self.gamespeed = 3.0;self.difficulty_idx = 5 },
200..=500 => { self.gamespeed = 4.0;self.difficulty_idx = 6 },
_ => { self.gamespeed = 5.0;self.difficulty_idx = 7 },
};
self.timer.advance();
let game_update_delay = 5.0 / self.gamespeed;
let enemy_spawn_time = match self.difficulty_idx {
1 => 10,
2 => 10,
3 => 10,
4 => 5,
5 => 2,
_ => 1,
};
// check if enemies overlap with player, if so decrease player health and remove enemy
self.enemies.retain(|e| {
if e.position.1 == self.player.position.y as i16
&& (0..5)
.map(|i| e.position.0 + i)
.collect::<Vec<_>>()
.contains(&(self.player.position.x as i16))
{
self.player.health -= 1;
self.hit = true;
false
} else {
true
}
});
// check if a movement update is required
if self.timer.get_move_time() as f64 >= game_update_delay {
self.timer.reset_move_time();
self.enemies.iter_mut().for_each(|e| {
e.position.0 -= e.speed as i16;
});
// check for out of bounds enemies after move
self.enemies.retain(|e| {
if e.position.0 <= 0 {
self.score += 1;
false
} else {
true
}
});
}
self.random_spawn_enemy(self.difficulty_idx as f64 / 10.0);
if let Some(input_key) = Stdin::try_keystroke() {
match input_key {
KeyStroke::Char('q') => return true,
KeyStroke::Char('w') => self.player.position.y -= 1,
KeyStroke::Char('s') => self.player.position.y += 1,
_ => (),
}
}
false
}
async fn render_end_screen(&mut self) -> Result<(), Error> {
let mut frame = Frame::new(Dimensions::new(0, 0), Dimensions::new(80, 25))
.map_err(|_| ApplicationError("idk".to_string()))?;
let msg = format!("your score was: {}", self.score);
msg.chars().enumerate().for_each(|(i, c)| {
serial_println!("{}", (80 - msg.len()) / 2 + i);
frame[12][(80 - msg.len()) / 2 + i] = ColouredChar {
character: c,
colour: ColorCode::new(Color::Cyan, Color::Black),
}
});
frame.write_to_screen().unwrap();
while let KeyStroke::Char(c) = Stdin::keystroke().await {
if c == 'q' {
break;
}
}
Screen::Terminal.set_mode().unwrap();
Ok(())
}
}
impl CgComponent for Game {
fn render(&self) -> Result<Frame, RenderError> {
let mut frame = Frame::new(Dimensions::new(1, 2), Dimensions::new(78, 22))?;
let pos = self.player.position;
let player_colour = match self.hit {
true => Color::Red,
false => Color::Cyan,
};
frame[pos.y][pos.x] = ColouredChar {
character: '@',
colour: ColorCode::new(player_colour, Color::Black),
};
for i in self
.enemies
.iter()
.map(|enemy| enemy.position)
.collect::<Vec<(i16, i16)>>()
{
frame[i.1 as usize][i.0 as usize] = ColouredChar {
character: '<',
colour: ColorCode::new(Color::LightGray, Color::Black),
};
(1..5).for_each(|offset| {
if i.0 + offset < frame.dimensions.x as i16 {
frame[i.1 as usize][(i.0 + offset) as usize] = ColouredChar {
character: '=',
colour: ColorCode::new(Color::LightGray, Color::Black),
}
}
});
}
Ok(frame)
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone)]
pub struct GameTimer {
pub time_since_spawn: u32,
pub time_since_move: u32,
}
impl GameTimer {
pub fn new() -> GameTimer {
GameTimer {
time_since_spawn: 0,
time_since_move: 0,
}
}
pub fn advance(&mut self) {
self.time_since_spawn += 1;
self.time_since_move += 1;
}
pub fn get_spawn_time(&self) -> u32 {
self.time_since_spawn
}
pub fn reset_spawn_time(&mut self) {
self.time_since_spawn = 0
}
pub fn get_move_time(&self) -> u32 {
self.time_since_move
}
pub fn reset_move_time(&mut self) {
self.time_since_move = 0
}
}
#[derive(Clone)]
pub struct Enemy {
pub position: (i16, i16), // x,y
pub speed: u8,
}
impl Enemy {
pub fn new(y: usize, speed: u8) -> Enemy {
Enemy {
position: (75, y as i16),
speed: speed,
}
}
}
-225
View File
@@ -1,225 +0,0 @@
use crate::system::std::application::Application;
use async_trait::async_trait;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::any::Any;
use crate::{serial_println, std};
use crate::std::application::Error;
use crate::std::application::Error::ApplicationError;
use crate::std::frame::{Position, Dimensions, RenderError, Frame, ColouredChar};
use crate::std::io::{Color, ColorCode, KeyStroke, Screen, Stdin};
use crate::std::random::Random;
use crate::user::lib::libgui::cg_core::{CgComponent, Widget};
use crate::user::lib::libgui::cg_widgets::{CgContainer, CgIndicatorWidget, CgLabel};
#[derive(Clone)]
pub struct Player {
pub health: u32,
pub position: Position,
}
impl Player {
pub fn new() -> Player {
Player {
health: 5,
position: Position::new(10, 12)
}
}
}
#[derive(Clone)]
pub struct Game {
pub player: Player,
pub enemies: Vec<Enemy>,
pub score: u32,
pub hit: bool,
pub difficulty_idx: u8,
pub gamespeed: f64,
}
#[async_trait]
impl Application for Game {
fn new() -> Self {
Self {
player: Player::new(),
enemies: Vec::new(),
score: 0,
hit: false,
difficulty_idx: 1,
gamespeed: 1.0,
}
}
async fn run(&mut self, args: Vec<String>) -> Result<(), Error> {
let mut spawn_timer: i32 = 0;
Screen::Application.set_mode().unwrap();
let mut container_data = CgContainer::new(
Position::new(0, 0),
Dimensions::new(80, 25),
true,
);
let score_label = Widget::insert(CgLabel::new(
String::new(),
Position::new(1, 1),
78,
true,
));
let self_ref = Widget::insert(self.clone());
container_data.insert("app", self_ref);
container_data.insert("score_label", score_label);
let self_ref = container_data.fetch("app").unwrap();
let score_ref = container_data.fetch("score_label").unwrap();
loop {
match self.score {
0..=9 => { self.gamespeed = 1.0; self.difficulty_idx = 1 },
10..=24 => { self.gamespeed = 2.0; self.difficulty_idx = 2 },
25..=49 => { self.gamespeed = 3.0; self.difficulty_idx = 3 },
50..=99 => { self.gamespeed = 4.0; self.difficulty_idx = 4 },
100..=199 => { self.gamespeed = 5.0; self.difficulty_idx = 5 },
_ => self.gamespeed = 10.0,
};
std::time::wait(0.2 / self.gamespeed);
spawn_timer += 1;
if spawn_timer >= 8 {
spawn_timer = 0;
self.new_enemy();
}
self.enemies.iter().for_each(|e| {
});
self.enemies.retain(|e| {
if e.position.y == self.player.position.y && (0..5).map(|i| e.position.x + i).collect::<Vec<_>>().contains(&self.player.position.x) {
self.player.health -= 1;
self.hit = true;
false
} else if e.position.x <= 0 {
self.score += 1;
false
} else {
true
}
});
self.enemies.iter_mut().for_each(|e| e.position.x -= 1);
if self.player.health == 0 {
break;
}
if let Some(input_key) = Stdin::try_keystroke() {
match input_key {
KeyStroke::Char('q') => {
break;
}
KeyStroke::Char('w') => self.player.position.y -= 1,
KeyStroke::Char('s') => self.player.position.y += 1,
KeyStroke::Char('a') => self.player.position.x -= 1,
KeyStroke::Char('d') => self.player.position.x += 1,
_ => (),
}
}
self_ref.update(self.clone());
score_ref.update(CgLabel::new(format!("< Score: {} >", self.score), Position::new(1, 1), 78, true, ));
if let Ok(frame) = container_data.render() {
frame.write_to_screen().unwrap();
}
self.hit = false;
}
let mut frame = Frame::new(Dimensions::new(0, 0), Dimensions::new(80, 25)).map_err(|_| ApplicationError("idk".to_string()))?;
let msg = format!("your score was: {}", self.score);
msg.chars().enumerate().for_each(|(i, c)| {
serial_println!("{}", (80 - msg.len()) / 2 + i);
frame[12][(80 - msg.len()) / 2 + i] = ColouredChar {
character: c,
colour: ColorCode::new(Color::Cyan, Color::Black),
}
});
frame.write_to_screen().unwrap();
while let KeyStroke::Char(c) = Stdin::keystroke().await {
if c == 'q' {
break;
}
}
Screen::Terminal.set_mode().unwrap();
Ok(())
}
}
impl Game {
fn new_enemy(&mut self) {
let enemy_num = match self.difficulty_idx {
1 => 1,
2 => 2,
3 => 3,
4 => 5,
_ => 7,
};
for _ in 0..enemy_num {
self.enemies.push(Enemy::new(Random::int(1, 21)));
}
}
}
impl CgComponent for Game {
fn render(&self) -> Result<Frame, RenderError> {
let mut frame = Frame::new(Dimensions::new(1, 2), Dimensions::new(78, 22))?;
let pos = self.player.position;
let player_colour = match self.hit {
true => Color::Red,
false => Color::Cyan
};
frame[pos.y][pos.x] = ColouredChar {
character: '@',
colour: ColorCode::new(player_colour, Color::Black),
};
for i in self.enemies.iter().map(|enemy| enemy.position).collect::<Vec<Position>>() {
frame[i.y][i.x] = ColouredChar {
character: '<',
colour: ColorCode::new(Color::LightGray, Color::Black),
};
(1..5).for_each(|offset| if i.x + offset < frame.dimensions.x { frame[i.y][i.x + offset] = ColouredChar {
character: '=',
colour: ColorCode::new(Color::LightGray, Color::Black),
}});
}
Ok(frame)
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Clone)]
pub struct Enemy {
pub position: Position
}
impl Enemy {
pub fn new(y: usize) -> Enemy {
Enemy {
position: Position::new(75, y)
}
}
}
-1
View File
@@ -1 +0,0 @@
pub mod game;
+146
View File
@@ -0,0 +1,146 @@
use alloc::{boxed::Box, string::String, vec::Vec};
use core::cmp::min;
struct Player {
username: String,
stats: EntityStats,
exp: u32,
level: u32,
skill_points: u32,
skills: Vec<Box<dyn Skill>>,
helmet: Option<Helmet>,
chestplate: Option<Chestplate>,
boots: Option<Boots>,
inventory: Vec<Item>,
}
struct EntityStats {
health: i32,
max_health: i32,
mana: i32,
max_mana: i32,
defence: i32,
}
impl Player {
fn new(username: String) -> Self {
Self {
username,
stats: EntityStats {
health: 100,
max_health: 100,
mana: 100,
max_mana: 100,
defence: 0,
},
exp: 0,
level: 0,
skill_points: 0,
skills: Vec::new(),
helmet: None,
chestplate: None,
boots: None,
inventory: Vec::new(),
}
}
fn heal(&mut self, amount: i32) {
let max_health = self.max_health();
self.stats.health = min(self.stats.health + amount, max_health);
}
fn damage(&mut self, amount: i32) {
let hp = self.health_points();
}
fn inventory_contents_mut(&mut self) -> &mut Vec<Item> {
&mut self.inventory
}
fn max_health(&self) -> i32 {
let mut max_health = self.stats.max_health;
if let Some(helmet) = &self.helmet {
max_health += helmet.stats.health_bonus;
}
if let Some(chestplate) = &self.chestplate {
max_health += chestplate.stats.health_bonus;
}
if let Some(boots) = &self.boots {
max_health += boots.stats.health_bonus;
}
max_health
}
fn health_points(&self) -> i32 {
let mut hp = self.stats.health;
if let Some(helmet) = &self.helmet {
hp += helmet.stats.health_bonus;
}
if let Some(chestplate) = &self.chestplate {
hp += chestplate.stats.health_bonus;
}
if let Some(boots) = &self.boots {
hp += boots.stats.health_bonus;
}
hp
}
}
enum Item {
Helmet(Helmet),
Chestplate(Chestplate),
Boots(Boots),
Sword,
Potion,
}
struct Helmet {
name: &'static str,
lore: &'static str,
stats: ArmourStats,
}
struct Chestplate {
name: &'static str,
lore: &'static str,
stats: ArmourStats,
}
struct Boots {
name: &'static str,
lore: &'static str,
stats: ArmourStats,
}
struct ArmourStats {
durability: i32,
max_durability: i32,
defence: i32,
health_bonus: i32,
mana_bonus: i32,
}
struct PlayerStats {}
trait Skill {
fn skill_name(&self) -> &str; // returns the name of the skill
fn skill_level(&self) -> &str; // returns the level of that skill
fn description(&self) -> &str; // returns the status of that skill
fn skillpoint_level_req(&self) -> i32;
fn increase_level(&mut self, level: &u32, skill_points: &mut u32) -> Result<(), GameError>;
fn decrease_level(&mut self, skill_points: &mut u32) -> Result<(), GameError>;
fn modify_stats(&self, stats: EntityStats) -> EntityStats;
}
enum GameError {
SkillLevelMaxed,
InsufficientSkillPoints,
InsufficientLevel,
}
+1
View File
@@ -0,0 +1 @@
mod entity;
+1
View File
@@ -11,3 +11,4 @@ mod grapher;
mod gameoflife;
mod tetris;
mod asteroids;
mod crystalrpg;
+1 -1
View File
@@ -126,7 +126,7 @@ async fn exec() -> Result<(), Error> {
game.run(args).await?;
}
"asteroids" => {
let mut asteroid_game = asteroids::game::Game::new();
let mut asteroid_game = asteroids::Game::new();
asteroid_game.run(args).await?;
}
"serial" => {