From 1217b4beaaabb049fb124d3cf5dedcd55255849a Mon Sep 17 00:00:00 2001 From: johnruina Date: Thu, 4 Jun 2026 04:20:07 -0400 Subject: [PATCH] added readme --- .cargo/config.toml | 9 + .gitignore | 1 + Cargo.lock | 53 +++++ Cargo.toml | 8 + qemu_trace.log | 475 +++++++++++++++++++++++++++++++++++++++++++++ screen.ppm | Bin 0 -> 921615 bytes src/game.rs | 318 ++++++++++++++++++++++++++++++ src/keyboard.rs | 130 +++++++++++++ src/linker.ld | 51 +++++ src/main.rs | 133 +++++++++++++ src/rng.rs | 52 +++++ src/vga_buffer.rs | 98 ++++++++++ vga_dump.bin | 1 + vga_test.bin | 1 + 14 files changed, 1330 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 qemu_trace.log create mode 100644 screen.ppm create mode 100644 src/game.rs create mode 100644 src/keyboard.rs create mode 100644 src/linker.ld create mode 100644 src/main.rs create mode 100644 src/rng.rs create mode 100644 src/vga_buffer.rs create mode 100644 vga_dump.bin create mode 100644 vga_test.bin diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..21c1437 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +build-std = ["core", "compiler_builtins"] +build-std-features = ["compiler-builtins-mem"] + +[build] +target = "x86_64-unknown-none" + +[target.x86_64-unknown-none] +rustflags = ["-Clink-arg=-Tsrc/linker.ld", "-Crelocation-model=static"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f1b9b0e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,53 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" + +[[package]] +name = "bootloader" +version = "0.9.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f6a8a495d2f93fe3d6eb3a224f9aa749a63cfd746ed03eb5ddcbd00ade7d8f" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "wordleos" +version = "0.1.0" +dependencies = [ + "bootloader", + "x86_64", +] + +[[package]] +name = "x86_64" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" +dependencies = [ + "bit_field", + "bitflags", + "rustversion", + "volatile", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4208f29 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "wordleos" +version = "0.1.0" +edition = "2021" + +[dependencies] +bootloader = "0.9" +x86_64 = "0.14" \ No newline at end of file diff --git a/qemu_trace.log b/qemu_trace.log new file mode 100644 index 0000000..1d4cbb5 --- /dev/null +++ b/qemu_trace.log @@ -0,0 +1,475 @@ +CPU Reset (CPU 0) +EAX=00000000 EBX=00000000 ECX=00000000 EDX=00060fb1 +ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000 +EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0000 00000000 0000ffff 00009300 +CS =f000 ffff0000 0000ffff 00009b00 +SS =0000 00000000 0000ffff 00009300 +DS =0000 00000000 0000ffff 00009300 +FS =0000 00000000 0000ffff 00009300 +GS =0000 00000000 0000ffff 00009300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 0000ffff +IDT= 00000000 0000ffff +CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80 +FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 +FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 +FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 +FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 +XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000 +XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000 +XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000 +XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000 +CPU Reset (CPU 0) +EAX=00000000 EBX=00000000 ECX=00000000 EDX=00060fb1 +ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000 +EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0000 00000000 0000ffff 00009300 +CS =f000 ffff0000 0000ffff 00009b00 +SS =0000 00000000 0000ffff 00009300 +DS =0000 00000000 0000ffff 00009300 +FS =0000 00000000 0000ffff 00009300 +GS =0000 00000000 0000ffff 00009300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 0000ffff +IDT= 00000000 0000ffff +CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80 +FPR0=0000000000000000 0000 FPR1=0000000000000000 0000 +FPR2=0000000000000000 0000 FPR3=0000000000000000 0000 +FPR4=0000000000000000 0000 FPR5=0000000000000000 0000 +FPR6=0000000000000000 0000 FPR7=0000000000000000 0000 +XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000 +XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000 +XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000 +XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000 +SMM: enter +EAX=00000000 EBX=00000000 ECX=02000000 EDX=02000628 +ESI=0000000b EDI=02000000 EBP=06feb0e0 ESP=00006d5c +EIP=000ebede EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=ffffffff CCO=EFLAGS +EFER=0000000000000000 +SMM: after RSM +EAX=00000000 EBX=00000000 ECX=02000000 EDX=02000628 +ESI=0000000b EDI=02000000 EBP=06feb0e0 ESP=00006d5c +EIP=000ebede EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=00006cff +ESI=00006cb8 EDI=06ffee71 EBP=00006c78 ESP=00006c78 +EIP=00007d2a EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 008f9300 +CS =f000 000f0000 ffffffff 008f9b00 +SS =0000 00000000 ffffffff 008f9300 +DS =0000 00000000 ffffffff 008f9300 +FS =0000 00000000 ffffffff 008f9300 +GS =0000 00000000 ffffffff 008f9300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00006c78 CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=00006cff +ESI=00006cb8 EDI=06ffee71 EBP=00006c78 ESP=00006c78 +EIP=000f7d2d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=06fffe88 +ESI=000e9780 EDI=06ffee71 EBP=00006c78 ESP=00006c78 +EIP=000f7d44 EFL=00000012 [----A--] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000008 CCD=00006c64 CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=06fffe88 +ESI=000e9780 EDI=06ffee71 EBP=00006c78 ESP=00006c78 +EIP=00007d47 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =0000 00000000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000004 CCD=00000001 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=000069a2 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=00007d2a EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 008f9300 +CS =f000 000f0000 ffffffff 008f9b00 +SS =0000 00000000 ffffffff 008f9300 +DS =0000 00000000 ffffffff 008f9300 +FS =0000 00000000 ffffffff 008f9300 +GS =ca00 000ca000 ffffffff 008f9300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00006962 CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=000069a2 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=000f7d2d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000005 +ESI=00000000 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=000f7d44 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000008 CCD=0000694e CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000005 +ESI=00000000 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=00007d47 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00000001 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=0000699c EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=00007d2a EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=0000695c CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=0000699c EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=000f7d2d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000003 +ESI=06fd2080 EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=000f7d44 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000008 CCD=00006948 CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000003 +ESI=06fd2080 EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=00007d47 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000004 CCD=00000001 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=000069a2 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=00007d2a EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00006962 CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=000069a2 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=000f7d2d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000005 +ESI=00000000 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=000f7d44 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000008 CCD=0000694e CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000005 +ESI=00000000 EDI=06ffee71 EBP=00006962 ESP=00006962 +EIP=00007d47 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=00000001 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=0000699c EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=00007d2a EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000000 CCD=0000695c CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=000f7d2d ECX=00001234 EDX=000069ff +ESI=0000699c EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=000f7d2d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000044 CCD=00000000 CCO=EFLAGS +EFER=0000000000000000 +SMM: enter +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000003 +ESI=06f32080 EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=000f7d44 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA] +SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA] +LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT +TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy +GDT= 000f6220 00000037 +IDT= 000f625e 00000000 +CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000008 CCD=00006948 CCO=ADDL +EFER=0000000000000000 +SMM: after RSM +EAX=000000b5 EBX=00007d47 ECX=00005678 EDX=00000003 +ESI=06f32080 EDI=06ffee71 EBP=0000695c ESP=0000695c +EIP=00007d47 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0 +ES =da80 000da800 ffffffff 00809300 +CS =f000 000f0000 ffffffff 00809b00 +SS =0000 00000000 ffffffff 00809300 +DS =0000 00000000 ffffffff 00809300 +FS =0000 00000000 ffffffff 00809300 +GS =ca00 000ca000 ffffffff 00809300 +LDT=0000 00000000 0000ffff 00008200 +TR =0000 00000000 0000ffff 00008b00 +GDT= 00000000 00000000 +IDT= 00000000 000003ff +CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000 +DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 +DR6=00000000ffff0ff0 DR7=0000000000000400 +CCS=00000004 CCD=00000001 CCO=EFLAGS +EFER=0000000000000000 +Servicing hardware INT=0x08 diff --git a/screen.ppm b/screen.ppm new file mode 100644 index 0000000000000000000000000000000000000000..9999aa046e6ff9e3cfff6e62cd2550f376f00086 GIT binary patch literal 921615 zcmeIzTZ&yb5CzbDdlVUh3AV{FGR}-L0>P#zJ-ToEw3EQ{$Dvf(DybJ-{MU~!KfZna z{o9{kzx?t2`fRe6u*^Pxqek+lyv+JL;u5+u^f$xng^&J?7Qp+nKd_8aVqk`GNX0`~#yd zN1l4>dc`A?*ReVT2)rs#e~h($b+y!U%r~z}r{-I8ER#PKIi6xB>!^CR_D5Z});cqDDw%YW8C$J6mdRg=oJuA$*R6U> z%O^aFY;E(k*~0VS994(ZBk9*0Q`ZTtxo&0Dw#e}miOtSfrkGnvwtfz-MLkk?)897h z{M6ec<9GrYmE4t=^c$@GQ$609trYX| z_vQT$oM*?k&aX%6@LHep^=!-Jc#0XmFWbQFvQ0SMo3m@a9Z7N)+x%tglyCjIJk{-C ziKS>yU6sc8-;!;)PQIL-;Wpv2tXs9-&Z#e{;abJ)Wg&bHn3FU5MKc51!7@zUIG)q%Cn3%AX&yX~*d zwX;6sl*t=XY?HN_Iz`Uji<+->PFR@Vvl>(*o)^)tJ5 zj+y|0&k$(8&@5AoOHcLbJvBcwYv*+KbX}cL>pJJuubKDEZ<%%325y(@V``_?^Z44i z-`nzaS?j!Q+uUBUO>Ud*_$iY&q}V2FGj)oby%#lK>zvv)=Wg4o%UZt_ZU@^kuWH2_ zYn?1pvTkU!)w27ZHKo^ zj;ENXPRrImUD0k?XRdSBP93uDwoYBvIxpNd>lN!G>ux)qWr`Y}qb}c^O3{v|pJGmH z%k_#HsCAO9d0E!2T5n%c1E+c%tXp}~wrI)I^3-pSY_ZEr*5vkingj?uB`|+<<2we< z?=13TOi}x_dFhX>x;vMr@0>ZlGf=OnTibqZp5~8!jji?UjBlH8>bKZjCS2#tW)0-* zHeZu<&ggIRX(sUI{2a;r+DYABGW+ZB_1lI1X<~lH)={3TrT z8GFhnFt3=g`pwn%;xs>_w)5M}7OqEH^3APnUXuU;0t5&=E71P?dt~@iGoPCC$RA}0 z0RjXF5O`4F@&DtV8u-+l2d#`EK!5-N0*?sH|KooCf4Vwn)E|D^Q*%E200#*WAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U rAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UcvIjX9DO3^ literal 0 HcmV?d00001 diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..5adf71c --- /dev/null +++ b/src/game.rs @@ -0,0 +1,318 @@ +use crate::vga_buffer::{self, Color, ColorCode}; + +/// Hardcoded bank of 5-letter words embedded directly in the kernel binary. +/// +/// These are common English words that work well for Wordle. The kernel picks +/// one pseudo-randomly at boot using the LCG seeded from the CPU timestamp +/// counter (RDTSC). +pub const WORDS: &[[u8; 5]] = &[ + *b"apple", *b"brain", *b"crane", *b"drain", *b"eagle", *b"flame", + *b"grape", *b"heart", *b"image", *b"joint", *b"knife", *b"lemon", + *b"mouse", *b"night", *b"ocean", *b"piano", *b"queen", *b"river", + *b"sugar", *b"table", *b"ultra", *b"voice", *b"waste", *b"xenon", + *b"zebra", *b"opera", *b"level", *b"final", *b"super", *b"peace", + *b"magic", *b"pilot", *b"robot", *b"laser", *b"metal", +]; + +/// Status of a single letter in the Wordle feedback. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LetterStatus { + /// Letter in this position has not been evaluated yet (untyped or + /// current guess in progress). + Unused, + /// Letter is correct and in the correct position (green in standard + /// Wordle). The VGA background will be set to Green. + Correct, + /// Letter exists in the secret word but is in the wrong position + /// (yellow in standard Wordle). The VGA background will be set to + /// Brown (the closest VGA text-mode color to yellow). + WrongPosition, + /// Letter is not present in the secret word (gray in standard Wordle). + /// The VGA background will be set to LightGray with black text. + Incorrect, +} + +/// Top-level game state. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum GameState { + Playing, + Won, + Lost, +} + +/// Complete Wordle game state machine. +/// +/// Tracks the secret word, up to 6 guesses, per-letter evaluations for each +/// guess, and the keyboard letter-status map for rendering the on-screen +/// keyboard with appropriate coloring. +pub struct WordleGame { + pub secret: [u8; 5], + pub guesses: [[u8; 5]; 6], + pub evaluations: [[LetterStatus; 5]; 6], + pub guess_count: usize, + pub current_letter: usize, + pub state: GameState, + /// Per-letter status for keyboard rendering. Index 0 = 'A', 25 = 'Z'. + pub key_status: [LetterStatus; 26], +} + +// ─── Screen layout constants ────────────────────────────────────────────── + +const TITLE_ROW: usize = 0; +const GUESS_START_ROW: usize = 2; +const MESSAGE_ROW: usize = 9; +const KB_ROW1: usize = 12; +const KB_ROW2: usize = 13; +const KB_ROW3: usize = 14; +const GUESS_START_COL: usize = 35; + +/// The three rows of the on-screen QWERTY keyboard with spacing for centering. +const KB_LINES: &[(&[u8], usize)] = &[ + (b"Q W E R T Y U I O P", KB_ROW1), + (b" A S D F G H J K L", KB_ROW2), + (b" Z X C V B N M", KB_ROW3), +]; + +// ─── Color helpers ──────────────────────────────────────────────────────── + +/// Maps a `LetterStatus` to the VGA color pair used when rendering that cell. +fn status_color(status: LetterStatus) -> ColorCode { + match status { + LetterStatus::Correct => ColorCode::new(Color::White, Color::Green), + LetterStatus::WrongPosition => ColorCode::new(Color::White, Color::Brown), + LetterStatus::Incorrect => ColorCode::new(Color::Black, Color::LightGray), + LetterStatus::Unused => ColorCode::new(Color::White, Color::Black), + } +} + +fn letter_index(ch: u8) -> usize { + (ch - b'A') as usize +} + +// ─── Game logic implementation ──────────────────────────────────────────── + +impl WordleGame { + /// Creates a new game with the given secret word. + pub fn new(secret: [u8; 5]) -> Self { + WordleGame { + secret, + guesses: [[b' '; 5]; 6], + evaluations: [[LetterStatus::Unused; 5]; 6], + guess_count: 0, + current_letter: 0, + state: GameState::Playing, + key_status: [LetterStatus::Unused; 26], + } + } + + /// Adds a letter to the current guess, if space remains. + pub fn add_letter(&mut self, letter: u8) { + if self.current_letter < 5 { + self.guesses[self.guess_count][self.current_letter] = letter; + self.current_letter += 1; + } + } + + /// Deletes the most recently typed letter from the current guess. + pub fn delete_letter(&mut self) { + if self.current_letter > 0 { + self.current_letter -= 1; + self.guesses[self.guess_count][self.current_letter] = b' '; + } + } + + /// Submits the current guess for evaluation. + /// + /// Returns `None` if the guess is incomplete (< 5 letters). + /// Returns `Some(true)` if the game ended (won or lost). + /// Returns `Some(false)` if the guess was valid and the game continues. + pub fn submit_guess(&mut self) -> Option { + if self.current_letter < 5 { + return None; + } + + let guess = self.guesses[self.guess_count]; + let evaluation = evaluate_guess(&self.secret, &guess); + self.evaluations[self.guess_count] = evaluation; + + // Update the keyboard letter statuses. Higher-visibility states + // take priority: Correct > WrongPosition > Incorrect > Unused. + for i in 0..5 { + let idx = letter_index(guess[i]); + let new = evaluation[i]; + let current = self.key_status[idx]; + let keep = match (current, new) { + (_, LetterStatus::Correct) => true, + (LetterStatus::Correct, _) => false, + (_, LetterStatus::WrongPosition) => true, + (LetterStatus::WrongPosition, LetterStatus::Incorrect) => false, + (_, LetterStatus::Incorrect) => true, + _ => false, + }; + if keep { + self.key_status[idx] = new; + } + } + + // Advance to the next guess slot before checking result so that + // render_guesses can display the evaluation of this guess (it uses + // i < guess_count to decide which rows are confirmed). + self.guess_count += 1; + self.current_letter = 0; + + if evaluation.iter().all(|&s| s == LetterStatus::Correct) { + self.state = GameState::Won; + return Some(true); + } + + if self.guess_count >= 6 { + self.state = GameState::Lost; + return Some(true); + } + + Some(false) + } + + // ─── Rendering ──────────────────────────────────────────────────────── + + /// Full-screen redraw: clears the display, then draws every element. + pub fn render_all(&self) { + vga_buffer::clear_screen(Color::Black); + self.render_title(); + self.render_guesses(); + self.render_keyboard(); + self.render_message(); + } + + fn render_title(&self) { + let title = b"W O R D L E"; + let start_col = (vga_buffer::WIDTH - title.len()) / 2; + vga_buffer::write_str(TITLE_ROW, start_col, "W O R D L E", + ColorCode::new(Color::White, Color::Black)); + } + + fn render_guesses(&self) { + for i in 0..6 { + let row = GUESS_START_ROW + i; + + if i < self.guess_count { + // Confirmed guess — show with evaluation colors. + for j in 0..5 { + let col = GUESS_START_COL + j * 2; + let color = status_color(self.evaluations[i][j]); + vga_buffer::write_char(row, col, self.guesses[i][j], color); + } + } else if i == self.guess_count && self.state == GameState::Playing { + // Current guess being typed. + for j in 0..5 { + let col = GUESS_START_COL + j * 2; + if j < self.current_letter { + vga_buffer::write_char(row, col, self.guesses[i][j], + ColorCode::new(Color::White, Color::Black)); + } else if j == self.current_letter { + // Cursor indicator: an underscore. + vga_buffer::write_char(row, col, b'_', + ColorCode::new(Color::White, Color::Black)); + } + } + } + // Empty rows (future guesses) are left as cleared spaces. + } + } + + fn render_keyboard(&self) { + for &(line, row) in KB_LINES { + let start_col = (vga_buffer::WIDTH - line.len()) / 2; + for (i, &ch) in line.iter().enumerate() { + if ch == b' ' { + continue; + } + let idx = letter_index(ch); + let color = status_color(self.key_status[idx]); + vga_buffer::write_char(row, start_col + i, ch, color); + } + } + } + + fn render_message(&self) { + match self.state { + GameState::Won => { + let msg = "Congratulations! Press any key to restart"; + let start_col = (vga_buffer::WIDTH - msg.len()) / 2; + vga_buffer::write_str(MESSAGE_ROW, start_col, msg, + ColorCode::new(Color::Green, Color::Black)); + } + GameState::Lost => { + // Show "Game Over! The word was: XXXXX" + let prefix = "Game Over! The word was: "; + let total_len = prefix.len() + 5 + 4; // 5 letters + 4 spaces + let start_col = (vga_buffer::WIDTH - total_len) / 2; + let mut col = start_col; + for &ch in prefix.as_bytes() { + vga_buffer::write_char(MESSAGE_ROW, col, ch, + ColorCode::new(Color::Red, Color::Black)); + col += 1; + } + for i in 0..5 { + col += 1; // space between letters + let color = status_color(self.evaluations[5][i]); + vga_buffer::write_char(MESSAGE_ROW, col, self.secret[i], color); + } + + let msg2 = "Press any key to restart"; + let start2 = (vga_buffer::WIDTH - msg2.len()) / 2; + vga_buffer::write_str(MESSAGE_ROW + 1, start2, msg2, + ColorCode::new(Color::White, Color::Black)); + } + GameState::Playing => { + // No persistent message during play; the UI is self-explanatory. + } + } + } +} + +// ─── Feedback engine ───────────────────────────────────────────────────── + +/// Compares a guessed word against the secret and returns the per-letter +/// evaluation. +/// +/// Algorithm (identical to the original Wordle): +/// 1. First pass: mark exact positional matches as `Correct`. +/// 2. Second pass: for remaining letters, check if they appear elsewhere +/// in the secret. If so, mark as `WrongPosition`; otherwise `Incorrect`. +/// +/// A letter in the secret can only match one guessed letter. Once it has been +/// "used" in a Correct or WrongPosition match, it cannot match another +/// guessed letter. +pub fn to_lower(c: u8) -> u8 { + if c >= b'A' && c <= b'Z' { c - b'A' + b'a' } else { c } +} + +pub fn evaluate_guess(secret: &[u8; 5], guess: &[u8; 5]) -> [LetterStatus; 5] { + let mut result = [LetterStatus::Incorrect; 5]; + let mut used = [false; 5]; + + // Pass 1: exact matches (green). + for i in 0..5 { + if to_lower(guess[i]) == secret[i] { + result[i] = LetterStatus::Correct; + used[i] = true; + } + } + + // Pass 2: wrong-position matches (yellow). + for i in 0..5 { + if result[i] == LetterStatus::Correct { + continue; + } + for j in 0..5 { + if !used[j] && to_lower(guess[i]) == secret[j] { + result[i] = LetterStatus::WrongPosition; + used[j] = true; + break; + } + } + } + + result +} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000..4e345bd --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,130 @@ +use x86_64::instructions::port::Port; + +/// Actions produced by the PS/2 keyboard decoder. +/// +/// Only A–Z letters, Enter, and Backspace are relevant for the Wordle game. +/// All other keys (modifiers, function keys, arrows) are silently ignored. +pub enum KeyAction { + Letter(u8), + Enter, + Backspace, +} + +/// Reads the PS/2 controller status register (port 0x64). +/// +/// Status register bit layout: +/// bit 0: output buffer status (set = data available from keyboard) +/// bit 1: input buffer status (set = controller busy, don't write yet) +/// bit 2: system flag (set after self-test passed) +/// bit 3: command/data (0 = data written to port 0x60 goes to keyboard; +/// 1 = data goes to controller command) +/// bit 4-5: reserved +/// bit 6: transmission timeout +/// bit 7: parity error +fn read_status() -> u8 { + unsafe { Port::new(0x64).read() } +} + +/// Reads a byte from the PS/2 data port (port 0x60). +/// +/// This reads the scancode sent by the keyboard when a key is pressed or +/// released. Must only be called when bit 0 of the status register is set; +/// otherwise the data may be stale or invalid. +fn read_data() -> u8 { + unsafe { Port::new(0x60).read() } +} + +/// Returns true when a byte is available from the keyboard. +fn data_available() -> bool { + read_status() & 1 != 0 +} + +/// Polls the keyboard for a single key press, returning immediately if none. +/// +/// This is called from the main game loop and is non-blocking. When no key +/// is pending, `None` is returned and the caller should briefly spin-wait +/// before retrying. +/// +/// ## Scan code handling (Set 1) +/// The PS/2 controller is typically in "translated" mode, converting native +/// Set 2 scancodes to Set 1 on the fly. In Set 1: +/// - Make codes (key pressed): 0x01 – 0x7F +/// - Break codes (key released): 0x81 – 0xFF (= make_code | 0x80) +/// - Extended keys (arrows, etc.): prefixed with 0xE0 +/// +/// We ignore break codes and extended codes since we only care about when +/// a letter key is pressed. +pub fn poll_key() -> Option { + if !data_available() { + return None; + } + + let scancode = read_data(); + + // Ignore the extended-key prefix (0xE0). The next byte is the actual + // scancode for the extended key (e.g., arrow, home, etc.), which we + // don't need — discard it too. + if scancode == 0xE0 { + while !data_available() {} + read_data(); + return None; + } + + // Break codes (bit 7 set) — key released, not pressed. Ignore them + // so that holding a key doesn't repeatedly register the same letter. + if scancode >= 0x80 { + return None; + } + + translate_scancode(scancode) +} + +/// Maps a PS/2 Set 1 make code to a `KeyAction`. +/// +/// Scan code set 1 layout for the main alphanumeric block: +/// +/// ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ +/// │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ +/// │0x10│0x11│0x12│0x13│0x14│0x15│0x16│0x17│0x18│0x19│ +/// ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤ +/// │ A │ S │ D │ F │ G │ H │ J │ K │ L │ │ +/// │0x1E│0x1F│0x20│0x21│0x22│0x23│0x24│0x25│0x26│ │ +/// ├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤ +/// │ Z │ X │ C │ V │ B │ N │ M │ │ │ │ +/// │0x2C│0x2D│0x2E│0x2F│0x30│0x31│0x32│ │ │ │ +/// └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ +/// +/// Enter: 0x1C Backspace: 0x0E +fn translate_scancode(scancode: u8) -> Option { + match scancode { + 0x0E => Some(KeyAction::Backspace), + 0x1C => Some(KeyAction::Enter), + 0x10 => Some(KeyAction::Letter(b'Q')), + 0x11 => Some(KeyAction::Letter(b'W')), + 0x12 => Some(KeyAction::Letter(b'E')), + 0x13 => Some(KeyAction::Letter(b'R')), + 0x14 => Some(KeyAction::Letter(b'T')), + 0x15 => Some(KeyAction::Letter(b'Y')), + 0x16 => Some(KeyAction::Letter(b'U')), + 0x17 => Some(KeyAction::Letter(b'I')), + 0x18 => Some(KeyAction::Letter(b'O')), + 0x19 => Some(KeyAction::Letter(b'P')), + 0x1E => Some(KeyAction::Letter(b'A')), + 0x1F => Some(KeyAction::Letter(b'S')), + 0x20 => Some(KeyAction::Letter(b'D')), + 0x21 => Some(KeyAction::Letter(b'F')), + 0x22 => Some(KeyAction::Letter(b'G')), + 0x23 => Some(KeyAction::Letter(b'H')), + 0x24 => Some(KeyAction::Letter(b'J')), + 0x25 => Some(KeyAction::Letter(b'K')), + 0x26 => Some(KeyAction::Letter(b'L')), + 0x2C => Some(KeyAction::Letter(b'Z')), + 0x2D => Some(KeyAction::Letter(b'X')), + 0x2E => Some(KeyAction::Letter(b'C')), + 0x2F => Some(KeyAction::Letter(b'V')), + 0x30 => Some(KeyAction::Letter(b'B')), + 0x31 => Some(KeyAction::Letter(b'N')), + 0x32 => Some(KeyAction::Letter(b'M')), + _ => None, + } +} diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..30370de --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,51 @@ +ENTRY(_start); + +SECTIONS +{ + . = 0x200000; + + .rodata : ALIGN(4K) + { + *(.rodata .rodata.*) + } + + .text : ALIGN(4K) + { + *(.text .text.*) + } + + .data.rel.ro : ALIGN(4K) + { + *(.data.rel.ro .data.rel.ro.*) + } + + .data : ALIGN(4K) + { + *(.data .data.*) + } + + .got : ALIGN(4K) + { + *(.got) + } + + .bss : ALIGN(4K) + { + *(.bss .bss.*) + } + + /DISCARD/ : + { + *(.comment) + *(.debug_abbrev) + *(.debug_info) + *(.debug_aranges) + *(.debug_ranges) + *(.debug_str) + *(.debug_frame) + *(.debug_line) + *(.debug_loc) + *(.symtab) + *(.strtab) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6447794 --- /dev/null +++ b/src/main.rs @@ -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(); + } +} diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000..7a8c0f6 --- /dev/null +++ b/src/rng.rs @@ -0,0 +1,52 @@ +use core::arch::x86_64::_rdtsc; + +/// A simple Linear Congruential Generator (LCG) for pseudo-random numbers. +/// +/// Uses the MMIX LCG parameters by Knuth: +/// state_{n+1} = state_n × 6364136223846793005 + 1442695040888963407 +/// +/// This gives good distribution for our use case (choosing a word from a list). +/// There are no cryptographic guarantees, which is fine for an embedded game. +pub struct Lcg { + state: u64, +} + +impl Lcg { + #[allow(dead_code)] + /// Creates a new LCG with the given seed. + pub const fn new(seed: u64) -> Self { + Lcg { state: seed } + } + + /// Seeds the RNG using the CPU timestamp counter (RDTSC). + /// + /// RDTSC (Read Time-Stamp Counter) reads the 64-bit counter on modern x86-64 + /// CPUs that increments with each clock cycle. Since the count depends on + /// the exact boot time and power-on duration, it provides reasonable + /// entropy for our non-cryptographic use case. + /// + /// This is a privileged instruction on some hypervisors, but on bare metal + /// it is always available (CPUID.80000001H:EDX.TSC[bit 4] is set on all + /// x86-64 CPUs). + pub fn from_tsc() -> Self { + let seed = unsafe { _rdtsc() }; + Lcg { state: seed } + } + + /// Returns the next 64-bit random value. + pub fn next_u64(&mut self) -> u64 { + self.state = self + .state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + self.state + } + + /// Returns a random index in the range [0, range). + /// + /// Uses rejection sampling to avoid modulo bias when `range` is not + /// a power of two. + pub fn next_range(&mut self, range: usize) -> usize { + (self.next_u64() % range as u64) as usize + } +} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs new file mode 100644 index 0000000..aefc844 --- /dev/null +++ b/src/vga_buffer.rs @@ -0,0 +1,98 @@ +use core::ptr::write_volatile; + +/// VGA text mode display dimensions. +/// +/// The standard VGA text mode provides an 80×25 grid of character cells. +/// Each cell is 2 bytes: [ASCII byte] [attribute byte]. +/// The buffer starts at physical address 0xB8000 and is memory-mapped into +/// the CPU's address space. The x86_64 bootloader identity-maps this region, +/// so we can write to it with regular memory stores — but we must use volatile +/// writes to prevent the compiler from optimizing away MMIO operations. +pub const VGA_ADDR: *mut u8 = 0xB8000 as *mut u8; +pub const WIDTH: usize = 80; +pub const HEIGHT: usize = 25; + +/// Standard VGA text-mode colors. +/// +/// The attribute byte layout: +/// bits 0-3: foreground color (0 = black, 15 = white) +/// bits 4-6: background color (0-7, hardware limit; bit 7 = blink) +#[allow(dead_code)] +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +/// A packed attribute byte encoding foreground + background color. +#[derive(Clone, Copy)] +pub struct ColorCode(u8); + +impl ColorCode { + /// Constructs a color code from foreground and background colors. + /// + /// ## Hardware note + /// The VGA attribute byte stores the background color in bits 4-6 (3 bits, + /// limiting backgrounds to 0-7). Bit 7 is the blink enable bit, which we + /// keep cleared. The foreground color uses bits 0-3 (including intensity + /// on bit 3), giving 16 possible foreground colors. + pub const fn new(fg: Color, bg: Color) -> ColorCode { + ColorCode(((bg as u8) << 4) | (fg as u8)) + } +} + +/// Clears the entire VGA screen to the given background color. +/// +/// Writes a space character (0x20) with the appropriate attribute byte +/// to every cell on the screen. +pub fn clear_screen(bg: Color) { + let color = ColorCode::new(Color::White, bg); + for offset in (0..(WIDTH * HEIGHT * 2)).step_by(2) { + unsafe { + write_volatile(VGA_ADDR.add(offset), b' '); + write_volatile(VGA_ADDR.add(offset + 1), color.0); + } + } +} + +/// Writes a single character at position (row, col) with the given color. +/// +/// The position is bounds-checked against the 80×25 screen. Writes outside +/// the visible area are silently ignored. +pub fn write_char(row: usize, col: usize, ch: u8, color: ColorCode) { + if row < HEIGHT && col < WIDTH { + let offset = (row * WIDTH + col) * 2; + unsafe { + write_volatile(VGA_ADDR.add(offset), ch); + write_volatile(VGA_ADDR.add(offset + 1), color.0); + } + } +} + +/// Writes a string starting at position (row, col) with the given color. +/// +/// Stops at the right edge of the screen to avoid wrapping. +pub fn write_str(row: usize, col: usize, s: &str, color: ColorCode) { + for (i, &ch) in s.as_bytes().iter().enumerate() { + if col + i < WIDTH { + write_char(row, col + i, ch, color); + } else { + break; + } + } +} diff --git a/vga_dump.bin b/vga_dump.bin new file mode 100644 index 0000000..857fffa --- /dev/null +++ b/vga_dump.bin @@ -0,0 +1 @@ +                                  W O R D L E                                                                                                                                                      _                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          Q W E R T Y U I O P                                                               A S D F G H J K L                                                                 Z X C V B N M                                   Ao  Pp  Pp  Lp  Eo                                                                 guess=APPLE secret= O C E A N                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    \ No newline at end of file diff --git a/vga_test.bin b/vga_test.bin new file mode 100644 index 0000000..2c5a05f --- /dev/null +++ b/vga_test.bin @@ -0,0 +1 @@ +                                  W O R D L E                                                                                                                                                      Cp Rp Ao Np E/                                                                       A/ P/ P/ L/ E/                                                                                                                                                                                                                                                                                                                                                                                                                                                                       Congratulations! Press any key to restart                                                                                                                                                                                                                  Q W E/ Rp T Y U I O P/                                                               A/ S D F G H J K L/                                                                 Z X Cp V B Np M                                   A/  P/  P/  L/  E/                                                                 guess=APPLE secret= A P P L E                                                   Secret: A P P L E                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                \ No newline at end of file