diff --git a/disk.img b/disk.img new file mode 100644 index 0000000..88fcce5 Binary files /dev/null and b/disk.img differ diff --git a/src/system/kernel/ahci.rs b/src/system/kernel/ahci.rs new file mode 100644 index 0000000..f26506a --- /dev/null +++ b/src/system/kernel/ahci.rs @@ -0,0 +1,298 @@ +use alloc::vec::Vec; +use volatile::Volatile; + +use crate::println; + +pub const ATA_CMD_IDENTIFY: u8 = 0xEC; + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FisType { + RegH2D = 0x27, // Register FIS - host to device + RegD2H = 0x34, // Register FIS - device to host + DmaAct = 0x39, // DMA activate FIS - device to host + DmaSetup = 0x41, // DMA setup FIS - bidirectional + Data = 0x46, // Data FIS - bidirectional + Bist = 0x58, // BIST activate FIS - bidirectional + PioSetup = 0x5F, // PIO setup FIS - device to host + DevBits = 0xA1, // Set device bits FIS - device to host +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct FisRegH2D { + // DWORD 0 + pub fis_type: u8, // FIS_TYPE_REG_H2D + pub pmport_c: u8, // Port multiplier (bits 0-3), Reserved (bits 4-6), Command/Control (bit 7) + pub command: u8, // Command register + pub featurel: u8, // Feature register, 7:0 + + // DWORD 1 + pub lba0: u8, // LBA low register, 7:0 + pub lba1: u8, // LBA mid register, 15:8 + pub lba2: u8, // LBA high register, 23:16 + pub device: u8, // Device register + + // DWORD 2 + pub lba3: u8, // LBA register, 31:24 + pub lba4: u8, // LBA register, 39:32 + pub lba5: u8, // LBA register, 47:40 + pub featureh: u8, // Feature register, 15:8 + + // DWORD 3 + pub countl: u8, // Count register, 7:0 + pub counth: u8, // Count register, 15:8 + pub icc: u8, // Isochronous command completion + pub control: u8, // Control register + + // DWORD 4 + pub rsv1: [u8; 4], // Reserved +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct FisRegD2H { + // DWORD 0 + pub fis_type: u8, // FIS_TYPE_REG_D2H + pub pmport_i: u8, // Port multiplier (bits 0-3), Reserved (bits 4-5), Interrupt (bit 6), Reserved (bit 7) + pub status: u8, // Status register + pub error: u8, // Error register + + // DWORD 1 + pub lba0: u8, // LBA low register, 7:0 + pub lba1: u8, // LBA mid register, 15:8 + pub lba2: u8, // LBA high register, 23:16 + pub device: u8, // Device register + + // DWORD 2 + pub lba3: u8, // LBA register, 31:24 + pub lba4: u8, // LBA register, 39:32 + pub lba5: u8, // LBA register, 47:40 + pub rsv2: u8, // Reserved + + // DWORD 3 + pub countl: u8, // Count register, 7:0 + pub counth: u8, // Count register, 15:8 + pub rsv3: [u8; 2], // Reserved + + // DWORD 4 + pub rsv4: [u8; 4], // Reserved +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct FisData { + // DWORD 0 + pub fis_type: u8, // FIS_TYPE_DATA + pub pmport: u8, // Port multiplier (bits 0-3) and reserved (bits 4-7) + pub rsv1: [u8; 2], // Reserved + + // DWORD 1 ~ N + pub data: [u32; 1], // Payload +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct FisPioSetup { + // DWORD 0 + pub fis_type: u8, // FIS_TYPE_PIO_SETUP + pub pmport_flags: u8, // Port multiplier and flags + pub status: u8, // Status register + pub error: u8, // Error register + + // DWORD 1 + pub lba0: u8, // LBA low register, 7:0 + pub lba1: u8, // LBA mid register, 15:8 + pub lba2: u8, // LBA high register, 23:16 + pub device: u8, // Device register + + // DWORD 2 + pub lba3: u8, // LBA register, 31:24 + pub lba4: u8, // LBA register, 39:32 + pub lba5: u8, // LBA register, 47:40 + pub rsv2: u8, // Reserved + + // DWORD 3 + pub countl: u8, // Count register, 7:0 + pub counth: u8, // Count register, 15:8 + pub rsv3: u8, // Reserved + pub e_status: u8, // New value of status register + + // DWORD 4 + pub tc: u16, // Transfer count + pub rsv4: [u8; 2], // Reserved +} + +#[repr(C, packed)] +#[derive(Debug, Clone, Copy)] +pub struct FisDmaSetup { + // DWORD 0 + pub fis_type: u8, // FIS_TYPE_DMA_SETUP + pub pmport_flags: u8, // Port multiplier and flags + pub rsved: [u8; 2], // Reserved + + // DWORD 1 & 2 + pub dma_buffer_id: u64, // DMA Buffer Identifier + + // DWORD 3 + pub rsvd: u32, // Reserved + + // DWORD 4 + pub dma_buf_offset: u32, // Byte offset into buffer + + // DWORD 5 + pub transfer_count: u32, // Number of bytes to transfer + + // DWORD 6 + pub resvd: u32, // Reserved +} + +// AHCI device detection constants +pub const SATA_SIG_ATAPI: u32 = 0xEB140101; +pub const SATA_SIG_ATA: u32 = 0x00000101; +pub const SATA_SIG_SEMB: u32 = 0xC33C0101; +pub const SATA_SIG_PM: u32 = 0x96690101; + +// Port status and control constants +pub const HBA_PORT_IPM_ACTIVE: u32 = 1; +pub const HBA_PORT_DET_PRESENT: u32 = 3; + +#[repr(C)] +pub struct HbaPort { + pub clb: Volatile, // 0x00, command list base address, 1K-byte aligned + pub clbu: Volatile, // 0x04, command list base address upper 32 bits + pub fb: Volatile, // 0x08, FIS base address, 256-byte aligned + pub fbu: Volatile, // 0x0C, FIS base address upper 32 bits + pub is: Volatile, // 0x10, interrupt status + pub ie: Volatile, // 0x14, interrupt enable + pub cmd: Volatile, // 0x18, command and status + pub rsv0: Volatile, // 0x1C, Reserved + pub tfd: Volatile, // 0x20, task file data + pub sig: Volatile, // 0x24, signature + pub ssts: Volatile, // 0x28, SATA status (SCR0:SStatus) + pub sctl: Volatile, // 0x2C, SATA control (SCR2:SControl) + pub serr: Volatile, // 0x30, SATA error (SCR1:SError) + pub sact: Volatile, // 0x34, SATA active (SCR3:SActive) + pub ci: Volatile, // 0x38, command issue + pub sntf: Volatile, // 0x3C, SATA notification (SCR4:SNotification) + pub fbs: Volatile, // 0x40, FIS-based switch control + pub rsv1: [Volatile; 11], // 0x44 ~ 0x6F, Reserved + pub vendor: [Volatile; 4], // 0x70 ~ 0x7F, vendor specific +} + +impl HbaPort { + pub fn is_device_present(&self) -> bool { + let ssts = self.ssts.read(); + let det = ssts & 0x0F; + let ipm = (ssts >> 8) & 0x0F; + det == HBA_PORT_DET_PRESENT && ipm == HBA_PORT_IPM_ACTIVE + } + + pub fn get_device_type(&self) -> Option { + if !self.is_device_present() { + return None; + } + + let sig = self.sig.read(); + match sig { + SATA_SIG_ATAPI => Some(DeviceType::SATAPI), + SATA_SIG_ATA => Some(DeviceType::SATA), + SATA_SIG_SEMB => Some(DeviceType::SEMB), + SATA_SIG_PM => Some(DeviceType::PM), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DeviceType { + SATA, // SATA drive + SATAPI, // SATAPI drive + SEMB, // Enclosure management bridge + PM, // Port multiplier +} + +#[repr(C)] +pub struct HbaMem { + // 0x00 - 0x2B, Generic Host Control + pub cap: Volatile, // 0x00, Host capability + pub ghc: Volatile, // 0x04, Global host control + pub is: Volatile, // 0x08, Interrupt status + pub pi: Volatile, // 0x0C, Port implemented + pub vs: Volatile, // 0x10, Version + pub ccc_ctl: Volatile, // 0x14, Command completion coalescing control + pub ccc_pts: Volatile, // 0x18, Command completion coalescing ports + pub em_loc: Volatile, // 0x1C, Enclosure management location + pub em_ctl: Volatile, // 0x20, Enclosure management control + pub cap2: Volatile, // 0x24, Host capabilities extended + pub bohc: Volatile, // 0x28, BIOS/OS handoff control and status + + // 0x2C - 0x9F, Reserved + pub rsv: [u8; 0xA0-0x2C], + + // 0xA0 - 0xFF, Vendor specific registers + pub vendor: [u8; 0x100-0xA0], + + // 0x100 - 0x10FF, Port control registers + pub ports: [HbaPort; 32], // 1 ~ 32 ports +} + +impl HbaMem { + pub fn probe_ports(&self) -> Vec<(usize, DeviceType)> { + let mut devices = Vec::new(); + let pi = self.pi.read(); + + // Check each bit in the ports implemented register + for i in 0..32 { + if pi & (1 << i) != 0 { + if let Some(device_type) = self.ports[i].get_device_type() { + devices.push((i, device_type)); + } + } + } + devices + } +} + +pub fn init_ahci(abar: usize) -> Option<&'static mut HbaMem> { + let hba = unsafe { &mut *(abar as *mut HbaMem) }; + + // Enable AHCI by setting GHC.AE + let ghc = hba.ghc.read(); + hba.ghc.write(ghc | (1 << 31)); + + let devices = hba.probe_ports(); + for (port_num, device_type) in devices { + // Found a device + match device_type { + DeviceType::SATA => println!("SATA drive found at port {}", port_num), + DeviceType::SATAPI => println!("SATAPI drive found at port {}", port_num), + DeviceType::SEMB => println!("SEMB drive found at port {}", port_num), + DeviceType::PM => println!("PM drive found at port {}", port_num), + } + } + + Some(hba) +} + +pub fn read_data() { + let mut fis = FisRegH2D { + fis_type: FisType::RegH2D as u8, + pmport_c: 1 << 7, // Set the command bit (c=1) + command: ATA_CMD_IDENTIFY, + featurel: 0, + lba0: 0, + lba1: 0, + lba2: 0, + device: 0, // Master device + lba3: 0, + lba4: 0, + lba5: 0, + featureh: 0, + countl: 0, + counth: 0, + icc: 0, + control: 0, + rsv1: [0; 4], + }; +} diff --git a/src/system/kernel/fs.rs b/src/system/kernel/fs.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/system/kernel/render.rs b/src/system/kernel/render.rs index 3186959..ef16559 100644 --- a/src/system/kernel/render.rs +++ b/src/system/kernel/render.rs @@ -332,6 +332,7 @@ pub fn special_char(ch: char) -> Option { '░' => 176, '▒' => 177, '▓' => 178, + '█' => 219, '«' => 174, _ => { return None; diff --git a/src/system/kernel/tasks/keyboard.rs b/src/system/kernel/tasks/keyboard.rs index 6a12a22..f45908f 100644 --- a/src/system/kernel/tasks/keyboard.rs +++ b/src/system/kernel/tasks/keyboard.rs @@ -2,10 +2,9 @@ use lazy_static::lazy_static; use spin::Mutex; use x86_64::instructions::interrupts; - use conquer_once::spin::OnceCell; use crossbeam_queue::ArrayQueue; -use crate::println; +use crate::{println, serial_print, serial_println, system::kernel::serial}; use core::{pin::Pin, task::{Poll, Context}}; use futures_util::stream::Stream; @@ -19,248 +18,242 @@ use alloc::{string::String}; static WAKER: AtomicWaker = AtomicWaker::new(); static SCANCODE_QUEUE: OnceCell> = OnceCell::uninit(); - lazy_static! { - pub static ref KEYBOARD: Mutex = Mutex::new(KeyboardHandler::new()); + pub static ref KEYBOARD: Mutex = Mutex::new(KeyboardHandler::new()); } pub struct KeyboardHandler { - scancodes: ScanCodeStream, - keyboard: Keyboard, + scancodes: ScanCodeStream, + keyboard: Keyboard, } enum CharOrKeystroke { - Char(char), - Keystroke(KeyCode), + Char(char), + Keystroke(KeyCode), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeyStroke { - Char(char), - Ctrl, - RCtrl, - Alt, - RAlt, - Shift, - RShift, - Meta, - RMeta, - Backspace, - Left, - Right, - Up, - Down, - None, - Enter, - Escape, - Del + Char(char), + Ctrl, + RCtrl, + Alt, + RAlt, + Shift, + RShift, + Meta, + RMeta, + Backspace, + Left, + Right, + Up, + Down, + None, + Enter, + Escape, + Del } impl KeyStroke { - pub fn from_keycode(key: KeyCode) -> KeyStroke { - match key { - KeyCode::ControlLeft => KeyStroke::Ctrl, - KeyCode::ControlRight => KeyStroke::RCtrl, - KeyCode::AltLeft => KeyStroke::Alt, - KeyCode::AltRight => KeyStroke::RAlt, - KeyCode::ShiftLeft => KeyStroke::Shift, - KeyCode::ShiftRight => KeyStroke::RShift, - KeyCode::WindowsLeft => KeyStroke::Meta, - KeyCode::WindowsRight => KeyStroke::RMeta, - KeyCode::Backspace => KeyStroke::Backspace, - KeyCode::ArrowLeft => KeyStroke::Left, - KeyCode::ArrowRight => KeyStroke::Right, - KeyCode::ArrowUp => KeyStroke::Up, - KeyCode::ArrowDown => KeyStroke::Down, - KeyCode::Enter => KeyStroke::Enter, - KeyCode::Escape => KeyStroke::Escape, - KeyCode::Delete => KeyStroke::Del, - _ => KeyStroke::None, - } - } + pub fn from_keycode(key: KeyCode) -> KeyStroke { + match key { + KeyCode::ControlLeft => KeyStroke::Ctrl, + KeyCode::ControlRight => KeyStroke::RCtrl, + KeyCode::AltLeft => KeyStroke::Alt, + KeyCode::AltRight => KeyStroke::RAlt, + KeyCode::ShiftLeft => KeyStroke::Shift, + KeyCode::ShiftRight => KeyStroke::RShift, + KeyCode::WindowsLeft => KeyStroke::Meta, + KeyCode::WindowsRight => KeyStroke::RMeta, + KeyCode::Backspace => KeyStroke::Backspace, + KeyCode::ArrowLeft => KeyStroke::Left, + KeyCode::ArrowRight => KeyStroke::Right, + KeyCode::ArrowUp => KeyStroke::Up, + KeyCode::ArrowDown => KeyStroke::Down, + KeyCode::Enter => KeyStroke::Enter, + KeyCode::Escape => KeyStroke::Escape, + KeyCode::Delete => KeyStroke::Del, + _ => KeyStroke::None, + } + } } impl core::fmt::Display for KeyStroke { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - KeyStroke::Char(c) => write!(f, "{}", c), - KeyStroke::Ctrl => write!(f, "CTRL"), - KeyStroke::RCtrl => write!(f, "RCtrl"), - KeyStroke::Alt => write!(f, "ALT"), - KeyStroke::RAlt => write!(f, "RAlt"), - KeyStroke::Shift => write!(f, "SHIFT"), - KeyStroke::RShift => write!(f, "RShift"), - KeyStroke::Meta => write!(f, "META"), - KeyStroke::RMeta => write!(f, "RMeta"), - KeyStroke::Backspace => write!(f, "BACKSPACE"), - KeyStroke::Left => write!(f, "LEFT"), - KeyStroke::Right => write!(f, "RIGHT"), - KeyStroke::Up => write!(f, "UP"), - KeyStroke::Down => write!(f, "DOWN"), - KeyStroke::Enter => write!(f, "ENTER"), - KeyStroke::Escape => write!(f, "ESCAPE"), - KeyStroke::None => write!(f, "NONE"), - KeyStroke::Del => write!(f, "DEL"), - } - } + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + KeyStroke::Char(c) => write!(f, "{}", c), + KeyStroke::Ctrl => write!(f, "CTRL"), + KeyStroke::RCtrl => write!(f, "RCtrl"), + KeyStroke::Alt => write!(f, "ALT"), + KeyStroke::RAlt => write!(f, "RAlt"), + KeyStroke::Shift => write!(f, "SHIFT"), + KeyStroke::RShift => write!(f, "RShift"), + KeyStroke::Meta => write!(f, "META"), + KeyStroke::RMeta => write!(f, "RMeta"), + KeyStroke::Backspace => write!(f, "BACKSPACE"), + KeyStroke::Left => write!(f, "LEFT"), + KeyStroke::Right => write!(f, "RIGHT"), + KeyStroke::Up => write!(f, "UP"), + KeyStroke::Down => write!(f, "DOWN"), + KeyStroke::Enter => write!(f, "ENTER"), + KeyStroke::Escape => write!(f, "ESCAPE"), + KeyStroke::None => write!(f, "NONE"), + KeyStroke::Del => write!(f, "DEL"), + } + } } impl KeyboardHandler { - pub fn new() -> KeyboardHandler { - KeyboardHandler { - scancodes: ScanCodeStream::new(), - keyboard: Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore), - } - } + pub fn new() -> KeyboardHandler { + KeyboardHandler { + scancodes: ScanCodeStream::new(), + keyboard: Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore), + } + } - pub async fn get_keystroke_inner(&mut self) -> Option { - loop { - if let Some(scancode) = self.scancodes.next().await { - if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { - if let Some(key) = self.keyboard.process_keyevent(key_event) { - match key { - DecodedKey::Unicode(character) => return Some(KeyStroke::Char(character)), - DecodedKey::RawKey(key) => { - print!("{:?}", key); - match KeyStroke::from_keycode(key) { - KeyStroke::None => (), - k => return Some(k) - } - }, + pub async fn get_keystroke_inner(&mut self) -> Option { + loop { + if let Some(scancode) = self.scancodes.next().await { + return self.process_keystroke(scancode); + } + } + } + + pub fn process_keystroke(&mut self, scancode: u8) -> Option { + if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { + if let Some(key) = self.keyboard.process_keyevent(key_event) { + match key { + DecodedKey::Unicode(character) => { + if character == b'\x08' as char { // checks if the character is a backspace + interrupts::without_interrupts(|| { + RENDERER.lock().backspace(); // runs the backspace function of the vga buffer to remove the last character + }); + return Some(KeyStroke::Char(character)); + } else { + return Some(KeyStroke::Char(character)); } - } + }, + DecodedKey::RawKey(key) => { + match KeyStroke::from_keycode(key) { + KeyStroke::None => (), + key => return Some(key) + } + }, } } } - } - - pub async fn get_keystroke(&mut self) -> KeyStroke { - loop { - match self.get_keystroke_inner().await { - Some(c) => match c { - KeyStroke::None => (), - c => return c - }, - None => () - } - } - } - - pub fn try_keystroke(&mut self) -> Option { - if let Some(scancode) = self.scancodes.try_next() { - if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { - if let Some(key) = self.keyboard.process_keyevent(key_event) { - match key { - DecodedKey::Unicode(character) => { - if character == b'\x08' as char { // checks if the character is a backspace - interrupts::without_interrupts(|| { - RENDERER.lock().backspace(); // runs the backspace function of the vga buffer to remove the last character - }); - return None; - } else { - return Some(KeyStroke::Char(character)); - } - }, - DecodedKey::RawKey(key) => { - match KeyStroke::from_keycode(key) { - KeyStroke::None => (), - key => return Some(key) - } - }, - } - } - } - }; None } - - pub async fn get_string(&mut self) -> String { - let mut val = String::new(); + pub async fn get_keystroke(&mut self) -> KeyStroke { loop { - let character = match self.get_keystroke_inner().await { - Some(c) => { c }, - None => { val.pop(); continue; }, - }; - - if let KeyStroke::Char(c) = character { - if c == '\x08' { - val.pop(); - interrupts::without_interrupts(|| { - RENDERER.lock().backspace(); // runs the backspace function of the vga buffer to remove the last character - }); - continue; - } - - print!("{}", c); - let (c, execute): (char, bool) = match c { - '\n' => (c, true), - _ => (c, false), - }; - val.push(c); - if execute { - return val; + if let Some(c) = self.scancodes.next().await { + if let Some(key) = self.process_keystroke(c) { + return key; } } - } + } - } + pub fn try_keystroke(&mut self) -> Option { + if let Some(scancode) = self.scancodes.try_next() { + self.process_keystroke(scancode) + } else { None } + } + + pub fn last_keystroke(&mut self) -> Option { + let mut last_scancode = None; + + // Keep getting scancodes until the queue is empty + while let Some(scancode) = self.scancodes.try_next() { + last_scancode = Some(scancode); + } + + // Process the last scancode + if let Some(scancode) = last_scancode { + self.process_keystroke(scancode) + } else { None } + } + + pub async fn get_string(&mut self) -> String { + let mut val = String::new(); + loop { + let character = match self.get_keystroke_inner().await { + Some(c) => { c }, + None => { continue; }, + }; + + if let KeyStroke::Char(c) = character { + if c == '\x08' { + val.pop(); + continue; + } + + print!("{}", c); + val.push(c); + + if c == '\n' { + return val; + } + } + + } + + } } pub(crate) fn add_scancode(scancode: u8) { - if let Ok(queue) = SCANCODE_QUEUE.try_get() { - if let Err(_) = queue.push(scancode) { - println!("WARNING: queue is full - ignoring input"); - } else { - WAKER.wake(); - } - } else { - println!("WARNING: scancode queue has not been initialised"); - } + if let Ok(queue) = SCANCODE_QUEUE.try_get() { + if let Err(_) = queue.push(scancode) { + println!("WARNING: queue is full - ignoring input"); + } else { + WAKER.wake(); + } + } else { + println!("WARNING: scancode queue has not been initialised"); + } } - pub struct ScanCodeStream { - _private: (), + _private: (), } impl ScanCodeStream { - pub fn new() -> Self { - SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100)) - .expect("ScanCodeStream::new has already been called once"); - ScanCodeStream { _private: () } - } + pub fn new() -> Self { + SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100)) + .expect("ScanCodeStream::new has already been called once"); + ScanCodeStream { _private: () } + } - pub fn try_next(&mut self) -> Option { - let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); - if let Ok(c) = queue.pop() { - Some(c) - } else { - None - } - } + pub fn try_next(&mut self) -> Option { + let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); + if let Ok(c) = queue.pop() { + Some(c) + } else { + None + } + } } impl Stream for ScanCodeStream { - type Item = u8; + type Item = u8; - fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { - let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); + fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { + let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); - if let Ok(scancode) = queue.pop() { - return Poll::Ready(Some(scancode)); - } + if let Ok(scancode) = queue.pop() { + return Poll::Ready(Some(scancode)); + } - WAKER.register(&ctx.waker()); - - match queue.pop() { - Ok(scancode) => { - WAKER.take(); - Poll::Ready(Some(scancode)) - }, - Err(crossbeam_queue::PopError) => Poll::Pending, - } - } + WAKER.register(&ctx.waker()); + + match queue.pop() { + Ok(scancode) => { + WAKER.take(); + Poll::Ready(Some(scancode)) + }, + Err(crossbeam_queue::PopError) => Poll::Pending, + } + } } diff --git a/src/system/std/fs.rs b/src/system/std/fs.rs new file mode 100644 index 0000000..55849a7 --- /dev/null +++ b/src/system/std/fs.rs @@ -0,0 +1,5 @@ +use crate::system::kernel::ahci::init_ahci; + +pub fn check_ahci() { + +} \ No newline at end of file diff --git a/src/system/std/io.rs b/src/system/std/io.rs index c30584e..0b430a8 100644 --- a/src/system/std/io.rs +++ b/src/system/std/io.rs @@ -33,6 +33,11 @@ impl Stdin { let chr = KEYBOARD.lock().try_keystroke(); chr } + + pub fn last_keystroke() -> Option { + let chr = KEYBOARD.lock().last_keystroke(); + chr + } } pub struct Serial {} diff --git a/src/user/bin/games/connect4.rs b/src/user/bin/games/connect4.rs new file mode 100644 index 0000000..9b6a4e9 --- /dev/null +++ b/src/user/bin/games/connect4.rs @@ -0,0 +1,407 @@ +use alloc::{string::String, vec::Vec, boxed::Box}; +use async_trait::async_trait; + +use crate::{std::{application::{Application, Error}, io::{Color, Display, KeyStroke, Stdin}, random::Random, render::{ColorCode, ColouredChar, Dimensions, Frame, Position, RenderError}, time}, user::lib::libgui::cg_core::CgComponent}; + +pub struct Game { + pub board: [[Cell; 7]; 6], + pub turn: u8, + pub vs_ai: bool, + pub game_over: bool, + pub winner: Option, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Cell { + Empty, + Player1, + Player2, + Victory, +} + +#[async_trait] +impl Application for Game { + fn new() -> Self { + Game { + board: [[Cell::Empty; 7]; 6], + turn: 1, + vs_ai: false, + game_over: false, + winner: None, + } + } + + async fn run(&mut self, _: Vec) -> Result<(), Error> { + let _display = Display::borrow(); + + self.get_next_mode().await; + + // Main game loop + loop { + if self.game_over { + self.render_end_screen().await.unwrap(); + let c = Stdin::keystroke().await; + match c { + KeyStroke::Char('`') => break, + _ => { + self.reset(); + if !self.get_next_mode().await { + break; + } + self.game_over = false; + }, + } + continue; + } + + self.render().unwrap().write_to_screen().unwrap(); + + if self.vs_ai && self.turn == 2 { + // AI's turn + self.make_ai_move(); + if !self.game_over { + self.turn = 1; + } + continue; + } + + if let Some(key) = Stdin::last_keystroke() { + match key { + KeyStroke::Char('`') => break, + KeyStroke::Char(c) => { + if let Some(col) = c.to_digit(10) { + if col > 0 && col <= 7 { + self.add_cell(col - 1, self.turn); + self.turn = if self.turn == 1 { 2 } else { 1 }; + } + } + } + _ => {} + } + } + + self.apply_gravity(); + self.check_victory(); + time::wait(0.1); + } + + Ok(()) + } +} + +impl Game { + fn apply_gravity(&mut self) { + // Process each column + for col in 0..7 { + // Start from second-to-last row and move up + // We don't need to check the bottom row since nothing can fall below it + for row in (0..5).rev() { + // If current cell is empty or the cell below is not empty, skip + if let Cell::Empty = self.board[row][col] { + continue; + } + + // Check if cell can fall one space + if let Cell::Empty = self.board[row + 1][col] { + // Move cell down one space + self.board[row + 1][col] = self.board[row][col]; + self.board[row][col] = Cell::Empty; + } + } + } + } + + async fn get_next_mode(&mut self) -> bool { + // Game mode selection + + let mut frame = Frame::new(Position::new(0, 0), Dimensions::new(80, 25)).unwrap(); + let title = "Connect 4"; + let mode1 = "1. Player vs Player"; + let mode2 = "2. Player vs AI"; + + // Center coordinates + let center_y = 10; + let title_x = 40 - (title.len() / 2) as u16; + let mode1_x = 40 - (mode1.len() / 2) as u16; + let mode2_x = 40 - (mode2.len() / 2) as u16; + + // Draw title + for (i, c) in title.chars().enumerate() { + frame[center_y][title_x as usize + i] = ColouredChar { + character: c, + colour: ColorCode::new(Color::Yellow, Color::Black), + }; + } + + // Draw mode options + for (i, c) in mode1.chars().enumerate() { + frame[center_y + 2][mode1_x as usize + i] = ColouredChar { + character: c, + colour: ColorCode::new(Color::White, Color::Black), + }; + } + + for (i, c) in mode2.chars().enumerate() { + frame[center_y + 3][mode2_x as usize + i] = ColouredChar { + character: c, + colour: ColorCode::new(Color::White, Color::Black), + }; + } + + frame.write_to_screen().unwrap(); + + loop { + let key = Stdin::keystroke().await; + match key { + KeyStroke::Char('1') => { + self.vs_ai = false; + break true; + } + KeyStroke::Char('2') => { + self.vs_ai = true; + break true; + } + KeyStroke::Char('`') => break false, + _ => {} + } + } + } + + fn add_cell(&mut self, col: u32, player: u8) { + if let Cell::Empty = self.board[0][col as usize] { + self.board[0][col as usize] = match player { + 1 => Cell::Player1, + 2 => Cell::Player2, + _ => panic!("Invalid player number"), + }; + } + } + + fn has_floating_tiles(&self) -> bool { + for col in 0..7 { + // Start from second-to-last row and move up + for row in (0..5).rev() { + // If current cell is not empty and cell below is empty, it's floating + if self.board[row][col] != Cell::Empty && self.board[row + 1][col] == Cell::Empty { + return true; + } + } + } + false + } + + fn check_victory(&mut self) { + // Only check for victory if there are no floating tiles + if self.has_floating_tiles() { + return; + } + + // Check horizontal + for row in 0..6 { + for col in 0..4 { + let cell = self.board[row][col]; + if let Cell::Empty = cell { continue; } + + if (0..4).all(|i| self.board[row][col + i] == cell) { + // Mark winning cells + for i in 0..4 { + self.board[row][col + i] = Cell::Victory; + } + self.game_over = true; + self.winner = Some(if cell == Cell::Player1 { 1 } else { 2 }); + } + } + } + + // Check vertical + for row in 0..3 { + for col in 0..7 { + let cell = self.board[row][col]; + if let Cell::Empty = cell { continue; } + + if (0..4).all(|i| self.board[row + i][col] == cell) { + // Mark winning cells + for i in 0..4 { + self.board[row + i][col] = Cell::Victory; + } + self.game_over = true; + self.winner = Some(if cell == Cell::Player1 { 1 } else { 2 }); + } + } + } + + // Check diagonal (down-right) + for row in 0..3 { + for col in 0..4 { + let cell = self.board[row][col]; + if let Cell::Empty = cell { continue; } + + if (0..4).all(|i| self.board[row + i][col + i] == cell) { + // Mark winning cells + for i in 0..4 { + self.board[row + i][col + i] = Cell::Victory; + } + self.game_over = true; + self.winner = Some(if cell == Cell::Player1 { 1 } else { 2 }); + } + } + } + + // Check diagonal (down-left) + for row in 0..3 { + for col in 3..7 { + let cell = self.board[row][col]; + if let Cell::Empty = cell { continue; } + + if (0..4).all(|i| self.board[row + i][col - i] == cell) { + // Mark winning cells + for i in 0..4 { + self.board[row + i][col - i] = Cell::Victory; + } + self.game_over = true; + self.winner = Some(if cell == Cell::Player1 { 1 } else { 2 }); + } + } + } + + // Check for draw + if !self.game_over { + if (0..6).all(|row| (0..7).all(|col| self.board[row][col] != Cell::Empty)) { + self.game_over = true; + self.winner = None; + } + } + } + + fn make_ai_move(&mut self) { + loop { + let col = (Random::int(0, 6)) as u32; + // Check if column is not full + if self.board[0][col as usize] == Cell::Empty { + self.add_cell(col, 2); + break; + } + } + } + + fn reset(&mut self) { + self.board = [[Cell::Empty; 7]; 6]; + self.turn = 1; + self.game_over = false; + self.winner = None; + } + + async fn render_end_screen(&mut self) -> Result<(), RenderError> { + let mut frame = Frame::new(Position::new(0, 0), Dimensions::new(80, 25))?; + + // Game Over message + let game_over = "Game Over!"; + let winner_text = match self.winner { + Some(1) => "Player 1 Wins!", + Some(2) => if self.vs_ai { "AI Wins!" } else { "Player 2 Wins!" }, + None => "Draw!", + _ => panic!("this shouldn't be possible"), + }; + let restart_text = "Press any key to play again"; + + let center_y = 12; + let game_over_x = 40 - (game_over.len() / 2) as u16; + let winner_x = 40 - (winner_text.len() / 2) as u16; + let restart_x = 40 - (restart_text.len() / 2) as u16; + + // Draw game over + for (i, c) in game_over.chars().enumerate() { + frame[center_y][game_over_x as usize + i] = ColouredChar { + character: c, + colour: ColorCode::new(Color::White, Color::Black), + }; + } + + // Draw winner + for (i, c) in winner_text.chars().enumerate() { + frame[center_y + 1][winner_x as usize + i] = ColouredChar { + character: c, + colour: ColorCode::new(Color::Yellow, Color::Black), + }; + } + + // Draw restart instruction + for (i, c) in restart_text.chars().enumerate() { + frame[center_y + 2][restart_x as usize + i] = ColouredChar { + character: c, + colour: ColorCode::new(Color::White, Color::Black), + }; + } + + frame.write_to_screen()?; + Ok(()) + } + + fn render(&self) -> Result { + let mut frame = Frame::new(Position::new(0, 0), Dimensions::new(80, 25))?; + + // Calculate center position to align board + let cell_width = 4; // 3 for cell + 1 for separator + let cell_height = 3; // 2 for cell + 1 for gap + let board_width = 7 * cell_width - 1; // -1 because last separator not needed + let board_height = 6 * cell_height - 1; // -1 because last gap not needed + let start_x = (80 - board_width) / 2; + let start_y = (25 - board_height) / 2; + + // Draw column numbers + for col in 0..7 { + let x = start_x + (col * cell_width); + // Center the 3-char number display "[X]" in the 3-space cell width + frame[start_y - 3][x] = ColouredChar::coloured('[', ColorCode::new(Color::White, Color::Black)); + frame[start_y - 3][x + 1] = ColouredChar::coloured((1 + col as u8 + b'0') as char, ColorCode::new(Color::White, Color::Black)); + frame[start_y - 3][x + 2] = ColouredChar::coloured(']', ColorCode::new(Color::White, Color::Black)); + } + + // Draw each cell + for row in 0..6 { + let y = start_y + (row * cell_height); + + for col in 0..7 { + let x = start_x + (col * cell_width); + + // Draw vertical separator after each cell (except last column) + if col < 6 { + let separator_x = x + 3; + for dy in 0..3 { + frame[y -1 + dy][separator_x] = ColouredChar::coloured('│', ColorCode::new(Color::White, Color::Black)); + } + } + + // Set color based on cell state + let color = match self.board[row][col] { + Cell::Empty => continue, + Cell::Player1 => ColorCode::new(Color::Red, Color::Red), + Cell::Player2 => ColorCode::new(Color::Yellow, Color::Yellow), + Cell::Victory => ColorCode::new(Color::Green, Color::Green), + }; + + // Draw a 3x2 block for each cell + for dy in 0..2 { + for dx in 0..3 { + frame[y + dy][x + dx] = ColouredChar::coloured('█', color); + } + } + + } + + // Draw horizontal gap after each row (except last row) + if row < 5 { + let gap_y = y + 2; + for x in start_x..start_x + board_width { + frame[gap_y][x] = ColouredChar::coloured(' ', ColorCode::new(Color::White, Color::Black)); + } + } + } + + Ok(frame) + } + + fn as_any(&self) -> &dyn core::any::Any { + todo!() + } +} \ No newline at end of file diff --git a/src/user/bin/games/mod.rs b/src/user/bin/games/mod.rs index bbb4b23..cdced0b 100644 --- a/src/user/bin/games/mod.rs +++ b/src/user/bin/games/mod.rs @@ -3,5 +3,6 @@ pub mod gameoflife; pub mod crystalrpg; pub mod pong; pub mod snake; -pub mod paper; -pub mod tetris; \ No newline at end of file +pub mod paper_rs; +pub mod tetris; +pub mod connect4; \ No newline at end of file diff --git a/src/user/bin/games/paper.rs b/src/user/bin/games/paper_rs/game.rs similarity index 71% rename from src/user/bin/games/paper.rs rename to src/user/bin/games/paper_rs/game.rs index 9752af1..98290d6 100644 --- a/src/user/bin/games/paper.rs +++ b/src/user/bin/games/paper_rs/game.rs @@ -3,26 +3,27 @@ use core::any::Any; use alloc::{boxed::Box, format, string::String, vec::Vec}; use async_trait::async_trait; -use crate::{std::{self, application::{Application, Error}, io::{Color, ColorCode, Display, KeyStroke, Stdin}, render::{ColouredChar, Dimensions, Frame, Position, RenderError}, time}, user::lib::libgui::cg_core::CgComponent}; +use crate::{ + std::{ + self, + application::{Application, Error}, + io::{Color, ColorCode, Display, KeyStroke, Stdin}, + render::{ColouredChar, Dimensions, Frame, Position, RenderError}, + time + }, + user::lib::libgui::cg_core::CgComponent +}; +use super::player::Player; #[derive(Copy, Clone)] -enum Cell { +pub enum Cell { Empty, Solid(u8, bool), Tail(u8, bool), Head(u8, bool), } -#[derive(Copy, Clone)] -struct Player { - id: u8, - alive: bool, - position: (i32, i32), - ai_ticks: u32, // Ticks until next direction change - ai_direction: (i32, i32), // Current direction for AI -} - pub struct GameBoard { board: [[Cell; 80]; 25], players: [Player; 6], @@ -36,12 +37,12 @@ impl Application for GameBoard { GameBoard { board: [[Cell::Empty; 80]; 25], players: [ - Player { id: 0, alive: true, position: (10, 10), ai_ticks: 0, ai_direction: (1, 0) }, - Player { id: 1, alive: true, position: (70, 10), ai_ticks: 5, ai_direction: (-1, 0) }, - Player { id: 2, alive: true, position: (10, 15), ai_ticks: 10, ai_direction: (1, 0) }, - Player { id: 3, alive: true, position: (70, 15), ai_ticks: 15, ai_direction: (-1, 0) }, - Player { id: 4, alive: true, position: (35, 5), ai_ticks: 20, ai_direction: (0, 1) }, - Player { id: 5, alive: true, position: (35, 20), ai_ticks: 25, ai_direction: (0, -1) }, + Player::new(0, (10, 10), false), + Player::new(1, (70, 10), true), + Player::new(2, (10, 15), true), + Player::new(3, (70, 15), true), + Player::new(4, (35, 5), true), + Player::new(5, (35, 20), true), ], max_territory: 0, current_territory: 0, @@ -52,15 +53,17 @@ impl Application for GameBoard { let _display = Display::borrow(); 'outer: loop { - - self.players.clone().into_iter().for_each(|p| { - self.move_player(p.id, 0, 0); - }); + // Set initial positions as solid territory + for player in &self.players { + let (x, y) = player.position; + self.board[y as usize][x as usize] = Cell::Head(player.id, true); + } let mut player_direction: (i32, i32) = (0, 0); // player controls player 1. loop { + self.render().unwrap().write_to_screen().unwrap(); time::wait(0.1); // first get player input @@ -86,7 +89,6 @@ impl Application for GameBoard { self.run_fill_algorithm(); - self.render().unwrap().write_to_screen().unwrap(); if !self.players[0].alive { self.render_end_screen().await.unwrap(); @@ -118,9 +120,9 @@ impl CgComponent for GameBoard { match self.board[i][j] { Cell::Empty => { character = ' '; pid = None; } - Cell::Solid(p, _) => { character = '░'; pid = Some(p); } + Cell::Solid(p, _) => { character = '█'; pid = Some(p); } Cell::Tail(p, _) => { character = '▒'; pid = Some(p); } - Cell::Head(p, _) => { character = '▓'; pid = Some(p); } + Cell::Head(p, _) => { character = '▓'; pid = Some(p); } } let colour = if let Some(p) = pid { @@ -157,7 +159,7 @@ impl GameBoard { const LEFT: (i32, i32) = (-1, 0); const RIGHT: (i32, i32) = (1, 0); const DIRS: [(i32, i32); 8] = [ - Self::UP, Self::DOWN, Self::LEFT, Self::RIGHT, // only cardinal directions + Self::UP, Self::DOWN, Self::LEFT, Self::RIGHT, (1, 1), (1, -1), (-1, 1), (-1, -1), ]; @@ -165,13 +167,20 @@ impl GameBoard { fn reset(&mut self) { self.board = [[Cell::Empty; 80]; 25]; self.players = [ - Player { id: 0, alive: true, position: (10, 10), ai_ticks: 0, ai_direction: (1, 0) }, - Player { id: 1, alive: true, position: (70, 10), ai_ticks: 5, ai_direction: (-1, 0) }, - Player { id: 2, alive: true, position: (10, 15), ai_ticks: 10, ai_direction: (1, 0) }, - Player { id: 3, alive: true, position: (70, 15), ai_ticks: 15, ai_direction: (-1, 0) }, - Player { id: 4, alive: true, position: (35, 5), ai_ticks: 20, ai_direction: (0, 1) }, - Player { id: 5, alive: true, position: (35, 20), ai_ticks: 25, ai_direction: (0, -1) }, + Player::new(0, (10, 10), false), + Player::new(1, (70, 10), true), + Player::new(2, (10, 15), true), + Player::new(3, (70, 15), true), + Player::new(4, (35, 5), true), + Player::new(5, (35, 20), true), ]; + + // Set initial positions as solid territory + for player in &self.players { + let (x, y) = player.position; + self.board[y as usize][x as usize] = Cell::Head(player.id, true); + } + self.max_territory = 0; self.current_territory = 0; } @@ -185,15 +194,9 @@ impl GameBoard { // Check if position is empty or not in someone's territory if let Cell::Empty = self.board[y as usize][x as usize] { let dir_idx = std::random::Random::int(0, 3) as usize; - self.players[player_id as usize] = Player { - id: player_id, - alive: true, - position: (x, y), - ai_ticks: std::random::Random::int(5, 10) as u32, - ai_direction: Self::DIRS[dir_idx], - }; + self.players[player_id as usize] = Player::new(player_id, (x, y), true); // Place the head at spawn position - self.board[y as usize][x as usize] = Cell::Head(player_id, false); + self.board[y as usize][x as usize] = Cell::Head(player_id, true); return; } } @@ -259,58 +262,12 @@ impl GameBoard { } fn move_player(&mut self, playerid: u8, dx: i32, dy: i32) { - let (ox, oy) = self.players[playerid as usize].position; - let nx = ox + dx; - let ny = oy + dy; - - // Bounds checking - if nx < 0 || nx >= 80 || ny < 0 || ny >= 25 { - if playerid == 0 { - self.players[playerid as usize].alive = false; - } - return; - } - - // Check for collisions with tails - let mut player_to_eliminate = None; - - // check if player hit an enemy's tail - match self.board[ny as usize][nx as usize] { - Cell::Tail(id, _) => { - if id != playerid { - // Hit other player's tail - player_to_eliminate = Some(id); - } - } - _ => {} - } - - // Convert old head to tail - if let Cell::Head(id, owned) = self.board[oy as usize][ox as usize] { - if owned { - self.board[oy as usize][ox as usize] = Cell::Solid(id, true); - } else { - self.board[oy as usize][ox as usize] = Cell::Tail(id, false); - } - } - - // Place new head - if let Cell::Solid(p, _) = self.board[ny as usize][nx as usize] { - self.board[ny as usize][nx as usize] = Cell::Head(playerid, p == playerid); - } else { - self.board[ny as usize][nx as usize] = Cell::Head(playerid, false); - } - - // Update the player's position - self.players[playerid as usize].position = (nx, ny); - - // Handle elimination if needed - if let Some(pid) = player_to_eliminate { - self.players[pid as usize].alive = false; + if let Some(eliminated_id) = self.players[playerid as usize].move_player(dx, dy, &mut self.board) { + self.players[eliminated_id as usize].alive = false; // Clear territory and respawn if AI - if pid != 0 { - self.clear_player_territory(pid); - self.respawn_ai_player(pid); + if eliminated_id != 0 { + self.clear_player_territory(eliminated_id); + self.respawn_ai_player(eliminated_id); } else { // Update max territory before game over self.current_territory = self.count_territory(); @@ -322,23 +279,10 @@ impl GameBoard { fn update_ai_players(&mut self) { // Skip player 0 (human player) for i in 1..6 { - if !self.players[i].alive { - continue; + let dir = self.players[i].update_ai(&self.board); + if dir != (0, 0) { + self.move_player(i as u8, dir.0, dir.1); } - - // Decrease tick counter - if self.players[i].ai_ticks > 0 { - self.players[i].ai_ticks -= 1; - } else { - // Time to change direction - let random_dir = std::random::Random::int(0, 3); - self.players[i].ai_direction = Self::DIRS[random_dir as usize]; // - self.players[i].ai_ticks = 5 + (random_dir % 5) as u32; // Random ticks between 5-10 - } - - // Move in current direction - let dir = self.players[i].ai_direction; - self.move_player(i as u8, dir.0, dir.1); } } @@ -351,6 +295,30 @@ impl GameBoard { for player in self.players.iter().filter(|p| p.alive) { let pid = player.id; + // Check if player's head is touching their territory + let (px, py) = player.position; + let mut head_touching_territory = false; + + // Check all adjacent cells to head + for (dy, dx) in Self::DIRS.iter().take(4) { // Only check cardinal directions + let ny = py + dy; + let nx = px + dx; + + if ny >= 0 && ny < 25 && nx >= 0 && nx < 80 { + if let Cell::Solid(p, _) = self.board[ny as usize][nx as usize] { + if p == pid { + head_touching_territory = true; + break; + } + } + } + } + + // Skip if head is not touching territory + if !head_touching_territory { + continue; + } + // Reset grid for row in fill_grid.iter_mut() { row.fill(false); @@ -397,7 +365,7 @@ impl GameBoard { // Mark current cell as visited in the fill grid fill_grid[y][x] = true; - // Check all four directions + // Check all 8 directions for (dy, dx) in Self::DIRS { let ny = (y as i32 + dy) as usize; let nx = (x as i32 + dx) as usize; diff --git a/src/user/bin/games/paper_rs/mod.rs b/src/user/bin/games/paper_rs/mod.rs new file mode 100644 index 0000000..d2b349f --- /dev/null +++ b/src/user/bin/games/paper_rs/mod.rs @@ -0,0 +1,4 @@ +mod player; + +mod game; +pub use game::GameBoard; diff --git a/src/user/bin/games/paper_rs/player.rs b/src/user/bin/games/paper_rs/player.rs new file mode 100644 index 0000000..dd505b2 --- /dev/null +++ b/src/user/bin/games/paper_rs/player.rs @@ -0,0 +1,419 @@ +use alloc::vec::Vec; + +use crate::std::random::Random; +use crate::serial_println; +use super::game::Cell; + +#[derive(Debug, Clone)] +pub struct MoveQueue { + points: Vec<(i32, i32)>, +} + +impl MoveQueue { + fn new() -> Self { + Self { + points: Vec::new(), + } + } + + fn is_empty(&self) -> bool { + self.points.len() == 0 + } + + fn current(&self) -> Option<(i32, i32)> { + self.points.first().cloned() + } + + fn next(&mut self) -> Option<(i32, i32)> { + if let Some(point) = self.current() { + self.points.remove(0); + self.current() + } else { + None + } + } + + fn clear(&mut self) { + self.points.clear(); + } + + fn push(&mut self, point: (i32, i32)) { + self.points.push(point); + } +} + +#[derive(Debug, Clone)] +pub enum AiBehavior { + Expand, + Hunt, + Defend, + Escape, +} + +#[derive(Clone)] +pub struct Player { + pub id: u8, + pub alive: bool, + pub position: (i32, i32), + pub ai_direction: (i32, i32), // Current direction for AI + pub ai_controlled: bool, // Whether this player is AI controlled + pub ai_behavior: AiBehavior, // Current AI behavior state + pub moves: MoveQueue, +} + +impl Player { + const DIRS: [(i32, i32); 8] = [ + (0, -1), (0, 1), (-1, 0), (1, 0), // Cardinal + (1, 1), (1, -1), (-1, 1), (-1, -1) // Diagonal + ]; + + pub fn new(id: u8, position: (i32, i32), ai_controlled: bool) -> Self { + // Set initial direction based on position to encourage better expansion + let ai_direction = if position.0 < 40 { + (1, 0) // If on left side, move right + } else { + (-1, 0) // If on right side, move left + }; + + Self { + id, + alive: true, + position, + ai_direction, + ai_controlled, + ai_behavior: AiBehavior::Expand, + moves: MoveQueue::new(), + } + } + + pub fn update_ai(&mut self, board: &[[Cell; 80]; 25]) -> (i32, i32) { + if !self.ai_controlled || !self.alive { + return (0, 0); + } + + + match self.ai_behavior { + AiBehavior::Expand => self.run_expand_behavior(board), + AiBehavior::Hunt => self.run_hunt_behavior(board), + AiBehavior::Defend => self.run_defend_behavior(board), + AiBehavior::Escape => self.run_escape_behavior(board), + } + } + + // BEHAVIOR METHOD + fn run_expand_behavior(&mut self, board: &[[Cell; 80]; 25]) -> (i32, i32) { + if self.moves.is_empty() { + let points = self.generate_expand_points(board); + for point in points { + self.moves.push(point); + } + } + + if let Some(target) = self.moves.current() { + if self.position == target { + // Only get next point if we've reached the current target + self.moves.next(); + + // If we've completed all points, generate new ones + if self.moves.is_empty() { + let points = self.generate_expand_points(board); + for point in points { + self.moves.push(point); + } + } + } + } + + // Get current target and move towards it + self.moves.current() + .map(|target| self.get_move_to_position(target)) + .unwrap_or((0, 0)) + } + + fn run_hunt_behavior(&mut self, board: &[[Cell; 80]; 25]) -> (i32, i32) { + if let Some(tail_pos) = self.find_nearest_enemy_tail(board, 20) { + self.get_move_to_position(tail_pos) + } else if let Some(territory_pos) = self.find_nearest_territory_point(board, self.position) { + self.get_move_to_position(territory_pos) + } else { + (0, 0) + } + } + + fn run_defend_behavior(&mut self, board: &[[Cell; 80]; 25]) -> (i32, i32) { + if let Some(territory_pos) = self.find_nearest_territory_point(board, self.position) { + self.get_move_to_position(territory_pos) + } else { + (0, 0) + } + } + + fn run_escape_behavior(&mut self, board: &[[Cell; 80]; 25]) -> (i32, i32) { + if let Some(territory_pos) = self.find_nearest_territory_point(board, self.position) { + self.get_move_to_position(territory_pos) + } else { + // Move away from center if no territory found + let (x, y) = self.position; + let center = (40, 12); + let away_x = if x < center.0 { -1 } else { 1 }; + let away_y = if y < center.1 { -1 } else { 1 }; + (away_x, away_y) + } + } + + + // + // HELPER FUNCTIONS + // + + // Calculate Manhattan distance to nearest owned territory + fn distance_to_territory(&self, board: &[[Cell; 80]; 25]) -> i32 { + let (x, y) = self.position; + let mut min_dist = i32::MAX; + + for (y2, row) in board.iter().enumerate() { + for (x2, cell) in row.iter().enumerate() { + if let Cell::Solid(id, _) = cell { + if *id == self.id { + let dist = (x2 as i32 - x).abs() + (y2 as i32 - y).abs(); + min_dist = min_dist.min(dist); + } + } + } + } + + min_dist + } + + // Find nearest territory point to a given position + fn find_nearest_territory_point(&self, board: &[[Cell; 80]; 25], from: (i32, i32)) -> Option<(i32, i32)> { + let (x, y) = from; + let mut nearest_point = None; + let mut min_dist = i32::MAX; + + for (y2, row) in board.iter().enumerate() { + for (x2, cell) in row.iter().enumerate() { + if let Cell::Solid(id, _) = cell { + if *id == self.id { + let dist = (x2 as i32 - x).abs() + (y2 as i32 - y).abs(); + if dist < min_dist { + min_dist = dist; + nearest_point = Some((x2 as i32, y2 as i32)); + } + } + } + } + } + nearest_point + } + + // Get optimal move direction to reach a target position + fn get_move_to_position(&self, target: (i32, i32)) -> (i32, i32) { + let (x, y) = self.position; + let (target_x, target_y) = target; + + let dx = (target_x - x).signum(); + let dy = (target_y - y).signum(); + + // If we're already at the target, return no movement + if dx == 0 && dy == 0 { + return (0, 0); + } + + // Move both horizontally and vertically if possible + if dx != 0 && dy != 0 { + return (dx, dy); + } + + // Otherwise move in the available direction + (dx, dy) + } + + // Get locations of enemy tails adjacent to our territory + fn get_adjacent_enemy_tails(&self, board: &[[Cell; 80]; 25]) -> Vec<(i32, i32)> { + let mut tails = Vec::new(); + + // First find all our territory cells + for (y, row) in board.iter().enumerate() { + for (x, cell) in row.iter().enumerate() { + if let Cell::Solid(id, _) = cell { + if *id == self.id { + // Check adjacent cells for enemy tails + for &(dx, dy) in Self::DIRS.iter() { + let nx = x as i32 + dx; + let ny = y as i32 + dy; + if nx >= 0 && nx < 80 && ny >= 0 && ny < 25 { + if let Cell::Tail(id, _) = board[ny as usize][nx as usize] { + if id != self.id { + tails.push((nx, ny)); + } + } + } + } + } + } + } + } + tails + } + + // Find nearest enemy tail within specified range + fn find_nearest_enemy_tail(&self, board: &[[Cell; 80]; 25], range: i32) -> Option<(i32, i32)> { + let (x, y) = self.position; + let mut nearest_tail = None; + let mut min_dist = range + 1; // Initialize to just over range to find only tails within range + + // Search in a square area around the position + for dy in -range..=range { + for dx in -range..=range { + let nx = x + dx; + let ny = y + dy; + + if nx >= 0 && nx < 80 && ny >= 0 && ny < 25 { + if let Cell::Tail(id, _) = board[ny as usize][nx as usize] { + if id != self.id { + let dist = dx.abs() + dy.abs(); // Manhattan distance + if dist < min_dist { + min_dist = dist; + nearest_tail = Some((nx, ny)); + } + } + } + } + } + } + + nearest_tail + } + + fn find_territory_edge(&self, board: &[[Cell; 80]; 25], dir_x: i32, dir_y: i32) -> (i32, i32) { + let (x, y) = self.position; + let mut edge_x = x; + let mut edge_y = y; + let mut found_x = false; + let mut found_y = false; + + // Search horizontally + let mut search_x = x; + while search_x >= 0 && search_x < 80 && !found_x { + if let Cell::Solid(id, _) = board[y as usize][search_x as usize] { + if id == self.id { + edge_x = search_x + dir_x; + found_x = true; + } + } + search_x -= dir_x; + } + + // Search vertically + let mut search_y = y; + while search_y >= 0 && search_y < 25 && !found_y { + if let Cell::Solid(id, _) = board[search_y as usize][x as usize] { + if id == self.id { + edge_y = search_y + dir_y; + found_y = true; + } + } + search_y -= dir_y; + } + + // If no territory found, use map boundaries + if !found_x { + edge_x = if dir_x > 0 { 0 } else { 79 }; + } + if !found_y { + edge_y = if dir_y > 0 { 0 } else { 24 }; + } + + (edge_x, edge_y) + } + + fn generate_expand_points(&self, board: &[[Cell; 80]; 25]) -> Vec<(i32, i32)> { + let mut points = Vec::new(); + let (x, y) = self.position; + + // Distance to expand beyond territory + let x_distance = 8; + let y_distance = 8; + + // Determine horizontal direction based on position + let dir_x = if x < 40 { 1 } else { -1 }; + // Random vertical direction + let dir_y = if Random::int(0, 1) == 0 { 1 } else { -1 }; + + // Find territory edge in both directions + let (edge_x, edge_y) = self.find_territory_edge(board, dir_x, dir_y); + + // Move beyond territory edge + let p1 = (edge_x + (dir_x * x_distance), y); + points.push(p1); + + // Move perpendicular + let p2 = (p1.0, p1.1 + (dir_y * y_distance)); + points.push(p2); + + // Move back parallel to territory + let p3 = (p2.1, y); + points.push(p3); + + // Complete rectangle + if let Some(p4) = self.find_nearest_territory_point(board, p3) { + serial_println!("START: [{}, {}][{:?} {:?} {:?} {:?}]", x, y, p1, p2, p3, p4); + points.push(p4); + } + + points + } + + // MOVEMENT AND COLLISION + + pub fn move_player(&mut self, dx: i32, dy: i32, board: &mut [[Cell; 80]; 25]) -> Option { + if !self.alive { + return None; + } + + let (ox, oy) = self.position; + let nx = ox + dx; + let ny = oy + dy; + + // Bounds checking + if nx < 0 || nx >= 80 || ny < 0 || ny >= 25 { + if !self.ai_controlled { + self.alive = false; + } + return None; + } + + // Check for collisions with tails + let mut player_to_eliminate = None; + + // Check if player hit an enemy's tail + if let Cell::Tail(id, _) = board[ny as usize][nx as usize] { + if id != self.id { + // Hit other player's tail + player_to_eliminate = Some(id); + } + } + + // Convert old head to tail + if let Cell::Head(id, owned) = board[oy as usize][ox as usize] { + if owned { + board[oy as usize][ox as usize] = Cell::Solid(id, true); + } else { + board[oy as usize][ox as usize] = Cell::Tail(id, false); + } + } + + // Place new head + if let Cell::Solid(p, _) = board[ny as usize][nx as usize] { + board[ny as usize][nx as usize] = Cell::Head(self.id, p == self.id); + } else { + board[ny as usize][nx as usize] = Cell::Head(self.id, false); + } + + // Update the player's position + self.position = (nx, ny); + + player_to_eliminate + } +} diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index d3d1be8..1cb0be4 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -36,8 +36,9 @@ use crate::{ }, games::{ asteroids::Game as AsteroidsGame, + connect4::Game as Connect4Game, gameoflife::GameOfLife, - paper::GameBoard, + paper_rs::GameBoard, pong::Game as PongGame, snake::Game as SnakeGame, // tetris::TetrisEngine, @@ -127,6 +128,11 @@ async fn exec() -> Result<(), Error> { cmd.run(args).await?; } + "games/connect4" => { + let mut cmd = Connect4Game::new(); + cmd.run(args).await?; + } + "rickroll" => { let mut cmd = Rickroll::new(); cmd.run(args).await?;