2014-03-12 21:32:34 +00:00
|
|
|
/* animations would be nice */
|
|
|
|
|
|
|
|
#include <stdio.h> /* for printf */
|
|
|
|
#include <stdlib.h> /* for malloc */
|
|
|
|
#include <termios.h>/* */
|
2014-03-12 23:35:03 +00:00
|
|
|
#include <time.h> /* for time */
|
2014-03-12 21:32:34 +00:00
|
|
|
#include <unistd.h> /* for getopts */
|
|
|
|
|
|
|
|
// Repeat an expression y, x times */
|
2014-03-12 23:35:03 +00:00
|
|
|
#define ITER(x, expr)\
|
2014-03-12 21:32:34 +00:00
|
|
|
do {\
|
|
|
|
int i;\
|
2014-03-12 23:35:03 +00:00
|
|
|
for (i = 0; i < x; i++){ expr;}\
|
2014-03-12 21:32:34 +00:00
|
|
|
} 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)\
|
|
|
|
do {\
|
|
|
|
gravitate(x);\
|
|
|
|
merge(x);\
|
|
|
|
gravitate(x);\
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
/* direction enumeration */
|
|
|
|
enum {DR, DU, DL, DD};
|
|
|
|
|
|
|
|
/* grid pointer */
|
|
|
|
int **g;
|
|
|
|
|
|
|
|
/* grid size */
|
|
|
|
int SZ;
|
|
|
|
|
|
|
|
/* score, and last turn score */
|
|
|
|
int s;
|
|
|
|
int sl;
|
|
|
|
|
2014-03-14 21:52:49 +00:00
|
|
|
/* highscore */
|
|
|
|
int hs;
|
|
|
|
|
|
|
|
/* highscore file */
|
|
|
|
char *file;
|
|
|
|
|
2014-03-12 21:32:34 +00:00
|
|
|
/* Merges adjacent squares of the same value together in a certain direction */
|
|
|
|
void merge(int d)
|
|
|
|
{
|
2014-04-17 19:46:34 +00:00
|
|
|
if (d == DL) {
|
2014-03-12 21:32:34 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-17 19:46:34 +00:00
|
|
|
else if (d == DU) {
|
2014-03-12 21:32:34 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-04-17 19:46:34 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-03-12 21:32:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
void gravitate(int d)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-14 21:52:49 +00:00
|
|
|
/* loads 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-12 21:32:34 +00:00
|
|
|
/* returns if there are any available spaces left on the grid */
|
|
|
|
int space_left()
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < SZ; i++)
|
|
|
|
for (j = 0; j < SZ; j++)
|
|
|
|
if (!g[i][j])
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-03-17 05:45:32 +00:00
|
|
|
/* places a random block onto the grid - either a 4, or a 2 with a ratio of 1:3 respectively */
|
2014-03-12 21:32:34 +00:00
|
|
|
/* do this in a smarter fashion */
|
|
|
|
void rand_block()
|
|
|
|
{
|
|
|
|
if (space_left()) {
|
|
|
|
int x_p, y_p;
|
|
|
|
while (g[x_p = rand() % SZ][y_p = rand() % SZ]);
|
2014-03-17 05:45:32 +00:00
|
|
|
g[x_p][y_p] = (rand() & 3) ? 2 : 4;
|
2014-03-12 21:32:34 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
printf("\n"
|
|
|
|
"YOU LOSE\n"
|
|
|
|
"Your score was %d\n", s);
|
2014-03-14 21:52:49 +00:00
|
|
|
save_score();
|
2014-03-12 21:32:34 +00:00
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* draws the grid and fills it with the current values */
|
|
|
|
void draw_grid()
|
|
|
|
{
|
2014-03-14 21:52:49 +00:00
|
|
|
printf("HISCORE: %d |", hs);
|
|
|
|
printf("| SCORE: %d ", s);
|
2014-03-12 23:35:03 +00:00
|
|
|
if (sl) printf("(+%d)", sl);
|
|
|
|
printf("\n");
|
2014-03-12 21:32:34 +00:00
|
|
|
|
|
|
|
// alter this SZ + 1 to match abitrary grid size
|
2014-03-14 01:19:37 +00:00
|
|
|
ITER(SZ, printf("------"));
|
|
|
|
printf("-\n");
|
2014-03-12 21:32:34 +00:00
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < SZ; i++) {
|
|
|
|
printf("|");
|
|
|
|
for (j = 0; j < SZ; j++) {
|
|
|
|
if (g[i][j])
|
|
|
|
printf("%*d |", 4, g[i][j]);
|
|
|
|
else
|
|
|
|
printf(" |");
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
2014-03-14 01:19:37 +00:00
|
|
|
ITER(SZ, printf("------"));
|
|
|
|
printf("-\n\n");
|
2014-03-12 21:32:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* store the terminal settings and call this function on exit to restore */
|
|
|
|
struct termios sattr;
|
|
|
|
void reset_term()
|
|
|
|
{
|
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &sattr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* entry point for the program */
|
|
|
|
/* parses options and stores the main game loop */
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
2014-03-14 21:52:49 +00:00
|
|
|
|
2014-03-12 21:32:34 +00:00
|
|
|
/* init variables */
|
2014-03-14 21:52:49 +00:00
|
|
|
file = ".hs2048g";
|
|
|
|
hs = 0;
|
2014-03-12 21:32:34 +00:00
|
|
|
s = 0;
|
|
|
|
sl = 0;
|
|
|
|
SZ = 4;
|
|
|
|
CALLOC2D(g, SZ);
|
|
|
|
|
2014-03-14 21:52:49 +00:00
|
|
|
load_score();
|
2014-03-12 21:32:34 +00:00
|
|
|
int n_blocks = 1;
|
|
|
|
|
|
|
|
/* parse options */
|
|
|
|
int c;
|
2014-03-14 21:52:49 +00:00
|
|
|
while ((c = getopt(argc, argv, "rhs:b:")) != -1) {
|
2014-03-12 21:32:34 +00:00
|
|
|
switch (c) {
|
|
|
|
// different board sizes
|
|
|
|
case 's':
|
|
|
|
FREE2D(g, SZ);
|
2014-03-14 21:52:49 +00:00
|
|
|
int optint = atoi(optarg);
|
|
|
|
SZ = optint > 4 ? optint : 4;
|
2014-03-12 21:32:34 +00:00
|
|
|
CALLOC2D(g, SZ);
|
|
|
|
break;
|
|
|
|
// different block spawn rate
|
|
|
|
case 'b':
|
|
|
|
n_blocks = atoi(optarg);
|
|
|
|
break;
|
2014-03-14 21:52:49 +00:00
|
|
|
// reset hiscores
|
|
|
|
case 'r':
|
|
|
|
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);
|
2014-03-12 23:35:03 +00:00
|
|
|
case 'h':
|
|
|
|
printf("Controls:\n"
|
|
|
|
" hjkl, wasd Movement\n"
|
|
|
|
" q Quit\n"
|
|
|
|
"\n"
|
|
|
|
"Usage:\n"
|
|
|
|
" 2048 [options]\n"
|
|
|
|
"\n"
|
|
|
|
"Options:\n"
|
2014-03-20 21:46:23 +00:00
|
|
|
" -s <size> Set the grid border length\n"
|
|
|
|
" -b <rate> Set the block spawn rate\n");
|
2014-03-12 23:35:03 +00:00
|
|
|
exit(EXIT_SUCCESS);
|
2014-03-12 21:32:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* random seed */
|
2014-03-14 01:19:37 +00:00
|
|
|
srand((unsigned int)time(NULL));
|
2014-03-12 21:32:34 +00:00
|
|
|
ITER(2, rand_block());
|
|
|
|
draw_grid();
|
|
|
|
|
|
|
|
/* store term settings so we can restore on exit */
|
|
|
|
tcgetattr(STDIN_FILENO, &sattr);
|
|
|
|
atexit(reset_term);
|
|
|
|
|
|
|
|
/* alters terminal stdin to not echo and doesn't need \n before reading getchar */
|
|
|
|
struct termios tattr;
|
|
|
|
tcgetattr(STDIN_FILENO, &tattr);
|
|
|
|
tattr.c_lflag &= ~(ICANON | ECHO);
|
|
|
|
tcsetattr(STDOUT_FILENO, TCSANOW, &tattr);
|
|
|
|
|
|
|
|
int key;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
/* will goto this if we didn't get a valid keypress */
|
|
|
|
retry:;
|
|
|
|
key = getchar();
|
|
|
|
sl = 0;
|
|
|
|
|
|
|
|
/* should check if anything changed during merge and if not retry */
|
|
|
|
switch (key) {
|
|
|
|
case 'h':
|
|
|
|
case 'a':
|
|
|
|
TURN(DL);
|
|
|
|
break;
|
|
|
|
case 'l':
|
|
|
|
case 'd':
|
|
|
|
TURN(DR);
|
|
|
|
break;
|
|
|
|
case 'j':
|
|
|
|
case 's':
|
|
|
|
TURN(DD);
|
|
|
|
break;
|
|
|
|
case 'k':
|
|
|
|
case 'w':
|
|
|
|
TURN(DU);
|
|
|
|
break;
|
|
|
|
case 'q':
|
2014-03-14 21:52:49 +00:00
|
|
|
save_score();
|
2014-03-12 21:32:34 +00:00
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
default:
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
|
|
|
|
ITER(n_blocks, rand_block());
|
|
|
|
draw_grid();
|
|
|
|
}
|
|
|
|
}
|