/* * 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 /* for malloc */ #include /* for time */ #include /* for getopts */ #include #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) { /* Move from upper left, across rows until second to second-last elem each row */ 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 if (d == DIR_UP) { /* Move from upper left, across rows until final row which is skipped */ 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] <<= 1; grid[i + 1][j] = 0; score_last += grid[i][j]; score += grid[i][j]; moved = 1; } } } } else if (d == DIR_RIGHT) { /* Move from lower right, backwards across rows until first elem each row */ for (i = grid_size - 1; i >= 0; i--) { for (j = grid_size - 1; j > 0; 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 if (d == DIR_DOWN) { /* Move from lower right, across rows until first row which is skipped */ for (i = grid_size - 1; i > 0; i--) { for (j = grid_size - 1; j >= 0; j--) { if (grid[i][j] == grid[i - 1][j]) { grid[i][j] <<= 1; 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+"); if (fscanf(fd, "%d", &s) == 1) {}; 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(unsigned 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; int enable_color = has_colors(); while ((c = getopt(argc, argv, "rcChs:b:")) != -1) { switch (c) { /* Color support */ case 'c': enable_color = 1; break; case 'C': enable_color = 0; 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); } } if (enable_color) { if (!has_colors()) { fprintf(stderr, "Terminal does not support color\n"); } else { 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); } } /* 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; }