unleashed-firmware/applications/external/tictactoe_game/tictactoe_game.c

387 lines
12 KiB
C
Raw Normal View History

2022-06-21 05:50:34 +03:00
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <gui/view.h>
#include <dolphin/dolphin.h>
2022-06-21 05:50:34 +03:00
#define TAG "TicTacToe"
typedef enum { EventTypeTick, EventTypeKey } EventType;
2022-06-21 05:50:34 +03:00
typedef struct {
FuriMutex* mutex;
FuriTimer* timer;
uint8_t selBoxX;
uint8_t selBoxY;
2022-06-21 05:50:34 +03:00
uint8_t selX;
uint8_t selY;
2022-06-21 05:50:34 +03:00
uint16_t scoreX;
uint16_t scoreO;
2022-06-21 05:50:34 +03:00
char player;
2022-06-21 05:50:34 +03:00
char field[3][3];
bool fieldx[3][3];
2022-06-21 05:50:34 +03:00
uint8_t coords[3];
2022-06-21 05:50:34 +03:00
bool button_state;
2022-06-21 05:50:34 +03:00
} 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(TicTacToeState* ts) {
if(ts->player == 'O') {
ts->player = 'X';
} else if(ts->player == 'X') {
ts->player = 'O';
2022-06-21 05:50:34 +03:00
}
}
void tictactoe_draw(Canvas* canvas, TicTacToeState* ts) {
2022-06-21 05:50:34 +03:00
// 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(ts->field[i][j] == 'O') {
drawCircle(canvas, ts->coords[i], ts->coords[j]);
} else if(ts->field[i][j] == 'X') {
drawCross(canvas, ts->coords[i], ts->coords[j]);
2022-06-21 05:50:34 +03:00
}
}
}
// Draws the selection box
if(ts->selX == 1) {
ts->selBoxX = 1;
} else if(ts->selX == 2) {
ts->selBoxX = 22;
} else if(ts->selX == 3) {
ts->selBoxX = 43;
2022-06-21 05:50:34 +03:00
}
if(ts->selY == 1) {
ts->selBoxY = 1;
} else if(ts->selY == 2) {
ts->selBoxY = 22;
} else if(ts->selY == 3) {
ts->selBoxY = 43;
2022-06-21 05:50:34 +03:00
}
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, ts->selBoxX, ts->selBoxY, 20, 20);
canvas_draw_frame(canvas, ts->selBoxX + 1, ts->selBoxY + 1, 18, 18);
2022-06-21 05:50:34 +03:00
// 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];
snprintf(scoreXBuffer, sizeof(scoreXBuffer), "%d", ts->scoreX);
2022-06-21 05:50:34 +03:00
canvas_draw_str(canvas, 88, 24, scoreXBuffer);
canvas_draw_str(canvas, 75, 35, "O:");
char scoreOBuffer[10];
snprintf(scoreOBuffer, sizeof(scoreOBuffer), "%d", ts->scoreO);
2022-06-21 05:50:34 +03:00
canvas_draw_str(canvas, 88, 35, scoreOBuffer);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 75, 46, "Player:");
if(ts->player == 'X') {
2022-06-21 05:50:34 +03:00
drawCross(canvas, 93, 50);
} else if(ts->player == 'O') {
2022-06-21 05:50:34 +03:00
drawCircle(canvas, 93, 50);
}
}
void clear_game_field(TicTacToeState* ts) {
2022-06-21 05:50:34 +03:00
// Clears the game field arrays
for(uint8_t i = 0; i <= 2; i++) {
for(uint8_t j = 0; j <= 2; j++) {
ts->field[i][j] = ' ';
ts->fieldx[i][j] = false;
2022-06-21 05:50:34 +03:00
}
}
ts->selX = 2; // Centers the selection box on X axis
ts->selY = 2; // Centers the selection box on Y axis
2022-06-21 05:50:34 +03:00
}
void reset_game_data(TicTacToeState* ts) {
ts->scoreO = 0;
ts->scoreX = 0;
ts->player = 'X';
2022-06-21 05:50:34 +03:00
}
void draw_win(Canvas* canvas, char player, TicTacToeState* ts) {
2022-06-21 05:50:34 +03:00
// Handles the score table
if(player == 'X') {
ts->scoreX++;
2022-06-21 05:50:34 +03:00
} else if(player == 'O') {
ts->scoreO++;
2022-06-21 05:50:34 +03:00
}
// Switches the players
player_switch(ts);
2022-06-21 05:50:34 +03:00
// Draws the board with players switched
tictactoe_draw(canvas, ts);
2022-06-21 05:50:34 +03:00
// Clear the game field
clear_game_field(ts);
2022-06-21 05:50:34 +03:00
// Draw the new board
tictactoe_draw(canvas, ts);
2022-06-21 05:50:34 +03:00
}
static void tictactoe_state_init(TicTacToeState* tictactoe_state) {
2022-06-21 05:50:34 +03:00
// Set the initial game state
tictactoe_state->selX = 2;
tictactoe_state->selY = 2;
tictactoe_state->player = 'X';
tictactoe_state->coords[0] = 6;
tictactoe_state->coords[1] = 27;
tictactoe_state->coords[2] = 48;
tictactoe_state->button_state = false;
2022-06-21 05:50:34 +03:00
clear_game_field(tictactoe_state);
2022-06-21 05:50:34 +03:00
reset_game_data(tictactoe_state);
2022-06-21 05:50:34 +03:00
}
static void tictactoe_draw_callback(Canvas* const canvas, void* ctx) {
furi_assert(ctx);
TicTacToeState* ticst = ctx;
furi_mutex_acquire(ticst->mutex, FuriWaitForever);
2022-06-21 05:50:34 +03:00
if(ticst->selX > 3) {
ticst->selX = 3;
} else if(ticst->selX < 1) {
ticst->selX = 1;
2022-06-21 05:50:34 +03:00
}
if(ticst->selY > 3) {
ticst->selY = 3;
} else if(ticst->selY < 1) {
ticst->selY = 1;
2022-06-21 05:50:34 +03:00
}
// Assigns the game field elements their value (X or O) when the OK button is pressed
if(ticst->button_state) {
ticst->button_state = false;
2022-06-21 05:50:34 +03:00
for(uint8_t i = 0; i <= 2; i++) {
for(uint8_t j = 0; j <= 2; j++) {
if((ticst->selX == i + 1) && (ticst->selY == j + 1) &&
(ticst->fieldx[i][j] == false)) {
if(ticst->player == 'X') {
ticst->field[i][j] = 'X';
ticst->fieldx[i][j] = true;
player_switch(ticst);
} else if(ticst->player == 'O') {
ticst->field[i][j] = 'O';
ticst->fieldx[i][j] = true;
player_switch(ticst);
2022-06-21 05:50:34 +03:00
}
}
}
}
}
// Checks the game field for winning combinations
if((ticst->field[0][0] == 'X') && (ticst->field[1][0] == 'X') && (ticst->field[2][0] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[0][1] == 'X') && (ticst->field[1][1] == 'X') &&
(ticst->field[2][1] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[0][2] == 'X') && (ticst->field[1][2] == 'X') &&
(ticst->field[2][2] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[0][0] == 'X') && (ticst->field[0][1] == 'X') &&
(ticst->field[0][2] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[1][0] == 'X') && (ticst->field[1][1] == 'X') &&
(ticst->field[1][2] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[2][0] == 'X') && (ticst->field[2][1] == 'X') &&
(ticst->field[2][2] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[0][0] == 'X') && (ticst->field[1][1] == 'X') &&
(ticst->field[2][2] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[2][0] == 'X') && (ticst->field[1][1] == 'X') &&
(ticst->field[0][2] == 'X')) {
draw_win(canvas, 'X', ticst);
} else if(
(ticst->field[0][0] == 'O') && (ticst->field[1][0] == 'O') &&
(ticst->field[2][0] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[0][1] == 'O') && (ticst->field[1][1] == 'O') &&
(ticst->field[2][1] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[0][2] == 'O') && (ticst->field[1][2] == 'O') &&
(ticst->field[2][2] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[0][0] == 'O') && (ticst->field[0][1] == 'O') &&
(ticst->field[0][2] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[1][0] == 'O') && (ticst->field[1][1] == 'O') &&
(ticst->field[1][2] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[2][0] == 'O') && (ticst->field[2][1] == 'O') &&
(ticst->field[2][2] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[0][0] == 'O') && (ticst->field[1][1] == 'O') &&
(ticst->field[2][2] == 'O')) {
draw_win(canvas, 'O', ticst);
} else if(
(ticst->field[2][0] == 'O') && (ticst->field[1][1] == 'O') &&
(ticst->field[0][2] == 'O')) {
draw_win(canvas, 'O', ticst);
2022-06-21 05:50:34 +03:00
} else if(
(ticst->fieldx[0][0] == true) && (ticst->fieldx[0][1] == true) &&
(ticst->fieldx[0][2] == true) && (ticst->fieldx[1][0] == true) &&
(ticst->fieldx[1][1] == true) && (ticst->fieldx[1][2] == true) &&
(ticst->fieldx[2][0] == true) && (ticst->fieldx[2][1] == true) &&
(ticst->fieldx[2][2] == true)) {
draw_win(canvas, 'T', ticst);
2022-06-21 05:50:34 +03:00
}
tictactoe_draw(canvas, ticst);
2022-06-21 05:50:34 +03:00
furi_mutex_release(ticst->mutex);
2022-06-21 05:50:34 +03:00
}
2022-07-20 19:01:38 +03:00
static void tictactoe_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
2022-06-21 05:50:34 +03:00
furi_assert(event_queue);
GameEvent event = {.type = EventTypeKey, .input = *input_event};
2022-07-20 19:01:38 +03:00
furi_message_queue_put(event_queue, &event, FuriWaitForever);
2022-06-21 05:50:34 +03:00
}
2022-07-20 19:01:38 +03:00
static void tictactoe_update_timer_callback(FuriMessageQueue* event_queue) {
2022-06-21 05:50:34 +03:00
furi_assert(event_queue);
GameEvent event = {.type = EventTypeTick};
2022-07-20 19:01:38 +03:00
furi_message_queue_put(event_queue, &event, 0);
2022-06-21 05:50:34 +03:00
}
int32_t tictactoe_game_app(void* p) {
UNUSED(p);
2022-07-20 19:01:38 +03:00
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
2022-06-21 05:50:34 +03:00
TicTacToeState* tictactoe_state = malloc(sizeof(TicTacToeState));
tictactoe_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!tictactoe_state->mutex) {
2022-06-21 05:50:34 +03:00
FURI_LOG_E(TAG, "Cannot create mutex\r\n");
2022-07-20 19:01:38 +03:00
furi_message_queue_free(event_queue);
free(tictactoe_state);
2022-06-21 05:50:34 +03:00
return 255;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, tictactoe_draw_callback, tictactoe_state);
2022-06-21 05:50:34 +03:00
view_port_input_callback_set(view_port, tictactoe_input_callback, event_queue);
tictactoe_state->timer =
2022-07-20 19:01:38 +03:00
furi_timer_alloc(tictactoe_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(tictactoe_state->timer, furi_kernel_get_tick_frequency() / 22);
2022-06-21 05:50:34 +03:00
tictactoe_state_init(tictactoe_state);
// Open GUI and register view_port
2022-08-13 17:58:46 +03:00
Gui* gui = furi_record_open(RECORD_GUI);
2022-06-21 05:50:34 +03:00
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Call dolphin deed on game start
2023-06-09 14:14:05 +03:00
dolphin_deed(DolphinDeedPluginGameStart);
2022-06-21 05:50:34 +03:00
GameEvent event;
for(bool processing = true; processing;) {
2022-07-20 19:01:38 +03:00
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
furi_mutex_acquire(tictactoe_state->mutex, FuriWaitForever);
2022-06-21 05:50:34 +03:00
2022-07-20 19:01:38 +03:00
if(event_status == FuriStatusOk) {
2022-06-21 05:50:34 +03:00
// Key events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyBack:
processing = false;
break;
case InputKeyRight:
tictactoe_state->selX++;
2022-06-21 05:50:34 +03:00
break;
case InputKeyLeft:
tictactoe_state->selX--;
2022-06-21 05:50:34 +03:00
break;
case InputKeyUp:
tictactoe_state->selY--;
2022-06-21 05:50:34 +03:00
break;
case InputKeyDown:
tictactoe_state->selY++;
2022-06-21 05:50:34 +03:00
break;
case InputKeyOk:
tictactoe_state->button_state = true;
2022-06-21 05:50:34 +03:00
break;
2022-11-02 23:19:33 +03:00
default:
break;
2022-06-21 05:50:34 +03:00
}
}
}
}
view_port_update(view_port);
furi_mutex_release(tictactoe_state->mutex);
2022-06-21 05:50:34 +03:00
}
2022-07-20 19:01:38 +03:00
furi_timer_free(tictactoe_state->timer);
2022-06-21 05:50:34 +03:00
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
2022-08-13 17:58:46 +03:00
furi_record_close(RECORD_GUI);
2022-06-21 05:50:34 +03:00
view_port_free(view_port);
2022-07-20 19:01:38 +03:00
furi_message_queue_free(event_queue);
furi_mutex_free(tictactoe_state->mutex);
2022-06-21 05:50:34 +03:00
free(tictactoe_state);
return 0;
}