Cleaned up source, directories, and added header file to improve general readability

This commit is contained in:
Tiehuis 2014-09-12 23:17:04 +12:00
parent 1bf0c51864
commit 8843fcb658
6 changed files with 448 additions and 503 deletions

View File

@ -1,413 +0,0 @@
#include <curses.h>
#include <stdlib.h> /* for malloc */
#include <time.h> /* for time */
#include <unistd.h> /* for getopts */
// Repeat an expression y, x times */
#define ITER(x, expr)\
do {\
int i;\
for (i = 0; i < x; i++){ expr;}\
} while (0)
/* Allocates a square pointer of array of arrays onto heap */
#define CALLOC2D(ptr, sz)\
do {\
int i;\
ptr = calloc(sz, sizeof(*ptr));\
for (i = 0; i < sz; i++)\
ptr[i] = calloc(sz, sizeof(*ptr));\
} while (0)
/* Frees a square pointer of arrays to arrays */
#define FREE2D(ptr, sz)\
do {\
int i;\
for (i = 0; i < sz; i++)\
free(ptr[i]);\
free(ptr);\
} while (0)
/* should be executed each turn */
#define TURN(x)\
gravitate(x) +\
merge(x) +\
gravitate(x)
/* maximum length of any value in grid, for printing */
int MAXVAL;
/* direction enumeration */
enum {DR, DU, DL, DD};
/* grid pointer */
int **g;
/* grid size */
int SZ;
/* score, and last turn score */
int s;
int sl;
/* highscore */
int hs;
/* highscore file */
char *file;
/* Merges adjacent squares of the same value together in a certain direction */
int merge(int d)
{
int moved = 0;
if (d == DL) {
int i, j;
for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ; j++) {
if (j + 1 < SZ && g[i][j] && g[i][j] == g[i][j + 1]) {
g[i][j] <<= 1;
sl += g[i][j];
s += g[i][j];
g[i][j++ + 1] = 0;
moved = 1;
}
}
}
}
else if (d == DU) {
int i, j;
for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ; j++) {
if (j + 1 < SZ && g[j][i] && g[j][i] == g[j + 1][i]) {
g[j][i] <<= 1;
sl += g[j][i];
s += g[j][i];
g[j++ + 1][i] = 0;
moved = 1;
}
}
}
}
else if (d == DR) {
int i, j;
for (i = SZ - 1; i >= 0; i--) {
for (j = SZ - 1; j >= 0; j--) {
if (j > 0 && g[i][j] && g[i][j] == g[i][j - 1]) {
g[i][j] <<= 1;
sl += g[i][j];
s += g[i][j];
g[i][j-- - 1] = 0;
moved = 1;
}
}
}
}
else if (d == DD) {
int i, j;
for (i = SZ - 1; i >= 0; i--) {
for (j = SZ - 1; j >= 0; j--) {
if (j > 0 && g[j][i] && g[j][i] == g[j - 1][i]) {
g[j][i] <<= 1;
sl += g[j][i];
s += g[j][i];
g[j-- - 1][i] = 0;
moved = 1;
}
}
}
}
return moved;
}
/* move all values in the grid to the edge given by the direction pressed */
/* would be nice to generalize this code a little so didn't need four loops */
/* if animations are wanted, then need to alter this so it moves each square one at a time */
int gravitate(int d)
{
int moved = 0;
if (d == DL) {
int i, j;
for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ - 1; j++) {
if (g[i][j]) continue;
int st = 1;
while (j + st < SZ && !g[i][j + st]) st++;
if (j + st < SZ) {
g[i][j] = g[i][j + st];
g[i][j + st] = 0;
moved = 1;
}
}
}
}
else if (d == DU) {
int i, j;
for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ - 1; j++) {
if (g[j][i]) continue;
int st = 1;
while (j + st < SZ && !g[j + st][i]) st++;
if (j + st < SZ) {
g[j][i] = g[j + st][i];
g[j + st][i] = 0;
moved = 1;
}
}
}
}
else if (d == DR) {
int i, j;
for (i = 0; i < SZ; i++) {
for (j = SZ - 1; j > 0; j--) {
if (g[i][j]) continue;
int st = 1;
while (j - st >= 0 && !g[i][j - st]) st++;
if (j - st >= 0) {
g[i][j] = g[i][j - st];
g[i][j - st] = 0;
moved = 1;
}
}
}
}
else if (d == DD) {
int i, j;
for (i = 0; i < SZ; i++) {
for (j = SZ - 1; j > 0; j--) {
if (g[j][i]) continue;
int st = 1;
while (j - st >= 0 && !g[j - st][i]) st++;
if (j - st >= 0) {
g[j][i] = g[j - st][i];
g[j - st][i] = 0;
moved = 1;
}
}
}
}
return moved;
}
/* load hiscore */
void load_score() {
FILE *fd = fopen(file, "r");
if (fd == NULL) fd = fopen(file, "w+");
if (fscanf(fd, "%d", &hs) == EOF) hs = 0;
fclose(fd);
}
/* saves hiscore, but only if playing on standard size grid */
void save_score() {
if (s > hs && SZ == 4) {
hs = s;
FILE *fd = fopen(file, "w+");
fprintf(fd, "%d", hs);
fclose(fd);
}
}
/* returns if there are any possible moves */
int moves_available()
{
int i, j;
for (i = 0; i < SZ; i++)
for (j = 0; j < SZ; j++)
if (!g[i][j] || ((i + 1 < SZ) && (g[i][j] == g[i + 1][j])) || ((j + 1 < SZ) && (g[i][j] == g[i][j + 1])))
return 1;
return 0;
}
/* places a random block onto the grid - either a 4, or a 2 with a chance of 1:3 respectively */
/* could do this in a much smarter fashion by finding which spaces are free */
void rand_block()
{
int x_p, y_p;
while (g[x_p = rand() % SZ][y_p = rand() % SZ]);
g[x_p][y_p] = (rand() & 3) ? 2 : 4;
}
/* quick floor log2(n) */
int flog2(int n)
{
int k = 0;
while (n)
k++, n >>= 1;
return k;
}
/* draws the grid and fills it with the current values */
/* colors just rotate around, works for now, can be confusing when you have some fairly high values on the board */
void draw_grid(WINDOW *gamewin)
{
// mvwprintw will sometimes have a useless arg, this is warned, but doesn't affect the program
char *scr = sl ? "SCORE: %d (+%d)\n" : "SCORE: %d\n";
mvwprintw(gamewin, 0, 0, scr, s, sl);
mvwprintw(gamewin, 1, 0, "HISCR: %d\n", hs);
ITER(SZ*(MAXVAL + 2) + 1, waddch(gamewin, '-'));
int i, j, xps = 0, yps = 3;
for (i = 0; i < SZ; i++, xps = 0, yps++) {
mvwprintw(gamewin, yps, xps++, "|");
for (j = 0; j < SZ; j++) {
if (g[i][j]) {
wattron(gamewin, COLOR_PAIR(flog2(g[i][j]) & 7));
mvwprintw(gamewin, yps, xps, "%*d", MAXVAL, g[i][j]);
wattroff(gamewin, COLOR_PAIR(flog2(g[i][j]) & 7));
mvwprintw(gamewin, yps, xps + MAXVAL, " |");
}
else {
ITER(MAXVAL + 1, waddch(gamewin, ' '));
waddch(gamewin, '|');
}
xps += (MAXVAL + 2);
}
}
ITER(SZ*(MAXVAL + 2) + 1, waddch(gamewin, '-'));
wrefresh(gamewin);
}
/* entry point for the program */
/* parses options and stores the main game loop */
int main(int argc, char **argv)
{
/* init ncurses environment */
initscr();
cbreak();
noecho();
curs_set(FALSE);
/* init variables */
file = ".hs2048g";
hs = 0;
s = 0;
sl = 0;
SZ = 4;
MAXVAL = 4;
CALLOC2D(g, SZ);
load_score();
int n_blocks = 1;
/* parse options */
int c;
while ((c = getopt(argc, argv, "rchs:b:")) != -1) {
switch (c) {
// color support - assumes your terminal can display colours
// should still work regardless
case 'c':
start_color();
init_pair(1, 1, 0);
init_pair(2, 2, 0);
init_pair(3, 3, 0);
init_pair(4, 4, 0);
init_pair(5, 5, 0);
init_pair(6, 6, 0);
init_pair(7, 7, 0);
break;
// different board sizes
case 's':
FREE2D(g, SZ);
int optint = atoi(optarg);
SZ = optint > 4 ? optint : 4;
CALLOC2D(g, SZ);
break;
// different block spawn rate
case 'b':
n_blocks = atoi(optarg);
break;
// reset hiscores
case 'r':
endwin();
printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n");
int response;
if ((response = getchar()) == 'y' || response == 'Y') {
FILE *fd = fopen(file, "w+");
fclose(fd);
}
exit(EXIT_SUCCESS);
// help menu
case 'h':
endwin();
printf("Controls:\n"
" hjkl, wasd Movement\n"
" q Quit\n"
"\n"
"Usage:\n"
" 2048 [options]\n"
"\n"
"Options:\n"
" -s <size> Set the grid border length\n"
" -b <rate> Set the block spawn rate\n"
" -c Enables color support\n");
exit(EXIT_SUCCESS);
}
}
int width = SZ * (MAXVAL + 2) + 1;
int height = SZ * (MAXVAL + 2) + 3;
// might center in middle of screen
WINDOW *gamewin = newwin(height, width, 1, 1);
keypad(gamewin, TRUE);
/* random seed */
srand((unsigned int)time(NULL));
ITER(2, rand_block());
draw_grid(gamewin);
int key, moved;
while (1) {
/* will goto this if we didn't get a valid keypress */
retry:;
moved = 0;
key = wgetch(gamewin);
sl = 0;
/* should check if anything changed during merge and if not retry */
switch (key) {
case 'h':
case 'a':
case KEY_LEFT:
moved = TURN(DL);
break;
case 'l':
case 'd':
case KEY_RIGHT:
moved = TURN(DR);
break;
case 'j':
case 's':
case KEY_DOWN:
moved = TURN(DD);
break;
case 'k':
case 'w':
case KEY_UP:
moved = TURN(DU);
break;
case 'q':
FREE2D(g, SZ);
erase();
refresh();
endwin();
save_score();
exit(EXIT_SUCCESS);
default:
goto retry;
}
if (!moves_available()) {
endwin();
printf("\n"
"YOU LOSE! - Your score was %d\n", s);
save_score();
exit(EXIT_SUCCESS);
}
if (moved) {
ITER(n_blocks, rand_block());
draw_grid(gamewin);
}
}
}

View File

@ -1,13 +1,14 @@
CC ?= gcc CC ?= gcc
CFLAGS+= CFLAGS += -Wall -Wextra
LIBS = -lcurses
all: 2048 2048_no_curses all: 2048
2048: 2048_curses.c 2048: src/2048.c
$(CC) $(CFLAGS) 2048_curses.c -lcurses -o 2048 $(CC) $(CFLAGS) src/2048.c -o 2048 $(LIBS)
2048_no_curses: 2048_no_curses.c 2048nc: src/2048_no_curses.c
$(CC) $(CFLAGS) 2048_no_curses.c -o 2048_no_curses $(CC) $(CFLAGS) src/2048_no_curses.c -o 2048nc
clean: clean:
rm -f 2048 2048_no_curses rm -f 2048 2048nc

View File

@ -1,6 +1,7 @@
#2048-cli #2048-cli
A cli version of the game [2048](https://github.com/gabrielecirulli/2048) for your Linux terminal. A cli version of the game [2048](https://github.com/gabrielecirulli/2048) for your Linux
terminal.
#####2048_curses.c #####2048_curses.c
![Screenshot](http://i.imgur.com/QU7t5mH.png) ![Screenshot](http://i.imgur.com/QU7t5mH.png)
@ -9,11 +10,13 @@ A cli version of the game [2048](https://github.com/gabrielecirulli/2048) for yo
![Screenshot](http://i.imgur.com/fwZEvdh.png) ![Screenshot](http://i.imgur.com/fwZEvdh.png)
## Installation ## Installation
If you want to use the ncurses version, make sure that you have the required ncurses library and link against this during compilation with `-lcurses`. The program creates and uses a file named `.hs2048g` in the directory it is run. Ensure that you have no file of this name in the directory otherwise it will be overwritten. This requires the ncurses library to link against during compilation. It is available
in most package managers. The program creates and uses a file name `.hs2048g` in the
working directory. Any file with this name will be modified and replaced.
### Get ### Get
cd ~/$INSTALL_DIR git clone https://github.com/Tiehuis/2048-cli.git
wget https://raw.githubusercontent.com/Tiehuis/2048-cli/master/2048_no_curses.c make
gcc 2048_no_curses.c -o 2048
### Run ### Run
./2048 ./2048

350
src/2048.c Normal file
View File

@ -0,0 +1,350 @@
/*
* 2048.c - Main version that should be worked on.
*
* Things to do:
* - Finish cleaning up code.
* - Ask user if they would like to play again instead of exiting immediately.
* - Add an actual goal (what happens when we reach 2048?).
* - Keep track list of past scores, and store in a better fashion independent
* of cwd. Also, store in not in plaintext.
* */
#include <stdlib.h> /* for malloc */
#include <time.h> /* for time */
#include <unistd.h> /* for getopts */
#include <ncurses.h>
#include "2048.h"
int **grid; /* grid pointer */
int grid_size; /* grid size */
int score; /* score */
int score_last; /* Score for last move */
int score_high; /* Hiscore */
int printwidth; /* maximum length of any value in grid, for printing */
/* Merges adjacent squares of the same value together in a certain direction */
int merge(dir_t d)
{
int moved = 0;
int i, j;
if (d == DIR_LEFT || d == DIR_RIGHT) {
for (i = 0; i < grid_size; i++) {
for (j = 0; j < grid_size - 1; j++) {
if (grid[i][j] == grid[i][j + 1]) {
grid[i][j] <<= 1;
grid[i][j + 1] = 0;
score_last += grid[i][j];
score += grid[i][j];
moved = 1;
}
}
}
}
else {
for (i = 0; i < grid_size - 1; i++) {
for (j = 0; j < grid_size; j++) {
if (grid[i][j] == grid[i + 1][j]) {
grid[i][j] <<= 2;
grid[i + 1][j] = 0;
score_last += grid[i][j];
score += grid[i][j];
moved = 1;
}
}
}
}
return moved;
}
/* move all values in the grid to the edge given by the direction pressed */
/* would be nice to generalize this code a little so didn't need four loops */
/* if animations are wanted, then need to alter this so it moves each square one at a time */
int gravitate(dir_t d)
{
int moved = 0;
int i, j;
if (d == DIR_LEFT) {
for (i = 0; i < grid_size; i++) {
for (j = 0; j < grid_size - 1; j++) {
if (grid[i][j]) continue;
int st = 1;
while (j + st < grid_size && !grid[i][j + st]) st++;
if (j + st < grid_size) {
grid[i][j] = grid[i][j + st];
grid[i][j + st] = 0;
moved = 1;
}
}
}
}
else if (d == DIR_UP) {
for (i = 0; i < grid_size; i++) {
for (j = 0; j < grid_size - 1; j++) {
if (grid[j][i]) continue;
int st = 1;
while (j + st < grid_size && !grid[j + st][i]) st++;
if (j + st < grid_size) {
grid[j][i] = grid[j + st][i];
grid[j + st][i] = 0;
moved = 1;
}
}
}
}
else if (d == DIR_RIGHT) {
for (i = 0; i < grid_size; i++) {
for (j = grid_size - 1; j > 0; j--) {
if (grid[i][j]) continue;
int st = 1;
while (j - st >= 0 && !grid[i][j - st]) st++;
if (j - st >= 0) {
grid[i][j] = grid[i][j - st];
grid[i][j - st] = 0;
moved = 1;
}
}
}
}
else if (d == DIR_DOWN) {
for (i = 0; i < grid_size; i++) {
for (j = grid_size - 1; j > 0; j--) {
if (grid[j][i]) continue;
int st = 1;
while (j - st >= 0 && !grid[j - st][i]) st++;
if (j - st >= 0) {
grid[j][i] = grid[j - st][i];
grid[j - st][i] = 0;
moved = 1;
}
}
}
}
return moved;
}
/* Return the current highscore */
int get_score_high() {
int s = 0;
FILE *fd = fopen(HISCORE_FILE, "r");
if (fd == NULL)
fd = fopen(HISCORE_FILE, "w+");
fscanf(fd, "%d", &s);
fclose(fd);
return s;
}
/* saves hiscore, but only if playing on standard size grid */
void save_score_high() {
if (score > score_high && grid_size == 4) {
score_high = score;
FILE *fd = fopen(HISCORE_FILE, "w+");
fprintf(fd, "%d", score_high);
fclose(fd);
}
}
/* returns if there are any possible moves */
int moves_available()
{
int i, j;
for (i = 0; i < grid_size; i++)
for (j = 0; j < grid_size; j++)
if (!grid[i][j]
|| ((i + 1 < grid_size) && (grid[i][j] == grid[i + 1][j]))
|| ((j + 1 < grid_size) && (grid[i][j] == grid[i][j + 1])))
return 1;
return 0;
}
/* places a random block onto the grid - either a 4, or a 2 with a chance of 1:3 respectively */
/* could do this in a much smarter fashion by finding which spaces are free */
void rand_block()
{
int x, y;
while (grid[x = rand() % grid_size][y = rand() % grid_size]);
grid[x][y] = (rand() & 3) ? 2 : 4;
}
/* quick floor log2(n) */
int flog2(int n)
{
int k = 0;
while (n)
k++, n >>= 1;
return k;
}
/* draws the grid and fills it with the current values */
/* colors just rotate around, works for now, can be confusing when you have some fairly high values on the board */
void draw_grid(WINDOW *gamewin)
{
// mvwprintw will sometimes have a useless arg, this is warned, but doesn't affect the program
char *scr = score_last ? "SCORE: %d (+%d)\n" : "SCORE: %d\n";
mvwprintw(gamewin, 0, 0, scr, score, score_last);
mvwprintw(gamewin, 1, 0, "HISCR: %d\n", score_high);
ITER(grid_size*(printwidth + 2) + 1, waddch(gamewin, '-'));
int i, j, xps = 0, yps = 3;
for (i = 0; i < grid_size; i++, xps = 0, yps++) {
mvwprintw(gamewin, yps, xps++, "|");
for (j = 0; j < grid_size; j++) {
if (grid[i][j]) {
wattron(gamewin, COLOR_PAIR(flog2(grid[i][j]) & 7));
mvwprintw(gamewin, yps, xps, "%*d", printwidth, grid[i][j]);
wattroff(gamewin, COLOR_PAIR(flog2(grid[i][j]) & 7));
mvwprintw(gamewin, yps, xps + printwidth, " |");
}
else {
ITER(printwidth + 1, waddch(gamewin, ' '));
waddch(gamewin, '|');
}
xps += (printwidth + 2);
}
}
ITER(grid_size*(printwidth + 2) + 1, waddch(gamewin, '-'));
wrefresh(gamewin);
}
/* entry point for the program */
/* parses options and stores the main game loop */
int main(int argc, char **argv)
{
/* init ncurses environment */
initscr();
cbreak();
noecho();
curs_set(FALSE);
/* init variables */
score = 0;
score_last = 0;
score_high = get_score_high();
grid_size = DEFAULT_GRID_SIZE;
printwidth = DEFAULT_GRID_SIZE;
int n_blocks = 1;
/* parse options */
int c;
while ((c = getopt(argc, argv, "rchs:b:")) != -1) {
switch (c) {
/* Color support */
case 'c':
if (has_colors()) {
start_color();
init_pair(0, 1, 0);
init_pair(1, 2, 0);
init_pair(2, 3, 0);
init_pair(3, 4, 0);
init_pair(4, 5, 0);
init_pair(5, 6, 0);
init_pair(6, 7, 0);
}
else {
fprintf(stderr, "Terminal does not support color\n");
}
break;
// different board sizes
case 's':;
int optint = strtol(optarg, NULL, 10);
grid_size = optint > DEFAULT_GRID_SIZE ? optint : DEFAULT_GRID_SIZE;
break;
// different block spawn rate
case 'b':
n_blocks = strtol(optarg, NULL, 10);
break;
// reset hiscores
case 'r':
endwin();
printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n");
int response;
if ((response = getchar()) == 'y' || response == 'Y') {
FILE *fd = fopen(HISCORE_FILE, "w+");
fclose(fd);
}
exit(EXIT_SUCCESS);
// help menu
case 'h':
endwin();
printf(USAGE_STR);
exit(EXIT_SUCCESS);
}
}
/* Allocate memory once we actually know amount */
CALLOC2D(grid, grid_size);
int width = grid_size * (printwidth + 2) + 1;
int height = grid_size * (printwidth + 2) + 3;
// might center in middle of screen
WINDOW *gamewin = newwin(height, width, 1, 1);
keypad(gamewin, TRUE);
/* random seed */
srand((unsigned int)time(NULL));
ITER(2, rand_block());
draw_grid(gamewin);
int key, moved;
while (1) {
/* will goto this if we didn't get a valid keypress */
retry:;
moved = 0;
score_last = 0;
key = wgetch(gamewin);
/* should check if anything changed during merge and if not retry */
switch (key) {
case 'h':
case 'a':
case KEY_LEFT:
moved = TURN(DIR_LEFT);
break;
case 'l':
case 'd':
case KEY_RIGHT:
moved = TURN(DIR_RIGHT);
break;
case 'j':
case 's':
case KEY_DOWN:
moved = TURN(DIR_DOWN);
break;
case 'k':
case 'w':
case KEY_UP:
moved = TURN(DIR_UP);
break;
case 'q':
FREE2D(grid, grid_size);
erase();
refresh();
endwin();
save_score_high();
exit(EXIT_SUCCESS);
default:
goto retry;
}
if (!moves_available()) {
endwin();
printf("\n"
"YOU LOSE! - Your score was %d\n", score);
save_score_high();
exit(EXIT_SUCCESS);
}
if (moved) {
ITER(n_blocks, rand_block());
draw_grid(gamewin);
}
}
return 0;
}

55
src/2048.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef _2048_H_
#define _2048_H_
typedef enum {
DIR_UP,
DIR_RIGHT,
DIR_DOWN,
DIR_LEFT
} dir_t;
// Repeat an expression y, x times */
#define ITER(x, expr)\
do {\
int i;\
for (i = 0; i < (x); i++){ expr;}\
} while (0)
/* Allocates a square pointer of array of arrays onto heap */
#define CALLOC2D(ptr, sz)\
do {\
int i;\
ptr = calloc((sz), sizeof(*ptr));\
for (i = 0; i < (sz); i++)\
ptr[i] = calloc((sz), sizeof(*ptr));\
} while (0)
/* Frees a square pointer of arrays to arrays */
#define FREE2D(ptr, sz)\
do {\
int i;\
for (i = 0; i < (sz); i++)\
free(ptr[i]);\
free(ptr);\
} while (0)
/* What occurs during a 'turn' of execution */
#define TURN(x) (gravitate(x) + merge(x) + gravitate(x))
/* Constants */
#define DEFAULT_GRID_SIZE 4
#define HISCORE_FILE ".hs2048g"
#define USAGE_STR\
"Usage:\n"\
" ./2048 [options]\n"\
"\n"\
"Controls:\n"\
" hjkl, wasd Movement\n"\
" q Quit\n"\
"\n"\
"Options:\n"\
" -s <size> Set the grid border length\n"\
" -b <rate> Set the block spawn rate\n"\
" -c Enables color support (ncurses version only)\n"
#endif

View File

@ -1,66 +1,25 @@
/* animations would be nice */ /*
* 2048_no_curses.c - Non-curses version that can be updated at a later time
**/
#include <stdio.h> /* for printf */ #include <stdio.h> /* for printf */
#include <stdlib.h> /* for malloc */ #include <stdlib.h> /* for malloc */
#include <termios.h>/* */ #include <termios.h>/* */
#include <time.h> /* for time */ #include <time.h> /* for time */
#include <unistd.h> /* for getopts */ #include <unistd.h> /* for getopts */
#include "2048.h"
// Repeat an expression y, x times */ int **g; /* grid pointer */
#define ITER(x, expr)\ int SZ; /* grid size */
do {\ int s; /* Current score */
int i;\ int sl; /* Score for last turn */
for (i = 0; i < x; i++){ expr;}\ int hs; /* Highscore */
} while (0)
/* Allocates a square pointer of array of arrays onto heap */
#define CALLOC2D(ptr, sz)\
do {\
int i;\
ptr = calloc(sz, sizeof(*ptr));\
for (i = 0; i < sz; i++)\
ptr[i] = calloc(sz, sizeof(*ptr));\
} while (0)
/* Frees a square pointer of arrays to arrays */
#define FREE2D(ptr, sz)\
do {\
int i;\
for (i = 0; i < sz; i++)\
free(ptr[i]);\
free(ptr);\
} while (0)
/* Define a sequence that should be executed each turn */
#define TURN(x)\
gravitate(x) +\
merge(x) +\
gravitate(x)
/* direction enumeration */
enum {DR, DU, DL, DD};
/* grid pointer */
int **g;
/* grid size */
int SZ;
/* score, and last turn score */
int s;
int sl;
/* highscore */
int hs;
/* highscore file */
char *file;
/* Merges adjacent squares of the same value together in a certain direction */ /* Merges adjacent squares of the same value together in a certain direction */
int merge(int d) int merge(int d)
{ {
int moved = 0; int moved = 0;
if (d == DL) { if (d == DIR_LEFT) {
int i, j; int i, j;
for (i = 0; i < SZ; i++) { for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ; j++) { for (j = 0; j < SZ; j++) {
@ -74,7 +33,7 @@ int merge(int d)
} }
} }
} }
else if (d == DU) { else if (d == DIR_UP) {
int i, j; int i, j;
for (i = 0; i < SZ; i++) { for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ; j++) { for (j = 0; j < SZ; j++) {
@ -88,7 +47,7 @@ int merge(int d)
} }
} }
} }
else if (d == DR) { else if (d == DIR_RIGHT) {
int i, j; int i, j;
for (i = SZ - 1; i >= 0; i--) { for (i = SZ - 1; i >= 0; i--) {
for (j = SZ - 1; j >= 0; j--) { for (j = SZ - 1; j >= 0; j--) {
@ -102,7 +61,7 @@ int merge(int d)
} }
} }
} }
else if (d == DD) { else if (d == DIR_DOWN) {
int i, j; int i, j;
for (i = SZ - 1; i >= 0; i--) { for (i = SZ - 1; i >= 0; i--) {
for (j = SZ - 1; j >= 0; j--) { for (j = SZ - 1; j >= 0; j--) {
@ -125,7 +84,7 @@ int merge(int d)
int gravitate(int d) int gravitate(int d)
{ {
int moved = 0; int moved = 0;
if (d == DL) { if (d == DIR_LEFT) {
int i, j; int i, j;
for (i = 0; i < SZ; i++) { for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ - 1; j++) { for (j = 0; j < SZ - 1; j++) {
@ -140,7 +99,7 @@ int gravitate(int d)
} }
} }
} }
else if (d == DU) { else if (d == DIR_UP) {
int i, j; int i, j;
for (i = 0; i < SZ; i++) { for (i = 0; i < SZ; i++) {
for (j = 0; j < SZ - 1; j++) { for (j = 0; j < SZ - 1; j++) {
@ -155,7 +114,7 @@ int gravitate(int d)
} }
} }
} }
else if (d == DR) { else if (d == DIR_RIGHT) {
int i, j; int i, j;
for (i = 0; i < SZ; i++) { for (i = 0; i < SZ; i++) {
for (j = SZ - 1; j > 0; j--) { for (j = SZ - 1; j > 0; j--) {
@ -170,7 +129,7 @@ int gravitate(int d)
} }
} }
} }
else if (d == DD) { else if (d == DIR_DOWN) {
int i, j; int i, j;
for (i = 0; i < SZ; i++) { for (i = 0; i < SZ; i++) {
for (j = SZ - 1; j > 0; j--) { for (j = SZ - 1; j > 0; j--) {
@ -190,8 +149,8 @@ int gravitate(int d)
/* loads hiscore */ /* loads hiscore */
void load_score() { void load_score() {
FILE *fd = fopen(file, "r"); FILE *fd = fopen(HISCORE_FILE, "r");
if (fd == NULL) fd = fopen(file, "w+"); if (fd == NULL) fd = fopen(HISCORE_FILE, "w+");
if (fscanf(fd, "%d", &hs) == EOF) hs = 0; if (fscanf(fd, "%d", &hs) == EOF) hs = 0;
fclose(fd); fclose(fd);
} }
@ -200,7 +159,7 @@ void load_score() {
void save_score() { void save_score() {
if (s > hs && SZ == 4) { if (s > hs && SZ == 4) {
hs = s; hs = s;
FILE *fd = fopen(file, "w+"); FILE *fd = fopen(HISCORE_FILE, "w+");
fprintf(fd, "%d", hs); fprintf(fd, "%d", hs);
fclose(fd); fclose(fd);
} }
@ -265,7 +224,6 @@ int main(int argc, char **argv)
{ {
/* init variables */ /* init variables */
file = ".hs2048g";
hs = 0; hs = 0;
s = 0; s = 0;
sl = 0; sl = 0;
@ -295,21 +253,12 @@ int main(int argc, char **argv)
printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n"); printf("Are you sure you want to reset your highscores? (Y)es or (N)o\n");
int response; int response;
if ((response = getchar()) == 'y' || response == 'Y') { if ((response = getchar()) == 'y' || response == 'Y') {
FILE *fd = fopen(file, "w+"); FILE *fd = fopen(HISCORE_FILE, "w+");
fclose(fd); fclose(fd);
} }
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
case 'h': case 'h':
printf("Controls:\n" printf(USAGE_STR);
" hjkl, wasd Movement\n"
" q Quit\n"
"\n"
"Usage:\n"
" 2048 [options]\n"
"\n"
"Options:\n"
" -s <size> Set the grid border length\n"
" -b <rate> Set the block spawn rate\n");
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
} }
@ -341,19 +290,19 @@ int main(int argc, char **argv)
switch (key) { switch (key) {
case 'h': case 'h':
case 'a': case 'a':
moved = TURN(DL); moved = TURN(DIR_LEFT);
break; break;
case 'l': case 'l':
case 'd': case 'd':
moved = TURN(DR); moved = TURN(DIR_RIGHT);
break; break;
case 'j': case 'j':
case 's': case 's':
moved = TURN(DD); moved = TURN(DIR_DOWN);
break; break;
case 'k': case 'k':
case 'w': case 'w':
moved = TURN(DU); moved = TURN(DIR_UP);
break; break;
case 'q': case 'q':
save_score(); save_score();