asteroids game rewrite
This commit is contained in:
+62
@@ -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"
|
||||||
@@ -5,7 +5,7 @@ use x86_64::{
|
|||||||
PhysAddr
|
PhysAddr
|
||||||
};
|
};
|
||||||
use bootloader::bootinfo::{MemoryMap, MemoryRegionType};
|
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 {
|
unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable {
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ unsafe impl FrameAllocator<Size4KiB> for BootInfoFrameAllocator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct StackBounds {
|
pub struct StackBounds {
|
||||||
@@ -119,7 +119,7 @@ fn reserve_stack_memory(size_in_pages: u64) -> Page {
|
|||||||
pub fn alloc_stack(
|
pub fn alloc_stack(
|
||||||
size_in_pages: u64, mapper: &mut impl Mapper<Size4KiB>,
|
size_in_pages: u64, mapper: &mut impl Mapper<Size4KiB>,
|
||||||
frame_allocator: &mut impl FrameAllocator<Size4KiB>
|
frame_allocator: &mut impl FrameAllocator<Size4KiB>
|
||||||
) -> Result<StackBounds, mapper::MapToError> {
|
) -> Result<StackBounds, mapper::MapToError<Size4KiB>> {
|
||||||
use x86_64::structures::paging::PageTableFlags as Flags;
|
use x86_64::structures::paging::PageTableFlags as Flags;
|
||||||
|
|
||||||
let guard_page = reserve_stack_memory(size_in_pages + 1);
|
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;
|
let stack_end = stack_start + size_in_pages;
|
||||||
|
|
||||||
for page in Page::range(stack_start, stack_end) {
|
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;
|
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 {
|
Ok(StackBounds {
|
||||||
@@ -137,4 +138,4 @@ pub fn alloc_stack(
|
|||||||
end: stack_end.start_address(),
|
end: stack_end.start_address(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ pub mod tasks;
|
|||||||
pub mod sysinit;
|
pub mod sysinit;
|
||||||
pub mod authenticator;
|
pub mod authenticator;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
pub mod multitasking;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod thread;
|
||||||
|
mod thread_switch;
|
||||||
@@ -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<VirtAddr>,
|
||||||
|
stack_bounds: Option<StackBounds>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
.intel_syntax noprefix
|
||||||
|
pushfq
|
||||||
|
|
||||||
|
mov rax, rsp
|
||||||
|
mov rsp, rdi
|
||||||
|
|
||||||
|
mov rdi, rax
|
||||||
|
call add_paused_thread
|
||||||
|
|
||||||
|
popfq
|
||||||
|
ret
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
use core::arch::global_asm;
|
||||||
|
global_asm!(include_str!("thread_switch.asm"));
|
||||||
@@ -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<VirtAddr>,
|
|
||||||
stack_bounds: Option<StackBounds>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
use core::arch::global_asm;
|
|
||||||
|
|
||||||
global_asm!(include_str!("thread_switch.s"));
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
asm_thread_switch:
|
|
||||||
pushfq
|
|
||||||
|
|
||||||
mov rax, rsp
|
|
||||||
mov rsp, rdi
|
|
||||||
|
|
||||||
mov rdi, rax
|
|
||||||
call add_paused_thread
|
|
||||||
|
|
||||||
popfq
|
|
||||||
ret
|
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 +0,0 @@
|
|||||||
pub mod game;
|
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
mod entity;
|
||||||
@@ -11,3 +11,4 @@ mod grapher;
|
|||||||
mod gameoflife;
|
mod gameoflife;
|
||||||
mod tetris;
|
mod tetris;
|
||||||
mod asteroids;
|
mod asteroids;
|
||||||
|
mod crystalrpg;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ async fn exec() -> Result<(), Error> {
|
|||||||
game.run(args).await?;
|
game.run(args).await?;
|
||||||
}
|
}
|
||||||
"asteroids" => {
|
"asteroids" => {
|
||||||
let mut asteroid_game = asteroids::game::Game::new();
|
let mut asteroid_game = asteroids::Game::new();
|
||||||
asteroid_game.run(args).await?;
|
asteroid_game.run(args).await?;
|
||||||
}
|
}
|
||||||
"serial" => {
|
"serial" => {
|
||||||
|
|||||||
Reference in New Issue
Block a user