From ed73a543e1ad0a032015b613d0a98e664c443d90 Mon Sep 17 00:00:00 2001 From: g-uwu Date: Tue, 26 Jul 2022 15:46:32 -0400 Subject: [PATCH] Initial commit --- .gitignore | 2 + Makefile | 15 +++ src/2048.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/2048.h | 56 +++++++++++ src/main.c | 246 +++++++++++++++++++++++++++++++++++++++++++++ src/main.h | 10 ++ src/utils.c | 67 +++++++++++++ src/utils.h | 25 +++++ 8 files changed, 700 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 src/2048.c create mode 100644 src/2048.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/utils.c create mode 100644 src/utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cc0e4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +2048 +*.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9ebd6c2 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +OBJ = $(patsubst src/%.c,%.o,$(wildcard src/*.c)) +HEADERS = $(wildcard src/*.h) +FLAGS = -std=c17 -pthread -O2 +LIB = -lncurses -lnotcurses-core + +%.o: src/%.c $(HEADERS) + $(CC) $(FLAGS) $(CFLAGS) -c $< -o $@ + +2048: $(OBJ) + $(CC) $(LIB) $(FLAGS) $(CFLAGS) $^ -o $@ + + +.PHONY: clean +clean: + $(RM) $(OBJ) diff --git a/src/2048.c b/src/2048.c new file mode 100644 index 0000000..a0b6985 --- /dev/null +++ b/src/2048.c @@ -0,0 +1,279 @@ +#include "2048.h" +#include "utils.h" + +#include +#include + +struct _2048_board *_2048_init_board() { + allocm(struct _2048_board, board); + + board->state = true; + + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + board->cells[y][x] = _2048_init_cell(y, x); + + return board; +} + +struct _2048_cell *_2048_init_cell(uint y, uint x) { + allocm(struct _2048_cell, cell); + + cell->x = x; + cell->y = y; + cell->level = 0; + + return cell; +} + +void _2048_destroy_board(struct _2048_board *board) { + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + free(board->cells[y][x]); + + free(board); +} + +void _2048_draw_cell(struct _2048_cell *cell, struct ncplane *plane, uint y, uint x) { + uint drawx = x / 2, drawy = y / 2; + + // Draw cell in appropriate place by + // manipulating the starting x draw points + switch (cell->x) { + case 0: + drawx -= 12; + case 1: + drawx -= 12; + break; + + case 3: + drawx += 12; + case 2: + drawx += 0; + break; + + default: + break; + } + + // Same for y + switch (cell->y) { + case 0: + drawy -= 6; + case 1: + drawy -= 2; + break; + + case 3: + drawy += 6; + case 2: + drawy += 4; + break; + + default: break; + } + + // Put cursor at starting draw position + ncplane_cursor_move_yx(plane, drawy, drawx); + + for (uint xd = 1; xd < 12u; xd++) { + ncplane_putwc_yx(plane, drawy, drawx + xd, 0x2500); + ncplane_putwc_yx(plane, drawy + 6, drawx + xd, 0x2500); + } + + for (uint yd = 1; yd < 6u; yd++) { + ncplane_putwc_yx(plane, drawy + yd, drawx, 0x2502); + ncplane_putwc_yx(plane, drawy + yd, drawx + 12, 0x2502); + } + + if (cell->level) { + struct string *level = pad_int(1 << cell->level, 0, 10); + ncplane_putstr_yx(plane, drawy + 3, drawx + 6 - (level->length / 2), level->s); + free_string(level); + } +} + +void _2048_draw_board(struct _2048_board *board, struct ncplane *plane, uint py, uint px) { + py -= 9; + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + _2048_draw_cell(board->cells[y][x], plane, py, px); + + uint drawx = px / 2, drawy = py / 2; + + // Draw intersections + ncplane_putwc_yx(plane, drawy - 8, drawx - 12, 0x252c); + ncplane_putwc_yx(plane, drawy - 8, drawx, 0x252c); + ncplane_putwc_yx(plane, drawy - 8, drawx + 12, 0x252c); + + ncplane_putwc_yx(plane, drawy - 2, drawx - 24, 0x251c); + ncplane_putwc_yx(plane, drawy - 2, drawx - 12, 0x253c); + ncplane_putwc_yx(plane, drawy - 2, drawx, 0x253c); + ncplane_putwc_yx(plane, drawy - 2, drawx + 12, 0x253c); + ncplane_putwc_yx(plane, drawy - 2, drawx + 24, 0x2524); + + ncplane_putwc_yx(plane, drawy + 4, drawx - 24, 0x251c); + ncplane_putwc_yx(plane, drawy + 4, drawx - 12, 0x253c); + ncplane_putwc_yx(plane, drawy + 4, drawx, 0x253c); + ncplane_putwc_yx(plane, drawy + 4, drawx + 12, 0x253c); + ncplane_putwc_yx(plane, drawy + 4, drawx + 24, 0x2524); + + ncplane_putwc_yx(plane, drawy + 10, drawx - 24, 0x251c); + ncplane_putwc_yx(plane, drawy + 10, drawx - 12, 0x253c); + ncplane_putwc_yx(plane, drawy + 10, drawx, 0x253c); + ncplane_putwc_yx(plane, drawy + 10, drawx + 12, 0x253c); + ncplane_putwc_yx(plane, drawy + 10, drawx + 24, 0x2524); + + ncplane_putwc_yx(plane, drawy + 16, drawx - 12, 0x2534); + ncplane_putwc_yx(plane, drawy + 16, drawx, 0x2534); + ncplane_putwc_yx(plane, drawy + 16, drawx + 12, 0x2534); + + // Draw corners + ncplane_putwc_yx(plane, drawy - 8, drawx - 24, 0x250c); + ncplane_putwc_yx(plane, drawy - 8, drawx + 24, 0x2510); + ncplane_putwc_yx(plane, drawy + 16, drawx - 24, 0x2514); + ncplane_putwc_yx(plane, drawy + 16, drawx + 24, 0x2518); +} + +void _2048_place_random(struct _2048_board *board) { + bool emptySpotAvailable = false; + + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + if (!board->cells[y][x]->level) + emptySpotAvailable = true; + + if (emptySpotAvailable) { + uint x = rand() % 4, y = rand() % 4; + + while (board->cells[y][x]->level) { + x = rand() % 4; + y = rand() % 4; + } + + board->cells[y][x]->level = (rand() % 4 == 3 ? 2 : 1); + } +} + +void transpose(struct _2048_board *board) { + uint temp_board[4][4]; + + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + temp_board[y][x] = board->cells[y][x]->level; + + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + board->cells[x][y]->level = temp_board[y][x]; +} + +void reverse(struct _2048_board *board) { + uint temp_board[4][4]; + + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + temp_board[y][x] = board->cells[y][x]->level; + + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + board->cells[y][x]->level = temp_board[y][3 - x]; +} + +bool _2048_compress(struct _2048_board *board) { + bool boardChanged = false; + + for (uint y = 0; y < 4; y++) { + uint p = 0; + uint cells[4] = { 0, 0, 0, 0 }; + + for (uint x = 0; x < 4; x++) { + if (board->cells[y][x]->level) { + cells[p] = board->cells[y][x]->level; + + if (x != p) + boardChanged = true; + + p++; + } + } + + for (uint x = 0; x < 4; x++) + board->cells[y][x]->level = cells[x]; + } + + return boardChanged; +} + +bool _2048_merge(struct _2048_board *board) { + bool boardChanged = false; + + for (uint y = 0; y < 4; y++) { + for (uint x = 0; x < 3; x++) { + if (board->cells[y][x]->level == board->cells[y][x + 1]->level && board->cells[y][x]->level) { + board->cells[y][x]->level++; + board->cells[y][x + 1]->level = 0; + + boardChanged = true; + } + } + } + + return boardChanged; +} + +bool _2048_move(struct _2048_board *board, enum _2048_direction dir) { + switch (dir) { + case _2048_left: break; + case _2048_right: reverse(board); break; + case _2048_up: transpose(board); break; + case _2048_down: transpose(board); reverse(board); break; + } + + bool changed = false; + changed |= _2048_compress(board); + changed |= _2048_merge(board); + changed |= _2048_compress(board); + + switch(dir) { + case _2048_left: break; + case _2048_right: reverse(board); break; + case _2048_up: transpose(board); break; + case _2048_down: reverse(board); transpose(board); break; + } + + _2048_place_random(board); + + // facepalm. we add a new tile regardless if it changed so we always need to redraw the board + return true; +} + +enum _2048_gameState _2048_determineState(struct _2048_board *board) { + // GG, you got 2048 + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + if (board->cells[y][x]->level > 10) // 2048 + return _2048_reached; + + // If there is any empty tile, its a normal game + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + if (!board->cells[y][x]->level) + return _2048_normal; + + + // If any move will create a new empty tile, its a normal game + for (uint y = 0; y < 3; y++) + for (uint x = 0; x < 3; x++) + if (board->cells[y][x]->level == board->cells[y + 1][x]->level || + board->cells[y][x]->level == board->cells[y][x + 1]->level) + return _2048_normal; + + for (uint i = 0; i < 3; i++) + if (board->cells[3][i]->level == board->cells[3][i + 1]->level || + board->cells[i][3]->level == board->cells[i + 1][3]->level) + return _2048_normal; + + + // If all else, you lose. + return _2048_cant_move; +} diff --git a/src/2048.h b/src/2048.h new file mode 100644 index 0000000..18ab854 --- /dev/null +++ b/src/2048.h @@ -0,0 +1,56 @@ +#ifndef __2048_H__ +#define __2048_H__ + +#include + +#include +#include +#include + +typedef unsigned int uint; + +struct _2048_cell { + // posiiton + uint y; + uint x; + + // 0 = empty + uint level; +}; + +struct _2048_board { + struct _2048_cell *cells[4][4]; + + // 1 = running; 0 = not running; + bool state; +}; + +enum _2048_direction { + _2048_left, + _2048_right, + _2048_up, + _2048_down +}; + +enum _2048_gameState { + _2048_normal, + _2048_reached, + _2048_cant_move +}; + +struct _2048_board *_2048_init_board(); +struct _2048_cell *_2048_init_cell(uint y, uint x); +void _2048_destroy_board(struct _2048_board *board); + +void _2048_draw_cell(struct _2048_cell *cell, struct ncplane *plane, uint y, uint x); +void _2048_draw_board(struct _2048_board *board, struct ncplane *plane, uint py, uint px); + +void _2048_place_random(struct _2048_board *board); + +bool _2048_compress(struct _2048_board *board); +bool _2048_merge(struct _2048_board *board); +bool _2048_move(struct _2048_board *board, enum _2048_direction dir); + +enum _2048_gameState _2048_determineState(struct _2048_board *board); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..5b27b25 --- /dev/null +++ b/src/main.c @@ -0,0 +1,246 @@ +#include "main.h" +#include "2048.h" +#include "utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +struct _2048_board *g_board; +struct notcurses *g_nc; +struct ncplane *g_stdp; +struct ncplane *p_game; +struct ncplane *p_status; +uint g_topscore; + +#define g_stop_nc() notcurses_stop(g_nc); g_nc == NULL + +int main(void) { + setlocale(LC_ALL, "en_US.UTF-8"); + + srand(time(NULL)); + + { // Create notcurses instance + allocm(struct notcurses_options, opt); + opt->flags = NCOPTION_SUPPRESS_BANNERS | NCOPTION_NO_QUIT_SIGHANDLERS; + g_nc = notcurses_core_init(opt, NULL); + free(opt); + } + + g_board = _2048_init_board(); + g_stdp = notcurses_stdplane(g_nc); + g_topscore = 0; + + nocbreak(); + noecho(); + + { // Create game plane + allocm(struct ncplane_options, opt); + opt->name = (char *)"Game window"; + opt->cols = COLS; + opt->rows = LINES - 2; + opt->x = 0; + opt->y = 2; + opt->flags = NCPLANE_OPTION_FIXED; + opt->margin_b = 0; + opt->margin_r = 0; + p_game = ncplane_create(g_stdp, opt); + free(opt); + + if (p_game == 0) { + g_stop_nc(); + printf("Game plane failed to init\n"); + cleanup(1); + } + } + + { // Create statusbar plane + allocm(struct ncplane_options, opt); + opt->name = (char *)"Status bar"; + opt->cols = COLS; + opt->rows = 2; + opt->x = 0; + opt->y = 0; + opt->flags = NCPLANE_OPTION_FIXED; + opt->margin_b = 0; + opt->margin_r = 0; + p_status = ncplane_create(g_stdp, opt); + free(opt); + + if (p_status == 0) { + g_stop_nc(); + printf("Status bar plane failed to init\n"); + cleanup(1); + } + + for (uint x = 0; x < COLS + 0U; x++) + ncplane_putwc_yx(p_status, 1, x, 0x2500); + } + + // Make sure everything is cleaned up when program is quit + signal(SIGINT, cleanup); + signal(SIGQUIT, cleanup); + signal(SIGTERM, cleanup); + signal(SIGSEGV, cleanup); + + // Status bar text + char *status_text = (char *)malloc(65); + memset(status_text, 0, 64); + snprintf(status_text, 64, "Press h for help"); + + // plane dimensions + uint psx, psy, pgx, pgy; + ncplane_dim_yx(p_status, &psy, &psx); + ncplane_dim_yx(p_game, &pgy, &pgx); + + _2048_place_random(g_board); + + bool f_redrawGameBoard = true; + bool f_redrawStatus = true; + bool f_seen2048 = false; + bool f_lost = false; + + // Game and event loop + while (g_board->state) { + + // Event processing + ncinput nc_event; + uint32_t nc_input; + + if ((nc_input = notcurses_get_nblock(g_nc, &nc_event))) { + if (nc_input == (uint32_t)-1) { + g_stop_nc(); + printf("[FATAL] Error occured while processing input\n"); + cleanup(1); + } + + if (nc_event.evtype == NCTYPE_PRESS) { + // Handle shortcuts + { + if (nc_event.id == 'Q') + g_board->state = 0; + + if (nc_event.id == 'R') { + _2048_destroy_board(g_board); + g_board = _2048_init_board(); + _2048_place_random(g_board); + + snprintf(status_text, 64, "Board cleared"); + + f_seen2048 = false; + f_lost = false; + + f_redrawStatus = true; + f_redrawGameBoard = true; + } + + if (nc_event.id == 'h') snprintf(status_text, 64, "shift+q to Quit, shift+r to Restart"), f_redrawStatus = true; + } + + if (!f_lost) { // Game movements + if (nc_event.id == NCKEY_LEFT || nc_event.id == 'a') f_redrawGameBoard = _2048_move(g_board, _2048_left); + if (nc_event.id == NCKEY_RIGHT || nc_event.id == 'd') f_redrawGameBoard = _2048_move(g_board, _2048_right); + if (nc_event.id == NCKEY_UP || nc_event.id == 'w') f_redrawGameBoard = _2048_move(g_board, _2048_up); + if (nc_event.id == NCKEY_DOWN || nc_event.id == 's') f_redrawGameBoard = _2048_move(g_board, _2048_down); + } + } + + // Handle terminal resize + if (nc_event.id == NCKEY_RESIZE) { + uint nx, ny; + + notcurses_refresh(g_nc, &ny, &nx); + + { // Resize planes + ncplane_resize_simple(p_status, 2, nx); + ncplane_resize_simple(p_game, ny - 2, nx); + } + + { // Update variables + ncplane_dim_yx(p_status, &psy, &psx); + ncplane_dim_yx(p_game, &pgy, &pgx); + } + + { // Recreate line seperator on status bar + for (uint x = 0; x < psx; x++) + ncplane_putwc_yx(p_status, 1, x, 0x2500); + } + + f_redrawGameBoard = true; + } + } + + if (f_redrawGameBoard) { + ncplane_erase(p_game); + _2048_draw_board(g_board, p_game, pgy, pgx); + + g_topscore = 0; + for (uint y = 0; y < 4; y++) + for (uint x = 0; x < 4; x++) + if (g_board->cells[y][x]->level) + g_topscore += 1 << g_board->cells[y][x]->level; + + enum _2048_gameState state = _2048_determineState(g_board); + + switch (state) { + case _2048_normal: break; + case _2048_reached: + if (!f_seen2048) { + snprintf(status_text, 64, "Congrats! You reached 2048"); + f_seen2048 = true; + } + break; + + case _2048_cant_move: + if (!f_lost) { + if (f_seen2048) + snprintf(status_text, 64, "Game over. Congrats on reaching 2048. Press shift+r to Restart"); + else + snprintf(status_text, 64, "Game over. You have lost. Press shift+r to Restart"); + + f_lost = true; + } + + } + + f_redrawStatus = true; + f_redrawGameBoard = false; + } + + if (f_redrawStatus) { + ncplane_erase_region(p_status, 0, 0, 1, psx); + ncplane_putstr_yx(p_status, 0, 1, "2048 in the terminal"); + ncplane_putstr_yx(p_status, 0, (psx / 2) - (strnlen(status_text, 64) / 2), status_text); + ncplane_putstr_yx(p_status, 0, psx - 14, "SCORE "); + ncplane_printf(p_status, "%i", g_topscore); + + f_redrawStatus = false; + } + + notcurses_render(g_nc); + } + + cleanup(0); + + return EXIT_SUCCESS; +} + +void cleanup(int sig) { + if (sig != 0 && sig != 1) + printf("[INFO] Caught signal %i\n", sig); + else if (sig == 1) + printf("[INFO] Cleaning up after error\n"); + + g_stop_nc(); + + if (g_board) + _2048_destroy_board(g_board); + + exit(sig); +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..d7a6c1d --- /dev/null +++ b/src/main.h @@ -0,0 +1,10 @@ +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include +#include +#include + +void cleanup(int sig); + +#endif \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..acecde1 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,67 @@ +#include "utils.h" + +#include + +struct string *pad_int(int n, uint pad, uint maxsize) { + struct string *str = (struct string *)malloc(sizeof (struct string) + 1); + maxsize++; + + str->s = (char *)malloc((sizeof(char) * maxsize) + 1); + str->length = 0; + + memset(str->s, 0, maxsize); + + // Figure out how many place values are in n + int _n = abs(n); + uint places = 1; + + for (uint i = 0; i < pad; i++) { + if (_n >= 10) { + _n /= 10; + places++; + } + else break; + } + + char *padding = (char *)malloc((sizeof(char) * pad) + 1); + for (uint i = 0; i < pad; i++) + padding[i] = '0'; + + uint required_padding = pad - places; + if (required_padding > pad) required_padding = 0; + + snprintf(str->s, maxsize, "%*.*s%i%c", 0, required_padding, padding, abs(n), 0); + free(padding); + + for (uint i = 0; i < maxsize + 1; i++) { + if (str->s[i] == 0) { + str->length = i; + break; + } + } + + return str; +} + +// alloc_string +struct string *alloc_string(uint l) { + struct string *s = (struct string *)malloc(sizeof(struct string) + 1); + s->s = (char *)malloc(l + 1); + s->length = l; + + memset(s->s, 0, l); + + return s; +} + +// free_string +void free_string(struct string *s) { + free(s->s); + free(s); +} + +uint load_highscore() { + char *home = getenv("HOME"); + allocm(struct stat, s); + stat("", s); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..5b4a044 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,25 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include +#include +#include + + +typedef unsigned int uint; + +#define allocm(type, name) type * name = (type *)malloc(sizeof(type) + 1); memset(name, 0, sizeof(type)) + +struct string { + char *s; + uint length; +}; + +struct string *pad_int(int n, uint pad, uint maxsize); +struct string *alloc_string(uint l); +void free_string(struct string *s); + +uint load_highscore(); + +#endif