diff --git a/Cargo.lock b/Cargo.lock index 90fbdef..7347b02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,23 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b13fd4109a4b9b49b1c747c64b2d813f33639b723e2c3b7b36190edd051bc06" +dependencies = [ + "embedded-hal", + "nb", + "void", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -121,9 +138,11 @@ dependencies = [ "bootloader", "conquer-once", "crossbeam-queue", + "embedded-timers", "futures-util", "lazy_static", "linked_list_allocator", + "nanorand", "pc-keyboard", "pic8259", "spin 0.5.2", @@ -132,6 +151,18 @@ dependencies = [ "x86_64", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "pc-keyboard" version = "0.7.0" @@ -203,6 +234,12 @@ dependencies = [ "x86_64", ] +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "volatile" version = "0.2.7" diff --git a/Cargo.toml b/Cargo.toml index a7e1f7f..8224cc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ x86_64 = "0.14.2" uart_16550 = "0.2.0" pic8259 = "0.10.1" pc-keyboard = "0.7.0" +nanorand = { version = "0.7.0", default-features = false, features = ["alloc", "wyrand"] } linked_list_allocator = "0.10.0" +embedded-timers = "0.4.0" [dependencies.lazy_static] version = "1.0" diff --git a/src/clock.rs b/src/clock.rs new file mode 100644 index 0000000..f4d7885 --- /dev/null +++ b/src/clock.rs @@ -0,0 +1,16 @@ +static TICKS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); + +pub fn tick_handler() { + TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed); +} + +pub struct MilliSecondClock32; + +impl embedded_timers::clock::Clock for MilliSecondClock32 { + type Instant = embedded_timers::instant::Instant32<1000>; + + fn now(&self) -> Self::Instant { + let ticks = TICKS.load(core::sync::atomic::Ordering::Relaxed); + embedded_timers::instant::Instant32::<1000>::new(ticks) + } +} diff --git a/src/interrupts.rs b/src/interrupts.rs index 32f14b8..2f8eaff 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -47,6 +47,21 @@ lazy_static! { pub fn init_idt() { IDT.load(); + set_timer_speed(); +} + +fn set_timer_speed() { + use x86_64::instructions::port::Port; + + let mut port = Port::new(0x43); + + unsafe { port.write(0x36 as u8); } + port = Port::new(0x40); + unsafe { + port.write(4 as u8); + port.write(169 as u8); + } + } extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { @@ -70,7 +85,8 @@ extern "x86-interrupt" fn page_fault_handler(stack_frame: InterruptStackFrame, e } extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { - print!("."); +// print!("."); + crate::clock::tick_handler(); unsafe { PICS.lock() .notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); diff --git a/src/lib.rs b/src/lib.rs index 7aa5ceb..71c16e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,15 @@ use core::panic::PanicInfo; extern crate alloc; pub mod allocator; +pub mod snake_game; +pub mod os_mode; pub mod gdt; pub mod interrupts; pub mod memory; pub mod serial; pub mod task; pub mod vga_buffer; +pub mod clock; pub trait Testable { fn run(&self) -> (); diff --git a/src/main.rs b/src/main.rs index 59c1055..e8fe226 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ extern crate alloc; use bootloader::{entry_point, BootInfo}; use core::panic::PanicInfo; -use milly_os::task::{executor::Executor, Task, keyboard}; +use milly_os::task::{executor::Executor, keyboard, Task}; use x86_64::VirtAddr; mod serial; @@ -31,7 +31,9 @@ entry_point!(kernel_main); fn kernel_main(boot_info: &'static BootInfo) -> ! { use milly_os::allocator; + use milly_os::clock::MilliSecondClock32; use milly_os::memory::{self, BootInfoFrameAllocator}; + use embedded_timers::delay::Delay; println!("Hello world{}", "!"); milly_os::init(); diff --git a/src/os_mode.rs b/src/os_mode.rs new file mode 100644 index 0000000..f4e3938 --- /dev/null +++ b/src/os_mode.rs @@ -0,0 +1,4 @@ +pub enum OsMode { + KernelMode, + SnakeMode, +} diff --git a/src/snake_game.rs b/src/snake_game.rs new file mode 100644 index 0000000..b4a8de7 --- /dev/null +++ b/src/snake_game.rs @@ -0,0 +1,2 @@ +mod game_main; +mod snake; diff --git a/src/snake_game/game_main.rs b/src/snake_game/game_main.rs new file mode 100644 index 0000000..adde2f6 --- /dev/null +++ b/src/snake_game/game_main.rs @@ -0,0 +1,176 @@ +use crate::clock::MilliSecondClock32; +use crate::println; +use crate::snake_game::snake::Command; +use crate::snake_game::snake::Direction; +use crate::snake_game::snake::Point; +use crate::snake_game::snake::Snake; +use crate::vga_buffer; +use crate::vga_buffer::BUFFER_HEIGHT; +use crate::vga_buffer::BUFFER_WIDTH; +use core::time::Duration; +use embedded_timers::clock::Clock; +use embedded_timers::instant::Instant; +use nanorand::{Rng, WyRand}; + +const MAX_INTERVAL: u16 = 700; +const MIN_INTERVAL: u16 = 200; +const MAX_SPEED: u16 = 20; + +#[derive(Debug)] +pub struct Game { + buffer_size: (u16, u16), + width: u16, + height: u16, + food: Option, + snake: Snake, + speed: u16, + score: u16, +} + +impl Game { + pub fn new(width: u16, height: u16) -> Self { + let buffer_size = ( + vga_buffer::BUFFER_WIDTH as u16, + vga_buffer::BUFFER_HEIGHT as u16, + ); + Self { + buffer_size, + width, + height, + food: None, + snake: Snake::new( + Point::new(width / 2, height / 2), + 3, + match WyRand::new().generate_range(0..4) { + 0 => Direction::Up, + 1 => Direction::Right, + 2 => Direction::Down, + _ => Direction::Left, + }, + ), + speed: 0, + score: 0, + } + } + + pub fn run(&mut self) { + self.place_food(); + self.prepare_ui(); + self.render(); + + let clock = MilliSecondClock32; + + let mut done = false; + while !done { + let interval = self.calculate_interval(); + let direction = self.snake.get_direction(); + let now = clock.now(); + + while clock.elapsed(now) < interval { + if let Some(command) = self.get_command(interval - clock.elapsed(now)) { + match command { + Command::Quit => { + done = true; + break; + } + Command::Turn(towards) => { + if direction != towards && direction.opposite() != towards { + self.snake.set_direction(towards); + } + } + } + } + } + + if self.has_collided_with_wall() || self.has_bitten_itself() { + done = true; + } else { + self.snake.slither(); + + if let Some(food_point) = self.food { + if self.snake.get_head_point() == food_point { + self.snake.grow(); + self.place_food(); + self.score += 1; + + if self.score % ((self.width * self.height) / MAX_SPEED) == 0 { + self.speed += 1; + } + } + } + + self.render(); + } + } + + println!("Game Over! Your score is {}", self.score); + } + + fn place_food(&mut self) { + loop { + let random_x = WyRand::new().generate_range(0..self.width); + let random_y = WyRand::new().generate_range(0..self.height); + let point = Point::new(random_x, random_y); + if !self.snake.contains_point(&point) { + self.food = Some(point); + break; + } + } + } + + fn prepare_ui(&mut self) {} + + fn calculate_interval(&self) -> Duration { + let speed = MAX_SPEED - self.speed; + Duration::from_millis( + (MIN_INTERVAL + (((MAX_INTERVAL - MIN_INTERVAL) / MAX_SPEED) * speed)) as u64, + ) + } + + fn get_command(&self, wait_for: Duration) -> Option { + None + } + + fn wait_for_key_event(&self, wait_for: Duration) -> ! { + loop {} + } + + fn has_collided_with_wall(&self) -> bool { + let head_point = self.snake.get_head_point(); + + match self.snake.get_direction() { + Direction::Up => head_point.y == 0, + Direction::Right => head_point.x == self.width - 1, + Direction::Down => head_point.y == self.height - 1, + Direction::Left => head_point.x == 0, + } + } + + fn has_bitten_itself(&self) -> bool { + let next_head_point = self + .snake + .get_head_point() + .transform(self.snake.get_direction(), 1); + let mut next_body_points = self.snake.get_body_points().clone(); + next_body_points.remove(next_body_points.len() - 1); + next_body_points.remove(0); + + next_body_points.contains(&next_head_point) + } + + fn render(&self) { + use vga_buffer::ScreenChar; + use vga_buffer::{Color, ColorCode}; + use volatile::Volatile; + + vga_buffer::print_buffer(core::array::from_fn::<_, BUFFER_HEIGHT, _>(|_| { + core::array::from_fn::<_, BUFFER_WIDTH, _>(|_| { + Volatile::new(ScreenChar { + ascii_character: 0, + color_code: ColorCode::new(Color::Red, Color::White), + }) + }) + })); + todo!() + } +} diff --git a/src/snake_game/snake.rs b/src/snake_game/snake.rs new file mode 100644 index 0000000..f6159ba --- /dev/null +++ b/src/snake_game/snake.rs @@ -0,0 +1,109 @@ +use ::alloc::vec::Vec; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Up, + Right, + Down, + Left, +} + +impl Direction { + pub fn opposite(&self) -> Self { + match self { + Self::Up => Self::Down, + Self::Right => Self::Left, + Self::Down => Self::Up, + Self::Left => Self::Right, + } + } +} + +pub enum Command { + Quit, + Turn(Direction), +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct Point { + pub x: u16, + pub y: u16, +} + +impl Point { + pub fn new(x: u16, y: u16) -> Self { + Self { x, y } + } + + pub fn transform(&self, direction: Direction, times: u16) -> Self { + let times = times as i16; + let transformation = match direction { + Direction::Up => (0, -times), + Direction::Right => (times, 0), + Direction::Down => (0, times), + Direction::Left => (-times, 0), + }; + + Self::new( + Self::transform_value(self.x, transformation.0), + Self::transform_value(self.y, transformation.1), + ) + } + + fn transform_value(value: u16, by: i16) -> u16 { + if by.is_negative() && by.abs() as u16 > value { + panic!("transforming vlaue {} by {} would result in a negative number", value, by) + } else { + (value as i16 + by) as u16 + } + } +} + +#[derive(Debug)] +pub struct Snake { + body: Vec, + direction: Direction, + digesting: bool, +} + +impl Snake { + pub fn new(start: Point, length: u16, direction: Direction) -> Self { + let opposite = direction.opposite(); + let body: Vec = (0..length).into_iter().map(|i| start.transform(opposite, i)).collect(); + + Self { body, direction, digesting: false } + } + + pub fn get_head_point(&self) -> Point { + self.body.first().unwrap().clone() + } + + pub fn get_body_points(&self) -> Vec { + self.body.clone() + } + + pub fn get_direction(&self) -> Direction { + self.direction.clone() + } + + pub fn contains_point(&self, point: &Point) -> bool { + self.body.contains(point) + } + + pub fn slither(&mut self) { + self.body.insert(0, self.body.first().unwrap().transform(self.direction, 1)); + if !self.digesting { + self.body.remove(self.body.len() - 1); + } else { + self.digesting = false; + } + } + + pub fn set_direction(&mut self, direction: Direction) { + self.direction = direction; + } + + pub fn grow(&mut self) { + self.digesting = true; + } +} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 6b3788d..2f55b0c 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -27,23 +27,23 @@ pub enum Color { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] -struct ColorCode(u8); +pub struct ColorCode(u8); impl ColorCode { - fn new(foreground: Color, background: Color) -> ColorCode { + pub fn new(foreground: Color, background: Color) -> ColorCode { ColorCode((background as u8) << 4 | (foreground as u8)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] -struct ScreenChar { - ascii_character: u8, - color_code: ColorCode, +pub struct ScreenChar { + pub ascii_character: u8, + pub color_code: ColorCode, } -const BUFFER_HEIGHT: usize = 25; -const BUFFER_WIDTH: usize = 80; +pub const BUFFER_HEIGHT: usize = 25; +pub const BUFFER_WIDTH: usize = 80; #[repr(transparent)] struct Buffer { @@ -64,6 +64,10 @@ impl fmt::Write for Writer { } impl Writer { + pub fn print_buffer(&mut self, buffer: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT]) { + self.buffer.chars = buffer; + } + pub fn write_byte(&mut self, byte: u8) { match byte { b'\n' => self.new_line(), @@ -130,6 +134,14 @@ macro_rules! println { ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } +pub fn print_buffer(buffer: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT]) { + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + WRITER.lock().print_buffer(buffer); + }) +} + #[doc(hidden)] pub fn _print(args: fmt::Arguments) { use core::fmt::Write;