2048-cli/2048_no_curses.c

380 lines
9.7 KiB
C
Raw Normal View History

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>/* */
#include <time.h> /* for time */
2014-03-12 21:32:34 +00:00
#include <unistd.h> /* for getopts */
// Repeat an expression y, x times */
#define ITER(x, expr)\
2014-03-12 21:32:34 +00:00
do {\
int i;\
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)\
gravitate(x) +\
merge(x) +\
gravitate(x)
2014-03-12 21:32:34 +00:00
/* 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 */
int merge(int d)
2014-03-12 21:32:34 +00:00
{
int moved = 0;
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;
moved = 1;
2014-03-12 21:32: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;
moved = 1;
2014-03-12 21:32: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;
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;
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 */
int gravitate(int d)
2014-03-12 21:32:34 +00:00
{
int moved = 0;
2014-03-12 21:32:34 +00:00
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;
2014-03-12 21:32:34 +00:00
}
}
}
}
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;
2014-03-12 21:32:34 +00:00
}
}
}
}
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;
2014-03-12 21:32:34 +00:00
}
}
}
}
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;
2014-03-12 21:32:34 +00:00
}
}
}
}
return moved;
2014-03-12 21:32:34 +00:00
}
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;
}
/* 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]);
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);
if (sl) printf("(+%d)", sl);
printf("\n");
2014-03-12 21:32:34 +00:00
// alter this SZ + 1 to match abitrary grid size
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");
}
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);
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");
exit(EXIT_SUCCESS);
2014-03-12 21:32:34 +00:00
}
}
/* random seed */
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, moved;
2014-03-12 21:32:34 +00:00
while (1) {
/* will goto this if we didn't get a valid keypress */
retry:;
moved = 0;
2014-03-12 21:32:34 +00:00
key = getchar();
sl = 0;
/* should check if anything changed during merge and if not retry */
switch (key) {
case 'h':
case 'a':
moved = TURN(DL);
2014-03-12 21:32:34 +00:00
break;
case 'l':
case 'd':
moved = TURN(DR);
2014-03-12 21:32:34 +00:00
break;
case 'j':
case 's':
moved = TURN(DD);
2014-03-12 21:32:34 +00:00
break;
case 'k':
case 'w':
moved = TURN(DU);
2014-03-12 21:32:34 +00:00
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;
}
if (moved) {
ITER(n_blocks, rand_block());
draw_grid();
}
2014-03-12 21:32:34 +00:00
}
}