247 lines
6.2 KiB
C
247 lines
6.2 KiB
C
|
#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);
|
||
|
}
|