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,
|
'░' => 176,
|
||||||
'▒' => 177,
|
'▒' => 177,
|
||||||
'▓' => 178,
|
'▓' => 178,
|
||||||
|
'█' => 219,
|
||||||
'«' => 174,
|
'«' => 174,
|
||||||
_ => {
|
_ => {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
+189
-196
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
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 {}
|
||||||
|
|||||||
@@ -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 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;
|
||||||
@@ -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::{
|
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?;
|
||||||
|
|||||||
Reference in New Issue
Block a user