add animations #28
@@ -1,22 +1,55 @@
|
|||||||
#pragma once
|
#ifndef DANGERZONE_RENDER_ANIM_H
|
||||||
|
#define DANGERZONE_RENDER_ANIM_H
|
||||||
|
|
||||||
#include <dangerzone/engine.h>
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/**
|
#define ANIM_MAX_TRACKS 64
|
||||||
* animate a rectangle's position changing over time
|
|
||||||
* => x0 -> x1, y0 -> y1, all else stays the same
|
typedef struct {
|
||||||
*
|
float x;
|
||||||
* ONLY DOES ONE FRAME OF THE ANIMATION
|
float y;
|
||||||
|
float w;
|
||||||
|
float h;
|
||||||
|
} anim_rect_t;
|
||||||
|
|
||||||
|
/* payload is application data; the animation module does not interpret it. */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t payload;
|
||||||
|
anim_rect_t from;
|
||||||
|
anim_rect_t to;
|
||||||
|
} anim_track_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t payload;
|
||||||
|
anim_rect_t rect;
|
||||||
|
} anim_item_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
anim_track_t tracks[ANIM_MAX_TRACKS];
|
||||||
|
anim_item_t items[ANIM_MAX_TRACKS];
|
||||||
|
size_t count;
|
||||||
|
float elapsed;
|
||||||
|
float duration;
|
||||||
|
bool active;
|
||||||
|
} anim_t;
|
||||||
|
|
||||||
|
void anim_init(anim_t *anim);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start tracks from their initial rectangles to their destination rectangles.
|
||||||
|
* Returns false for invalid input or if count exceeds ANIM_MAX_TRACKS.
|
||||||
|
* A zero-duration animation completes immediately.
|
||||||
*/
|
*/
|
||||||
void
|
bool anim_start(anim_t *anim, const anim_track_t *tracks,
|
||||||
anim_rect_pos(
|
size_t count, float duration);
|
||||||
framebuffer_e framebuffer,
|
|
||||||
uint32_t x0,
|
/* Advance by nonnegative seconds. Returns true once the animation is finished. */
|
||||||
uint32_t y0,
|
bool anim_update(anim_t *anim, float dt);
|
||||||
uint32_t x1,
|
|
||||||
uint32_t y1,
|
bool anim_is_active(const anim_t *anim);
|
||||||
uint32_t w,
|
size_t anim_count(const anim_t *anim);
|
||||||
uint32_t h,
|
const anim_item_t *anim_items(const anim_t *anim);
|
||||||
uint32_t color
|
|
||||||
);
|
#endif
|
||||||
|
|||||||
+76
-25
@@ -1,38 +1,89 @@
|
|||||||
#include <dangerzone/render/anim.h>
|
#include <dangerzone/render/anim.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
void
|
void
|
||||||
anim_rect_pos(
|
anim_init(anim_t *anim)
|
||||||
framebuffer_e framebuffer,
|
{
|
||||||
uint32_t x0,
|
if (anim != NULL)
|
||||||
uint32_t y0,
|
memset(anim, 0, sizeof *anim);
|
||||||
uint32_t x1,
|
}
|
||||||
uint32_t y1,
|
|
||||||
uint32_t w,
|
bool
|
||||||
uint32_t h,
|
anim_start(anim_t *anim, const anim_track_t *tracks,
|
||||||
uint32_t color
|
size_t count, float duration)
|
||||||
) {
|
{
|
||||||
// there's nowhere to move
|
if (anim == NULL || (count != 0 && tracks == NULL) ||
|
||||||
if (x0 == x1 && y0 == y1) {
|
count > ANIM_MAX_TRACKS || duration < 0.0f) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anim->count = count;
|
||||||
|
anim->elapsed = 0.0f;
|
||||||
|
anim->duration = duration;
|
||||||
|
anim->active = duration > 0.0f && count > 0;
|
||||||
|
|
||||||
uint32_t dirx = (x1 - x0 > 0) ? 1 : -1;
|
for (size_t i = 0; i < count; ++i) {
|
||||||
uint32_t diry = (y1 - y0 > 0) ? 1 : -1;
|
anim->tracks[i] = tracks[i];
|
||||||
|
anim->items[i].payload = tracks[i].payload;
|
||||||
|
anim->items[i].rect = tracks[i].from;
|
||||||
|
}
|
||||||
|
|
||||||
if (x0 == x1) {
|
if (!anim->active) {
|
||||||
dirx = 0;
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
anim->items[i].rect = tracks[i].to;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y0 == y1) {
|
return true;
|
||||||
diry = 0;
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
anim_update(anim_t *anim, float dt)
|
||||||
|
{
|
||||||
|
if (anim == NULL || !anim->active)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (dt < 0.0f)
|
||||||
|
dt = 0.0f;
|
||||||
|
|
||||||
|
anim->elapsed += dt;
|
||||||
|
|
||||||
|
float t = anim->elapsed / anim->duration;
|
||||||
|
if (t >= 1.0f) {
|
||||||
|
t = 1.0f;
|
||||||
|
anim->active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* Ease-out. Replace with t for linear motion. */
|
||||||
* 1. shift the rectangle by one pixel in the direction of movement
|
float eased = 1.0f - (1.0f - t) * (1.0f - t);
|
||||||
* 2. remove the trailing row / column left
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (true) {
|
for (size_t i = 0; i < anim->count; ++i) {
|
||||||
|
const anim_track_t *track = &anim->tracks[i];
|
||||||
|
anim_rect_t *rect = &anim->items[i].rect;
|
||||||
|
|
||||||
|
rect->x = track->from.x + (track->to.x - track->from.x) * eased;
|
||||||
|
rect->y = track->from.y + (track->to.y - track->from.y) * eased;
|
||||||
|
rect->w = track->from.w + (track->to.w - track->from.w) * eased;
|
||||||
|
rect->h = track->from.h + (track->to.h - track->from.h) * eased;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !anim->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
anim_is_active(const anim_t *anim)
|
||||||
|
{
|
||||||
|
return anim != NULL && anim->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
anim_count(const anim_t *anim)
|
||||||
|
{
|
||||||
|
return anim != NULL ? anim->count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anim_item_t *
|
||||||
|
anim_items(const anim_t *anim)
|
||||||
|
{
|
||||||
|
return anim != NULL ? anim->items : NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
+311
-418
@@ -1,8 +1,10 @@
|
|||||||
#include <dangerzone/engine.h>
|
#include <dangerzone/engine.h>
|
||||||
|
#include <dangerzone/render/anim.h>
|
||||||
#include <dangerzone/render/misc.h>
|
#include <dangerzone/render/misc.h>
|
||||||
#include <dangerzone/render/rect.h>
|
#include <dangerzone/render/rect.h>
|
||||||
#include <dangerzone/render/text.h>
|
#include <dangerzone/render/text.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -10,6 +12,8 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#define SIZE 4
|
#define SIZE 4
|
||||||
|
#define MOVE_DURATION 0.12f
|
||||||
|
#define BORDER_WIDTH 10
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LEFT,
|
LEFT,
|
||||||
@@ -18,492 +22,381 @@ typedef enum {
|
|||||||
DOWN
|
DOWN
|
||||||
} dir_e;
|
} dir_e;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint32_t x;
|
|
||||||
uint32_t y;
|
|
||||||
} v2_t;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
v2_t pos;
|
int row;
|
||||||
} tile_t;
|
int col;
|
||||||
|
} line_tile_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
tile_t area[SIZE][SIZE];
|
uint32_t area[SIZE][SIZE];
|
||||||
dir_e direction;
|
dir_e direction;
|
||||||
uint32_t score;
|
uint32_t score;
|
||||||
} game_t;
|
} game_t;
|
||||||
|
|
||||||
static game_t game;
|
static game_t game;
|
||||||
|
|
||||||
#define MOVE_DURATION 0.12f
|
static anim_t movement;
|
||||||
|
static anim_track_t move_tracks[SIZE * SIZE];
|
||||||
|
static size_t move_track_count;
|
||||||
|
|
||||||
typedef struct {
|
static double last_frame_time;
|
||||||
uint32_t value;
|
|
||||||
int from_row, from_col;
|
|
||||||
int to_row, to_col;
|
|
||||||
bool merge_target;
|
|
||||||
} tile_anim_t;
|
|
||||||
|
|
||||||
static tile_anim_t anims[SIZE * SIZE];
|
/* Pixel size is calculated from the current window size while drawing. */
|
||||||
static size_t anim_count;
|
static uint32_t
|
||||||
static float anim_elapsed;
|
colors(uint32_t number)
|
||||||
static bool anim_active;
|
|
||||||
uint32_t before[SIZE][SIZE];
|
|
||||||
|
|
||||||
void
|
|
||||||
print_area()
|
|
||||||
{
|
{
|
||||||
printf("AREA:\n");
|
switch (number) {
|
||||||
for (int i = 0; i < SIZE; i++) {
|
case 0: return 0xFFFFFF;
|
||||||
printf("[");
|
case 2: return 0x00FF00;
|
||||||
for (int j = 0; j < SIZE - 1; j++) {
|
case 4: return 0xFF0000;
|
||||||
printf("%d, ", game.area[i][j].value);
|
case 8: return 0x0000FF;
|
||||||
}
|
default: return 0xFFFFFF;
|
||||||
printf("%d]\n", game.area[i][SIZE - 1].value);
|
|
||||||
}
|
}
|
||||||
printf("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
/*
|
||||||
move()
|
* 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)
|
||||||
{
|
{
|
||||||
bool retval = false;
|
switch (direction) {
|
||||||
tile_t *current, *next;
|
|
||||||
int k_start, k_end, k_step;
|
|
||||||
switch (game.direction) {
|
|
||||||
case LEFT:
|
case LEFT:
|
||||||
case UP:
|
*row = line;
|
||||||
k_start = 0;
|
*col = p;
|
||||||
k_end = SIZE - 1;
|
|
||||||
k_step = 1;
|
|
||||||
break;
|
break;
|
||||||
case RIGHT:
|
case RIGHT:
|
||||||
|
*row = line;
|
||||||
|
*col = SIZE - 1 - p;
|
||||||
|
break;
|
||||||
|
case UP:
|
||||||
|
*row = p;
|
||||||
|
*col = line;
|
||||||
|
break;
|
||||||
case DOWN:
|
case DOWN:
|
||||||
k_start = SIZE - 1;
|
*row = SIZE - 1 - p;
|
||||||
k_end = 0;
|
*col = line;
|
||||||
k_step = -1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < SIZE; i++) {
|
|
||||||
for (int j = 0; j < SIZE; j++) {
|
|
||||||
for (int k = k_start; (k_start <= k_end) ? k < k_end : k > k_end; k += k_step) {
|
|
||||||
switch (game.direction) {
|
|
||||||
case LEFT:
|
|
||||||
case RIGHT:
|
|
||||||
current = &game.area[i][k];
|
|
||||||
next = &game.area[i][k + k_step];
|
|
||||||
break;
|
|
||||||
case UP:
|
|
||||||
case DOWN:
|
|
||||||
current = &game.area[k][i];
|
|
||||||
next = &game.area[k + k_step][i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current->value == 0 && next->value != 0) {
|
|
||||||
current->pos = (v2_t) {
|
|
||||||
.x = next->pos.x,
|
|
||||||
.y = next->pos.y
|
|
||||||
};
|
|
||||||
current->value = next->value;
|
|
||||||
next->value = 0;
|
|
||||||
retval = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
static bool
|
||||||
merge_line(
|
process_line(dir_e direction, int line, uint32_t output[SIZE])
|
||||||
uint32_t line[SIZE]
|
{
|
||||||
) {
|
line_tile_t input[SIZE];
|
||||||
uint32_t out[SIZE] = {0};
|
int input_count = 0;
|
||||||
int write = 0;
|
int output_count = 0;
|
||||||
bool previous_was_merged = false;
|
|
||||||
|
|
||||||
for (int read = 0; read < SIZE; ++read) {
|
for (int p = 0; p < SIZE; ++p) {
|
||||||
uint32_t value = line[read];
|
int row, col;
|
||||||
|
line_coordinate(direction, line, p, &row, &col);
|
||||||
|
|
||||||
if (value == 0)
|
uint32_t value = game.area[row][col];
|
||||||
continue;
|
if (value != 0) {
|
||||||
|
input[input_count++] = (line_tile_t) {
|
||||||
if (write > 0 &&
|
.value = value,
|
||||||
out[write - 1] == value &&
|
.row = row,
|
||||||
!previous_was_merged) {
|
.col = col
|
||||||
out[write - 1] *= 2;
|
};
|
||||||
game.score += out[write - 1];
|
|
||||||
previous_was_merged = true;
|
|
||||||
} else {
|
|
||||||
out[write++] = value;
|
|
||||||
previous_was_merged = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = memcmp(line, out, sizeof out) != 0;
|
memset(output, 0, sizeof(uint32_t) * SIZE);
|
||||||
memcpy(line, out, sizeof out);
|
|
||||||
|
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;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
combine(
|
slide(dir_e direction)
|
||||||
bool has_moved
|
{
|
||||||
) {
|
if (anim_is_active(&movement))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
game.direction = direction;
|
||||||
|
move_track_count = 0;
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
uint32_t line[SIZE];
|
|
||||||
|
|
||||||
for (int n = 0; n < SIZE; ++n) {
|
for (int line = 0; line < SIZE; ++line) {
|
||||||
for (int p = 0; p < SIZE; ++p) {
|
uint32_t output[SIZE];
|
||||||
int r, c;
|
if (process_line(direction, line, output))
|
||||||
|
changed = true;
|
||||||
switch (game.direction) {
|
|
||||||
case LEFT: r = n; c = p; break;
|
|
||||||
case RIGHT: r = n; c = SIZE - 1 - p; break;
|
|
||||||
case UP: r = p; c = n; break;
|
|
||||||
case DOWN: r = SIZE - 1 - p; c = n; break;
|
|
||||||
default: return has_moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
line[p] = game.area[r][c].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
changed |= merge_line(line);
|
|
||||||
|
|
||||||
for (int p = 0; p < SIZE; ++p) {
|
|
||||||
int r, c;
|
|
||||||
|
|
||||||
switch (game.direction) {
|
|
||||||
case LEFT: r = n; c = p; break;
|
|
||||||
case RIGHT: r = n; c = SIZE - 1 - p; break;
|
|
||||||
case UP: r = p; c = n; break;
|
|
||||||
case DOWN: r = SIZE - 1 - p; c = n; break;
|
|
||||||
default: return has_moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
game.area[r][c].value = line[p];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return has_moved || changed;
|
if (!changed) {
|
||||||
|
move_track_count = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return anim_start(&movement, move_tracks, move_track_count,
|
||||||
|
MOVE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
// finds a random empty position and puts a two there
|
static void
|
||||||
void
|
new_2(void)
|
||||||
new_2()
|
|
||||||
{
|
{
|
||||||
// collect all zeroes
|
int empty_rows[SIZE * SIZE];
|
||||||
v2_t zeroes[SIZE * SIZE] = {0};
|
int empty_cols[SIZE * SIZE];
|
||||||
uint32_t zero_count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < SIZE; i++) {
|
|
||||||
for (int j = 0; j < SIZE; j++) {
|
for (int r = 0; r < SIZE; ++r) {
|
||||||
if (game.area[i][j].value == 0) {
|
for (int c = 0; c < SIZE; ++c) {
|
||||||
zeroes[zero_count++] = (v2_t){.x=j,.y=i};
|
if (game.area[r][c] == 0) {
|
||||||
|
empty_rows[count] = r;
|
||||||
|
empty_cols[count] = c;
|
||||||
|
++count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// choose among them
|
if (count == 0)
|
||||||
uint32_t rand_index = rand() % zero_count;
|
return;
|
||||||
game.area[zeroes[rand_index].y][zeroes[rand_index].x].value = 2;
|
|
||||||
|
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
|
void
|
||||||
slide()
|
client_update_framebuffer(void)
|
||||||
{
|
{
|
||||||
// 1. move all squares in one direction
|
double now = monotonic_seconds();
|
||||||
bool has_moved = move();
|
float dt = last_frame_time == 0.0
|
||||||
|
? 0.0f
|
||||||
|
: (float)(now - last_frame_time);
|
||||||
|
last_frame_time = now;
|
||||||
|
|
||||||
// 2. then combine them and shift the potential rest by one in the given direction
|
if (dt > 0.05f)
|
||||||
bool has_combined = combine(has_moved);
|
dt = 0.05f;
|
||||||
|
|
||||||
// 3. generate a new 2
|
if (anim_is_active(&movement) && anim_update(&movement, dt)) {
|
||||||
if (has_moved || has_combined) {
|
|
||||||
new_2();
|
new_2();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// polling events every frame
|
|
||||||
void
|
|
||||||
client_poll_event()
|
|
||||||
{
|
|
||||||
SDL_Event ev = game_state->event;
|
|
||||||
|
|
||||||
if (ev.type == SDL_EVENT_QUIT) {
|
|
||||||
game_state->is_running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.type == SDL_EVENT_KEY_DOWN) {
|
|
||||||
switch (ev.key.key) {
|
|
||||||
case SDLK_Q:
|
|
||||||
game_state->is_running = false;
|
|
||||||
break;
|
|
||||||
case SDLK_W:
|
|
||||||
game.direction = UP;
|
|
||||||
for (int r = 0; r < SIZE; ++r)
|
|
||||||
for (int c = 0; c < SIZE; ++c)
|
|
||||||
before[r][c] = game.area[r][c].value;
|
|
||||||
slide();
|
|
||||||
break;
|
|
||||||
case SDLK_S:
|
|
||||||
game.direction = DOWN;
|
|
||||||
for (int r = 0; r < SIZE; ++r)
|
|
||||||
for (int c = 0; c < SIZE; ++c)
|
|
||||||
before[r][c] = game.area[r][c].value;
|
|
||||||
slide();
|
|
||||||
break;
|
|
||||||
case SDLK_A:
|
|
||||||
game.direction = LEFT;
|
|
||||||
for (int r = 0; r < SIZE; ++r)
|
|
||||||
for (int c = 0; c < SIZE; ++c)
|
|
||||||
before[r][c] = game.area[r][c].value;
|
|
||||||
slide();
|
|
||||||
break;
|
|
||||||
case SDLK_D:
|
|
||||||
game.direction = RIGHT;
|
|
||||||
for (int r = 0; r < SIZE; ++r)
|
|
||||||
for (int c = 0; c < SIZE; ++c)
|
|
||||||
before[r][c] = game.area[r][c].value;
|
|
||||||
slide();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CHR(ord) ((ord) + 48)
|
|
||||||
#define ORD(chr) ((chr) - 48)
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t
|
|
||||||
numlen(
|
|
||||||
uint32_t n
|
|
||||||
) {
|
|
||||||
uint32_t len = 0;
|
|
||||||
|
|
||||||
while (n > 0) {
|
|
||||||
len++;
|
|
||||||
n /= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t
|
|
||||||
power(
|
|
||||||
uint32_t x,
|
|
||||||
uint32_t n
|
|
||||||
) {
|
|
||||||
// x ^ n
|
|
||||||
uint32_t acc = 1;
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
acc *= x;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t
|
|
||||||
numrev(
|
|
||||||
uint32_t n
|
|
||||||
) {
|
|
||||||
uint32_t len = numlen(n);
|
|
||||||
uint32_t new = 0;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
while (n > 0) {
|
|
||||||
new += (n % 10) * power(10, len - (++i));
|
|
||||||
n /= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
v2_t last_area_positions[SIZE][SIZE] = {0};
|
|
||||||
|
|
||||||
// performed every frame
|
|
||||||
void
|
|
||||||
client_update_framebuffer()
|
|
||||||
{
|
|
||||||
// clear_framebuffer(0xFFFF00);
|
|
||||||
// put_string("Welcome to the most\n*DANGEROUS* 2048 game!",
|
|
||||||
// 20, 20, 2, 2, 3, 0x000000, 0xFFFF00
|
|
||||||
// );
|
|
||||||
|
|
||||||
// FIXME: why?
|
|
||||||
//clear_framebuffer(BASE, 0x000000);
|
|
||||||
|
|
||||||
uint32_t tile_size = game_state->width / SIZE;
|
uint32_t tile_size = game_state->width / SIZE;
|
||||||
uint8_t border_width = 10;
|
uint32_t border_width = BORDER_WIDTH;
|
||||||
char *n_str;
|
|
||||||
bool alloced = false;
|
|
||||||
for (int i = 0; i < SIZE; i++) {
|
|
||||||
for (int j = 0; j < SIZE; j++) {
|
|
||||||
alloced = true;
|
|
||||||
// background
|
|
||||||
uint32_t x = (border_width / 2) + i * tile_size;
|
|
||||||
uint32_t y = (border_width / 2) + j * tile_size;
|
|
||||||
uint32_t w = tile_size - border_width;
|
|
||||||
uint32_t h = tile_size - border_width;
|
|
||||||
uint32_t background_color = colors(game.area[j][i].value);
|
|
||||||
|
|
||||||
put_rect(BASE, x, y, w, h, 0xFFFFFF);
|
clear_framebuffer(OVERDRAW, 0x000000);
|
||||||
put_rect(OVERDRAW, x, y, w, h, background_color);
|
|
||||||
|
|
||||||
// number in foreground
|
/* Draw all cell backgrounds first. */
|
||||||
uint32_t foreground_color = 0x000000;
|
for (int r = 0; r < SIZE; ++r) {
|
||||||
uint8_t font_size = 3;
|
for (int c = 0; c < SIZE; ++c) {
|
||||||
|
uint32_t x = border_width / 2 + (uint32_t)c * tile_size;
|
||||||
uint32_t n = game.area[j][i].value;
|
uint32_t y = border_width / 2 + (uint32_t)r * tile_size;
|
||||||
uint32_t n_len = numlen(n);
|
uint32_t side = tile_size - border_width;
|
||||||
n_str = calloc(n_len + 1, sizeof(char));
|
|
||||||
if (n_str == NULL) {
|
put_rect(BASE, x, y, side, side, 0xFFFFFF);
|
||||||
game_state->is_running = false;
|
put_rect(OVERDRAW, x, y, side, side, 0xFFFFFF);
|
||||||
}
|
}
|
||||||
sprintf(n_str, "%d", n);
|
}
|
||||||
uint32_t n_rev = numrev(n);
|
|
||||||
uint32_t digit_offset = 1;
|
if (anim_is_active(&movement)) {
|
||||||
if (n != 0) {
|
const anim_item_t *items = anim_items(&movement);
|
||||||
put_string(OVERDRAW, n_str, x + border_width, y + border_width, font_size, digit_offset, 12, foreground_color, background_color);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// score display line (single line)
|
char score_text[32];
|
||||||
char *score_text = calloc(7 + 6 + 1, sizeof(char));
|
snprintf(score_text, sizeof score_text, "score: %u", game.score);
|
||||||
if (score_text == NULL) {
|
put_string(OVERDRAW, score_text,
|
||||||
game_state->is_running = false;
|
border_width / 2, tile_size * SIZE + border_width / 2,
|
||||||
}
|
6, 1, 12, 0xFFFFFF, 0x000000);
|
||||||
alloced = true;
|
|
||||||
|
|
||||||
sprintf(score_text, "score: %6d", game.score);
|
|
||||||
put_string(OVERDRAW, score_text, border_width / 2, tile_size * SIZE + border_width / 2, 6, 1, 12, 0xFFFFFF, 0x000000);
|
|
||||||
|
|
||||||
if (alloced) {
|
|
||||||
free(n_str);
|
|
||||||
free(score_text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static void
|
||||||
v2_eq(v2_t a, v2_t b)
|
game_init(void)
|
||||||
{
|
{
|
||||||
return a.x == b.x && a.y == b.y;
|
memset(&game, 0, sizeof game);
|
||||||
|
anim_init(&movement);
|
||||||
|
srand((unsigned)time(NULL));
|
||||||
|
|
||||||
|
/* Start with two 2-tiles. */
|
||||||
|
new_2();
|
||||||
|
new_2();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
int
|
||||||
v2_contains(v2_t v[], size_t size, v2_t elem)
|
main(void)
|
||||||
{
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
if (v2_eq(v[i], elem)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
game_init(
|
|
||||||
uint32_t window_width
|
|
||||||
) {
|
|
||||||
srand(time(NULL));
|
|
||||||
|
|
||||||
// generate MAX_NUMBER_OF_4 + MAX_NUMBER_OF_2 random indices in range 0..15 (row in 0..3, col in 0..3)
|
|
||||||
// this means that there will be at least MAX_NUMBER_OF_2 of 2s and MAX_NUMBER_OF_4 <= MAX_NUMBER_OF_2 4s
|
|
||||||
// => usually less 4s than 2s because the indices can get replaced
|
|
||||||
#define MAX_NUMBER_OF_4 1
|
|
||||||
#define MAX_NUMBER_OF_2 2
|
|
||||||
|
|
||||||
uint8_t row, col;
|
|
||||||
v2_t elem;
|
|
||||||
v2_t fours[MAX_NUMBER_OF_4], twos[MAX_NUMBER_OF_2];
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < MAX_NUMBER_OF_4) {
|
|
||||||
row = rand() % SIZE;
|
|
||||||
col = rand() % SIZE;
|
|
||||||
elem = (v2_t){ .x = col, .y = row };
|
|
||||||
if (!v2_contains(twos, MAX_NUMBER_OF_4, elem)) {
|
|
||||||
fours[i++] = elem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
while (i < MAX_NUMBER_OF_2) {
|
|
||||||
row = rand() % SIZE;
|
|
||||||
col = rand() % SIZE;
|
|
||||||
elem = (v2_t){ .x = col, .y = row };
|
|
||||||
if (!v2_contains(twos, MAX_NUMBER_OF_2, elem)) {
|
|
||||||
twos[i++] = elem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int row = 0; row < SIZE; row++) {
|
|
||||||
for (int col = 0; col < SIZE; col++) {
|
|
||||||
game.area[row][col].pos = (v2_t){
|
|
||||||
.x = col * (window_width / SIZE),
|
|
||||||
.y = row * (window_width / SIZE)
|
|
||||||
};
|
|
||||||
game.area[row][col].value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NUMBER_OF_4; i++) {
|
|
||||||
game.area[fours[i].y][fours[i].x].value = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NUMBER_OF_2; i++) {
|
|
||||||
game.area[twos[i].y][twos[i].x].value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
game.direction = RIGHT;
|
|
||||||
game.score = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main()
|
|
||||||
{
|
{
|
||||||
uint32_t window_width = 500;
|
uint32_t window_width = 500;
|
||||||
uint32_t window_height = window_width * 1.12f;
|
uint32_t window_height = (uint32_t)(window_width * 1.12f);
|
||||||
|
|
||||||
game_init(window_width);
|
game_init();
|
||||||
|
|
||||||
// initialize the engine
|
if (engine_init(window_width, window_height,
|
||||||
if (engine_init(
|
"Dangerous 2048",
|
||||||
window_width, window_height,
|
client_poll_event,
|
||||||
"Dangerous 2048",
|
client_update_framebuffer)) {
|
||||||
client_poll_event,
|
engine_start();
|
||||||
client_update_framebuffer
|
engine_stop();
|
||||||
)) {
|
return 0;
|
||||||
engine_start();
|
}
|
||||||
engine_stop();
|
|
||||||
return 0;
|
return 1;
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user