From eb625f971f157f073791e8e4ee70632d70cdffc8 Mon Sep 17 00:00:00 2001 From: Eng1n33r Date: Tue, 21 Jun 2022 05:50:34 +0300 Subject: [PATCH] two new games & music player patch --- CHANGELOG.md | 9 +- ReadMe.md | 4 +- applications/applications.c | 19 + applications/applications.mk | 18 +- applications/arkanoid/arkanoid_game.c | 410 ++++++++++++++++++ applications/music_player/music_player.c | 2 +- .../music_player/music_player_worker.c | 2 +- applications/snake_game/snake_game.c | 5 + applications/tetris_game/tetris_game.c | 7 +- applications/tictactoe_game/tictactoe_game.c | 349 +++++++++++++++ 10 files changed, 817 insertions(+), 8 deletions(-) create mode 100644 applications/arkanoid/arkanoid_game.c create mode 100644 applications/tictactoe_game/tictactoe_game.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b19b0029..0c511eae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ ### New Update -* Fix KeeLoq Uknown behavior, patch StarLine same way -* Fix incorrect var in protocol Scher-Khan (by @Skorpionm) -* Came Atomo working emulation algorytm! +* Games: Snake & Tetis now shows score +* Volume patch in music player (testing needed) +* Two new games: Arkanoid & Tic Tac Toe #### Previous changes +* Fixed KeeLoq Uknown behavior, patched StarLine same way +* Fixed incorrect var in protocol Scher-Khan (by @Skorpionm) +* Came Atomo working emulation algorithm! * Updated UniRF Remix app, .txt file support * Fixed macOS Brewfile, so compiling on macOS now works * Updated CAME Atomo diff --git a/ReadMe.md b/ReadMe.md index 7e4953982..596fb3273 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -5,7 +5,7 @@ Welcome to Flipper Zero's Custom Firmware repo! Our goal is to make any features possible in this device without any limitations! -Please help us realize emulation for all dynamic (rolling codes) protocols and brute-force app! +Please help us implement emulation for all dynamic (rolling codes) protocols and brute-force app!

@@ -177,6 +177,8 @@ Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. - [WAV Player (By DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player) With Fix From [Atmanos](https://github.com/at-manos) - [Tetris (By jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game) - [Spectrum Analyzer (By jolcese)](https://github.com/jolcese/flipperzero-firmware/tree/spectrum/applications/spectrum_analyzer) +- [Arkanoid (By gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins) +- [Tic Tac Toe (By gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins) # Links diff --git a/applications/applications.c b/applications/applications.c index 247c601c5..d06e48aa8 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -56,6 +56,8 @@ extern int32_t spectrum_analyzer_app(void* p); // Games extern int32_t snake_game_app(void* p); extern int32_t tetris_game_app(void *p); +extern int32_t tictactoe_game_app(void* p); +extern int32_t arkanoid_game_app(void* p); // On system start hooks declaration extern void bt_on_system_start(); @@ -405,6 +407,23 @@ const FlipperApplication FLIPPER_GAMES[] = { .flags = FlipperApplicationFlagDefault}, #endif + +#ifdef APP_ARKANOID_GAME + {.app = arkanoid_game_app, + .name = "Arkanoid", + .stack_size = 1024, + .icon = &A_Plugins_14, + .flags = FlipperApplicationFlagDefault}, +#endif + +#ifdef APP_TICTACTOE_GAME + {.app = tictactoe_game_app, + .name = "Tic Tac Toe", + .stack_size = 1024, + .icon = &A_Plugins_14, + .flags = FlipperApplicationFlagDefault}, +#endif + }; const size_t FLIPPER_GAMES_COUNT = COUNT_OF(FLIPPER_GAMES); diff --git a/applications/applications.mk b/applications/applications.mk index 60a339bdf..d176e5ac4 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -51,9 +51,13 @@ APP_SPECTRUM_ANALYZER = 1 # Plugins APP_MUSIC_PLAYER = 1 -APP_SNAKE_GAME = 1 APP_WAV_PLAYER = 1 + +# Games +APP_SNAKE_GAME = 1 APP_TETRIS_GAME = 1 +APP_TICTACTOE_GAME = 1 +APP_ARKANOID_GAME = 1 # Debug APP_ACCESSOR = 1 @@ -278,6 +282,18 @@ CFLAGS += -DAPP_SPECTRUM_ANALYZER SRV_GUI = 1 endif +APP_TICTACTOE_GAME ?= 0 +ifeq ($(APP_TICTACTOE_GAME),1) +CFLAGS += -DAPP_TICTACTOE_GAME +SRV_GUI = 1 +endif + +APP_ARKANOID_GAME ?= 0 +ifeq ($(APP_ARKANOID_GAME),1) +CFLAGS += -DAPP_ARKANOID_GAME +SRV_GUI = 1 +endif + APP_IBUTTON ?= 0 ifeq ($(APP_IBUTTON), 1) CFLAGS += -DAPP_IBUTTON diff --git a/applications/arkanoid/arkanoid_game.c b/applications/arkanoid/arkanoid_game.c new file mode 100644 index 000000000..676b13a55 --- /dev/null +++ b/applications/arkanoid/arkanoid_game.c @@ -0,0 +1,410 @@ +#include +#include +#include +#include +#include +#include + +#define TAG "Arkanoid" + +unsigned int COLUMNS = 13; //Columns of bricks +unsigned int ROWS = 4; //Rows of bricks +int dx = -1; //Initial movement of ball +int dy = -1; //Initial movement of ball +int xb; //Balls starting possition +int yb; //Balls starting possition +bool released; //If the ball has been released by the player +bool paused = false; //If the game has been paused +int xPaddle; //X position of paddle +bool isHit[4][13]; //Array of if bricks are hit or not +bool bounced = false; //Used to fix double bounce glitch +int lives = 3; //Amount of lives +int level = 1; //Current level +unsigned int score = 0; //Score for the game +unsigned int brickCount; //Amount of bricks hit +int pad, pad2, pad3; //Button press buffer used to stop pause repeating +int oldpad, oldpad2, oldpad3; +char text[16]; //General string buffer +bool start = false; //If in menu or in game +bool initialDraw = false; //If the inital draw has happened +char initials[3]; //Initials used in high score + +//Ball Bounds used in collision detection +int leftBall; +int rightBall; +int topBall; +int bottomBall; + +//Brick Bounds used in collision detection +int leftBrick; +int rightBrick; +int topBrick; +int bottomBrick; + +int tick; + +#define FLIPPER_LCD_WIDTH 128 +#define FLIPPER_LCD_HEIGHT 64 + +typedef enum { EventTypeTick, EventTypeKey } EventType; + +typedef enum { DirectionUp, DirectionRight, DirectionDown, DirectionLeft } Direction; + +typedef enum { GameStatePlaying, GameStateGameOver } GameState; + +typedef struct { + int x; + int y; +} Point; + +typedef struct { + GameState game_state; +} ArkanoidState; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +// generate number in range [min,max) +int rand_range(int min, int max) { + int number = min + rand() % (max - min); + return number; +} + +void intro(Canvas* canvas) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 46, 0, "Arkanoid"); + + //arduboy.tunes.tone(987, 160); + //delay(160); + //arduboy.tunes.tone(1318, 400); + //delay(2000); +} + +void move_ball(Canvas* canvas) { + tick++; + if(released) { + //Move ball + if(abs(dx) == 2) { + xb += dx / 2; + // 2x speed is really 1.5 speed + if(tick % 2 == 0) xb += dx / 2; + } else { + xb += dx; + } + yb = yb + dy; + + //Set bounds + leftBall = xb; + rightBall = xb + 2; + topBall = yb; + bottomBall = yb + 2; + + //Bounce off top edge + if(yb <= 0) { + yb = 2; + dy = -dy; + // arduboy.tunes.tone(523, 250); + } + + //Lose a life if bottom edge hit + if(yb >= FLIPPER_LCD_HEIGHT) { + canvas_draw_frame(canvas, xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1); + xPaddle = 54; + yb = 60; + released = false; + lives--; + + sprintf(text, "LIVES:%u", lives); + canvas_draw_str(canvas, 0, 90, text); + + // arduboy.tunes.tone(175, 250); + if(rand_range(0, 2) == 0) { + dx = 1; + } else { + dx = -1; + } + } + + //Bounce off left side + if(xb <= 0) { + xb = 2; + dx = -dx; + // arduboy.tunes.tone(523, 250); + } + + //Bounce off right side + if(xb >= FLIPPER_LCD_WIDTH - 2) { + xb = FLIPPER_LCD_WIDTH - 4; + dx = -dx; + // arduboy.tunes.tone(523, 250); + } + + //Bounce off paddle + if(xb + 1 >= xPaddle && xb <= xPaddle + 12 && yb + 2 >= FLIPPER_LCD_HEIGHT - 1 && + yb <= FLIPPER_LCD_HEIGHT) { + dy = -dy; + dx = ((xb - (xPaddle + 6)) / 3); //Applies spin on the ball + // prevent straight bounce + if(dx == 0) { + dx = (rand_range(0, 2) == 1) ? 1 : -1; + } + // arduboy.tunes.tone(200, 250); + } + + //Bounce off Bricks + for(int row = 0; row < ROWS; row++) { + for(int column = 0; column < COLUMNS; column++) { + if(!isHit[row][column]) { + //Sets Brick bounds + leftBrick = 10 * column; + rightBrick = 10 * column + 10; + topBrick = 6 * row + 1; + bottomBrick = 6 * row + 7; + + //If A collison has occured + if(topBall <= bottomBrick && bottomBall >= topBrick && + leftBall <= rightBrick && rightBall >= leftBrick) { + // Draw score + score += (level * 10); + sprintf(text, "SCORE:%u", score); + canvas_draw_str(canvas, 80, 90, text); + + brickCount++; + isHit[row][column] = true; + canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4); + + //Vertical collision + if(bottomBall > bottomBrick || topBall < topBrick) { + //Only bounce once each ball move + if(!bounced) { + dy = -dy; + yb += dy; + bounced = true; + // arduboy.tunes.tone(261, 250); + } + } + + //Hoizontal collision + if(leftBall < leftBrick || rightBall > rightBrick) { + //Only bounce once brick each ball move + if(!bounced) { + dx = -dx; + xb += dx; + bounced = true; + // arduboy.tunes.tone(261, 250); + } + } + } + } + } + } + + //Reset Bounce + bounced = false; + } else { + //Ball follows paddle + xb = xPaddle + 5; + } +} + +void draw_lives(Canvas* canvas) { + sprintf(text, "LIVES:%u", lives); + canvas_draw_str(canvas, 0, 90, text); +} + +void draw_ball(Canvas* canvas) { + canvas_draw_dot(canvas, xb, yb); + canvas_draw_dot(canvas, xb + 1, yb); + canvas_draw_dot(canvas, xb, yb + 1); + canvas_draw_dot(canvas, xb + 1, yb + 1); + + move_ball(canvas); +} + +void draw_paddle(Canvas* canvas) { + canvas_draw_frame(canvas, xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1); +} + +void reset_level(Canvas* canvas) { + //Undraw paddle + canvas_draw_frame(canvas, xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1); + + //Undraw ball + canvas_draw_dot(canvas, xb, yb); + canvas_draw_dot(canvas, xb + 1, yb); + canvas_draw_dot(canvas, xb, yb + 1); + canvas_draw_dot(canvas, xb + 1, yb + 1); + + //Alter various variables to reset the game + xPaddle = 54; + yb = 60; + brickCount = 0; + released = false; + + // Reset all brick hit states + for(int row = 0; row < ROWS; row++) { + for(int column = 0; column < COLUMNS; column++) { + isHit[row][column] = false; + } + } +} + +static void arkanoid_state_init(ArkanoidState* const arkanoid_state) { + // Set the initial game state + arkanoid_state->game_state = GameStatePlaying; + + // Reset initial state + initialDraw = false; +} + +static void arkanoid_draw_callback(Canvas* const canvas, void* ctx) { + const ArkanoidState* arkanoid_state = acquire_mutex((ValueMutex*)ctx, 25); + if(arkanoid_state == NULL) { + return; + } + + //Initial level draw + if(!initialDraw) { + initialDraw = true; + + // Set default font for text + canvas_set_font(canvas, FontPrimary); + + //Draws the new level + reset_level(canvas); + } + + //Draws new bricks and resets their values + for(int row = 0; row < ROWS; row++) { + for(int column = 0; column < COLUMNS; column++) { + if(!isHit[row][column]) { + canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4); + } + } + } + + if(lives > 0) { + draw_paddle(canvas); + + draw_ball(canvas); + + if(brickCount == ROWS * COLUMNS) { + level++; + reset_level(canvas); + } + } else { + reset_level(canvas); + initialDraw = false; + start = false; + lives = 3; + score = 0; + } + + release_mutex((ValueMutex*)ctx, arkanoid_state); +} + +static void arkanoid_input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +static void arkanoid_update_timer_callback(osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + osMessageQueuePut(event_queue, &event, 0, 0); +} + +int32_t arkanoid_game_app(void* p) { + osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(GameEvent), NULL); + + ArkanoidState* arkanoid_state = malloc(sizeof(ArkanoidState)); + arkanoid_state_init(arkanoid_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, arkanoid_state, sizeof(ArkanoidState))) { + FURI_LOG_E(TAG, "Cannot create mutex\r\n"); + free(arkanoid_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, arkanoid_draw_callback, &state_mutex); + view_port_input_callback_set(view_port, arkanoid_input_callback, event_queue); + + osTimerId_t timer = + osTimerNew(arkanoid_update_timer_callback, osTimerPeriodic, event_queue, NULL); + osTimerStart(timer, osKernelGetTickFreq() / 22); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); + ArkanoidState* arkanoid_state = (ArkanoidState*)acquire_mutex_block(&state_mutex); + + if(event_status == osOK) { + // Key events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress || event.input.type == InputTypeLong || + event.input.type == InputTypeRepeat) { + switch(event.input.key) { + case InputKeyBack: + processing = false; + break; + case InputKeyRight: + if(xPaddle < FLIPPER_LCD_WIDTH - 12) { + xPaddle += 8; + } + break; + case InputKeyLeft: + if(xPaddle > 0) { + xPaddle -= 8; + } + break; + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyOk: + //Release ball if FIRE pressed + released = true; + + //Apply random direction to ball on release + if(rand_range(0, 2) == 0) { + dx = 1; + } else { + dx = -1; + } + + //Makes sure the ball heads upwards + dy = -1; + break; + } + } + } + } else { + // Event timeout + FURI_LOG_D(TAG, "osMessageQueue: Event timeout"); + } + + view_port_update(view_port); + release_mutex(&state_mutex, arkanoid_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + osMessageQueueDelete(event_queue); + furi_record_close("notification"); + + return 0; +} \ No newline at end of file diff --git a/applications/music_player/music_player.c b/applications/music_player/music_player.c index 9b5dda0fa..06c7140d1 100644 --- a/applications/music_player/music_player.c +++ b/applications/music_player/music_player.c @@ -253,7 +253,7 @@ MusicPlayer* music_player_alloc() { instance->model = malloc(sizeof(MusicPlayerModel)); memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); - instance->model->volume = 3; + instance->model->volume = 1; instance->model_mutex = osMutexNew(NULL); diff --git a/applications/music_player/music_player_worker.c b/applications/music_player/music_player_worker.c index f43aa7be8..949477e11 100644 --- a/applications/music_player/music_player_worker.c +++ b/applications/music_player/music_player_worker.c @@ -79,7 +79,7 @@ static int32_t music_player_worker_thread_callback(void* context) { furi_hal_speaker_stop(); furi_hal_speaker_start(frequency, volume); while(instance->should_work && furi_hal_get_tick() < next_tick) { - volume *= 0.9945679; + volume *= 1.0000000; furi_hal_speaker_set_volume(volume); furi_hal_delay_ms(2); } diff --git a/applications/snake_game/snake_game.c b/applications/snake_game/snake_game.c index b07210ee5..1f33d414b 100644 --- a/applications/snake_game/snake_game.c +++ b/applications/snake_game/snake_game.c @@ -84,6 +84,11 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) { canvas_draw_box(canvas, p.x, p.y, 4, 4); } + // Show score on the game field + char buffer2[6]; + snprintf(buffer2, sizeof(buffer2), "%u", snake_state->len - 7); + canvas_draw_str_aligned(canvas, 126, 8, AlignRight, AlignBottom, buffer2); + // Game Over banner if(snake_state->state == GameStateGameOver) { // Screen is 128x64 px diff --git a/applications/tetris_game/tetris_game.c b/applications/tetris_game/tetris_game.c index 18c221b89..04b621226 100644 --- a/applications/tetris_game/tetris_game.c +++ b/applications/tetris_game/tetris_game.c @@ -150,6 +150,11 @@ static void tetris_game_render_callback(Canvas* const canvas, void* ctx) { tetris_game_draw_border(canvas); tetris_game_draw_playfield(canvas, tetris_state); + // Show score on the game field + char buffer2[6]; + snprintf(buffer2, sizeof(buffer2), "%u", tetris_state->numLines); + canvas_draw_str_aligned(canvas, 61, 8, AlignRight, AlignBottom, buffer2); + if(tetris_state->gameState == GameStateGameOver) { // 128 x 64 canvas_set_color(canvas, ColorWhite); @@ -476,4 +481,4 @@ int32_t tetris_game_app() { free(tetris_state); return 0; -} +} \ No newline at end of file diff --git a/applications/tictactoe_game/tictactoe_game.c b/applications/tictactoe_game/tictactoe_game.c new file mode 100644 index 000000000..d69684c1c --- /dev/null +++ b/applications/tictactoe_game/tictactoe_game.c @@ -0,0 +1,349 @@ +#include +#include +#include +#include +#include +#include + +#define TAG "TicTacToe" + +uint8_t selBoxX; +uint8_t selBoxY; + +uint8_t selX = 2; +uint8_t selY = 2; + +uint16_t scoreX; +uint16_t scoreO; + +char player = 'X'; + +char field[3][3]; +bool fieldx[3][3]; + +const uint8_t coords[3] = {6, 27, 48}; + +bool button_state = false; + +typedef enum { EventTypeTick, EventTypeKey } EventType; + +typedef enum { DirectionUp, DirectionRight, DirectionDown, DirectionLeft } Direction; + +typedef enum { GameStatePlaying, GameStateGameOver } GameState; + +typedef struct { + GameState game_state; + osTimerId_t timer; +} TicTacToeState; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +void drawCross(Canvas* const canvas, uint8_t x, uint8_t y) { + canvas_draw_line(canvas, x, y, x + 9, y + 9); // top left - bottom right slash + canvas_draw_line(canvas, x + 9, y, x, y + 9); // down left - top right slash +} + +void drawCircle(Canvas* const canvas, uint8_t x, uint8_t y) { + canvas_draw_circle(canvas, x + 4, y + 5, 5); +} + +void player_switch() { + if(player == 'O') { + player = 'X'; + } else if(player == 'X') { + player = 'O'; + } +} + +void tictactoe_draw(Canvas* canvas) { + // Draws the game field + canvas_draw_frame(canvas, 0, 0, 64, 64); // frame + canvas_draw_line(canvas, 0, 21, 63, 21); // horizontal line + canvas_draw_line(canvas, 0, 42, 63, 42); // horizontal line + canvas_draw_line(canvas, 21, 0, 21, 63); // vertical line + canvas_draw_line(canvas, 42, 0, 42, 63); // vertical line + + // Draws the game field elements (X or O) + for(uint8_t i = 0; i <= 2; i++) { + for(uint8_t j = 0; j <= 2; j++) { + if(field[i][j] == 'O') { + drawCircle(canvas, coords[i], coords[j]); + } else if(field[i][j] == 'X') { + drawCross(canvas, coords[i], coords[j]); + } + } + } + + // Draws the selection box + if(selX == 1) { + selBoxX = 1; + } else if(selX == 2) { + selBoxX = 22; + } else if(selX == 3) { + selBoxX = 43; + } + + if(selY == 1) { + selBoxY = 1; + } else if(selY == 2) { + selBoxY = 22; + } else if(selY == 3) { + selBoxY = 43; + } + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, selBoxX, selBoxY, 20, 20); + canvas_draw_frame(canvas, selBoxX + 1, selBoxY + 1, 18, 18); + + // Draws the sidebar + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 81, 10, "SCORE"); + canvas_draw_str(canvas, 75, 24, "X:"); + + char scoreXBuffer[10]; + sprintf(scoreXBuffer, "%d", scoreX); + canvas_draw_str(canvas, 88, 24, scoreXBuffer); + canvas_draw_str(canvas, 75, 35, "O:"); + + char scoreOBuffer[10]; + sprintf(scoreOBuffer, "%d", scoreO); + canvas_draw_str(canvas, 88, 35, scoreOBuffer); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 75, 46, "Player:"); + + if(player == 'X') { + drawCross(canvas, 93, 50); + } else if(player == 'O') { + drawCircle(canvas, 93, 50); + } +} + +void clear_game_field() { + // Clears the game field arrays + for(uint8_t i = 0; i <= 2; i++) { + for(uint8_t j = 0; j <= 2; j++) { + field[i][j] = ' '; + fieldx[i][j] = false; + } + } + + selX = 2; // Centers the selection box on X axis + selY = 2; // Centers the selection box on Y axis +} + +void reset_game_data() { + scoreO = 0; + scoreX = 0; + player = 'X'; +} + +void draw_win(Canvas* canvas, char player) { + // Handles the score table + if(player == 'X') { + scoreX++; + } else if(player == 'O') { + scoreO++; + } + + // Switches the players + player_switch(); + + // Draws the board with players switched + tictactoe_draw(canvas); + + // Clear the game field + clear_game_field(); + + // Draw the new board + tictactoe_draw(canvas); +} + +static void tictactoe_state_init(TicTacToeState* const tictactoe_state) { + // Set the initial game state + tictactoe_state->game_state = GameStatePlaying; + + clear_game_field(); + + reset_game_data(); +} + +static void tictactoe_draw_callback(Canvas* const canvas, void* ctx) { + const TicTacToeState* tictactoe_state = acquire_mutex((ValueMutex*)ctx, 25); + if(tictactoe_state == NULL) { + return; + } + + if(selX > 3) { + selX = 3; + } else if(selX < 1) { + selX = 1; + } + + if(selY > 3) { + selY = 3; + } else if(selY < 1) { + selY = 1; + } + + // Assigns the game field elements their value (X or O) when the OK button is pressed + if(button_state) { + button_state = false; + for(uint8_t i = 0; i <= 2; i++) { + for(uint8_t j = 0; j <= 2; j++) { + if((selX == i + 1) && (selY == j + 1) && (fieldx[i][j] == false)) { + if(player == 'X') { + field[i][j] = 'X'; + fieldx[i][j] = true; + player_switch(); + } else if(player == 'O') { + field[i][j] = 'O'; + fieldx[i][j] = true; + player_switch(); + } + } + } + } + } + + // Checks the game field for winning combinations + if((field[0][0] == 'X') && (field[1][0] == 'X') && (field[2][0] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[0][1] == 'X') && (field[1][1] == 'X') && (field[2][1] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[0][2] == 'X') && (field[1][2] == 'X') && (field[2][2] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[0][0] == 'X') && (field[0][1] == 'X') && (field[0][2] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[1][0] == 'X') && (field[1][1] == 'X') && (field[1][2] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[2][0] == 'X') && (field[2][1] == 'X') && (field[2][2] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[0][0] == 'X') && (field[1][1] == 'X') && (field[2][2] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[2][0] == 'X') && (field[1][1] == 'X') && (field[0][2] == 'X')) { + draw_win(canvas, 'X'); + } else if((field[0][0] == 'O') && (field[1][0] == 'O') && (field[2][0] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[0][1] == 'O') && (field[1][1] == 'O') && (field[2][1] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[0][2] == 'O') && (field[1][2] == 'O') && (field[2][2] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[0][0] == 'O') && (field[0][1] == 'O') && (field[0][2] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[1][0] == 'O') && (field[1][1] == 'O') && (field[1][2] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[2][0] == 'O') && (field[2][1] == 'O') && (field[2][2] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[0][0] == 'O') && (field[1][1] == 'O') && (field[2][2] == 'O')) { + draw_win(canvas, 'O'); + } else if((field[2][0] == 'O') && (field[1][1] == 'O') && (field[0][2] == 'O')) { + draw_win(canvas, 'O'); + } else if( + (fieldx[0][0] == true) && (fieldx[0][1] == true) && (fieldx[0][2] == true) && + (fieldx[1][0] == true) && (fieldx[1][1] == true) && (fieldx[1][2] == true) && + (fieldx[2][0] == true) && (fieldx[2][1] == true) && (fieldx[2][2] == true)) { + draw_win(canvas, 'T'); + } + + tictactoe_draw(canvas); + + release_mutex((ValueMutex*)ctx, tictactoe_state); +} + +static void tictactoe_input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +static void tictactoe_update_timer_callback(osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + osMessageQueuePut(event_queue, &event, 0, 0); +} + +int32_t tictactoe_game_app(void* p) { + osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(GameEvent), NULL); + + TicTacToeState* tictactoe_state = malloc(sizeof(TicTacToeState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tictactoe_state, sizeof(TicTacToeState))) { + FURI_LOG_E(TAG, "Cannot create mutex\r\n"); + free(tictactoe_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, tictactoe_draw_callback, &state_mutex); + view_port_input_callback_set(view_port, tictactoe_input_callback, event_queue); + + tictactoe_state->timer = + osTimerNew(tictactoe_update_timer_callback, osTimerPeriodic, event_queue, NULL); + osTimerStart(tictactoe_state->timer, osKernelGetTickFreq() / 22); + + tictactoe_state_init(tictactoe_state); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); + TicTacToeState* tictactoe_state = (TicTacToeState*)acquire_mutex_block(&state_mutex); + + if(event_status == osOK) { + // Key events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyBack: + processing = false; + break; + case InputKeyRight: + selX++; + break; + case InputKeyLeft: + selX--; + break; + case InputKeyUp: + selY--; + break; + case InputKeyDown: + selY++; + break; + case InputKeyOk: + button_state = true; + break; + } + } + } + } else { + // Event timeout + FURI_LOG_D(TAG, "osMessageQueue: Event timeout"); + } + + view_port_update(view_port); + release_mutex(&state_mutex, tictactoe_state); + } + + osTimerDelete(tictactoe_state->timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + osMessageQueueDelete(event_queue); + furi_record_close("notification"); + delete_mutex(&state_mutex); + free(tictactoe_state); + + return 0; +} \ No newline at end of file