403 lines
9.2 KiB
C
403 lines
9.2 KiB
C
#include <dangerzone/engine.h>
|
|
#include <dangerzone/render/anim.h>
|
|
#include <dangerzone/render/misc.h>
|
|
#include <dangerzone/render/rect.h>
|
|
#include <dangerzone/render/text.h>
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define SIZE 4
|
|
#define MOVE_DURATION 0.12f
|
|
#define BORDER_WIDTH 10
|
|
|
|
typedef enum {
|
|
LEFT,
|
|
RIGHT,
|
|
UP,
|
|
DOWN
|
|
} dir_e;
|
|
|
|
typedef struct {
|
|
uint32_t value;
|
|
int row;
|
|
int col;
|
|
} line_tile_t;
|
|
|
|
typedef struct {
|
|
uint32_t area[SIZE][SIZE];
|
|
dir_e direction;
|
|
uint32_t score;
|
|
} game_t;
|
|
|
|
static game_t game;
|
|
|
|
static anim_t movement;
|
|
static anim_track_t move_tracks[SIZE * SIZE];
|
|
static size_t move_track_count;
|
|
|
|
static double last_frame_time;
|
|
|
|
/* Pixel size is calculated from the current window size while drawing. */
|
|
static uint32_t
|
|
colors(uint32_t number)
|
|
{
|
|
switch (number) {
|
|
case 0: return 0xFFFFFF;
|
|
case 2: return 0x00FF00;
|
|
case 4: return 0xFF0000;
|
|
case 8: return 0x0000FF;
|
|
default: return 0xFFFFFF;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return the board coordinate for position p in a line, where p=0 is
|
|
* the side toward which tiles move.
|
|
*/
|
|
static void
|
|
line_coordinate(dir_e direction, int line, int p, int *row, int *col)
|
|
{
|
|
switch (direction) {
|
|
case LEFT:
|
|
*row = line;
|
|
*col = p;
|
|
break;
|
|
case RIGHT:
|
|
*row = line;
|
|
*col = SIZE - 1 - p;
|
|
break;
|
|
case UP:
|
|
*row = p;
|
|
*col = line;
|
|
break;
|
|
case DOWN:
|
|
*row = SIZE - 1 - p;
|
|
*col = line;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert a board cell into the pixel rectangle used to draw its tile.
|
|
* Keep this consistent with draw_tile_rect() and the cell backgrounds.
|
|
*/
|
|
static anim_rect_t
|
|
tile_rect(int row, int col, uint32_t tile_size)
|
|
{
|
|
float side = (float)(tile_size - BORDER_WIDTH);
|
|
|
|
return (anim_rect_t) {
|
|
.x = BORDER_WIDTH / 2.0f + col * (float)tile_size,
|
|
.y = BORDER_WIDTH / 2.0f + row * (float)tile_size,
|
|
.w = side,
|
|
.h = side
|
|
};
|
|
}
|
|
|
|
static void
|
|
add_move_track(uint32_t value,
|
|
int from_row, int from_col,
|
|
int to_row, int to_col)
|
|
{
|
|
if (move_track_count >= SIZE * SIZE)
|
|
return;
|
|
|
|
uint32_t tile_size = game_state->width / SIZE;
|
|
|
|
move_tracks[move_track_count++] = (anim_track_t) {
|
|
.payload = value,
|
|
.from = tile_rect(from_row, from_col, tile_size),
|
|
.to = tile_rect(to_row, to_col, tile_size)
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Process one row or column in movement order. Update the board and record
|
|
* pixel-space animation tracks for every original tile.
|
|
*/
|
|
static bool
|
|
process_line(dir_e direction, int line, uint32_t output[SIZE])
|
|
{
|
|
line_tile_t input[SIZE];
|
|
int input_count = 0;
|
|
int output_count = 0;
|
|
|
|
for (int p = 0; p < SIZE; ++p) {
|
|
int row, col;
|
|
line_coordinate(direction, line, p, &row, &col);
|
|
|
|
uint32_t value = game.area[row][col];
|
|
if (value != 0) {
|
|
input[input_count++] = (line_tile_t) {
|
|
.value = value,
|
|
.row = row,
|
|
.col = col
|
|
};
|
|
}
|
|
}
|
|
|
|
memset(output, 0, sizeof(uint32_t) * SIZE);
|
|
|
|
for (int i = 0; i < input_count; ) {
|
|
int dest_row, dest_col;
|
|
line_coordinate(direction, line, output_count,
|
|
&dest_row, &dest_col);
|
|
|
|
if (i + 1 < input_count &&
|
|
input[i].value == input[i + 1].value) {
|
|
uint32_t merged_value = input[i].value * 2;
|
|
|
|
output[output_count] = merged_value;
|
|
game.score += merged_value;
|
|
|
|
/* Both source tiles travel to the same merge destination. */
|
|
add_move_track(input[i].value,
|
|
input[i].row, input[i].col,
|
|
dest_row, dest_col);
|
|
add_move_track(input[i + 1].value,
|
|
input[i + 1].row, input[i + 1].col,
|
|
dest_row, dest_col);
|
|
|
|
i += 2;
|
|
} else {
|
|
output[output_count] = input[i].value;
|
|
|
|
add_move_track(input[i].value,
|
|
input[i].row, input[i].col,
|
|
dest_row, dest_col);
|
|
|
|
++i;
|
|
}
|
|
|
|
++output_count;
|
|
}
|
|
|
|
bool changed = false;
|
|
for (int p = 0; p < SIZE; ++p) {
|
|
int row, col;
|
|
line_coordinate(direction, line, p, &row, &col);
|
|
|
|
if (game.area[row][col] != output[p])
|
|
changed = true;
|
|
|
|
game.area[row][col] = output[p];
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static bool
|
|
slide(dir_e direction)
|
|
{
|
|
if (anim_is_active(&movement))
|
|
return false;
|
|
|
|
game.direction = direction;
|
|
move_track_count = 0;
|
|
|
|
bool changed = false;
|
|
|
|
for (int line = 0; line < SIZE; ++line) {
|
|
uint32_t output[SIZE];
|
|
if (process_line(direction, line, output))
|
|
changed = true;
|
|
}
|
|
|
|
if (!changed) {
|
|
move_track_count = 0;
|
|
return false;
|
|
}
|
|
|
|
return anim_start(&movement, move_tracks, move_track_count,
|
|
MOVE_DURATION);
|
|
}
|
|
|
|
static void
|
|
new_2(void)
|
|
{
|
|
int empty_rows[SIZE * SIZE];
|
|
int empty_cols[SIZE * SIZE];
|
|
int count = 0;
|
|
|
|
for (int r = 0; r < SIZE; ++r) {
|
|
for (int c = 0; c < SIZE; ++c) {
|
|
if (game.area[r][c] == 0) {
|
|
empty_rows[count] = r;
|
|
empty_cols[count] = c;
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count == 0)
|
|
return;
|
|
|
|
int pick = rand() % count;
|
|
game.area[empty_rows[pick]][empty_cols[pick]] = 2;
|
|
}
|
|
|
|
/* Polling events every frame. */
|
|
void
|
|
client_poll_event(void)
|
|
{
|
|
SDL_Event ev = game_state->event;
|
|
|
|
if (ev.type == SDL_EVENT_QUIT) {
|
|
game_state->is_running = false;
|
|
return;
|
|
}
|
|
|
|
if (ev.type != SDL_EVENT_KEY_DOWN)
|
|
return;
|
|
|
|
switch (ev.key.key) {
|
|
case SDLK_Q:
|
|
game_state->is_running = false;
|
|
break;
|
|
case SDLK_W:
|
|
slide(UP);
|
|
break;
|
|
case SDLK_S:
|
|
slide(DOWN);
|
|
break;
|
|
case SDLK_A:
|
|
slide(LEFT);
|
|
break;
|
|
case SDLK_D:
|
|
slide(RIGHT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static double
|
|
monotonic_seconds(void)
|
|
{
|
|
struct timespec ts;
|
|
timespec_get(&ts, TIME_UTC);
|
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
|
}
|
|
|
|
static void
|
|
draw_tile_rect(uint32_t value, float x, float y, float w, float h)
|
|
{
|
|
uint32_t ix = (uint32_t)x;
|
|
uint32_t iy = (uint32_t)y;
|
|
uint32_t iw = (uint32_t)w;
|
|
uint32_t ih = (uint32_t)h;
|
|
uint32_t color = colors(value);
|
|
|
|
put_rect(OVERDRAW, ix, iy, iw, ih, color);
|
|
|
|
if (value != 0) {
|
|
char text[16];
|
|
snprintf(text, sizeof text, "%u", value);
|
|
put_string(OVERDRAW, text,
|
|
ix + BORDER_WIDTH, iy + BORDER_WIDTH,
|
|
3, 1, 12, 0x000000, color);
|
|
}
|
|
}
|
|
|
|
static void
|
|
draw_tile(uint32_t value, int row, int col, uint32_t tile_size)
|
|
{
|
|
anim_rect_t rect = tile_rect(row, col, tile_size);
|
|
draw_tile_rect(value, rect.x, rect.y, rect.w, rect.h);
|
|
}
|
|
|
|
void
|
|
client_update_framebuffer(void)
|
|
{
|
|
double now = monotonic_seconds();
|
|
float dt = last_frame_time == 0.0
|
|
? 0.0f
|
|
: (float)(now - last_frame_time);
|
|
last_frame_time = now;
|
|
|
|
if (dt > 0.05f)
|
|
dt = 0.05f;
|
|
|
|
if (anim_is_active(&movement) && anim_update(&movement, dt)) {
|
|
new_2();
|
|
}
|
|
|
|
uint32_t tile_size = game_state->width / SIZE;
|
|
uint32_t border_width = BORDER_WIDTH;
|
|
|
|
clear_framebuffer(OVERDRAW, 0x000000);
|
|
|
|
/* Draw all cell backgrounds first. */
|
|
for (int r = 0; r < SIZE; ++r) {
|
|
for (int c = 0; c < SIZE; ++c) {
|
|
uint32_t x = border_width / 2 + (uint32_t)c * tile_size;
|
|
uint32_t y = border_width / 2 + (uint32_t)r * tile_size;
|
|
uint32_t side = tile_size - border_width;
|
|
|
|
put_rect(BASE, x, y, side, side, 0xFFFFFF);
|
|
put_rect(OVERDRAW, x, y, side, side, 0xFFFFFF);
|
|
}
|
|
}
|
|
|
|
if (anim_is_active(&movement)) {
|
|
const anim_item_t *items = anim_items(&movement);
|
|
size_t count = anim_count(&movement);
|
|
|
|
for (size_t i = 0; i < count; ++i) {
|
|
draw_tile_rect(items[i].payload,
|
|
items[i].rect.x, items[i].rect.y,
|
|
items[i].rect.w, items[i].rect.h);
|
|
}
|
|
} else {
|
|
for (int r = 0; r < SIZE; ++r) {
|
|
for (int c = 0; c < SIZE; ++c) {
|
|
draw_tile(game.area[r][c], r, c, tile_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
char score_text[32];
|
|
snprintf(score_text, sizeof score_text, "score: %u", game.score);
|
|
put_string(OVERDRAW, score_text,
|
|
border_width / 2, tile_size * SIZE + border_width / 2,
|
|
6, 1, 12, 0xFFFFFF, 0x000000);
|
|
}
|
|
|
|
static void
|
|
game_init(void)
|
|
{
|
|
memset(&game, 0, sizeof game);
|
|
anim_init(&movement);
|
|
srand((unsigned)time(NULL));
|
|
|
|
/* Start with two 2-tiles. */
|
|
new_2();
|
|
new_2();
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
uint32_t window_width = 500;
|
|
uint32_t window_height = (uint32_t)(window_width * 1.12f);
|
|
|
|
game_init();
|
|
|
|
if (engine_init(window_width, window_height,
|
|
"Dangerous 2048",
|
|
client_poll_event,
|
|
client_update_framebuffer)) {
|
|
engine_start();
|
|
engine_stop();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|