#include #include #include #include #include #include #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 ╎╎ ╎ └╌╌└╌╌╌╌┘└╌╌└╌╌╌╌┘└╌╌╌╌┘ */ DolphinDeed getRandomDeed() { DolphinDeed returnGrp[14] = {1, 5, 8, 10, 12, 15, 17, 20, 21, 25, 26, 28, 29, 32}; static bool rand_generator_inited = false; if(!rand_generator_inited) { srand(furi_get_tick()); rand_generator_inited = true; } uint8_t diceRoll = (rand() % COUNT_OF(returnGrp)); // JUST TO GET IT GOING? AND FIX BUG diceRoll = (rand() % COUNT_OF(returnGrp)); return returnGrp[diceRoll]; } 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; } } } // game_state->direction = DirectionIdle; memcpy(game_state->field, game_state->next_field, sizeof(game_state->field)); // } 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; }