idk
This commit is contained in:
@@ -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],
|
||||
};
|
||||
}
|
||||
@@ -332,6 +332,7 @@ pub fn special_char(ch: char) -> Option<u8> {
|
||||
'░' => 176,
|
||||
'▒' => 177,
|
||||
'▓' => 178,
|
||||
'█' => 219,
|
||||
'«' => 174,
|
||||
_ => {
|
||||
return None;
|
||||
|
||||
+189
-196
@@ -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<ArrayQueue<u8>> = OnceCell::uninit();
|
||||
|
||||
|
||||
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 {
|
||||
scancodes: ScanCodeStream,
|
||||
keyboard: Keyboard<layouts::Uk105Key, ScancodeSet1>,
|
||||
scancodes: ScanCodeStream,
|
||||
keyboard: Keyboard<layouts::Uk105Key, ScancodeSet1>,
|
||||
}
|
||||
|
||||
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<KeyStroke> {
|
||||
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<KeyStroke> {
|
||||
loop {
|
||||
if let Some(scancode) = self.scancodes.next().await {
|
||||
return self.process_keystroke(scancode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_keystroke(&mut self, scancode: u8) -> Option<KeyStroke> {
|
||||
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<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
|
||||
}
|
||||
|
||||
|
||||
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<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) {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<Option<u8>> {
|
||||
let queue = SCANCODE_QUEUE.try_get().expect("not initialised");
|
||||
fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Option<u8>> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
use crate::system::kernel::ahci::init_ahci;
|
||||
|
||||
pub fn check_ahci() {
|
||||
|
||||
}
|
||||
@@ -33,6 +33,11 @@ impl Stdin {
|
||||
let chr = KEYBOARD.lock().try_keystroke();
|
||||
chr
|
||||
}
|
||||
|
||||
pub fn last_keystroke() -> Option<KeyStroke> {
|
||||
let chr = KEYBOARD.lock().last_keystroke();
|
||||
chr
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Serial {}
|
||||
|
||||
@@ -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,5 +3,6 @@ pub mod gameoflife;
|
||||
pub mod crystalrpg;
|
||||
pub mod pong;
|
||||
pub mod snake;
|
||||
pub mod paper;
|
||||
pub mod tetris;
|
||||
pub mod paper_rs;
|
||||
pub mod tetris;
|
||||
pub mod connect4;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
mod player;
|
||||
|
||||
mod game;
|
||||
pub use game::GameBoard;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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?;
|
||||
|
||||
Reference in New Issue
Block a user