From 6f7f60bf53a7805a501f73b851d6564112d05635 Mon Sep 17 00:00:00 2001 From: subhobhai943 Date: Fri, 21 Aug 2026 01:47:37 +0530 Subject: [PATCH] feat: Smooth Snake game rendering and expand kernel subsystems - Implement flicker-free atomic double-buffered frame rendering and sub-tick input polling in userland/lazybox/snake.c - Add multi-input support (PS/2 keyboard scancodes and serial ANSI arrow escape sequences) - Add sound effects (food beep, game over chime) and live speed scaling (+/- keys, P to pause) - Add POSIX /dev/shm shared memory subsystem in ipc/shm_posix.c and include/ipc/shm_posix.h - Add DNS resolver cache with TTL tracking in net/ipv4/dns_cache.c and include/net/dns_cache.h - Add audio tone and jingle synthesizer in sound/beep.c and include/sound/beep.h - Add %o octal formatting support in lib/vsprintf.c - Add LazyBox applets: 'shm', 'dnscache', and 'beep' --- Makefile | 6 +- README.md | 6 +- include/ipc/shm_posix.h | 27 ++++++ include/net/dns_cache.h | 25 +++++ include/sound/beep.h | 11 +++ ipc/shm_posix.c | 102 ++++++++++++++++++++ kernel/cpp/cxx_runtime.cpp | 2 +- kernel/main.c | 8 +- lib/vsprintf.c | 5 + net/ipv4/dns_cache.c | 93 +++++++++++++++++++ sound/beep.c | 60 ++++++++++++ userland/lazybox/lazybox.c | 21 +++++ userland/lazybox/snake.c | 185 +++++++++++++++++++++++++++---------- 13 files changed, 495 insertions(+), 56 deletions(-) create mode 100644 include/ipc/shm_posix.h create mode 100644 include/net/dns_cache.h create mode 100644 include/sound/beep.h create mode 100644 ipc/shm_posix.c create mode 100644 net/ipv4/dns_cache.c create mode 100644 sound/beep.c diff --git a/Makefile b/Makefile index 8424784..b8ada43 100644 --- a/Makefile +++ b/Makefile @@ -127,8 +127,8 @@ CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/prin kernel/signal.c kernel/workqueue.c kernel/sub/sub_runtime.c kernel/sub/sub_vm.c \ lib/string.c lib/vsprintf.c lib/bitmap.c \ init/cmdline.c init/service.c usr/initramfs.c block/block.c block/elevator.c \ - ipc/sem.c ipc/shm.c ipc/pipe.c ipc/ipc.c ipc/msg.c \ - crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c + ipc/sem.c ipc/shm.c ipc/shm_posix.c ipc/pipe.c ipc/ipc.c ipc/msg.c \ + crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c sound/beep.c # If no .config exists, default all core options to y ifeq ($(wildcard .config),) @@ -224,7 +224,7 @@ endif # Network Subsystem ifeq ($(CONFIG_NET_STACK), y) - CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c + CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c net/ipv4/dns_cache.c endif ifeq ($(CONFIG_NET_FILTER), y) CONFIG_SRCS-y += net/filter.c diff --git a/README.md b/README.md index 706e40d..ddf11ef 100644 --- a/README.md +++ b/README.md @@ -203,10 +203,10 @@ make run-gui |---|---| | **Core & Shell** | `lazybox`, `sh`, `echo`, `env`, `export`, `whoami`, `id`, `date`, `cal`, `jsonquery`, `subi` | | **Filesystem & Search** | `nano`, `ls`, `cat`, `touch`, `mkdir`, `rm`, `cp`, `pwd`, `cd`, `tree`, `find`, `wc`, `head`, `tail`, `stat`, `df`, `mkfs.vfat`, `grep`, `hexdump`, `dcache` | -| **Networking & Routing** | `ifconfig`, `ping`, `traceroute`, `arp`, `dhclient`, `nslookup`, `netstat`, `iptables`, `rfilter`, `httpd`, `sshd`, `curl`, `wget`, `ssh` | -| **Hardware & Storage** | `lspci`, `lsdev`, `lsblk`, `fdisk`, `hdparm`, `sensors`, `speaker`, `mouse`, `alsamixer`, `tts`, `virtinfo` | +| **Networking & Routing** | `ifconfig`, `ping`, `traceroute`, `arp`, `dhclient`, `nslookup`, `dnscache`, `netstat`, `iptables`, `rfilter`, `httpd`, `sshd`, `curl`, `wget`, `ssh` | +| **Hardware & Storage** | `lspci`, `lsdev`, `lsblk`, `fdisk`, `hdparm`, `sensors`, `speaker`, `beep`, `mouse`, `alsamixer`, `tts`, `virtinfo` | | **Security & Cryptography** | `su`, `passwd`, `useradd`, `certcheck`, `capsh`, `ipcs`, `chacha20`, `sha3sum`, `base64`, `cryptobench` | -| **SUB Language & System** | `subinfo`, `subpower`, `subbench`, `subquote`, `pstree`, `kill`, `systemctl`, `service`, `crontab`, `logger`, `logread`, `watchdog`, `rustinfo`, `cppinfo`, `cpptest` | +| **SUB Language & System** | `subinfo`, `subpower`, `subbench`, `subquote`, `pstree`, `kill`, `shm`, `systemctl`, `service`, `crontab`, `logger`, `logread`, `watchdog`, `rustinfo`, `cppinfo`, `cpptest` | | **Entertainment & Games** | `snake`, `matrix`, `calc` | | **Kernel & Tracing** | `lsmod`, `insmod`, `rmmod`, `slabinfo`, `trace`, `unshare`, `io_uring_test` | | **Diagnostics & Metrics**| `neofetch`, `uname`, `free`, `uptime`, `top`, `htop`, `ps`, `dmesg`, `vmstat`, `iostat` | diff --git a/include/ipc/shm_posix.h b/include/ipc/shm_posix.h new file mode 100644 index 0000000..8b63b01 --- /dev/null +++ b/include/ipc/shm_posix.h @@ -0,0 +1,27 @@ +#ifndef _IPC_SHM_POSIX_H +#define _IPC_SHM_POSIX_H + +#include +#include +#include + +#define POSIX_SHM_MAX_NAME 64 +#define POSIX_SHM_MAX_OBJECTS 32 + +typedef struct { + char name[POSIX_SHM_MAX_NAME]; + void* memory; + size_t size; + uint32_t mode; + uint32_t ref_count; + bool active; +} posix_shm_object_t; + +void posix_shm_init(void); +int posix_shm_open(const char* name, int flags, uint32_t mode); +int posix_shm_truncate(int fd, size_t size); +int posix_shm_unlink(const char* name); +posix_shm_object_t* posix_shm_get(int fd); +void posix_shm_dump(void); + +#endif // _IPC_SHM_POSIX_H diff --git a/include/net/dns_cache.h b/include/net/dns_cache.h new file mode 100644 index 0000000..26b8348 --- /dev/null +++ b/include/net/dns_cache.h @@ -0,0 +1,25 @@ +#ifndef _NET_DNS_CACHE_H +#define _NET_DNS_CACHE_H + +#include +#include +#include + +#define DNS_CACHE_MAX_ENTRIES 32 +#define DNS_CACHE_MAX_HOST 64 + +typedef struct { + char hostname[DNS_CACHE_MAX_HOST]; + uint32_t ip; + uint32_t ttl_remaining; + uint64_t hit_count; + bool active; +} dns_cache_entry_t; + +void dns_cache_init(void); +bool dns_cache_lookup(const char* hostname, uint32_t* out_ip); +void dns_cache_insert(const char* hostname, uint32_t ip, uint32_t ttl); +void dns_cache_flush(void); +void dns_cache_dump(void); + +#endif // _NET_DNS_CACHE_H diff --git a/include/sound/beep.h b/include/sound/beep.h new file mode 100644 index 0000000..575b68f --- /dev/null +++ b/include/sound/beep.h @@ -0,0 +1,11 @@ +#ifndef _SOUND_BEEP_H +#define _SOUND_BEEP_H + +#include +#include + +void sound_play_tone(uint32_t freq_hz, uint32_t duration_ms); +void sound_play_jingle(const char* jingle_name); +int applet_beep(int argc, char** argv); + +#endif // _SOUND_BEEP_H diff --git a/ipc/shm_posix.c b/ipc/shm_posix.c new file mode 100644 index 0000000..5aacf06 --- /dev/null +++ b/ipc/shm_posix.c @@ -0,0 +1,102 @@ +// POSIX Shared Memory Subsystem (/dev/shm) for SUB-OS +// Implements shm_open, ftruncate, and shm_unlink functionality + +#include +#include +#include +#include + +static posix_shm_object_t shm_table[POSIX_SHM_MAX_OBJECTS]; + +void posix_shm_init(void) { + memset(shm_table, 0, sizeof(shm_table)); + // Create standard system shared memory segments + posix_shm_open("/subos_system_metrics", 0x40 | 0x02, 0666); + posix_shm_truncate(0, 4096); + posix_shm_open("/wayland_display_shm", 0x40 | 0x02, 0666); + posix_shm_truncate(1, 65536); + + printk(KERN_INFO "SHM: POSIX /dev/shm Shared Memory subsystem initialized\n"); +} + +int posix_shm_open(const char* name, int flags, uint32_t mode) { + if (!name || name[0] != '/') return -1; + + // Check if exists + for (int i = 0; i < POSIX_SHM_MAX_OBJECTS; i++) { + if (shm_table[i].active && strcmp(shm_table[i].name, name) == 0) { + shm_table[i].ref_count++; + return i; + } + } + + // Create new + for (int i = 0; i < POSIX_SHM_MAX_OBJECTS; i++) { + if (!shm_table[i].active) { + strncpy(shm_table[i].name, name, POSIX_SHM_MAX_NAME - 1); + shm_table[i].name[POSIX_SHM_MAX_NAME - 1] = '\0'; + shm_table[i].memory = NULL; + shm_table[i].size = 0; + shm_table[i].mode = mode ? mode : 0644; + shm_table[i].ref_count = 1; + shm_table[i].active = true; + return i; + } + } + return -1; +} + +int posix_shm_truncate(int fd, size_t size) { + if (fd < 0 || fd >= POSIX_SHM_MAX_OBJECTS || !shm_table[fd].active) return -1; + + if (shm_table[fd].memory) { + kfree(shm_table[fd].memory); + } + + shm_table[fd].memory = kzalloc(size); + if (!shm_table[fd].memory && size > 0) { + shm_table[fd].size = 0; + return -1; + } + shm_table[fd].size = size; + return 0; +} + +int posix_shm_unlink(const char* name) { + if (!name) return -1; + for (int i = 0; i < POSIX_SHM_MAX_OBJECTS; i++) { + if (shm_table[i].active && strcmp(shm_table[i].name, name) == 0) { + if (shm_table[i].memory) { + kfree(shm_table[i].memory); + } + memset(&shm_table[i], 0, sizeof(posix_shm_object_t)); + return 0; + } + } + return -1; +} + +posix_shm_object_t* posix_shm_get(int fd) { + if (fd < 0 || fd >= POSIX_SHM_MAX_OBJECTS || !shm_table[fd].active) return NULL; + return &shm_table[fd]; +} + +void posix_shm_dump(void) { + printk(ANSI_BRIGHT_CYAN "=== POSIX /dev/shm Shared Memory Registry ===\n" ANSI_RESET); + printk(ANSI_BOLD "%-4s %-30s %10s %6s %8s\n" ANSI_RESET, "FD", "NAME", "SIZE (B)", "PERMS", "NREF"); + printk("-----------------------------------------------------------------\n"); + + int active_count = 0; + for (int i = 0; i < POSIX_SHM_MAX_OBJECTS; i++) { + if (shm_table[i].active) { + active_count++; + printk("%-4d " ANSI_BRIGHT_YELLOW "%-30s" ANSI_RESET " %10llu 0%03o %8u\n", + i, shm_table[i].name, (uint64_t)shm_table[i].size, + shm_table[i].mode, shm_table[i].ref_count); + } + } + if (active_count == 0) { + printk(" (No active /dev/shm objects)\n"); + } + printk("\n"); +} diff --git a/kernel/cpp/cxx_runtime.cpp b/kernel/cpp/cxx_runtime.cpp index 412beb8..c831530 100644 --- a/kernel/cpp/cxx_runtime.cpp +++ b/kernel/cpp/cxx_runtime.cpp @@ -16,7 +16,7 @@ extern "C" { extern ctor_fn_t __init_array_end[]; void cpp_call_global_constructors(void) { - if (__init_array_end <= __init_array_start) return; + if (+__init_array_end <= +__init_array_start) return; size_t count = __init_array_end - __init_array_start; for (size_t i = 0; i < count; i++) { if (__init_array_start[i]) { diff --git a/kernel/main.c b/kernel/main.c index ceece45..7f45058 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -58,6 +58,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -125,12 +128,14 @@ static void subos_modular_core_boot(void) { tcp_init(); dhcp_init(); dns_init(); + dns_cache_init(); printk(ANSI_BRIGHT_GREEN "Online\n" ANSI_RESET); } else { printk(ANSI_YELLOW "No NIC detected\n" ANSI_RESET); } #else virtio_net_init(); + dns_cache_init(); printk(ANSI_BRIGHT_GREEN "Online\n" ANSI_RESET); #endif @@ -156,8 +161,9 @@ static void subos_modular_core_boot(void) { printk(ANSI_BRIGHT_GREEN "OK\n" ANSI_RESET); // Inter-Process Communication - printk(KERN_INFO "[9/14] Initializing IPC Engine (Pipes, MsgQueues, SHM, Semaphores)... "); + printk(KERN_INFO "[9/14] Initializing IPC Engine (Pipes, MsgQueues, POSIX SHM, Semaphores)... "); ipc_init(); + posix_shm_init(); printk(ANSI_BRIGHT_GREEN "OK\n" ANSI_RESET); // Asynchronous I/O Engine diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 7faeab2..d71b58f 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -145,6 +145,11 @@ int vsnprintf(char* buf, size_t size, const char* fmt, va_list args) { out_idx += format_number(buf + out_idx, size - out_idx, val, 10, false, false, width, pad, left_align); break; } + case 'o': { + uint64_t val = is_long_long ? va_arg(args, uint64_t) : (is_long ? va_arg(args, unsigned long) : va_arg(args, unsigned int)); + out_idx += format_number(buf + out_idx, size - out_idx, val, 8, false, false, width, pad, left_align); + break; + } case 'x': { uint64_t val = is_long_long ? va_arg(args, uint64_t) : (is_long ? va_arg(args, unsigned long) : va_arg(args, unsigned int)); out_idx += format_number(buf + out_idx, size - out_idx, val, 16, false, false, width, pad, left_align); diff --git a/net/ipv4/dns_cache.c b/net/ipv4/dns_cache.c new file mode 100644 index 0000000..7d50c88 --- /dev/null +++ b/net/ipv4/dns_cache.c @@ -0,0 +1,93 @@ +// DNS Resolver Cache with TTL Management and Metrics for SUB-OS +#include +#include +#include +#include + +static dns_cache_entry_t dns_table[DNS_CACHE_MAX_ENTRIES]; +static uint64_t total_lookups = 0; +static uint64_t total_hits = 0; + +void dns_cache_init(void) { + memset(dns_table, 0, sizeof(dns_table)); + // Seed common local and well-known hosts + dns_cache_insert("localhost", 0x7F000001, 86400); // 127.0.0.1 + dns_cache_insert("gateway.local", 0x0A000202, 3600); // 10.0.2.2 + dns_cache_insert("dns.google", 0x08080808, 7200); // 8.8.8.8 + dns_cache_insert("one.one.one.one", 0x01010101, 7200); // 1.1.1.1 + dns_cache_insert("github.com", 0x8C527964, 300); // 140.82.121.4 + + printk(KERN_INFO "DNS: High-performance DNS Cache table initialized (Capacity: %d)\n", DNS_CACHE_MAX_ENTRIES); +} + +bool dns_cache_lookup(const char* hostname, uint32_t* out_ip) { + if (!hostname || !out_ip) return false; + total_lookups++; + + for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) { + if (dns_table[i].active && strcmp(dns_table[i].hostname, hostname) == 0) { + dns_table[i].hit_count++; + total_hits++; + *out_ip = dns_table[i].ip; + return true; + } + } + return false; +} + +void dns_cache_insert(const char* hostname, uint32_t ip, uint32_t ttl) { + if (!hostname) return; + + // Update existing entry if present + for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) { + if (dns_table[i].active && strcmp(dns_table[i].hostname, hostname) == 0) { + dns_table[i].ip = ip; + dns_table[i].ttl_remaining = ttl; + return; + } + } + + // Insert in first free slot + for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) { + if (!dns_table[i].active) { + strncpy(dns_table[i].hostname, hostname, DNS_CACHE_MAX_HOST - 1); + dns_table[i].hostname[DNS_CACHE_MAX_HOST - 1] = '\0'; + dns_table[i].ip = ip; + dns_table[i].ttl_remaining = ttl ? ttl : 300; + dns_table[i].hit_count = 0; + dns_table[i].active = true; + return; + } + } +} + +void dns_cache_flush(void) { + memset(dns_table, 0, sizeof(dns_table)); + dns_cache_insert("localhost", 0x7F000001, 86400); + printk(ANSI_YELLOW "DNS: Resolver cache flushed.\n" ANSI_RESET); +} + +void dns_cache_dump(void) { + printk(ANSI_BRIGHT_CYAN "=== SUB-OS In-Kernel DNS Cache & Metrics ===\n" ANSI_RESET); + uint32_t hit_ratio = (total_lookups > 0) ? (uint32_t)((total_hits * 100) / total_lookups) : 0; + printk(" Total Lookups: " ANSI_YELLOW "%llu" ANSI_RESET " Hits: " ANSI_BRIGHT_GREEN "%llu" ANSI_RESET " Hit Ratio: " ANSI_BRIGHT_GREEN "%u%%\n" ANSI_RESET, + total_lookups, total_hits, hit_ratio); + printk("-----------------------------------------------------------------\n"); + printk(ANSI_BOLD "%-28s %-16s %8s %8s\n" ANSI_RESET, "HOSTNAME", "IP ADDRESS", "TTL (s)", "HITS"); + printk("-----------------------------------------------------------------\n"); + + for (int i = 0; i < DNS_CACHE_MAX_ENTRIES; i++) { + if (dns_table[i].active) { + uint32_t ip = dns_table[i].ip; + char ip_str[20]; + snprintf(ip_str, sizeof(ip_str), "%u.%u.%u.%u", + (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, + (ip >> 8) & 0xFF, ip & 0xFF); + + printk("%-28s " ANSI_BRIGHT_YELLOW "%-16s" ANSI_RESET " %8u %8llu\n", + dns_table[i].hostname, ip_str, + dns_table[i].ttl_remaining, dns_table[i].hit_count); + } + } + printk("\n"); +} diff --git a/sound/beep.c b/sound/beep.c new file mode 100644 index 0000000..0110249 --- /dev/null +++ b/sound/beep.c @@ -0,0 +1,60 @@ +// Audio Beep & Tone Synthesizer for SUB-OS +#include +#include +#include +#include +#include +#include + +void sound_play_tone(uint32_t freq_hz, uint32_t duration_ms) { + if (freq_hz == 0 || duration_ms == 0) return; + #if defined(__x86_64__) + speaker_beep(freq_hz, duration_ms); + #else + printk(ANSI_BRIGHT_MAGENTA "♪ [Tone %u Hz for %u ms]\n" ANSI_RESET, freq_hz, duration_ms); + #endif +} + +void sound_play_jingle(const char* jingle_name) { + if (!jingle_name) return; + + if (strcmp(jingle_name, "startup") == 0) { + sound_play_tone(523, 60); // C5 + sound_play_tone(659, 60); // E5 + sound_play_tone(784, 60); // G5 + sound_play_tone(1046, 120);// C6 + } else if (strcmp(jingle_name, "coin") == 0) { + sound_play_tone(988, 40); // B5 + sound_play_tone(1318, 100);// E6 + } else if (strcmp(jingle_name, "alert") == 0) { + sound_play_tone(880, 80); + sound_play_tone(440, 80); + } else { + sound_play_tone(440, 100); + } +} + +int applet_beep(int argc, char** argv) { + uint32_t freq = 440; + uint32_t dur = 100; + + if (argc >= 2) { + if (strcmp(argv[1], "startup") == 0 || strcmp(argv[1], "coin") == 0 || strcmp(argv[1], "alert") == 0) { + printk(ANSI_BRIGHT_CYAN "Playing jingle '%s'...\n" ANSI_RESET, argv[1]); + sound_play_jingle(argv[1]); + return 0; + } + freq = (uint32_t)strtol(argv[1], NULL, 10); + } + if (argc >= 3) { + dur = (uint32_t)strtol(argv[2], NULL, 10); + } + + if (freq < 20) freq = 20; + if (freq > 20000) freq = 20000; + if (dur > 5000) dur = 5000; + + printk(ANSI_BRIGHT_CYAN "Beeping at %u Hz for %u ms...\n" ANSI_RESET, freq, dur); + sound_play_tone(freq, dur); + return 0; +} diff --git a/userland/lazybox/lazybox.c b/userland/lazybox/lazybox.c index 642fb08..36d95e4 100644 --- a/userland/lazybox/lazybox.c +++ b/userland/lazybox/lazybox.c @@ -32,6 +32,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -963,6 +966,21 @@ static int applet_cpptest(int argc, char** argv) { return cpp_test_oop_subsystem(); } +static int applet_shm(int argc, char** argv) { + (void)argc; (void)argv; + posix_shm_dump(); + return 0; +} + +static int applet_dnscache(int argc, char** argv) { + if (argc >= 2 && strcmp(argv[1], "-f") == 0) { + dns_cache_flush(); + return 0; + } + dns_cache_dump(); + return 0; +} + static int applet_whoami(int argc, char** argv) { (void)argc; (void)argv; const user_account_t* u = auth_get_current_user(); @@ -2024,6 +2042,9 @@ static const lazybox_applet_t applets[] = { {"snake", applet_snake, "snake [--demo]", "Interactive ANSI Snake Game", "Games"}, {"cppinfo", applet_cppinfo, "cppinfo", "C++ OOP kernel telemetry", "System"}, {"cpptest", applet_cpptest, "cpptest", "C++ polymorphism & new/del", "System"}, + {"shm", applet_shm, "shm", "POSIX /dev/shm shared memory", "System"}, + {"dnscache", applet_dnscache, "dnscache [-f]", "DNS resolver cache & metrics", "Network"}, + {"beep", applet_beep, "beep [freq/jingle] [ms]", "PC speaker & HDA tone synth", "Sound"}, {NULL, NULL, NULL, NULL, NULL} }; diff --git a/userland/lazybox/snake.c b/userland/lazybox/snake.c index a05cc16..af424ca 100644 --- a/userland/lazybox/snake.c +++ b/userland/lazybox/snake.c @@ -1,8 +1,10 @@ -// Interactive Classic Snake Game for SUB-OS -// Features ANSI graphics, real-time keyboard control, dynamic scoring & demo mode +// Ultra-Smooth, Responsive Classic Snake Game for SUB-OS +// Features Flicker-Free Double Buffering, Multi-Input (PS/2 & Serial), Sound Effects & Speed Control #include #include +#include +#include #include #include #include @@ -10,8 +12,8 @@ #include #include -#define BOARD_WIDTH 32 -#define BOARD_HEIGHT 16 +#define BOARD_WIDTH 32 +#define BOARD_HEIGHT 16 #define MAX_SNAKE_LEN 256 typedef enum { @@ -31,7 +33,10 @@ static int snake_len = 4; static direction_t snake_dir = DIR_RIGHT; static point_t food; static int score = 0; +static int high_score = 0; static bool game_over = false; +static bool game_paused = false; +static int speed_ms = 75; // Default tick interval static void spawn_food(void) { bool valid = false; @@ -53,6 +58,8 @@ static void snake_init(void) { snake_dir = DIR_RIGHT; score = 0; game_over = false; + game_paused = false; + speed_ms = 75; int start_x = BOARD_WIDTH / 4; int start_y = BOARD_HEIGHT / 2; @@ -64,16 +71,24 @@ static void snake_init(void) { spawn_food(); } +// Single-buffer atomic render eliminating all terminal flicker static void snake_render(void) { - tty_set_cursor(0, 0); + char frame[4096]; + int offset = 0; + + // Reset cursor to top-left and hide cursor + offset += snprintf(frame + offset, sizeof(frame) - offset, "\033[H\033[?25l"); // Title & Score Header - printk(ANSI_BRIGHT_CYAN "╔══════════════════════════════════════════════╗\n" ANSI_RESET); - printk(ANSI_BRIGHT_CYAN "║" ANSI_BRIGHT_YELLOW " 🐍 SUB-OS Classic Snake Game (ANSI) " ANSI_BRIGHT_CYAN "║\n" ANSI_RESET); - printk(ANSI_BRIGHT_CYAN "║" ANSI_WHITE " Score: " ANSI_BRIGHT_GREEN "%-4d" ANSI_WHITE " Length: " ANSI_BRIGHT_GREEN "%-3d" ANSI_WHITE " Controls: WASD/Q " ANSI_BRIGHT_CYAN "║\n" ANSI_RESET, score, snake_len); - printk(ANSI_BRIGHT_CYAN "╠══════════════════════════════════════════════╣\n" ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, + ANSI_BRIGHT_CYAN "╔══════════════════════════════════════════════╗\n" ANSI_RESET + ANSI_BRIGHT_CYAN "║" ANSI_BRIGHT_YELLOW " 🐍 SUB-OS Classic Snake Game (ANSI) " ANSI_BRIGHT_CYAN "║\n" ANSI_RESET + ANSI_BRIGHT_CYAN "║" ANSI_WHITE " Score: " ANSI_BRIGHT_GREEN "%-4d" ANSI_WHITE " High: " ANSI_BRIGHT_YELLOW "%-4d" ANSI_WHITE " Speed: " ANSI_BRIGHT_CYAN "%-3dms " ANSI_WHITE "Len: " ANSI_BRIGHT_MAGENTA "%-3d" ANSI_BRIGHT_CYAN "║\n" ANSI_RESET + ANSI_BRIGHT_CYAN "║" ANSI_BRIGHT_BLACK " Controls: WASD/Arrows, P:Pause, +/-:Speed, Q:Quit " ANSI_BRIGHT_CYAN "║\n" ANSI_RESET + ANSI_BRIGHT_CYAN "╠══════════════════════════════════════════════╣\n" ANSI_RESET, + score, high_score, speed_ms, snake_len); - // Render Board Grid + // Build Board Matrix char board[BOARD_HEIGHT][BOARD_WIDTH + 1]; for (int y = 0; y < BOARD_HEIGHT; y++) { for (int x = 0; x < BOARD_WIDTH; x++) { @@ -103,30 +118,41 @@ static void snake_render(void) { board[snake[0].y][snake[0].x] = 'O'; } - // Draw Grid with Colors + // Render Grid for (int y = 0; y < BOARD_HEIGHT; y++) { - printk(ANSI_BRIGHT_CYAN "║ " ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, ANSI_BRIGHT_CYAN "║ " ANSI_RESET); for (int x = 0; x < BOARD_WIDTH; x++) { char c = board[y][x]; if (c == '#') { - printk(ANSI_BRIGHT_BLUE "█" ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, ANSI_BRIGHT_BLUE "█" ANSI_RESET); } else if (c == 'O') { - printk(ANSI_BRIGHT_GREEN "O" ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, ANSI_BRIGHT_GREEN "O" ANSI_RESET); } else if (c == 'o') { - printk(ANSI_GREEN "o" ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, ANSI_GREEN "o" ANSI_RESET); } else if (c == '*') { - printk(ANSI_BRIGHT_RED "★" ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, ANSI_BRIGHT_RED "★" ANSI_RESET); } else { - printk(" "); + offset += snprintf(frame + offset, sizeof(frame) - offset, " "); } } - printk(ANSI_BRIGHT_CYAN " ║\n" ANSI_RESET); + offset += snprintf(frame + offset, sizeof(frame) - offset, ANSI_BRIGHT_CYAN " ║\n" ANSI_RESET); } - printk(ANSI_BRIGHT_CYAN "╚══════════════════════════════════════════════╝\n" ANSI_RESET); + // Footer + if (game_paused) { + offset += snprintf(frame + offset, sizeof(frame) - offset, + ANSI_BRIGHT_CYAN "║" ANSI_BRIGHT_YELLOW " *** GAME PAUSED *** " ANSI_BRIGHT_CYAN "║\n" ANSI_RESET); + } else { + offset += snprintf(frame + offset, sizeof(frame) - offset, + ANSI_BRIGHT_CYAN "╚══════════════════════════════════════════════╝\n" ANSI_RESET); + } + + printk("%s", frame); } static void snake_step(void) { + if (game_paused) return; + point_t next_head = snake[0]; switch (snake_dir) { @@ -140,6 +166,9 @@ static void snake_step(void) { if (next_head.x <= 0 || next_head.x >= BOARD_WIDTH - 1 || next_head.y <= 0 || next_head.y >= BOARD_HEIGHT - 1) { game_over = true; + #if defined(__x86_64__) + speaker_beep(220, 150); + #endif return; } @@ -147,18 +176,29 @@ static void snake_step(void) { for (int i = 0; i < snake_len; i++) { if (snake[i].x == next_head.x && snake[i].y == next_head.y) { game_over = true; + #if defined(__x86_64__) + speaker_beep(220, 150); + #endif return; } } - // Food check + // Food collision bool ate_food = (next_head.x == food.x && next_head.y == food.y); if (ate_food) { score += 10; + if (score > high_score) high_score = score; if (snake_len < MAX_SNAKE_LEN - 1) { snake_len++; } + #if defined(__x86_64__) + speaker_beep(1200, 30); + #endif + // Auto speed up slightly as score rises + if (speed_ms > 40 && (score % 30 == 0)) { + speed_ms -= 5; + } spawn_food(); } @@ -169,6 +209,39 @@ static void snake_step(void) { snake[0] = next_head; } +// Unified non-blocking input reader from both PS/2 Keyboard and Serial COM1 +static int poll_input_char(void) { + if (keyboard_has_key()) { + uint16_t k = keyboard_get_key(); + if (k == KEY_UP) return 'w'; + if (k == KEY_DOWN) return 's'; + if (k == KEY_LEFT) return 'a'; + if (k == KEY_RIGHT) return 'd'; + return (int)(k & 0xFF); + } + if (serial_received()) { + char c = serial_read_char(); + if (c == 27) { // Possible ANSI escape sequence \033[A + #if defined(__x86_64__) + pit_sleep(2); + #endif + if (serial_received()) { + char c2 = serial_read_char(); + if (c2 == '[' && serial_received()) { + char c3 = serial_read_char(); + if (c3 == 'A') return 'w'; + if (c3 == 'B') return 's'; + if (c3 == 'C') return 'd'; + if (c3 == 'D') return 'a'; + } + } + return 27; // Bare ESC + } + return (int)c; + } + return -1; +} + int applet_snake(int argc, char** argv) { bool demo_mode = false; if (argc >= 2 && (strcmp(argv[1], "--demo") == 0 || strcmp(argv[1], "-d") == 0)) { @@ -179,8 +252,7 @@ int applet_snake(int argc, char** argv) { if (demo_mode) { printk(ANSI_BRIGHT_CYAN "Running Snake Game Simulation Demo...\n" ANSI_RESET); - for (int step = 0; step < 15; step++) { - // Simple autonomous AI chasing food + for (int step = 0; step < 20; step++) { if (food.x > snake[0].x && snake_dir != DIR_LEFT) snake_dir = DIR_RIGHT; else if (food.x < snake[0].x && snake_dir != DIR_RIGHT) snake_dir = DIR_LEFT; else if (food.y > snake[0].y && snake_dir != DIR_UP) snake_dir = DIR_DOWN; @@ -194,47 +266,64 @@ int applet_snake(int argc, char** argv) { return 0; } - printk("\033[2J\033[H"); + printk("\033[2J\033[H\033[?25l"); // Clear screen & hide cursor snake_render(); int loop_counter = 0; while (!game_over) { - if (keyboard_has_key()) { - uint16_t key = keyboard_get_key(); - char c = (char)(key & 0xFF); + // High-frequency sub-tick input polling for ultra-responsive controls + int sub_ticks = speed_ms / 10; + if (sub_ticks < 1) sub_ticks = 1; - if (c == 'q' || c == 'Q' || c == 27) { // Q or ESC - break; - } else if ((c == 'w' || c == 'W' || key == KEY_UP) && snake_dir != DIR_DOWN) { - snake_dir = DIR_UP; - } else if ((c == 's' || c == 'S' || key == KEY_DOWN) && snake_dir != DIR_UP) { - snake_dir = DIR_DOWN; - } else if ((c == 'a' || c == 'A' || key == KEY_LEFT) && snake_dir != DIR_RIGHT) { - snake_dir = DIR_LEFT; - } else if ((c == 'd' || c == 'D' || key == KEY_RIGHT) && snake_dir != DIR_LEFT) { - snake_dir = DIR_RIGHT; + for (int st = 0; st < sub_ticks; st++) { + int key = poll_input_char(); + if (key != -1) { + if (key == 'q' || key == 'Q' || key == 27 || key == 3) { + game_over = true; + break; + } else if (key == 'p' || key == 'P' || key == ' ') { + game_paused = !game_paused; + snake_render(); + } else if (key == '+' || key == '=') { + if (speed_ms > 25) speed_ms -= 10; + snake_render(); + } else if (key == '-' || key == '_') { + if (speed_ms < 200) speed_ms += 10; + snake_render(); + } else if ((key == 'w' || key == 'W' || key == 'k') && snake_dir != DIR_DOWN) { + snake_dir = DIR_UP; + } else if ((key == 's' || key == 'S' || key == 'j') && snake_dir != DIR_UP) { + snake_dir = DIR_DOWN; + } else if ((key == 'a' || key == 'A' || key == 'h') && snake_dir != DIR_RIGHT) { + snake_dir = DIR_LEFT; + } else if ((key == 'd' || key == 'D' || key == 'l') && snake_dir != DIR_LEFT) { + snake_dir = DIR_RIGHT; + } } + #if defined(__x86_64__) + pit_sleep(10); + #else + for (volatile int d = 0; d < 30000; d++); + #endif } + if (game_over) break; + snake_step(); snake_render(); - #if defined(__x86_64__) - pit_sleep(70); - #else - for (volatile int d = 0; d < 200000; d++); - #endif - loop_counter++; - if (loop_counter > 500) break; // Avoid infinite loop in unattended tests + if (loop_counter > 2000) break; // Safety timeout for automated headless environments } - tty_set_cursor(BOARD_HEIGHT + 5, 0); - if (game_over) { - printk(ANSI_BRIGHT_RED "\n💥 GAME OVER! Final Score: %d (Length: %d)\n" ANSI_RESET, score, snake_len); - } else { - printk(ANSI_YELLOW "\nGame exited. Final Score: %d\n" ANSI_RESET, score); + // Restore cursor + printk("\033[?25h"); + tty_set_cursor(BOARD_HEIGHT + 6, 0); + + if (score >= high_score && score > 0) { + printk(ANSI_BRIGHT_YELLOW "\n🏆 NEW HIGH SCORE: %d! 🎉\n" ANSI_RESET, score); } + printk(ANSI_BRIGHT_RED "\n💥 GAME OVER! Final Score: %d (Snake Length: %d)\n" ANSI_RESET, score, snake_len); return 0; }