2048-in-C/main.c
2024-01-16 23:31:36 -08:00

372 lines
9.9 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <math.h>
#include <ncurses.h>
#define BLANK 0
#define FRAME_DELAY 55
#define COLOR_PAIR_DEFAULT 10
int board[4][4];
int score = 0;
bool won = false;
enum Direction {
UP,
DOWN,
LEFT,
RIGHT
};
enum MoveType {
NOMOVE,
MOVE,
MERGE,
WIN
};
void delay(int ms) {
/* delay the program by a given number of ms*/
clock_t start_time = clock();
while (clock() < start_time + (ms * 1000))
;
}
void draw_grid() {
/* draw the gridlines of the board */
attron(COLOR_PAIR(COLOR_PAIR_DEFAULT));
move(0, 0);
printw(" ");
move(0, 0);
printw("Score: %d", score);
// horizontal gridlines
for (int a = 0; a < 5; a++) {
move(a * 4 + 1, 0);
int left_piece = ACS_LTEE;
int middle_piece = ACS_PLUS;
int right_piece = ACS_RTEE;
if (a == 0) { // top row
left_piece = ACS_ULCORNER;
middle_piece = ACS_TTEE;
right_piece = ACS_URCORNER;
} else if (a == 4) { // bottom row
left_piece = ACS_LLCORNER;
middle_piece = ACS_BTEE;
right_piece = ACS_LRCORNER;
}
addch(left_piece);
for (int n = 0; n < 3; n++) {
for (int m = 0; m < 7; m++) addch(ACS_HLINE); addch(middle_piece);
}
for (int n = 0; n < 7; n++) addch(ACS_HLINE); addch(right_piece);
// vertical gridlines
if (a != 4) {
for (int n = 1; n < 4; n++) {
for (int m = 0; m < 5; m++) {
move(a * 4 + n + 1, m * 8);
addch(ACS_VLINE);
}
}
}
}
}
void _addstr_centered(int tile_value) {
/* draw the tile number so that it's centered */
int length = 1;
if (tile_value != BLANK) {
// calculate length of integer
length = log10(tile_value) + 1;
}
switch (length) {
case 1:
printw(" %d ", tile_value);
break;
case 2:
printw(" %d ", tile_value);
break;
case 3:
printw(" %d ", tile_value);
break;
case 4:
printw(" %d ", tile_value);
break;
}
}
void display_board() {
/* display the board */
clear();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int tile_value = board[i][j];
int color_pair = COLOR_PAIR_DEFAULT;
if (tile_value != BLANK) {
color_pair = (int)log2(tile_value);
}
attron(COLOR_PAIR(color_pair));
move(i * 4 + 2, j * 8 + 1);
addstr(" ");
move(i * 4 + 3, j * 8 + 1);
if (tile_value == BLANK) {
addstr(" ");
} else {
_addstr_centered(tile_value);
}
move(i * 4 + 4, j * 8 + 1);
addstr(" ");
}
}
draw_grid();
refresh();
}
void place_random() {
/* place a "2" (90% chance) or a "4" (10% chance) somewhere random on the board */
int possible_positions[16][2];
int n = 0;
// collect all free squares
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (board[i][j] == BLANK) {
possible_positions[n][0] = i;
possible_positions[n][1] = j;
n++;
}
}
}
// choose one of the free squares at random
int rindex = rand() % n;
// choose a 2 or a 4
int rnum = rand() % 10;
int num;
if (rnum == 0) {
num = 4;
} else {
num = 2;
}
board[possible_positions[rindex][0]][possible_positions[rindex][1]] = num;
}
void reset_board() {
/* reset everything */
score = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
board[i][j] = BLANK;
}
}
place_random();
place_random();
}
bool game_lost() {
/* check if the game is lost */
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int tile_value = board[i][j];
if ((tile_value == BLANK) ||
(i != 3 && board[i + 1][j] == tile_value) ||
(j != 3 && board[i][j+1] == tile_value)) {
return false;
}
}
}
return true;
}
enum MoveType move_tile(int row, int column, enum Direction direction, bool dont_merge) {
/* attempt to move a tile in a direction, and return how it moved */
int tile_value = board[row][column];
if (tile_value == BLANK) {
return NOMOVE;
}
int next_row = row;
int next_column = column;
switch (direction) {
case RIGHT:
next_column = column + 1;
break;
case LEFT:
next_column = column - 1;
break;
case UP:
next_row = row - 1;
break;
case DOWN:
next_row = row + 1;
break;
}
int next_value = board[next_row][next_column];
if (next_value == BLANK) {
board[row][column] = BLANK;
board[next_row][next_column] = tile_value;
return MOVE;
}
else if (next_value == tile_value && !dont_merge) {
board[row][column] = BLANK;
int new_value = tile_value << 1; // lshift just for fun
if (new_value == 2048) {
won = true;
return WIN;
}
board[next_row][next_column] = new_value;
score += new_value;
return MERGE;
} else {
return NOMOVE;
}
}
int _tile_index(int row, int column) {
/* map a tile's coords to a single index number */
return (row << 2) | column;
}
bool move_board(enum Direction direction) {
/* move every tile on the board in a direction */
int row_start = 0;
int row_increment = 1;
int row_end = 4;
int column_start = 0;
int column_increment = 1;
int column_end = 4;
switch (direction) {
case LEFT:
column_start = 1;
break;
case RIGHT:
column_start = 2;
column_increment = -1;
column_end = -1;
break;
case UP:
row_start = 1;
break;
case DOWN:
row_start = 2;
row_increment = -1;
row_end = -1;
break;
}
bool merged[16] = {false}; // keeps track of which tiles have been merged (via index)
int tile_index;
bool moved = false;
enum MoveType move_type;
for (int s = 0; s < 3; s++) { // tiles can move at max 3 spaces in one move
for (int i = row_start; i != row_end; i += row_increment) {
for (int j = column_start; j != column_end; j += column_increment) {
tile_index = _tile_index(i, j);
move_type = move_tile(i, j, direction, merged[tile_index]);
if (move_type == WIN) {
return true;
}
else if (move_type == MERGE) {
// blocks the tile from being merged a second time
merged[tile_index] = true;
// also blocks off the next tile
int next_row = i + ((direction == UP || direction == DOWN) ? row_increment * -1 : 0);
int next_column = j + ((direction == RIGHT || direction == LEFT) ? column_increment * -1 : 0);
int next_tile_index = _tile_index(next_row, next_column);
merged[next_tile_index] = true;
}
else if (move_type != NOMOVE) {
moved = true;
}
}
}
delay(FRAME_DELAY);
display_board();
}
return moved;
}
void init_color_255 (int colornum, int r, int g, int b) {
/* same thing as init_color but using 0-255 instead of 0-1000 */
init_color(colornum, (r/255.0)*1000, (g/255.0)*1000, (b/255.0)*1000);
}
int main() {
// initialize ncurses
initscr();
keypad(stdscr, TRUE);
start_color();
// set color values
// start at 8 as not to interfere with any of the default color codes
init_color_255(8, 238, 227, 218); // 2
init_color_255(9, 238, 224, 201); // 4
init_color_255(10, 243, 178, 121); // 8
init_color_255(11, 246, 149, 99); // 16
init_color_255(12, 247, 124, 95); // 32
init_color_255(13, 246, 94, 58); // 64
init_color_255(14, 237, 208, 115); // 128
init_color_255(15, 237, 204, 97); // 256
init_color_255(16, 237, 199, 80); // 512
init_color_255(17, 237, 197, 62); // 1024
init_color_255(18, 190, 175, 157); // background
// set color pairs
for (int i = 1; i < 10; i++) {
init_pair(i, COLOR_BLACK, i+7);
}
init_pair(COLOR_PAIR_DEFAULT, COLOR_BLACK, 18);
attron(A_BOLD);
attron(COLOR_PAIR(COLOR_PAIR_DEFAULT));
// initialize the rand() function
time_t t;
srand((unsigned) time(&t));
// set up the board
reset_board();
while (true) {
display_board();
int moved = false;
int ch = getch();
switch (ch) {
case KEY_UP:
moved = move_board(UP);
break;
case KEY_DOWN:
moved = move_board(DOWN);
break;
case KEY_LEFT:
moved = move_board(LEFT);
break;
case KEY_RIGHT:
moved = move_board(RIGHT);
break;
case 'r':
reset_board();
default:
continue;
}
if (moved) {
place_random();
}
if (game_lost() || won) {
break;
}
}
endwin();
if (won) {
printf("Congratulations nerd, you won! Final Score: %d\n", score);
} else {
printf("You Lost! Final Score: %d\n", score);
}
return 1;
}