Files
C2048/game.c
T

572 lines
15 KiB
C

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "game.h"
#include "timer.h"
#include "zephr.h"
#define ANIM_SPEED 3.0f
// TODO: configurable color palette
Game game = {0};
///////////////////////////////////
//
//
// Utils
//
//
///////////////////////////////////
void print_board() {
printf("Board:\n");
for (int i = 0; i < 4; i++) {
printf(" ");
for (int j = 0; j < 4; j++) {
printf("%d ", game.board[i][j].value);
}
printf("\n");
}
}
void print_new_board() {
printf("New Board:\n");
for (int i = 0; i < 4; i++) {
printf(" ");
for (int j = 0; j < 4; j++) {
printf("%d ", game.board[i][j].new_value);
}
printf("\n");
}
}
Color get_color_from_tile(Tile tile) {
switch (tile.value) {
case 2:
return ColorRGBA(238, 228, 218, 255);
case 4:
return ColorRGBA(237, 224, 200, 255);
case 8:
return ColorRGBA(242, 177, 121, 255);
case 16:
return ColorRGBA(245, 149, 99, 255);
case 32:
return ColorRGBA(246, 124, 95, 255);
case 64:
return ColorRGBA(246, 94, 59, 255);
case 128:
return ColorRGBA(237, 207, 114, 255);
case 256:
return ColorRGBA(237, 204, 97, 255);
case 512:
return ColorRGBA(237, 200, 80, 255);
case 1024:
return ColorRGBA(237, 197, 63, 255);
case 2048:
return ColorRGBA(237, 194, 46, 255);
default:
return ColorRGBA(205, 193, 180, 255);
}
}
u8 get_tile_font_size(Tile tile) {
switch (tile.value) {
case 2:
case 4:
case 8:
return 60;
case 16:
case 32:
case 64:
return 55;
case 128:
case 256:
case 512:
return 50;
case 1024:
case 2048:
return 45;
default:
return 35;
}
}
///////////////////////////////////
//
//
// Drawing
//
//
///////////////////////////////////
void draw_board() {
Size window_size = zephr_get_window_size();
UIConstraints board_con = {0};
set_width_constraint(&board_con, window_size.width, UI_CONSTRAINT_FIXED);
set_height_constraint(&board_con, window_size.height, UI_CONSTRAINT_FIXED);
// bg
draw_quad(&board_con, ColorRGBA(248, 250, 230, 255), 0, ALIGN_TOP_LEFT);
// text
UIConstraints text_con = {0};
set_y_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE);
set_width_constraint(&text_con, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS);
draw_text("2048", 100, text_con, ColorRGBA(34, 234, 82, 155), ALIGN_TOP_CENTER);
// board
set_y_constraint(&board_con, 0.05f, UI_CONSTRAINT_RELATIVE);
set_height_constraint(&board_con, 0.6f, UI_CONSTRAINT_RELATIVE);
set_width_constraint(&board_con, 1, UI_CONSTRAINT_ASPECT_RATIO);
draw_quad(&board_con, ColorRGBA(140, 154, 147, 255), 8, ALIGN_CENTER);
// tiles
const float tile_padding = board_con.width * 0.02f;
const float tile_height = board_con.height * 0.225f;
UIConstraints empty_tile_con = {0};
set_parent_constraint(&empty_tile_con, &board_con);
set_height_constraint(&empty_tile_con, tile_height, UI_CONSTRAINT_FIXED);
set_width_constraint(&empty_tile_con, 1, UI_CONSTRAINT_ASPECT_RATIO);
for (int i = 0; i < 4; i++) {
set_y_constraint(&empty_tile_con, (tile_height + tile_padding) * i + tile_padding, UI_CONSTRAINT_FIXED);
for (int j = 0; j < 4; j++) {
set_x_constraint(&empty_tile_con, (tile_height + tile_padding) * j + tile_padding, UI_CONSTRAINT_FIXED);
draw_quad(&empty_tile_con, ColorRGBA(120, 133, 126, 255), 16, ALIGN_TOP_LEFT);
}
}
UIConstraints tile_con = {0};
set_parent_constraint(&tile_con, &board_con);
set_height_constraint(&tile_con, tile_height, UI_CONSTRAINT_FIXED);
set_width_constraint(&tile_con, 1, UI_CONSTRAINT_ASPECT_RATIO);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
Tile *tile = &game.board[i][j];
Color tile_color = get_color_from_tile(*tile);
char value[16];
sprintf(value, "%d", tile->value);
set_y_constraint(&tile_con, (f32)((tile_height + tile_padding) * i + tile_padding + game.board[i][j].anim_y_offset_relative * board_con.height), UI_CONSTRAINT_FIXED);
set_x_constraint(&tile_con, (f32)((tile_height + tile_padding) * j + tile_padding + game.board[i][j].anim_x_offset_relative * board_con.height), UI_CONSTRAINT_FIXED);
if (tile->value > 0) {
draw_quad(&tile_con, tile_color, 16, ALIGN_TOP_LEFT);
// value text
set_parent_constraint(&text_con, &tile_con);
set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED);
set_y_constraint(&text_con, 0, UI_CONSTRAINT_FIXED);
draw_text(value, get_tile_font_size(*tile), text_con, mul_color(tile_color, 0.5), ALIGN_CENTER);
}
}
}
}
///////////////////////////////////
//
//
// Game logic
//
//
///////////////////////////////////
// TODO: merging tiles still needs to be done, along with its animation
void move_right() {
if (game.animating) return;
game.last_move_dir = MOVE_DIR_RIGHT;
for (int i = 0; i < 4; i++) {
for (int j = 2; j >= 0; j--) {
if (game.board[i][j].value == 0) {
continue;
}
u8 tiles_to_move = 0;
u8 x = j;
printf("tile: %d,%d\n", i, j);
do {
x++;
// next tile is empty so we can move there
if (game.board[i][x].new_value == 0) {
printf("can move. empty\n");
tiles_to_move++;
continue;
}
// next tile has the same value so we can merge and move
if (game.board[i][x].new_value == game.board[i][j].new_value) {
printf("can move. merge\n");
tiles_to_move++;
break;
} else {
// next tile has a different value so we can't move there
x--;
break;
}
} while (x < 3);
printf("tiles to move: %d\n", tiles_to_move);
game.board[i][j].tiles_to_move = tiles_to_move;
if (tiles_to_move > 0) {
game.board[i][x].new_value = game.board[i][j].value;
game.board[i][j].new_value = 0;
game.animating = true;
}
}
}
}
void move_left() {
if (game.animating) return;
game.last_move_dir = MOVE_DIR_LEFT;
for (int i = 0; i < 4; i++) {
for (int j = 1; j < 4; j++) {
if (game.board[i][j].value == 0) {
continue;
}
u8 tiles_to_move = 0;
u8 x = j;
do {
x--;
// next tile is empty so we can move there
if (game.board[i][x].new_value == 0) {
tiles_to_move++;
continue;
}
// next tile has the same value so we can merge and move
if (game.board[i][x].new_value == game.board[i][j].new_value) {
tiles_to_move++;
break;
} else {
// next tile has a different value so we can't move there
x++;
break;
}
} while (x > 0);
game.board[i][j].tiles_to_move = tiles_to_move;
if (tiles_to_move > 0) {
game.board[i][x].new_value = game.board[i][j].value;
game.board[i][j].new_value = 0;
game.animating = true;
}
}
}
}
void move_down() {
if (game.animating) return;
game.last_move_dir = MOVE_DIR_DOWN;
for (int i = 2; i >= 0; i--) {
for (int j = 0; j < 4; j++) {
if (game.board[i][j].value == 0) {
continue;
}
u8 tiles_to_move = 0;
u8 y = i;
do {
y++;
if (game.board[y][j].new_value == 0) {
tiles_to_move++;
continue;
}
if (game.board[y][j].new_value == game.board[i][j].new_value) {
tiles_to_move++;
break;
} else {
y--;
break;
}
} while (y < 3);
game.board[i][j].tiles_to_move = tiles_to_move;
if (tiles_to_move > 0) {
game.board[y][j].new_value = game.board[i][j].value;
game.board[i][j].new_value = 0;
game.animating = true;
}
}
}
}
void move_up() {
if (game.animating) return;
game.last_move_dir = MOVE_DIR_UP;
for (int i = 1; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (game.board[i][j].value == 0) {
continue;
}
u8 tiles_to_move = 0;
u8 y = i;
do {
y--;
if (game.board[y][j].new_value == 0) {
tiles_to_move++;
continue;
}
if (game.board[y][j].new_value == game.board[i][j].new_value) {
tiles_to_move++;
break;
} else {
y++;
break;
}
} while (y > 0);
game.board[i][j].tiles_to_move = tiles_to_move;
if (tiles_to_move > 0) {
game.board[y][j].new_value = game.board[i][j].value;
game.board[i][j].new_value = 0;
game.animating = true;
}
}
}
}
void update_positions(f64 delta_t) {
if (game.animating) {
switch (game.last_move_dir) {
case MOVE_DIR_LEFT:
for (int i = 0; i < 4; i++) {
for (int j = 1; j < 4; j++) {
u8 tiles_to_move = game.board[i][j].tiles_to_move;
f32 speed = ANIM_SPEED * tiles_to_move;
if (game.board[i][j].tiles_to_move != 0) {
game.board[i][j].anim_x_offset_relative -= speed * delta_t;
if (game.board[i][j].anim_x_offset_relative <= -0.240f * (tiles_to_move + (tiles_to_move * 0.02f))) {
game.board[i][j].anim_x_offset_relative = -0.240f * (tiles_to_move + (tiles_to_move * 0.02f));
game.board[i][j - tiles_to_move].value = game.board[i][j].value;
game.board[i][j - tiles_to_move].new_value = game.board[i][j].value;
game.board[i][j].value = 0;
game.board[i][j].new_value = 0;
game.board[i][j].tiles_to_move = 0;
game.board[i][j].anim_x_offset_relative = 0;
game.animating = false;
}
}
}
}
break;
case MOVE_DIR_RIGHT:
for (int i = 0; i < 4; i++) {
for (int j = 2; j >= 0; j--) {
u8 tiles_to_move = game.board[i][j].tiles_to_move;
f32 speed = ANIM_SPEED * tiles_to_move;
if (game.board[i][j].tiles_to_move != 0) {
game.board[i][j].anim_x_offset_relative += speed * delta_t;
if (game.board[i][j].anim_x_offset_relative >= 0.240f * (tiles_to_move + (tiles_to_move * 0.02f))) {
game.board[i][j].anim_x_offset_relative = 0.240f * (tiles_to_move + (tiles_to_move * 0.02f));
game.board[i][j + tiles_to_move].value = game.board[i][j].value;
game.board[i][j + tiles_to_move].new_value = game.board[i][j].value;
game.board[i][j].value = 0;
game.board[i][j].new_value = 0;
game.board[i][j].tiles_to_move = 0;
game.board[i][j].anim_x_offset_relative = 0;
game.animating = false;
}
}
}
}
break;
case MOVE_DIR_UP:
for (int i = 1; i < 4; i++) {
for (int j = 0; j < 4; j++) {
u8 tiles_to_move = game.board[i][j].tiles_to_move;
f32 speed = ANIM_SPEED * tiles_to_move;
if (game.board[i][j].tiles_to_move != 0) {
game.board[i][j].anim_y_offset_relative -= speed * delta_t;
if (game.board[i][j].anim_y_offset_relative <= -0.240f * (tiles_to_move + (tiles_to_move * 0.02f))) {
game.board[i][j].anim_y_offset_relative = -0.240f * (tiles_to_move + (tiles_to_move * 0.02f));
game.board[i - tiles_to_move][j].value = game.board[i][j].value;
game.board[i - tiles_to_move][j].new_value = game.board[i][j].value;
game.board[i][j].value = 0;
game.board[i][j].new_value = 0;
game.board[i][j].tiles_to_move = 0;
game.board[i][j].anim_y_offset_relative = 0;
game.animating = false;
}
}
}
}
break;
case MOVE_DIR_DOWN:
for (int i = 2; i >= 0; i--) {
for (int j = 0; j < 4; j++) {
u8 tiles_to_move = game.board[i][j].tiles_to_move;
f32 speed = ANIM_SPEED * tiles_to_move;
if (game.board[i][j].tiles_to_move != 0) {
game.board[i][j].anim_y_offset_relative += speed * delta_t;
if (game.board[i][j].anim_y_offset_relative >= 0.240f * (tiles_to_move + (tiles_to_move * 0.02f))) {
game.board[i][j].anim_y_offset_relative = 0.240f * (tiles_to_move + (tiles_to_move * 0.02f));
game.board[i + tiles_to_move][j].value = game.board[i][j].value;
game.board[i + tiles_to_move][j].new_value = game.board[i][j].value;
game.board[i][j].value = 0;
game.board[i][j].new_value = 0;
game.board[i][j].tiles_to_move = 0;
game.board[i][j].anim_y_offset_relative = 0;
game.animating = false;
}
}
}
}
break;
}
}
}
void spawn_new_tile(u8 x, u8 y) {
if (rand() % 10 < 9) {
game.board[x][y].value = 2;
game.board[x][y].new_value = 2;
} else {
game.board[x][y].value = 4;
game.board[x][y].new_value = 4;
}
}
void spawn_random_tile() {
u8 available_tiles_count = 0;
u8 available_tiles[16];
for (int i = 0; i < 4; i++) {
for (int j = 0; j <= 3; j++) {
if (game.board[i][j].value == 0) {
available_tiles[available_tiles_count] = i * 4 + j;
available_tiles_count++;
}
}
}
u8 rand_idx = rand() % available_tiles_count;
u8 x = available_tiles[rand_idx] / 4;
u8 y = available_tiles[rand_idx] % 4;
spawn_new_tile(x, y);
}
void game_init(void) {
srand(time(NULL));
// TODO: this shouldn't merge all of them and instead should
// merge every 2 together
/* game.board[0][0].value = 2; */
/* game.board[0][1].value = 2; */
/* game.board[0][2].value = 2; */
/* game.board[0][3].value = 2; */
/* game.board[0][0].new_value = 2; */
/* game.board[0][1].new_value = 2; */
/* game.board[0][2].new_value = 2; */
/* game.board[0][3].new_value = 2; */
for (int i = 0; i < 2; i++) {
spawn_random_tile();
}
}
///////////////////////////////////
//
//
// Loop and input handling
//
//
///////////////////////////////////
void handle_keyboard_input(ZephrEvent e) {
if (
(e.key.mods & ZEPHR_KEY_MOD_CTRL && e.key.code == ZEPHR_KEYCODE_Q) ||
e.key.code == ZEPHR_KEYCODE_ESCAPE
) {
// TODO: show confirmation dialog
zephr_quit();
} else if (e.key.code == ZEPHR_KEYCODE_F11) {
zephr_toggle_fullscreen();
} else if (e.key.code == ZEPHR_KEYCODE_UP) {
move_up();
/* spawn_random_tile(); */
} else if (e.key.code == ZEPHR_KEYCODE_DOWN) {
move_down();
/* spawn_random_tile(); */
} else if (e.key.code == ZEPHR_KEYCODE_LEFT) {
move_left();
/* spawn_random_tile(); */
} else if (e.key.code == ZEPHR_KEYCODE_RIGHT) {
move_right();
/* spawn_random_tile(); */
}
}
void game_loop(void) {
game_init();
f64 last_frame = 0.0;
while (!zephr_should_quit()) {
ZephrEvent e;
while (zephr_iter_events(&e)) {
switch (e.type) {
case ZEPHR_EVENT_WINDOW_CLOSED:
zephr_quit();
break;
case ZEPHR_EVENT_KEY_PRESSED:
handle_keyboard_input(e);
break;
default:
break;
}
}
f64 now = get_time();
f64 delta_t = now - last_frame;
last_frame = now;
update_positions(delta_t);
draw_board();
zephr_swap_buffers();
}
}