From 914c60e1f19fb11f29f19519a2eb4501bcf2d2af Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:58:08 +0200 Subject: [PATCH 1/3] Adding 2048.c --- src/userland/games/2048.c | 519 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 src/userland/games/2048.c diff --git a/src/userland/games/2048.c b/src/userland/games/2048.c new file mode 100644 index 0000000..3700d25 --- /dev/null +++ b/src/userland/games/2048.c @@ -0,0 +1,519 @@ +#include "libc/syscall.h" +#include "libc/libui.h" +#include "libc/stdlib.h" +#include +#include +#include + +/* +@Lluciocc +2048 for BoredOS +Controls: +- WASD keys or arrow keys or numpad keys to move +- R key to restart +*/ + +#define WINDOW_W 300 +#define WINDOW_H 430 + +#define BOARD_SIZE 4 +#define TILE_SIZE 56 +#define TILE_GAP 8 +#define BOARD_X 18 +#define BOARD_Y 96 + +#define BTN_W 54 +#define BTN_H 30 + +#define COLOR_BG 0xFF121212 +#define COLOR_PANEL 0xFF202020 +#define COLOR_PANEL_2 0xFF2A2A2A +#define COLOR_BORDER 0xFF3D3D3D +#define COLOR_TEXT 0xFFF2F2F2 +#define COLOR_TEXT_DARK 0xFF202020 +#define COLOR_MUTED 0xFFBBBBBB +#define COLOR_ACCENT 0xFF6EA8FE +#define COLOR_GREEN 0xFF69DB7C +#define COLOR_RED 0xFFFF6B6B +#define COLOR_EMPTY_TILE 0xFF2D2D2D + +static int board[BOARD_SIZE][BOARD_SIZE]; +static int score = 0; +static int best_tile = 0; +static bool game_over = false; +static bool game_won = false; +static bool has_moved_last_turn = false; + +static uint32_t random_seed = 0xC0FFEE12u; + +static uint32_t random_next(void) { + random_seed = random_seed * 1664525u + 1013904223u; + return random_seed; +} + +static int max_int(int a, int b) { + return (a > b) ? a : b; +} + +static void clear_board(void) { + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + board[y][x] = 0; + } + } +} + +static int count_empty_cells(void) { + int count = 0; + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + if (board[y][x] == 0) { + count++; + } + } + } + return count; +} + +static void add_random_tile(void) { + int empty_count = count_empty_cells(); + if (empty_count <= 0) { + return; + } + + int pick = (int)(random_next() % (uint32_t)empty_count); + + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + if (board[y][x] == 0) { + if (pick == 0) { + /* 90% chance of a 2, 10% chance of a 4 */ + board[y][x] = ((random_next() % 10u) == 0u) ? 4 : 2; + best_tile = max_int(best_tile, board[y][x]); + return; + } + pick--; + } + } + } +} + +static bool can_make_any_move(void) { + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + if (board[y][x] == 0) { + return true; + } + if (x + 1 < BOARD_SIZE && board[y][x] == board[y][x + 1]) { + return true; + } + if (y + 1 < BOARD_SIZE && board[y][x] == board[y + 1][x]) { + return true; + } + } + } + return false; +} + +static void refresh_end_state(void) { + game_won = false; + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + if (board[y][x] >= 2048) { + game_won = true; + } + if (board[y][x] > best_tile) { + best_tile = board[y][x]; + } + } + } + + game_over = !can_make_any_move(); +} + +static void init_game(void) { + clear_board(); + score = 0; + best_tile = 0; + game_over = false; + game_won = false; + has_moved_last_turn = false; + + add_random_tile(); + add_random_tile(); + refresh_end_state(); +} + +static void copy_line_from_row(int row, int out[BOARD_SIZE]) { + for (int i = 0; i < BOARD_SIZE; i++) { + out[i] = board[row][i]; + } +} + +static void copy_line_to_row(int row, const int in[BOARD_SIZE]) { + for (int i = 0; i < BOARD_SIZE; i++) { + board[row][i] = in[i]; + } +} + +static void copy_line_from_col(int col, int out[BOARD_SIZE]) { + for (int i = 0; i < BOARD_SIZE; i++) { + out[i] = board[i][col]; + } +} + +static void copy_line_to_col(int col, const int in[BOARD_SIZE]) { + for (int i = 0; i < BOARD_SIZE; i++) { + board[i][col] = in[i]; + } +} + +static void reverse_line(int line[BOARD_SIZE]) { + for (int i = 0; i < BOARD_SIZE / 2; i++) { + int tmp = line[i]; + line[i] = line[BOARD_SIZE - 1 - i]; + line[BOARD_SIZE - 1 - i] = tmp; + } +} + +static bool slide_and_merge_line_left(int line[BOARD_SIZE]) { + int compact[BOARD_SIZE]; + int merged[BOARD_SIZE]; + int compact_len = 0; + int write_index = 0; + bool changed = false; + + for (int i = 0; i < BOARD_SIZE; i++) { + merged[i] = 0; + if (line[i] != 0) { + compact[compact_len++] = line[i]; + } + } + + for (int i = 0; i < compact_len; i++) { + if (i + 1 < compact_len && compact[i] == compact[i + 1]) { + int value = compact[i] * 2; + merged[write_index++] = value; + score += value; + best_tile = max_int(best_tile, value); + i++; /* Skip the second tile, it has been merged. */ + } else { + merged[write_index++] = compact[i]; + } + } + + while (write_index < BOARD_SIZE) { + merged[write_index++] = 0; + } + + for (int i = 0; i < BOARD_SIZE; i++) { + if (line[i] != merged[i]) { + changed = true; + } + line[i] = merged[i]; + } + + return changed; +} + +typedef enum { + MOVE_LEFT, + MOVE_RIGHT, + MOVE_UP, + MOVE_DOWN +} move_dir_t; + +static bool apply_move(move_dir_t dir) { + bool changed = false; + + if (game_over) { + return false; + } + + if (dir == MOVE_LEFT || dir == MOVE_RIGHT) { + for (int row = 0; row < BOARD_SIZE; row++) { + int line[BOARD_SIZE]; + copy_line_from_row(row, line); + + if (dir == MOVE_RIGHT) { + reverse_line(line); + } + + if (slide_and_merge_line_left(line)) { + changed = true; + } + + if (dir == MOVE_RIGHT) { + reverse_line(line); + } + + copy_line_to_row(row, line); + } + } else { + for (int col = 0; col < BOARD_SIZE; col++) { + int line[BOARD_SIZE]; + copy_line_from_col(col, line); + + if (dir == MOVE_DOWN) { + reverse_line(line); + } + + if (slide_and_merge_line_left(line)) { + changed = true; + } + + if (dir == MOVE_DOWN) { + reverse_line(line); + } + + copy_line_to_col(col, line); + } + } + + if (changed) { + add_random_tile(); + } + + has_moved_last_turn = changed; + refresh_end_state(); + return changed; +} + +static uint32_t get_tile_color(int value) { + switch (value) { + case 0: return COLOR_EMPTY_TILE; + case 2: return 0xFFEEE4DA; + case 4: return 0xFFEDE0C8; + case 8: return 0xFFF2B179; + case 16: return 0xFFF59563; + case 32: return 0xFFF67C5F; + case 64: return 0xFFF65E3B; + case 128: return 0xFFEDCF72; + case 256: return 0xFFEDCC61; + case 512: return 0xFFEDC850; + case 1024: return 0xFFEDC53F; + case 2048: return 0xFFEDC22E; + default: return 0xFF3C91E6; + } +} + +static uint32_t get_tile_text_color(int value) { + (void)value; + return 0xFF000000; // for visibility +} + +static void int_to_text(int value, char *out) { + itoa(value, out); +} + +static void draw_centered_text(ui_window_t win, int x, int y, int w, int h, + const char *text, uint32_t color, float scale) { + (void)scale; + + uint32_t text_w = ui_get_string_width(text); + uint32_t text_h = ui_get_font_height(); + + int draw_x = x + (w - (int)text_w) / 2; + int draw_y = y + (h - (int)text_h) / 2; + + ui_draw_string(win, draw_x, draw_y, text, color); +} + +static void draw_button(ui_window_t win, int x, int y, int w, int h, + const char *label, uint32_t color) { + ui_draw_rounded_rect_filled(win, x, y, w, h, 6, color); + draw_centered_text(win, x, y, w, h, label, COLOR_TEXT, 1.0f); +} + +static void draw_score_box(ui_window_t win, int x, int y, int w, int h, + const char *title, int value) { + char buf[16]; + int_to_text(value, buf); + + ui_draw_rounded_rect_filled(win, x, y, w, h, 8, COLOR_PANEL_2); + draw_centered_text(win, x, y + 3, w, 14, title, COLOR_MUTED, 0.8f); + draw_centered_text(win, x, y + 16, w, 18, buf, COLOR_TEXT, 1.0f); +} + +static void draw_tile(ui_window_t win, int x, int y, int value) { + char buf[16]; + float scale = 1.6f; + + ui_draw_rounded_rect_filled(win, x, y, TILE_SIZE, TILE_SIZE, 8, get_tile_color(value)); + + if (value == 0) { + return; + } + + int_to_text(value, buf); + + if (value >= 1000) { + scale = 1.2f; + } else if (value >= 100) { + scale = 1.4f; + } + + draw_centered_text( + win, + x, y, + TILE_SIZE, TILE_SIZE, + buf, + 0xFF000000, + scale + ); +} + +static void draw_board(ui_window_t win) { + int board_w = BOARD_SIZE * TILE_SIZE + (BOARD_SIZE + 1) * TILE_GAP; + int board_h = board_w; + + ui_draw_rounded_rect_filled(win, BOARD_X, BOARD_Y, board_w, board_h, 10, COLOR_PANEL_2); + + for (int y = 0; y < BOARD_SIZE; y++) { + for (int x = 0; x < BOARD_SIZE; x++) { + int px = BOARD_X + TILE_GAP + x * (TILE_SIZE + TILE_GAP); + int py = BOARD_Y + TILE_GAP + y * (TILE_SIZE + TILE_GAP); + draw_tile(win, px, py, board[y][x]); + } + } +} + +static void draw_status(ui_window_t win) { + if (game_over) { + ui_draw_string(win, 18, 70, "Game over", COLOR_RED); + } else if (game_won) { + ui_draw_string(win, 18, 70, "2048 reached - keep going", COLOR_GREEN); + } else { + ui_draw_string(win, 18, 70, "Combine tiles to reach 2048", COLOR_MUTED); + } +} + +static void game_paint(ui_window_t win) { + ui_draw_rect(win, 0, 0, WINDOW_W, WINDOW_H, COLOR_BG); + + ui_draw_string_scaled(win, 18, 12, "2048", COLOR_TEXT, 1.6f); + ui_draw_string(win, 18, 42, "Use WASD keys", COLOR_MUTED); + + draw_score_box(win, 155, 14, 56, 40, "SCORE", score); + draw_score_box(win, 220, 14, 62, 40, "BEST", best_tile); + draw_status(win); + + draw_board(win); + /* Not recommended to use + draw_button(win, 18, 352, 86, 30, "Restart", COLOR_ACCENT); + draw_button(win, 123, 352, BTN_W, BTN_H, "Left", COLOR_BORDER); + draw_button(win, 186, 352, BTN_W, BTN_H, "Right", COLOR_BORDER); + draw_button(win, 92, 390, BTN_W, BTN_H, "Up", COLOR_BORDER); + draw_button(win, 155, 390, BTN_W, BTN_H, "Down", COLOR_BORDER); + */ +} + +static bool point_in_rect(int px, int py, int x, int y, int w, int h) { + return px >= x && px < x + w && py >= y && py < y + h; +} + +/* +37 = left arrow +38 = up arrow +39 = right arrow +40 = down arrow +72 = numpad 8 +75 = numpad 4 +77 = numpad 6 +80 = numpad 2 +key = letter +*/ +static bool is_left_key(int key) { + return key == 'a' || key == 'A' || key == 37 || key == 75; +} + +static bool is_right_key(int key) { + return key == 'd' || key == 'D' || key == 39 || key == 77; +} + +static bool is_up_key(int key) { + return key == 'w' || key == 'W' || key == 38 || key == 72; +} + +static bool is_down_key(int key) { + return key == 's' || key == 'S' || key == 40 || key == 80; +} + +static void handle_click(int x, int y) { + if (point_in_rect(x, y, 18, 352, 86, 30)) { + init_game(); + return; + } + if (point_in_rect(x, y, 123, 352, BTN_W, BTN_H)) { + apply_move(MOVE_LEFT); + return; + } + if (point_in_rect(x, y, 186, 352, BTN_W, BTN_H)) { + apply_move(MOVE_RIGHT); + return; + } + if (point_in_rect(x, y, 92, 390, BTN_W, BTN_H)) { + apply_move(MOVE_UP); + return; + } + if (point_in_rect(x, y, 155, 390, BTN_W, BTN_H)) { + apply_move(MOVE_DOWN); + return; + } +} + +static void handle_key(int key) { + if (key == 'r' || key == 'R') { + init_game(); + return; + } + + if (is_left_key(key)) { + apply_move(MOVE_LEFT); + } else if (is_right_key(key)) { + apply_move(MOVE_RIGHT); + } else if (is_up_key(key)) { + apply_move(MOVE_UP); + } else if (is_down_key(key)) { + apply_move(MOVE_DOWN); + } +} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + ui_window_t win = ui_window_create("2048", 240, 120, WINDOW_W, WINDOW_H); + if (!win) { + return 1; + } + + init_game(); + + gui_event_t ev; + while (1) { + if (ui_get_event(win, &ev)) { + if (ev.type == GUI_EVENT_PAINT) { + game_paint(win); + ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H); + } else if (ev.type == GUI_EVENT_CLICK) { + handle_click(ev.arg1, ev.arg2); + game_paint(win); + ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H); + } else if (ev.type == GUI_EVENT_KEY) { + handle_key(ev.arg1); + game_paint(win); + ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H); + } else if (ev.type == GUI_EVENT_CLOSE) { + sys_exit(0); + } else if (ev.type == GUI_EVENT_RESIZE) { + game_paint(win); + ui_mark_dirty(win, 0, 0, WINDOW_W, WINDOW_H); + } + } else { + sys_yield(); + } + } + + return 0; +} From 8a8fb7de2745d8453dbe68e0961ed90aa442e4f1 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:06:47 +0200 Subject: [PATCH 2/3] Remove credit --- src/userland/games/2048.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/userland/games/2048.c b/src/userland/games/2048.c index 3700d25..766d0b2 100644 --- a/src/userland/games/2048.c +++ b/src/userland/games/2048.c @@ -6,7 +6,6 @@ #include /* -@Lluciocc 2048 for BoredOS Controls: - WASD keys or arrow keys or numpad keys to move From 66f55242a78310f7681ffa243e90805c1924d089 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:39:21 +0200 Subject: [PATCH 3/3] Update man_entries.h --- src/core/man_entries.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/man_entries.h b/src/core/man_entries.h index d578763..cf3b0e9 100644 --- a/src/core/man_entries.h +++ b/src/core/man_entries.h @@ -70,6 +70,7 @@ void create_man_entries(void) { write_man_file("math", "MATH - Expression evaluator\n\nUsage: math \n\nEvaluates simple arithmetic expressions from the command line."); write_man_file("viewer", "VIEWER - Image viewer\n\nUsage: viewer \n\nA graphical application for viewing image files."); write_man_file("settings", "SETTINGS - System settings\n\nUsage: settings\n\nOpens the graphical system configuration tool."); + write_man_file("2048", "2048 - Classic game\n\nUsage: 2048\n\nPlays the classic 2048 game."); } #endif