From b0da71942abfd4e7d04dfe1240a14e055b7dc5d9 Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:42:17 +0000 Subject: [PATCH] asteroids game rewrite --- bacon.toml | 62 ++++ src/system/kernel/memory.rs | 13 +- src/system/kernel/mod.rs | 3 +- src/system/kernel/multitasking/mod.rs | 2 + src/system/kernel/multitasking/thread.rs | 9 + .../kernel/multitasking/thread_switch.asm | 12 + .../kernel/multitasking/thread_switch.rs | 2 + src/system/kernel/threading2/mod.rs | 15 - src/system/kernel/threading2/thread_switch.rs | 3 - src/system/kernel/threading2/thread_switch.s | 13 - src/user/bin/asteroids.rs | 324 ++++++++++++++++++ src/user/bin/asteroids/game.rs | 225 ------------ src/user/bin/asteroids/mod.rs | 1 - src/user/bin/crystalrpg/entity.rs | 146 ++++++++ src/user/bin/crystalrpg/mod.rs | 1 + src/user/bin/mod.rs | 1 + src/user/bin/shell.rs | 2 +- 17 files changed, 569 insertions(+), 265 deletions(-) create mode 100644 bacon.toml create mode 100644 src/system/kernel/multitasking/mod.rs create mode 100644 src/system/kernel/multitasking/thread.rs create mode 100644 src/system/kernel/multitasking/thread_switch.asm create mode 100644 src/system/kernel/multitasking/thread_switch.rs delete mode 100644 src/system/kernel/threading2/mod.rs delete mode 100644 src/system/kernel/threading2/thread_switch.rs delete mode 100644 src/system/kernel/threading2/thread_switch.s create mode 100644 src/user/bin/asteroids.rs delete mode 100644 src/user/bin/asteroids/game.rs delete mode 100644 src/user/bin/asteroids/mod.rs create mode 100644 src/user/bin/crystalrpg/entity.rs create mode 100644 src/user/bin/crystalrpg/mod.rs diff --git a/bacon.toml b/bacon.toml new file mode 100644 index 0000000..6fc8e01 --- /dev/null +++ b/bacon.toml @@ -0,0 +1,62 @@ +# This is a configuration file for the bacon tool +# +# Bacon repository: https://github.com/Canop/bacon +# Complete help on configuration: https://dystroy.org/bacon/config/ +# You can also check bacon's own bacon.toml file +# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml + +default_job = "check" + +[jobs.check] +command = ["cargo", "check", "--color", "always"] +need_stdout = false + +[jobs.check-all] +command = ["cargo", "check", "--all-targets", "--color", "always"] +need_stdout = false + +[jobs.clippy] +command = [ + "cargo", "clippy", + "--all-targets", + "--color", "always", +] +need_stdout = false + +[jobs.test] +command = [ + "cargo", "test", "--color", "always", + "--", "--color", "always", # see https://github.com/Canop/bacon/issues/124 +] +need_stdout = true + +[jobs.doc] +command = ["cargo", "doc", "--color", "always", "--no-deps"] +need_stdout = false + +# If the doc compiles, then it opens in your browser and bacon switches +# to the previous job +[jobs.doc-open] +command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"] +need_stdout = false +on_success = "back" # so that we don't open the browser at each change + +# You can run your application and have the result displayed in bacon, +# *if* it makes sense for this crate. You can run an example the same +# way. Don't forget the `--color always` part or the errors won't be +# properly parsed. +[jobs.run] +command = [ + "kill", "qemu-system-x86", ";", "cargo", "run", + "--color", "always", + # put launch parameters for your program behind a `--` separator +] +need_stdout = true +allow_warnings = true + +# You may define here keybindings that would be specific to +# a project, for example a shortcut to launch a specific job. +# Shortcuts to internal functions (scrolling, toggling, etc.) +# should go in your personal global prefs.toml file instead. +[keybindings] +# alt-m = "job:my-job" diff --git a/src/system/kernel/memory.rs b/src/system/kernel/memory.rs index 74c4ffc..7ce9a3e 100644 --- a/src/system/kernel/memory.rs +++ b/src/system/kernel/memory.rs @@ -5,7 +5,7 @@ use x86_64::{ PhysAddr }; use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; -use x86_64::structures::paging::OffsetPageTable; +use x86_64::structures::paging::{mapper, OffsetPageTable}; unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { @@ -72,7 +72,7 @@ unsafe impl FrameAllocator for BootInfoFrameAllocator { } } -/* + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct StackBounds { @@ -119,7 +119,7 @@ fn reserve_stack_memory(size_in_pages: u64) -> Page { pub fn alloc_stack( size_in_pages: u64, mapper: &mut impl Mapper, frame_allocator: &mut impl FrameAllocator -) -> Result { +) -> Result> { use x86_64::structures::paging::PageTableFlags as Flags; let guard_page = reserve_stack_memory(size_in_pages + 1); @@ -127,9 +127,10 @@ pub fn alloc_stack( let stack_end = stack_start + size_in_pages; for page in Page::range(stack_start, stack_end) { - let frame = frame_allocator.allocate_frame().ok_or(mapper.MapToError::FrameAllocatorFailed)?; + let frame = frame_allocator.allocate_frame().ok_or(mapper::MapToError::FrameAllocationFailed)?; let flags = Flags::PRESENT | Flags::WRITABLE; - mapper.map_to(page, frame, flags, frame_allocator)?.flush(); + + unsafe { mapper.map_to(page, frame, flags, frame_allocator)?.flush() }; } Ok(StackBounds { @@ -137,4 +138,4 @@ pub fn alloc_stack( end: stack_end.start_address(), }) } -*/ + diff --git a/src/system/kernel/mod.rs b/src/system/kernel/mod.rs index 25a8106..1e241d6 100644 --- a/src/system/kernel/mod.rs +++ b/src/system/kernel/mod.rs @@ -6,4 +6,5 @@ pub mod serial; pub mod tasks; pub mod sysinit; pub mod authenticator; -pub mod render; \ No newline at end of file +pub mod render; +pub mod multitasking; \ No newline at end of file diff --git a/src/system/kernel/multitasking/mod.rs b/src/system/kernel/multitasking/mod.rs new file mode 100644 index 0000000..217656a --- /dev/null +++ b/src/system/kernel/multitasking/mod.rs @@ -0,0 +1,2 @@ +pub mod thread; +mod thread_switch; \ No newline at end of file diff --git a/src/system/kernel/multitasking/thread.rs b/src/system/kernel/multitasking/thread.rs new file mode 100644 index 0000000..bf6ac1b --- /dev/null +++ b/src/system/kernel/multitasking/thread.rs @@ -0,0 +1,9 @@ +use x86_64::VirtAddr; +use crate::system::kernel::memory::{StackBounds, ThreadId}; + +#[derive(Debug)] +pub struct Thread { + id: ThreadId, + stack_ptr: Option, + stack_bounds: Option, +} \ No newline at end of file diff --git a/src/system/kernel/multitasking/thread_switch.asm b/src/system/kernel/multitasking/thread_switch.asm new file mode 100644 index 0000000..8cf39c1 --- /dev/null +++ b/src/system/kernel/multitasking/thread_switch.asm @@ -0,0 +1,12 @@ + +.intel_syntax noprefix + pushfq + + mov rax, rsp + mov rsp, rdi + + mov rdi, rax + call add_paused_thread + + popfq + ret \ No newline at end of file diff --git a/src/system/kernel/multitasking/thread_switch.rs b/src/system/kernel/multitasking/thread_switch.rs new file mode 100644 index 0000000..b287729 --- /dev/null +++ b/src/system/kernel/multitasking/thread_switch.rs @@ -0,0 +1,2 @@ +use core::arch::global_asm; +global_asm!(include_str!("thread_switch.asm")); \ No newline at end of file diff --git a/src/system/kernel/threading2/mod.rs b/src/system/kernel/threading2/mod.rs deleted file mode 100644 index 518d558..0000000 --- a/src/system/kernel/threading2/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ - - -pub mod thread_switch; - -use x86_64::VirtAddr; -use crate::memory::{ThreadId, StackBounds}; - - -#[derive(Debug)] -pub struct Thread { - id: ThreadId, - stack_pointer: Option, - stack_bounds: Option, -} - diff --git a/src/system/kernel/threading2/thread_switch.rs b/src/system/kernel/threading2/thread_switch.rs deleted file mode 100644 index 12a5a6d..0000000 --- a/src/system/kernel/threading2/thread_switch.rs +++ /dev/null @@ -1,3 +0,0 @@ -use core::arch::global_asm; - -global_asm!(include_str!("thread_switch.s")); diff --git a/src/system/kernel/threading2/thread_switch.s b/src/system/kernel/threading2/thread_switch.s deleted file mode 100644 index 06877f8..0000000 --- a/src/system/kernel/threading2/thread_switch.s +++ /dev/null @@ -1,13 +0,0 @@ - - -asm_thread_switch: - pushfq - - mov rax, rsp - mov rsp, rdi - - mov rdi, rax - call add_paused_thread - - popfq - ret diff --git a/src/user/bin/asteroids.rs b/src/user/bin/asteroids.rs new file mode 100644 index 0000000..2039179 --- /dev/null +++ b/src/user/bin/asteroids.rs @@ -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, + 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) -> 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::>() + .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 { + 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::>() + { + 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, + } + } +} diff --git a/src/user/bin/asteroids/game.rs b/src/user/bin/asteroids/game.rs deleted file mode 100644 index 7365ede..0000000 --- a/src/user/bin/asteroids/game.rs +++ /dev/null @@ -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, - 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) -> 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::>().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 { - 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::>() { - 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) - } - } -} - diff --git a/src/user/bin/asteroids/mod.rs b/src/user/bin/asteroids/mod.rs deleted file mode 100644 index f7ee1cd..0000000 --- a/src/user/bin/asteroids/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod game; diff --git a/src/user/bin/crystalrpg/entity.rs b/src/user/bin/crystalrpg/entity.rs new file mode 100644 index 0000000..cc90662 --- /dev/null +++ b/src/user/bin/crystalrpg/entity.rs @@ -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>, + + helmet: Option, + chestplate: Option, + boots: Option, + + inventory: Vec, +} + +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 { + &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, +} diff --git a/src/user/bin/crystalrpg/mod.rs b/src/user/bin/crystalrpg/mod.rs new file mode 100644 index 0000000..6f6ff5b --- /dev/null +++ b/src/user/bin/crystalrpg/mod.rs @@ -0,0 +1 @@ +mod entity; \ No newline at end of file diff --git a/src/user/bin/mod.rs b/src/user/bin/mod.rs index c687b42..77cfef3 100644 --- a/src/user/bin/mod.rs +++ b/src/user/bin/mod.rs @@ -11,3 +11,4 @@ mod grapher; mod gameoflife; mod tetris; mod asteroids; +mod crystalrpg; diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index f70948a..00fa655 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -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" => {