two new games & music player patch

This commit is contained in:
Eng1n33r 2022-06-21 05:50:34 +03:00
parent 79f216ec48
commit eb625f971f
10 changed files with 817 additions and 8 deletions

View file

@ -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

View file

@ -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!
<br>
<br>
@ -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

View file

@ -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);

View file

@ -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

View file

@ -0,0 +1,410 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <notification/notification_messages.h>
#include <gui/view.h>
#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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -0,0 +1,349 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <notification/notification_messages.h>
#include <gui/view.h>
#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;
}