Files
engfinal/src/main.rs
T
2026-06-04 04:20:07 -04:00

134 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#![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();
}
}