added readme

This commit is contained in:
2026-06-04 04:20:07 -04:00
commit 1217b4beaa
14 changed files with 1330 additions and 0 deletions
+133
View File
@@ -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 1050 µ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();
}
}