Initial commit

This commit is contained in:
g-uwu 2022-07-26 15:46:32 -04:00
commit ed73a543e1
8 changed files with 700 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
2048
*.o

15
Makefile Normal file
View File

@ -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)

279
src/2048.c Normal file
View File

@ -0,0 +1,279 @@
#include "2048.h"
#include "utils.h"
#include <notcurses/notcurses.h>
#include <stdlib.h>
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;
}

56
src/2048.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef __2048_H__
#define __2048_H__
#include <stdbool.h>
#include <curses.h>
#include <notcurses/nckeys.h>
#include <notcurses/notcurses.h>
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

246
src/main.c Normal file
View File

@ -0,0 +1,246 @@
#include "main.h"
#include "2048.h"
#include "utils.h"
#include <curses.h>
#include <notcurses/nckeys.h>
#include <notcurses/notcurses.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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);
}

10
src/main.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef __MAIN_H__
#define __MAIN_H__
#include <curses.h>
#include <notcurses/nckeys.h>
#include <notcurses/notcurses.h>
void cleanup(int sig);
#endif

67
src/utils.c Normal file
View File

@ -0,0 +1,67 @@
#include "utils.h"
#include <sys/stat.h>
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);
}

25
src/utils.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef __UTILS_H__
#define __UTILS_H__
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
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