mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-26 09:02:46 +00:00
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'
This commit is contained in:
1 parent
7c510dae24
commit
6f7f60bf53
13 files changed
+495
-56
No files matched your search
+137
-48
@@ -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 <userland/snake.h>
|
||||
#include <drivers/keyboard.h>
|
||||
#include <drivers/serial.h>
|
||||
#include <drivers/speaker.h>
|
||||
#include <drivers/tty.h>
|
||||
#include <arch/x86_64/pit.h>
|
||||
#include <crypto/crypto.h>
|
||||
@@ -10,8 +12,8 @@
|
||||
#include <lib/printf.h>
|
||||
#include <kernel/printk.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in new issue
Block a user