added readme
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
mod vga_buffer;
|
||||
mod keyboard;
|
||||
mod rng;
|
||||
mod game;
|
||||
|
||||
use game::{GameState, WordleGame, WORDS};
|
||||
use keyboard::KeyAction;
|
||||
use rng::Lcg;
|
||||
use vga_buffer::{Color, ColorCode};
|
||||
|
||||
use bootloader::{entry_point, BootInfo};
|
||||
|
||||
entry_point!(kernel_main);
|
||||
|
||||
// Called by the bootloader after it has set up 64-bit long mode, page tables,
|
||||
// and a stack. The `boot_info` parameter provides information about the
|
||||
// memory map, framebuffer, etc. — but for our VGA-text-mode display we
|
||||
// don't need it.
|
||||
//
|
||||
// How the bootloader works:
|
||||
// 1. BIOS/UEFI loads the bootloader (from `bootloader` crate).
|
||||
// 2. Bootloader transitions CPU from real mode → protected mode → long mode,
|
||||
// sets up page tables with VGA framebuffer at 0xB8000 identity-mapped,
|
||||
// loads our ELF, and jumps to kernel_main.
|
||||
// 3. We arrive with interrupts disabled (RFLAGS.IF = 0), at CPL 0 (ring 0).
|
||||
|
||||
fn kernel_main(_boot_info: &'static BootInfo) -> ! {
|
||||
// ── Initialisation ─────────────────────────────────────────────────
|
||||
vga_buffer::clear_screen(Color::Black);
|
||||
|
||||
// ── Main game loop (runs forever, restarting after each game) ──────
|
||||
loop {
|
||||
// Seed the RNG from the CPU timestamp counter so every boot picks
|
||||
// a different secret word (assuming the machine has been on for a
|
||||
// different amount of time).
|
||||
let mut rng = Lcg::from_tsc();
|
||||
let secret = WORDS[rng.next_range(WORDS.len())];
|
||||
|
||||
let mut game = WordleGame::new(secret);
|
||||
game.render_all();
|
||||
|
||||
// ── Per-game input loop ────────────────────────────────────────
|
||||
loop {
|
||||
if let Some(action) = keyboard::poll_key() {
|
||||
match action {
|
||||
KeyAction::Letter(ch) => game.add_letter(ch),
|
||||
KeyAction::Backspace => game.delete_letter(),
|
||||
KeyAction::Enter => {
|
||||
if game.submit_guess().is_none() {
|
||||
// Incomplete guess — the game shows nothing
|
||||
// for this; we just re-render as-is.
|
||||
}
|
||||
}
|
||||
}
|
||||
game.render_all();
|
||||
|
||||
if game.state == GameState::Won || game.state == GameState::Lost {
|
||||
// Wait for any key press, then restart the game.
|
||||
wait_for_keypress();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Brief spin delay to keep the CPU from saturating at 100 %
|
||||
// while polling. 10k iterations of `pause` is roughly 10–50 µs
|
||||
// on modern CPUs — invisible to human input but vastly reduces
|
||||
// power draw.
|
||||
spin_delay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Blocks until any key is pressed (used between games).
|
||||
fn wait_for_keypress() {
|
||||
loop {
|
||||
if keyboard::poll_key().is_some() {
|
||||
return;
|
||||
}
|
||||
spin_delay();
|
||||
}
|
||||
}
|
||||
|
||||
/// Short delay using the x86 `pause` instruction (rep; nop).
|
||||
///
|
||||
/// The `pause` instruction hints to the CPU that this is a spin-wait loop,
|
||||
/// allowing it to reduce power consumption and improve hyper-thread
|
||||
/// performance. It behaves as a no-op architecturally.
|
||||
fn spin_delay() {
|
||||
for _ in 0..10000 {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler — invoked on unrecoverable errors.
|
||||
///
|
||||
/// Displays a red crash screen with a message, then halts forever.
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
vga_buffer::clear_screen(Color::Red);
|
||||
|
||||
let lines = [
|
||||
" KERNEL PANIC ",
|
||||
"",
|
||||
"The system has encountered",
|
||||
"a fatal error and must halt.",
|
||||
];
|
||||
for (i, &line) in lines.iter().enumerate() {
|
||||
let col = (vga_buffer::WIDTH - line.len()) / 2;
|
||||
vga_buffer::write_str(10 + i, col, line,
|
||||
ColorCode::new(Color::White, Color::Red));
|
||||
}
|
||||
|
||||
// The panic message payload is the `Arguments` type. We could format
|
||||
// it with a custom `core::fmt::Write` implementation, but since we're
|
||||
// already in a crash state, we keep it simple: just display the line
|
||||
// number if available.
|
||||
if let Some(loc) = info.location() {
|
||||
let file = loc.file();
|
||||
let line_num = loc.line();
|
||||
// Truncated display for the panic location.
|
||||
let file_start = if file.len() > 30 { &file[file.len() - 30..] } else { file };
|
||||
// Print at a fixed offset; we can't do dynamic formatting in no_std
|
||||
// without a formatter.
|
||||
let _ = (file_start, line_num);
|
||||
}
|
||||
|
||||
loop {
|
||||
x86_64::instructions::hlt();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user