This commit is contained in:
FantasyPvP
2024-12-10 00:36:45 +00:00
parent 8f3a96c282
commit d77ce2bc47
13 changed files with 1413 additions and 306 deletions
BIN
View File
Binary file not shown.
+298
View File
@@ -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<u32>, // 0x00, command list base address, 1K-byte aligned
pub clbu: Volatile<u32>, // 0x04, command list base address upper 32 bits
pub fb: Volatile<u32>, // 0x08, FIS base address, 256-byte aligned
pub fbu: Volatile<u32>, // 0x0C, FIS base address upper 32 bits
pub is: Volatile<u32>, // 0x10, interrupt status
pub ie: Volatile<u32>, // 0x14, interrupt enable
pub cmd: Volatile<u32>, // 0x18, command and status
pub rsv0: Volatile<u32>, // 0x1C, Reserved
pub tfd: Volatile<u32>, // 0x20, task file data
pub sig: Volatile<u32>, // 0x24, signature
pub ssts: Volatile<u32>, // 0x28, SATA status (SCR0:SStatus)
pub sctl: Volatile<u32>, // 0x2C, SATA control (SCR2:SControl)
pub serr: Volatile<u32>, // 0x30, SATA error (SCR1:SError)
pub sact: Volatile<u32>, // 0x34, SATA active (SCR3:SActive)
pub ci: Volatile<u32>, // 0x38, command issue
pub sntf: Volatile<u32>, // 0x3C, SATA notification (SCR4:SNotification)
pub fbs: Volatile<u32>, // 0x40, FIS-based switch control
pub rsv1: [Volatile<u32>; 11], // 0x44 ~ 0x6F, Reserved
pub vendor: [Volatile<u32>; 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<DeviceType> {
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<u32>, // 0x00, Host capability
pub ghc: Volatile<u32>, // 0x04, Global host control
pub is: Volatile<u32>, // 0x08, Interrupt status
pub pi: Volatile<u32>, // 0x0C, Port implemented
pub vs: Volatile<u32>, // 0x10, Version
pub ccc_ctl: Volatile<u32>, // 0x14, Command completion coalescing control
pub ccc_pts: Volatile<u32>, // 0x18, Command completion coalescing ports
pub em_loc: Volatile<u32>, // 0x1C, Enclosure management location
pub em_ctl: Volatile<u32>, // 0x20, Enclosure management control
pub cap2: Volatile<u32>, // 0x24, Host capabilities extended
pub bohc: Volatile<u32>, // 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],
};
}
View File
+1
View File
@@ -332,6 +332,7 @@ pub fn special_char(ch: char) -> Option<u8> {
'░' => 176, '░' => 176,
'▒' => 177, '▒' => 177,
'▓' => 178, '▓' => 178,
'█' => 219,
'«' => 174, '«' => 174,
_ => { _ => {
return None; return None;
+189 -196
View File
@@ -2,10 +2,9 @@ use lazy_static::lazy_static;
use spin::Mutex; use spin::Mutex;
use x86_64::instructions::interrupts; use x86_64::instructions::interrupts;
use conquer_once::spin::OnceCell; use conquer_once::spin::OnceCell;
use crossbeam_queue::ArrayQueue; 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 core::{pin::Pin, task::{Poll, Context}};
use futures_util::stream::Stream; use futures_util::stream::Stream;
@@ -19,248 +18,242 @@ use alloc::{string::String};
static WAKER: AtomicWaker = AtomicWaker::new(); static WAKER: AtomicWaker = AtomicWaker::new();
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit(); static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
lazy_static! { lazy_static! {
pub static ref KEYBOARD: Mutex<KeyboardHandler> = Mutex::new(KeyboardHandler::new()); pub static ref KEYBOARD: Mutex<KeyboardHandler> = Mutex::new(KeyboardHandler::new());
} }
pub struct KeyboardHandler { pub struct KeyboardHandler {
scancodes: ScanCodeStream, scancodes: ScanCodeStream,
keyboard: Keyboard<layouts::Uk105Key, ScancodeSet1>, keyboard: Keyboard<layouts::Uk105Key, ScancodeSet1>,
} }
enum CharOrKeystroke { enum CharOrKeystroke {
Char(char), Char(char),
Keystroke(KeyCode), Keystroke(KeyCode),
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyStroke { pub enum KeyStroke {
Char(char), Char(char),
Ctrl, Ctrl,
RCtrl, RCtrl,
Alt, Alt,
RAlt, RAlt,
Shift, Shift,
RShift, RShift,
Meta, Meta,
RMeta, RMeta,
Backspace, Backspace,
Left, Left,
Right, Right,
Up, Up,
Down, Down,
None, None,
Enter, Enter,
Escape, Escape,
Del Del
} }
impl KeyStroke { impl KeyStroke {
pub fn from_keycode(key: KeyCode) -> KeyStroke { pub fn from_keycode(key: KeyCode) -> KeyStroke {
match key { match key {
KeyCode::ControlLeft => KeyStroke::Ctrl, KeyCode::ControlLeft => KeyStroke::Ctrl,
KeyCode::ControlRight => KeyStroke::RCtrl, KeyCode::ControlRight => KeyStroke::RCtrl,
KeyCode::AltLeft => KeyStroke::Alt, KeyCode::AltLeft => KeyStroke::Alt,
KeyCode::AltRight => KeyStroke::RAlt, KeyCode::AltRight => KeyStroke::RAlt,
KeyCode::ShiftLeft => KeyStroke::Shift, KeyCode::ShiftLeft => KeyStroke::Shift,
KeyCode::ShiftRight => KeyStroke::RShift, KeyCode::ShiftRight => KeyStroke::RShift,
KeyCode::WindowsLeft => KeyStroke::Meta, KeyCode::WindowsLeft => KeyStroke::Meta,
KeyCode::WindowsRight => KeyStroke::RMeta, KeyCode::WindowsRight => KeyStroke::RMeta,
KeyCode::Backspace => KeyStroke::Backspace, KeyCode::Backspace => KeyStroke::Backspace,
KeyCode::ArrowLeft => KeyStroke::Left, KeyCode::ArrowLeft => KeyStroke::Left,
KeyCode::ArrowRight => KeyStroke::Right, KeyCode::ArrowRight => KeyStroke::Right,
KeyCode::ArrowUp => KeyStroke::Up, KeyCode::ArrowUp => KeyStroke::Up,
KeyCode::ArrowDown => KeyStroke::Down, KeyCode::ArrowDown => KeyStroke::Down,
KeyCode::Enter => KeyStroke::Enter, KeyCode::Enter => KeyStroke::Enter,
KeyCode::Escape => KeyStroke::Escape, KeyCode::Escape => KeyStroke::Escape,
KeyCode::Delete => KeyStroke::Del, KeyCode::Delete => KeyStroke::Del,
_ => KeyStroke::None, _ => KeyStroke::None,
} }
} }
} }
impl core::fmt::Display for KeyStroke { impl core::fmt::Display for KeyStroke {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self { match self {
KeyStroke::Char(c) => write!(f, "{}", c), KeyStroke::Char(c) => write!(f, "{}", c),
KeyStroke::Ctrl => write!(f, "CTRL"), KeyStroke::Ctrl => write!(f, "CTRL"),
KeyStroke::RCtrl => write!(f, "RCtrl"), KeyStroke::RCtrl => write!(f, "RCtrl"),
KeyStroke::Alt => write!(f, "ALT"), KeyStroke::Alt => write!(f, "ALT"),
KeyStroke::RAlt => write!(f, "RAlt"), KeyStroke::RAlt => write!(f, "RAlt"),
KeyStroke::Shift => write!(f, "SHIFT"), KeyStroke::Shift => write!(f, "SHIFT"),
KeyStroke::RShift => write!(f, "RShift"), KeyStroke::RShift => write!(f, "RShift"),
KeyStroke::Meta => write!(f, "META"), KeyStroke::Meta => write!(f, "META"),
KeyStroke::RMeta => write!(f, "RMeta"), KeyStroke::RMeta => write!(f, "RMeta"),
KeyStroke::Backspace => write!(f, "BACKSPACE"), KeyStroke::Backspace => write!(f, "BACKSPACE"),
KeyStroke::Left => write!(f, "LEFT"), KeyStroke::Left => write!(f, "LEFT"),
KeyStroke::Right => write!(f, "RIGHT"), KeyStroke::Right => write!(f, "RIGHT"),
KeyStroke::Up => write!(f, "UP"), KeyStroke::Up => write!(f, "UP"),
KeyStroke::Down => write!(f, "DOWN"), KeyStroke::Down => write!(f, "DOWN"),
KeyStroke::Enter => write!(f, "ENTER"), KeyStroke::Enter => write!(f, "ENTER"),
KeyStroke::Escape => write!(f, "ESCAPE"), KeyStroke::Escape => write!(f, "ESCAPE"),
KeyStroke::None => write!(f, "NONE"), KeyStroke::None => write!(f, "NONE"),
KeyStroke::Del => write!(f, "DEL"), KeyStroke::Del => write!(f, "DEL"),
} }
} }
} }
impl KeyboardHandler { impl KeyboardHandler {
pub fn new() -> KeyboardHandler { pub fn new() -> KeyboardHandler {
KeyboardHandler { KeyboardHandler {
scancodes: ScanCodeStream::new(), scancodes: ScanCodeStream::new(),
keyboard: Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore), keyboard: Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore),
} }
} }
pub async fn get_keystroke_inner(&mut self) -> Option<KeyStroke> { pub async fn get_keystroke_inner(&mut self) -> Option<KeyStroke> {
loop { loop {
if let Some(scancode) = self.scancodes.next().await { if let Some(scancode) = self.scancodes.next().await {
if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { return self.process_keystroke(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); pub fn process_keystroke(&mut self, scancode: u8) -> Option<KeyStroke> {
match KeyStroke::from_keycode(key) { if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) {
KeyStroke::None => (), if let Some(key) = self.keyboard.process_keyevent(key_event) {
k => return Some(k) 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<KeyStroke> {
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 None
} }
pub async fn get_keystroke(&mut self) -> KeyStroke {
pub async fn get_string(&mut self) -> String {
let mut val = String::new();
loop { loop {
let character = match self.get_keystroke_inner().await { if let Some(c) = self.scancodes.next().await {
Some(c) => { c }, if let Some(key) = self.process_keystroke(c) {
None => { val.pop(); continue; }, return key;
};
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;
} }
} }
} }
}
} pub fn try_keystroke(&mut self) -> Option<KeyStroke> {
if let Some(scancode) = self.scancodes.try_next() {
self.process_keystroke(scancode)
} else { None }
}
pub fn last_keystroke(&mut self) -> Option<KeyStroke> {
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) { pub(crate) fn add_scancode(scancode: u8) {
if let Ok(queue) = SCANCODE_QUEUE.try_get() { if let Ok(queue) = SCANCODE_QUEUE.try_get() {
if let Err(_) = queue.push(scancode) { if let Err(_) = queue.push(scancode) {
println!("WARNING: queue is full - ignoring input"); println!("WARNING: queue is full - ignoring input");
} else { } else {
WAKER.wake(); WAKER.wake();
} }
} else { } else {
println!("WARNING: scancode queue has not been initialised"); println!("WARNING: scancode queue has not been initialised");
} }
} }
pub struct ScanCodeStream { pub struct ScanCodeStream {
_private: (), _private: (),
} }
impl ScanCodeStream { impl ScanCodeStream {
pub fn new() -> Self { pub fn new() -> Self {
SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100)) SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100))
.expect("ScanCodeStream::new has already been called once"); .expect("ScanCodeStream::new has already been called once");
ScanCodeStream { _private: () } ScanCodeStream { _private: () }
} }
pub fn try_next(&mut self) -> Option<u8> { pub fn try_next(&mut self) -> Option<u8> {
let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); let queue = SCANCODE_QUEUE.try_get().expect("not initialised");
if let Ok(c) = queue.pop() { if let Ok(c) = queue.pop() {
Some(c) Some(c)
} else { } else {
None None
} }
} }
} }
impl Stream for ScanCodeStream { impl Stream for ScanCodeStream {
type Item = u8; type Item = u8;
fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Option<u8>> { fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Option<u8>> {
let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); let queue = SCANCODE_QUEUE.try_get().expect("not initialised");
if let Ok(scancode) = queue.pop() { if let Ok(scancode) = queue.pop() {
return Poll::Ready(Some(scancode)); return Poll::Ready(Some(scancode));
} }
WAKER.register(&ctx.waker()); WAKER.register(&ctx.waker());
match queue.pop() { match queue.pop() {
Ok(scancode) => { Ok(scancode) => {
WAKER.take(); WAKER.take();
Poll::Ready(Some(scancode)) Poll::Ready(Some(scancode))
}, },
Err(crossbeam_queue::PopError) => Poll::Pending, Err(crossbeam_queue::PopError) => Poll::Pending,
} }
} }
} }
+5
View File
@@ -0,0 +1,5 @@
use crate::system::kernel::ahci::init_ahci;
pub fn check_ahci() {
}
+5
View File
@@ -33,6 +33,11 @@ impl Stdin {
let chr = KEYBOARD.lock().try_keystroke(); let chr = KEYBOARD.lock().try_keystroke();
chr chr
} }
pub fn last_keystroke() -> Option<KeyStroke> {
let chr = KEYBOARD.lock().last_keystroke();
chr
}
} }
pub struct Serial {} pub struct Serial {}
+407
View File
@@ -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<u8>,
}
#[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<String>) -> 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<Frame, RenderError> {
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!()
}
}
+3 -2
View File
@@ -3,5 +3,6 @@ pub mod gameoflife;
pub mod crystalrpg; pub mod crystalrpg;
pub mod pong; pub mod pong;
pub mod snake; pub mod snake;
pub mod paper; pub mod paper_rs;
pub mod tetris; pub mod tetris;
pub mod connect4;
@@ -3,26 +3,27 @@ use core::any::Any;
use alloc::{boxed::Box, format, string::String, vec::Vec}; use alloc::{boxed::Box, format, string::String, vec::Vec};
use async_trait::async_trait; 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)] #[derive(Copy, Clone)]
enum Cell { pub enum Cell {
Empty, Empty,
Solid(u8, bool), Solid(u8, bool),
Tail(u8, bool), Tail(u8, bool),
Head(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 { pub struct GameBoard {
board: [[Cell; 80]; 25], board: [[Cell; 80]; 25],
players: [Player; 6], players: [Player; 6],
@@ -36,12 +37,12 @@ impl Application for GameBoard {
GameBoard { GameBoard {
board: [[Cell::Empty; 80]; 25], board: [[Cell::Empty; 80]; 25],
players: [ players: [
Player { id: 0, alive: true, position: (10, 10), ai_ticks: 0, ai_direction: (1, 0) }, Player::new(0, (10, 10), false),
Player { id: 1, alive: true, position: (70, 10), ai_ticks: 5, ai_direction: (-1, 0) }, Player::new(1, (70, 10), true),
Player { id: 2, alive: true, position: (10, 15), ai_ticks: 10, ai_direction: (1, 0) }, Player::new(2, (10, 15), true),
Player { id: 3, alive: true, position: (70, 15), ai_ticks: 15, ai_direction: (-1, 0) }, Player::new(3, (70, 15), true),
Player { id: 4, alive: true, position: (35, 5), ai_ticks: 20, ai_direction: (0, 1) }, Player::new(4, (35, 5), true),
Player { id: 5, alive: true, position: (35, 20), ai_ticks: 25, ai_direction: (0, -1) }, Player::new(5, (35, 20), true),
], ],
max_territory: 0, max_territory: 0,
current_territory: 0, current_territory: 0,
@@ -52,15 +53,17 @@ impl Application for GameBoard {
let _display = Display::borrow(); let _display = Display::borrow();
'outer: loop { 'outer: loop {
// Set initial positions as solid territory
self.players.clone().into_iter().for_each(|p| { for player in &self.players {
self.move_player(p.id, 0, 0); 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); let mut player_direction: (i32, i32) = (0, 0);
// player controls player 1. // player controls player 1.
loop { loop {
self.render().unwrap().write_to_screen().unwrap();
time::wait(0.1); time::wait(0.1);
// first get player input // first get player input
@@ -86,7 +89,6 @@ impl Application for GameBoard {
self.run_fill_algorithm(); self.run_fill_algorithm();
self.render().unwrap().write_to_screen().unwrap();
if !self.players[0].alive { if !self.players[0].alive {
self.render_end_screen().await.unwrap(); self.render_end_screen().await.unwrap();
@@ -118,9 +120,9 @@ impl CgComponent for GameBoard {
match self.board[i][j] { match self.board[i][j] {
Cell::Empty => { character = ' '; pid = None; } 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::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 { let colour = if let Some(p) = pid {
@@ -157,7 +159,7 @@ impl GameBoard {
const LEFT: (i32, i32) = (-1, 0); const LEFT: (i32, i32) = (-1, 0);
const RIGHT: (i32, i32) = (1, 0); const RIGHT: (i32, i32) = (1, 0);
const DIRS: [(i32, i32); 8] = [ 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), (1, 1), (1, -1), (-1, 1), (-1, -1),
]; ];
@@ -165,13 +167,20 @@ impl GameBoard {
fn reset(&mut self) { fn reset(&mut self) {
self.board = [[Cell::Empty; 80]; 25]; self.board = [[Cell::Empty; 80]; 25];
self.players = [ self.players = [
Player { id: 0, alive: true, position: (10, 10), ai_ticks: 0, ai_direction: (1, 0) }, Player::new(0, (10, 10), false),
Player { id: 1, alive: true, position: (70, 10), ai_ticks: 5, ai_direction: (-1, 0) }, Player::new(1, (70, 10), true),
Player { id: 2, alive: true, position: (10, 15), ai_ticks: 10, ai_direction: (1, 0) }, Player::new(2, (10, 15), true),
Player { id: 3, alive: true, position: (70, 15), ai_ticks: 15, ai_direction: (-1, 0) }, Player::new(3, (70, 15), true),
Player { id: 4, alive: true, position: (35, 5), ai_ticks: 20, ai_direction: (0, 1) }, Player::new(4, (35, 5), true),
Player { id: 5, alive: true, position: (35, 20), ai_ticks: 25, ai_direction: (0, -1) }, 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.max_territory = 0;
self.current_territory = 0; self.current_territory = 0;
} }
@@ -185,15 +194,9 @@ impl GameBoard {
// Check if position is empty or not in someone's territory // Check if position is empty or not in someone's territory
if let Cell::Empty = self.board[y as usize][x as usize] { if let Cell::Empty = self.board[y as usize][x as usize] {
let dir_idx = std::random::Random::int(0, 3) as usize; let dir_idx = std::random::Random::int(0, 3) as usize;
self.players[player_id as usize] = Player { self.players[player_id as usize] = Player::new(player_id, (x, y), true);
id: player_id,
alive: true,
position: (x, y),
ai_ticks: std::random::Random::int(5, 10) as u32,
ai_direction: Self::DIRS[dir_idx],
};
// Place the head at spawn position // 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; return;
} }
} }
@@ -259,58 +262,12 @@ impl GameBoard {
} }
fn move_player(&mut self, playerid: u8, dx: i32, dy: i32) { fn move_player(&mut self, playerid: u8, dx: i32, dy: i32) {
let (ox, oy) = self.players[playerid as usize].position; if let Some(eliminated_id) = self.players[playerid as usize].move_player(dx, dy, &mut self.board) {
let nx = ox + dx; self.players[eliminated_id as usize].alive = false;
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;
// Clear territory and respawn if AI // Clear territory and respawn if AI
if pid != 0 { if eliminated_id != 0 {
self.clear_player_territory(pid); self.clear_player_territory(eliminated_id);
self.respawn_ai_player(pid); self.respawn_ai_player(eliminated_id);
} else { } else {
// Update max territory before game over // Update max territory before game over
self.current_territory = self.count_territory(); self.current_territory = self.count_territory();
@@ -322,23 +279,10 @@ impl GameBoard {
fn update_ai_players(&mut self) { fn update_ai_players(&mut self) {
// Skip player 0 (human player) // Skip player 0 (human player)
for i in 1..6 { for i in 1..6 {
if !self.players[i].alive { let dir = self.players[i].update_ai(&self.board);
continue; 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) { for player in self.players.iter().filter(|p| p.alive) {
let pid = player.id; 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 // Reset grid
for row in fill_grid.iter_mut() { for row in fill_grid.iter_mut() {
row.fill(false); row.fill(false);
@@ -397,7 +365,7 @@ impl GameBoard {
// Mark current cell as visited in the fill grid // Mark current cell as visited in the fill grid
fill_grid[y][x] = true; fill_grid[y][x] = true;
// Check all four directions // Check all 8 directions
for (dy, dx) in Self::DIRS { for (dy, dx) in Self::DIRS {
let ny = (y as i32 + dy) as usize; let ny = (y as i32 + dy) as usize;
let nx = (x as i32 + dx) as usize; let nx = (x as i32 + dx) as usize;
+4
View File
@@ -0,0 +1,4 @@
mod player;
mod game;
pub use game::GameBoard;
+419
View File
@@ -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<u8> {
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
}
}
+7 -1
View File
@@ -36,8 +36,9 @@ use crate::{
}, },
games::{ games::{
asteroids::Game as AsteroidsGame, asteroids::Game as AsteroidsGame,
connect4::Game as Connect4Game,
gameoflife::GameOfLife, gameoflife::GameOfLife,
paper::GameBoard, paper_rs::GameBoard,
pong::Game as PongGame, pong::Game as PongGame,
snake::Game as SnakeGame, snake::Game as SnakeGame,
// tetris::TetrisEngine, // tetris::TetrisEngine,
@@ -127,6 +128,11 @@ async fn exec() -> Result<(), Error> {
cmd.run(args).await?; cmd.run(args).await?;
} }
"games/connect4" => {
let mut cmd = Connect4Game::new();
cmd.run(args).await?;
}
"rickroll" => { "rickroll" => {
let mut cmd = Rickroll::new(); let mut cmd = Rickroll::new();
cmd.run(args).await?; cmd.run(args).await?;