mirror of
https://github.com/UberGuidoZ/Flipper.git
synced 2024-12-23 15:00:13 +00:00
494 lines
15 KiB
C
494 lines
15 KiB
C
|
#include <stdint.h>
|
||
|
#include <gui/gui.h>
|
||
|
#include <time.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#include "font.h"
|
||
|
|
||
|
#define DEBUG false
|
||
|
/*
|
||
|
0 empty
|
||
|
1 2
|
||
|
2 4
|
||
|
3 8
|
||
|
4 16
|
||
|
5 32
|
||
|
6 64
|
||
|
7 128
|
||
|
8 256
|
||
|
9 512
|
||
|
10 1024
|
||
|
11 2048
|
||
|
12 4096
|
||
|
...
|
||
|
*/
|
||
|
typedef uint8_t cell_state;
|
||
|
|
||
|
/* DirectionLeft <--
|
||
|
┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐
|
||
|
╎ ╎╎ ╎╎ ╎╎ ╎
|
||
|
└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘
|
||
|
┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐
|
||
|
╎ ╎╎ ╎╎ ╎╎ ╎
|
||
|
└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘
|
||
|
┌╌╌┌╌╌╌╌┐╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐
|
||
|
╎ 2╎ 2 ╎ ╎╎ ╎╎ ╎
|
||
|
└╌╌└╌╌╌╌┘╌╌┘└╌╌╌╌┘└╌╌╌╌┘
|
||
|
┌╌╌┌╌╌╌╌┐┌╌╌┌╌╌╌╌┐┌╌╌╌╌┐
|
||
|
╎ 4╎ 4 ╎╎ 2╎ 2 ╎╎ ╎
|
||
|
└╌╌└╌╌╌╌┘└╌╌└╌╌╌╌┘└╌╌╌╌┘
|
||
|
*/
|
||
|
typedef enum {
|
||
|
DirectionIdle,
|
||
|
DirectionUp,
|
||
|
DirectionRight,
|
||
|
DirectionDown,
|
||
|
DirectionLeft,
|
||
|
} Direction;
|
||
|
|
||
|
typedef struct {
|
||
|
uint8_t y; // 0 <= y <= 3
|
||
|
uint8_t x; // 0 <= x <= 3
|
||
|
} Point;
|
||
|
|
||
|
typedef struct {
|
||
|
uint32_t gameScore;
|
||
|
uint32_t highScore;
|
||
|
} Score;
|
||
|
|
||
|
typedef struct {
|
||
|
/*
|
||
|
+----X
|
||
|
|
|
||
|
| field[x][y]
|
||
|
Y
|
||
|
*/
|
||
|
uint8_t field[4][4];
|
||
|
|
||
|
uint8_t next_field[4][4];
|
||
|
|
||
|
Score score; // original scoring
|
||
|
|
||
|
Direction direction;
|
||
|
/*
|
||
|
field {
|
||
|
animation-timing-function: linear;
|
||
|
animation-duration: 300ms;
|
||
|
}
|
||
|
*/
|
||
|
uint32_t animation_start_ticks;
|
||
|
|
||
|
Point keyframe_from[4][4];
|
||
|
|
||
|
Point keyframe_to[4][4];
|
||
|
|
||
|
bool debug;
|
||
|
|
||
|
} GameState;
|
||
|
|
||
|
#define XtoPx(x) (33 + x * 15)
|
||
|
|
||
|
#define YtoPx(x) (1 + y * 15)
|
||
|
|
||
|
static void game_2048_render_callback(Canvas* const canvas, ValueMutex* const vm) {
|
||
|
const GameState* game_state = acquire_mutex(vm, 25);
|
||
|
if(game_state == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Before the function is called, the state is set with the canvas_reset(canvas)
|
||
|
|
||
|
if(game_state->direction == DirectionIdle) {
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
uint8_t field = game_state->field[y][x];
|
||
|
canvas_set_color(canvas, ColorBlack);
|
||
|
canvas_draw_frame(canvas, XtoPx(x), YtoPx(y), 16, 16);
|
||
|
if(field != 0) {
|
||
|
game_2048_draw_number(canvas, XtoPx(x), YtoPx(y), 1 << field);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// display score
|
||
|
char buffer[12];
|
||
|
snprintf(buffer, sizeof(buffer), "%lu", game_state->score.gameScore);
|
||
|
canvas_draw_str_aligned(canvas, 127, 8, AlignRight, AlignBottom, buffer);
|
||
|
|
||
|
if(game_state->score.highScore > 0) {
|
||
|
char buffer2[12];
|
||
|
snprintf(buffer2, sizeof(buffer2), "%lu", game_state->score.highScore);
|
||
|
canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, buffer2);
|
||
|
}
|
||
|
} else { // if animation
|
||
|
// for animation
|
||
|
// (osKernelGetSysTimerCount() - game_state->animation_start_ticks) / osKernelGetSysTimerFreq();
|
||
|
|
||
|
// TODO: end animation event/callback/set AnimationIdle
|
||
|
}
|
||
|
|
||
|
release_mutex(vm, game_state);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
game_2048_input_callback(const InputEvent* const input_event, FuriMessageQueue* event_queue) {
|
||
|
furi_assert(event_queue);
|
||
|
|
||
|
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||
|
}
|
||
|
|
||
|
// if return false then Game Over
|
||
|
static bool game_2048_set_new_number(GameState* const game_state) {
|
||
|
uint8_t empty = 0;
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
if(game_state->field[y][x] == 0) {
|
||
|
empty++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(empty == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(empty == 1) {
|
||
|
// If it is 1 move before losing, we help the player and get rid of randomness.
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
if(game_state->field[y][x] == 0) {
|
||
|
bool haveFour =
|
||
|
// +----X
|
||
|
// |
|
||
|
// | field[x][y], 0 <= x, y <= 3
|
||
|
// Y
|
||
|
|
||
|
// up == 4 or
|
||
|
(y > 0 && game_state->field[y - 1][x] == 2) ||
|
||
|
// right == 4 or
|
||
|
(x < 3 && game_state->field[y][x + 1] == 2) ||
|
||
|
// down == 4
|
||
|
(y < 3 && game_state->field[y + 1][x] == 2) ||
|
||
|
// left == 4
|
||
|
(x > 0 && game_state->field[y][x - 1] == 2);
|
||
|
|
||
|
if(haveFour) {
|
||
|
game_state->field[y][x] = 2;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
game_state->field[y][x] = 1;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint8_t target = rand() % empty;
|
||
|
uint8_t twoOrFore = rand() % 4 < 3;
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
if(game_state->field[y][x] == 0) {
|
||
|
if(target == 0) {
|
||
|
if(twoOrFore) {
|
||
|
game_state->field[y][x] = 1; // 2^1 == 2 75%
|
||
|
} else {
|
||
|
game_state->field[y][x] = 2; // 2^2 == 4 25%
|
||
|
}
|
||
|
goto exit;
|
||
|
}
|
||
|
target--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exit:
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// static void game_2048_process_row(uint8_t before[4], uint8_t *(after[4])) {
|
||
|
// // move 1 row left.
|
||
|
// for(uint8_t i = 0; i <= 2; i++) {
|
||
|
// if(before[i] != 0 && before[i] == before[i + 1]) {
|
||
|
// before[i]++;
|
||
|
// before[i + 1] = 0;
|
||
|
// i++;
|
||
|
// }
|
||
|
// }
|
||
|
// for(uint8_t i = 0, j = 0; i <= 3; i++) {
|
||
|
// if (before[i] != 0) {
|
||
|
// before[j] = before[i];
|
||
|
// i++;
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
static void game_2048_process_move(GameState* const game_state) {
|
||
|
memset(game_state->next_field, 0, sizeof(game_state->next_field));
|
||
|
// +----X
|
||
|
// |
|
||
|
// | field[x][y], 0 <= x, y <= 3
|
||
|
// Y
|
||
|
|
||
|
// up
|
||
|
if(game_state->direction == DirectionUp) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
uint8_t next_y = 0;
|
||
|
for(int8_t y = 0; y < 4; y++) {
|
||
|
uint8_t field = game_state->field[y][x];
|
||
|
if(field == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(game_state->next_field[next_y][x] == 0) {
|
||
|
game_state->next_field[next_y][x] = field;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(field == game_state->next_field[next_y][x]) {
|
||
|
game_state->next_field[next_y][x]++;
|
||
|
game_state->score.gameScore += pow(2, game_state->next_field[next_y][x]);
|
||
|
/*if(game_state->next_field[next_y][x] == 11 && !game_state->debug) {
|
||
|
DOLPHIN_DEED(getRandomDeed());
|
||
|
} // get some xp for making a 2048 tile*/
|
||
|
next_y++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
next_y++;
|
||
|
game_state->next_field[next_y][x] = field;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// right
|
||
|
if(game_state->direction == DirectionRight) {
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
uint8_t next_x = 3;
|
||
|
for(int8_t x = 3; x >= 0; x--) {
|
||
|
uint8_t field = game_state->field[y][x];
|
||
|
if(field == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(game_state->next_field[y][next_x] == 0) {
|
||
|
game_state->next_field[y][next_x] = field;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(field == game_state->next_field[y][next_x]) {
|
||
|
game_state->next_field[y][next_x]++;
|
||
|
game_state->score.gameScore += pow(2, game_state->next_field[y][next_x]);
|
||
|
/*if(game_state->next_field[y][next_x] == 11 && !game_state->debug) {
|
||
|
DOLPHIN_DEED(getRandomDeed());
|
||
|
} // get some xp for making a 2048 tile*/
|
||
|
next_x--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
next_x--;
|
||
|
game_state->next_field[y][next_x] = field;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// down
|
||
|
if(game_state->direction == DirectionDown) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
uint8_t next_y = 3;
|
||
|
for(int8_t y = 3; y >= 0; y--) {
|
||
|
uint8_t field = game_state->field[y][x];
|
||
|
if(field == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(game_state->next_field[next_y][x] == 0) {
|
||
|
game_state->next_field[next_y][x] = field;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(field == game_state->next_field[next_y][x]) {
|
||
|
game_state->next_field[next_y][x]++;
|
||
|
game_state->score.gameScore += pow(2, game_state->next_field[next_y][x]);
|
||
|
/*if(game_state->next_field[next_y][x] == 11 && !game_state->debug) {
|
||
|
DOLPHIN_DEED(getRandomDeed());
|
||
|
} // get some xp for making a 2048 tile*/
|
||
|
next_y--;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
next_y--;
|
||
|
game_state->next_field[next_y][x] = field;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 0, 0, 1, 1
|
||
|
// 1, 0, 0, 0
|
||
|
|
||
|
// left
|
||
|
if(game_state->direction == DirectionLeft) {
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
uint8_t next_x = 0;
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
uint8_t field = game_state->field[y][x];
|
||
|
if(field == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(game_state->next_field[y][next_x] == 0) {
|
||
|
game_state->next_field[y][next_x] = field;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(field == game_state->next_field[y][next_x]) {
|
||
|
game_state->next_field[y][next_x]++;
|
||
|
game_state->score.gameScore += pow(2, game_state->next_field[y][next_x]);
|
||
|
/*if(game_state->next_field[y][next_x] == 11 && !game_state->debug) {
|
||
|
DOLPHIN_DEED(getRandomDeed());
|
||
|
} // get some xp for making a 2048 tile*/
|
||
|
next_x++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
next_x++;
|
||
|
game_state->next_field[y][next_x] = field;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// <debug>
|
||
|
game_state->direction = DirectionIdle;
|
||
|
memcpy(game_state->field, game_state->next_field, sizeof(game_state->field));
|
||
|
// </debug>
|
||
|
}
|
||
|
|
||
|
static void game_2048_restart(GameState* const game_state) {
|
||
|
game_state->debug = DEBUG;
|
||
|
|
||
|
// check score
|
||
|
if(game_state->score.gameScore > game_state->score.highScore) {
|
||
|
game_state->score.highScore = game_state->score.gameScore;
|
||
|
}
|
||
|
|
||
|
// clear all cells
|
||
|
for(uint8_t y = 0; y < 4; y++) {
|
||
|
for(uint8_t x = 0; x < 4; x++) {
|
||
|
game_state->field[y][x] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// start next game
|
||
|
game_state->score.gameScore = 0;
|
||
|
game_2048_set_new_number(game_state);
|
||
|
game_2048_set_new_number(game_state);
|
||
|
}
|
||
|
|
||
|
int32_t game_2048_app(void* p) {
|
||
|
UNUSED(p);
|
||
|
int32_t return_code = 0;
|
||
|
|
||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||
|
|
||
|
GameState* game_state = malloc(sizeof(GameState));
|
||
|
|
||
|
ValueMutex state_mutex;
|
||
|
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
|
||
|
return_code = 255;
|
||
|
goto free_and_exit;
|
||
|
}
|
||
|
|
||
|
ViewPort* view_port = view_port_alloc();
|
||
|
view_port_draw_callback_set(
|
||
|
view_port, (ViewPortDrawCallback)game_2048_render_callback, &state_mutex);
|
||
|
view_port_input_callback_set(
|
||
|
view_port, (ViewPortInputCallback)game_2048_input_callback, event_queue);
|
||
|
|
||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||
|
|
||
|
game_state->direction = DirectionIdle;
|
||
|
game_2048_restart(game_state);
|
||
|
|
||
|
if(game_state->debug) {
|
||
|
game_state->field[0][0] = 0;
|
||
|
game_state->field[0][1] = 0;
|
||
|
game_state->field[0][2] = 0;
|
||
|
game_state->field[0][3] = 0;
|
||
|
|
||
|
game_state->field[1][0] = 1;
|
||
|
game_state->field[1][1] = 2;
|
||
|
game_state->field[1][2] = 3;
|
||
|
game_state->field[1][3] = 4;
|
||
|
|
||
|
game_state->field[2][0] = 5;
|
||
|
game_state->field[2][1] = 6;
|
||
|
game_state->field[2][2] = 7;
|
||
|
game_state->field[2][3] = 8;
|
||
|
|
||
|
game_state->field[3][0] = 9;
|
||
|
game_state->field[3][1] = 10;
|
||
|
game_state->field[3][2] = 11;
|
||
|
game_state->field[3][3] = 12;
|
||
|
}
|
||
|
|
||
|
InputEvent event;
|
||
|
for(bool loop = true; loop;) {
|
||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||
|
GameState* game_state = (GameState*)acquire_mutex_block(&state_mutex);
|
||
|
|
||
|
if(event_status == FuriStatusOk) {
|
||
|
if(event.type == InputTypeShort) {
|
||
|
switch(event.key) {
|
||
|
case InputKeyUp:
|
||
|
game_state->direction = DirectionUp;
|
||
|
game_2048_process_move(game_state);
|
||
|
game_2048_set_new_number(game_state);
|
||
|
break;
|
||
|
case InputKeyDown:
|
||
|
game_state->direction = DirectionDown;
|
||
|
game_2048_process_move(game_state);
|
||
|
game_2048_set_new_number(game_state);
|
||
|
break;
|
||
|
case InputKeyRight:
|
||
|
game_state->direction = DirectionRight;
|
||
|
game_2048_process_move(game_state);
|
||
|
game_2048_set_new_number(game_state);
|
||
|
break;
|
||
|
case InputKeyLeft:
|
||
|
game_state->direction = DirectionLeft;
|
||
|
game_2048_process_move(game_state);
|
||
|
game_2048_set_new_number(game_state);
|
||
|
break;
|
||
|
case InputKeyOk:
|
||
|
game_state->direction = DirectionIdle;
|
||
|
break;
|
||
|
case InputKeyBack:
|
||
|
loop = false;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
} else if(event.type == InputTypeLong) {
|
||
|
if(event.key == InputKeyOk) {
|
||
|
game_state->direction = DirectionIdle;
|
||
|
game_2048_restart(game_state);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
view_port_update(view_port);
|
||
|
release_mutex(&state_mutex, game_state);
|
||
|
}
|
||
|
|
||
|
view_port_enabled_set(view_port, false);
|
||
|
gui_remove_view_port(gui, view_port);
|
||
|
furi_record_close(RECORD_GUI);
|
||
|
view_port_free(view_port);
|
||
|
delete_mutex(&state_mutex);
|
||
|
|
||
|
free_and_exit:
|
||
|
free(game_state);
|
||
|
furi_message_queue_free(event_queue);
|
||
|
|
||
|
return return_code;
|
||
|
}
|