diff --git a/.gitmodules b/.gitmodules index ba7644981..931eefbb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "lib/cxxheaderparser"] path = lib/cxxheaderparser url = https://github.com/robotpy/cxxheaderparser.git +[submodule "applications/plugins/tama_p1/tamalib"] + path = applications/plugins/tama_p1/tamalib + url = https://github.com/jcrona/tamalib.git diff --git a/applications/plugins/tama_p1/README.md b/applications/plugins/tama_p1/README.md new file mode 100644 index 000000000..20690c7a2 --- /dev/null +++ b/applications/plugins/tama_p1/README.md @@ -0,0 +1,35 @@ +Tama P1 Emulator for Flipper Zero +======================================= + +This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://github.com/jcrona/tamalib/). + +How to play +----------- +Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`. +Left button is A, OK is B, and right button is C. Hold the back button to exit. +There is currently no saving, so your progress will be reset when you exit the +app. + +Building +-------- +Run the following to compile icons: +``` +scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled +``` + +Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in +`site_cons/cc.scons` to suppress unused parameter errors in TamaLIB. + +Implemented +----------- +- Basic emulation +- Input +- Sound + +To-do +----- +- Saving/loading + - Multiple slots? +- In-game reset +- Test mode? +- Volume adjustment diff --git a/applications/plugins/tama_p1/application.fam b/applications/plugins/tama_p1/application.fam new file mode 100644 index 000000000..075a1e118 --- /dev/null +++ b/applications/plugins/tama_p1/application.fam @@ -0,0 +1,12 @@ +App( + appid="GAME_TAMA_P1", + name="TAMA P1", + apptype=FlipperAppType.EXTERNAL, + entry_point="tama_p1_app", + cdefines=["APP_TAMA_P1"], + requires=["gui", "storage"], + stack_size=1 * 1024, + order=215, + fap_icon="tamaIcon.png", + fap_category="Games", +) diff --git a/applications/plugins/tama_p1/compiled/assets_icons.h b/applications/plugins/tama_p1/compiled/assets_icons.h new file mode 100644 index 000000000..aa2735b72 --- /dev/null +++ b/applications/plugins/tama_p1/compiled/assets_icons.h @@ -0,0 +1,66 @@ +#include + +const uint8_t _I_icon_0_0[] = { + 0x01, 0x00, 0x1a, 0x00, 0x00, 0x0d, 0xaa, 0x1d, 0x7e, 0x00, 0x9c, 0x3e, 0xf9, 0x0f, 0x9e, + 0x43, 0xe3, 0x00, 0x12, 0x9c, 0x43, 0xa7, 0x10, 0xc9, 0xe4, 0x30, 0x0a, 0x31, 0x08, 0x60, +}; +const uint8_t* const _I_icon_0[] = {_I_icon_0_0}; + +const uint8_t _I_icon_1_0[] = { + 0x00, 0x00, 0x00, 0x40, 0x04, 0x04, 0x04, 0xf0, 0x11, 0xf9, 0x1b, 0xf8, 0x07, 0x8c, 0x06, + 0xed, 0x36, 0xac, 0x26, 0xe8, 0x02, 0x52, 0x0b, 0x02, 0x18, 0xe0, 0x01, 0xe0, 0x01, +}; +const uint8_t* const _I_icon_1[] = {_I_icon_1_0}; + +const uint8_t _I_icon_2_0[] = { + 0x00, 0x00, 0x00, 0x0e, 0x00, 0x13, 0x00, 0x21, 0x3c, 0x21, 0x3e, 0x23, 0x3f, 0x9f, 0x1f, + 0xc0, 0x0f, 0xe0, 0x07, 0xf0, 0x01, 0x7c, 0x00, 0x1f, 0x00, 0x06, 0x00, 0x06, 0x00, +}; +const uint8_t* const _I_icon_2[] = {_I_icon_2_0}; + +const uint8_t _I_icon_3_0[] = { + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x1c, 0x40, 0x3a, 0xc0, 0x36, 0xf0, 0x37, 0x18, 0x2d, + 0x0c, 0x2b, 0x0e, 0x02, 0x1f, 0x06, 0x3e, 0x07, 0xfe, 0x00, 0x7f, 0x00, 0x18, 0x00, +}; +const uint8_t* const _I_icon_3[] = {_I_icon_3_0}; + +const uint8_t _I_icon_4_0[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0xc7, 0x3c, 0x82, 0x2f, 0xf2, 0x26, 0xc7, 0x2c, + 0x69, 0x28, 0x2f, 0x2c, 0xe7, 0x27, 0x02, 0x20, 0x02, 0x30, 0x06, 0x1c, 0xfc, 0x0f, +}; +const uint8_t* const _I_icon_4[] = {_I_icon_4_0}; + +const uint8_t _I_icon_5_0[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0xfe, 0x0f, 0x03, 0x38, 0xc9, 0x22, 0x9a, 0x32, + 0xa2, 0x28, 0x24, 0x2c, 0x21, 0x20, 0x61, 0x30, 0x21, 0x10, 0xf3, 0x11, 0x1e, 0x0f, +}; +const uint8_t* const _I_icon_5[] = {_I_icon_5_0}; + +const uint8_t _I_icon_6_0[] = { + 0x01, 0x00, 0x17, 0x00, 0x00, 0x44, 0x62, 0xfd, 0x38, 0xbf, 0xcf, 0xb7, 0xf3, 0xf8, + 0xfc, 0x6e, 0x3f, 0x1a, 0xff, 0xc0, 0x3f, 0xf0, 0x1f, 0xf4, 0x02, 0x71, 0x00, +}; +const uint8_t* const _I_icon_6[] = {_I_icon_6_0}; + +const uint8_t _I_icon_7_0[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x41, 0x0e, 0xc4, 0x1f, 0x94, 0x20, + 0x00, 0x21, 0x22, 0x1f, 0x1d, 0x0a, 0x63, 0x20, 0xde, 0x20, 0x80, 0x1f, 0x00, 0x0e, +}; +const uint8_t* const _I_icon_7[] = {_I_icon_7_0}; + +const Icon I_icon_0 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_0}; +const Icon I_icon_1 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_1}; +const Icon I_icon_2 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_2}; +const Icon I_icon_3 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_3}; +const Icon I_icon_4 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_4}; +const Icon I_icon_5 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_5}; +const Icon I_icon_6 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_6}; +const Icon I_icon_7 = + {.width = 14, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_icon_7}; \ No newline at end of file diff --git a/applications/plugins/tama_p1/hal.c b/applications/plugins/tama_p1/hal.c new file mode 100644 index 000000000..0ed97a201 --- /dev/null +++ b/applications/plugins/tama_p1/hal.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include "tama.h" + +#define TAG_HAL "TamaLIB" + +static void* tama_p1_hal_malloc(u32_t size) { + return malloc(size); +} + +static void tama_p1_hal_free(void* ptr) { + free(ptr); +} + +static void tama_p1_hal_halt(void) { + g_ctx->halted = true; +} + +static bool_t tama_p1_hal_is_log_enabled(log_level_t level) { + switch(level) { + case LOG_ERROR: + return true; + case LOG_INFO: + return true; + case LOG_MEMORY: + return false; + case LOG_CPU: + return false; + default: + return false; + } +} + +static void tama_p1_hal_log(log_level_t level, char* buff, ...) { + if(!tama_p1_hal_is_log_enabled(level)) return; + + string_t string; + va_list args; + va_start(args, buff); + string_init_vprintf(string, buff, args); + va_end(args); + + switch(level) { + case LOG_ERROR: + FURI_LOG_E(TAG_HAL, "%s", string_get_cstr(string)); + break; + case LOG_INFO: + FURI_LOG_I(TAG_HAL, "%s", string_get_cstr(string)); + break; + case LOG_MEMORY: + case LOG_CPU: + default: + FURI_LOG_D(TAG_HAL, "%s", string_get_cstr(string)); + break; + } + + string_clear(string); +} + +static void tama_p1_hal_sleep_until(timestamp_t ts) { + while(true) { + uint32_t count = LL_TIM_GetCounter(TIM2); + uint32_t delay = ts - count; + // FURI_LOG_D(TAG, "delay: %x", delay); + // Stolen from furi_delay_until_tick + if(delay != 0 && 0 == (delay >> (8 * sizeof(uint32_t) - 1))) { + // Not the best place to release mutex, but this is the only place we know whether + // we're ahead or behind, otherwise around the step call we'll always have to + // delay a tick and run more and more behind. + furi_mutex_release(g_state_mutex); + furi_delay_tick(1); + while(furi_mutex_acquire(g_state_mutex, FuriWaitForever) != FuriStatusOk) + furi_delay_tick(1); + } else { + break; + } + } +} + +static timestamp_t tama_p1_hal_get_timestamp(void) { + return LL_TIM_GetCounter(TIM2); +} + +static void tama_p1_hal_update_screen(void) { + // Do nothing, covered by main loop +} + +static void tama_p1_hal_set_lcd_matrix(u8_t x, u8_t y, bool_t val) { + if(val) + g_ctx->framebuffer[y] |= 1 << x; + else + g_ctx->framebuffer[y] &= ~(1 << x); +} + +static void tama_p1_hal_set_lcd_icon(u8_t icon, bool_t val) { + if(val) + g_ctx->icons |= 1 << icon; + else + g_ctx->icons &= ~(1 << icon); +} + +static void tama_p1_hal_play_frequency(bool_t en) { + if(en) + furi_hal_speaker_start(g_ctx->frequency, 0.5f); + else + furi_hal_speaker_stop(); + + g_ctx->buzzer_on = en; +} + +static void tama_p1_hal_set_frequency(u32_t freq) { + g_ctx->frequency = freq / 10.0F; + if(g_ctx->buzzer_on) tama_p1_hal_play_frequency(true); +} + +static int tama_p1_hal_handler(void) { + // Do nothing + return 0; +} + +void tama_p1_hal_init(hal_t* hal) { + hal->malloc = tama_p1_hal_malloc; + hal->free = tama_p1_hal_free; + hal->halt = tama_p1_hal_halt; + hal->is_log_enabled = tama_p1_hal_is_log_enabled; + hal->log = tama_p1_hal_log; + hal->sleep_until = tama_p1_hal_sleep_until; + hal->get_timestamp = tama_p1_hal_get_timestamp; + hal->update_screen = tama_p1_hal_update_screen; + hal->set_lcd_matrix = tama_p1_hal_set_lcd_matrix; + hal->set_lcd_icon = tama_p1_hal_set_lcd_icon; + hal->set_frequency = tama_p1_hal_set_frequency; + hal->play_frequency = tama_p1_hal_play_frequency; + hal->handler = tama_p1_hal_handler; +} diff --git a/applications/plugins/tama_p1/hal_types.h b/applications/plugins/tama_p1/hal_types.h new file mode 100644 index 000000000..d27e6dfbe --- /dev/null +++ b/applications/plugins/tama_p1/hal_types.h @@ -0,0 +1,35 @@ +/* + * TamaLIB - A hardware agnostic tama P1 emulation library + * + * Copyright (C) 2021 Jean-Christophe Rona + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _HAL_TYPES_H_ +#define _HAL_TYPES_H_ + +#include + +typedef bool bool_t; +typedef uint8_t u4_t; +typedef uint8_t u5_t; +typedef uint8_t u8_t; +typedef uint16_t u12_t; +typedef uint16_t u13_t; +typedef uint32_t u32_t; +typedef uint32_t + timestamp_t; // WARNING: Must be an unsigned type to properly handle wrapping (u32 wraps in around 1h11m when expressed in us) + +#endif /* _HAL_TYPES_H_ */ diff --git a/applications/plugins/tama_p1/icons/icon_0.png b/applications/plugins/tama_p1/icons/icon_0.png new file mode 100644 index 000000000..7b94d3511 Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_0.png differ diff --git a/applications/plugins/tama_p1/icons/icon_1.png b/applications/plugins/tama_p1/icons/icon_1.png new file mode 100644 index 000000000..934cf7187 Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_1.png differ diff --git a/applications/plugins/tama_p1/icons/icon_2.png b/applications/plugins/tama_p1/icons/icon_2.png new file mode 100644 index 000000000..b83e449d9 Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_2.png differ diff --git a/applications/plugins/tama_p1/icons/icon_3.png b/applications/plugins/tama_p1/icons/icon_3.png new file mode 100644 index 000000000..34f4db24d Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_3.png differ diff --git a/applications/plugins/tama_p1/icons/icon_4.png b/applications/plugins/tama_p1/icons/icon_4.png new file mode 100644 index 000000000..fe02b83af Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_4.png differ diff --git a/applications/plugins/tama_p1/icons/icon_5.png b/applications/plugins/tama_p1/icons/icon_5.png new file mode 100644 index 000000000..889acd50e Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_5.png differ diff --git a/applications/plugins/tama_p1/icons/icon_6.png b/applications/plugins/tama_p1/icons/icon_6.png new file mode 100644 index 000000000..57d2f181b Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_6.png differ diff --git a/applications/plugins/tama_p1/icons/icon_7.png b/applications/plugins/tama_p1/icons/icon_7.png new file mode 100644 index 000000000..4ff6e504a Binary files /dev/null and b/applications/plugins/tama_p1/icons/icon_7.png differ diff --git a/applications/plugins/tama_p1/tama.h b/applications/plugins/tama_p1/tama.h new file mode 100644 index 000000000..d3b67b90d --- /dev/null +++ b/applications/plugins/tama_p1/tama.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "tamalib/tamalib.h" + +#define TAG "TamaP1" +#define TAMA_ROM_PATH EXT_PATH("tama_p1/rom.bin") +#define TAMA_SCREEN_SCALE_FACTOR 2 +#define TAMA_LCD_ICON_SIZE 14 +#define TAMA_LCD_ICON_MARGIN 1 + +typedef struct { + FuriThread* thread; + hal_t hal; + uint8_t* rom; + // 32x16 screen, perfectly represented through uint32_t + uint32_t framebuffer[16]; + uint8_t icons; + bool halted; + bool fast_forward_done; + bool buzzer_on; + float frequency; +} TamaApp; + +typedef enum { + EventTypeInput, + EventTypeTick, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} TamaEvent; + +extern TamaApp* g_ctx; +extern FuriMutex* g_state_mutex; + +void tama_p1_hal_init(hal_t* hal); diff --git a/applications/plugins/tama_p1/tamaIcon.png b/applications/plugins/tama_p1/tamaIcon.png new file mode 100644 index 000000000..485f70e5c Binary files /dev/null and b/applications/plugins/tama_p1/tamaIcon.png differ diff --git a/applications/plugins/tama_p1/tama_p1.c b/applications/plugins/tama_p1/tama_p1.c new file mode 100644 index 000000000..f2da6b394 --- /dev/null +++ b/applications/plugins/tama_p1/tama_p1.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include "tamalib/tamalib.h" +#include "tama.h" +#include "compiled/assets_icons.h" + +TamaApp* g_ctx; +FuriMutex* g_state_mutex; + +static const Icon* icons_list[] = { + &I_icon_0, + &I_icon_1, + &I_icon_2, + &I_icon_3, + &I_icon_4, + &I_icon_5, + &I_icon_6, + &I_icon_7, +}; + +static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) { + furi_assert(cb_ctx); + + FuriMutex* const mutex = cb_ctx; + if(furi_mutex_acquire(mutex, 25) != FuriStatusOk) return; + + if(g_ctx->rom == NULL) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 30, 30, "No ROM"); + } else if(g_ctx->halted) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 30, 30, "Halted"); + } else { + // FURI_LOG_D(TAG, "Drawing frame"); + // Calculate positioning + uint16_t canv_width = canvas_width(canvas); + uint16_t canv_height = canvas_height(canvas); + uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR; + uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR; + uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2; + uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2; + uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN; + uint16_t lcd_icon_upper_left = lcd_matrix_left; + uint16_t lcd_icon_lower_top = + lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN; + uint16_t lcd_icon_lower_left = lcd_matrix_left; + uint16_t lcd_icon_spacing_horiz = + (lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE; + + // Draw pixels + // canvas_draw_frame( + // canvas, + // lcd_matrix_left, + // lcd_matrix_top, + // lcd_matrix_scaled_width, + // lcd_matrix_scaled_height); + + uint16_t y = lcd_matrix_top; + for(uint8_t row = 0; row < 16; ++row) { + uint16_t x = lcd_matrix_left; + uint32_t row_pixels = g_ctx->framebuffer[row]; + for(uint8_t col = 0; col < 32; ++col) { + if(row_pixels & 1) { + canvas_draw_box( + canvas, x, y, TAMA_SCREEN_SCALE_FACTOR, TAMA_SCREEN_SCALE_FACTOR); + } + x += TAMA_SCREEN_SCALE_FACTOR; + row_pixels >>= 1; + } + y += TAMA_SCREEN_SCALE_FACTOR; + } + + // Draw icons + uint8_t lcd_icons = g_ctx->icons; + // Top + y = lcd_icon_upper_top; + uint16_t x_ic = lcd_icon_upper_left; + for(uint8_t i = 0; i < 4; ++i) { + // canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE); + if(lcd_icons & 1) { + canvas_draw_icon(canvas, x_ic, y, icons_list[i]); + } + x_ic += lcd_icon_spacing_horiz; + lcd_icons >>= 1; + } + + // Bottom + y = lcd_icon_lower_top; + x_ic = lcd_icon_lower_left; + for(uint8_t i = 4; i < 8; ++i) { + // canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE); + if(lcd_icons & 1) { + canvas_draw_icon(canvas, x_ic, y, icons_list[i]); + } + x_ic += lcd_icon_spacing_horiz; + lcd_icons >>= 1; + } + } + + furi_mutex_release(mutex); +} + +static void tama_p1_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TamaEvent event = {.type = EventTypeInput, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void tama_p1_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TamaEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static int32_t tama_p1_worker(void* context) { + bool running = true; + FuriMutex* mutex = context; + while(furi_mutex_acquire(mutex, FuriWaitForever) != FuriStatusOk) furi_delay_tick(1); + + cpu_sync_ref_timestamp(); + LL_TIM_EnableCounter(TIM2); + while(running) { + if(furi_thread_flags_get()) { + running = false; + } else { + // FURI_LOG_D(TAG, "Stepping"); + // for (int i = 0; i < 100; ++i) + tamalib_step(); + } + } + LL_TIM_DisableCounter(TIM2); + furi_mutex_release(mutex); + return 0; +} + +static void tama_p1_init(TamaApp* const ctx) { + g_ctx = ctx; + memset(ctx, 0, sizeof(TamaApp)); + tama_p1_hal_init(&ctx->hal); + + // Load ROM + Storage* storage = furi_record_open(RECORD_STORAGE); + FileInfo fi; + if(storage_common_stat(storage, TAMA_ROM_PATH, &fi) == FSE_OK) { + File* rom_file = storage_file_alloc(storage); + if(storage_file_open(rom_file, TAMA_ROM_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + ctx->rom = malloc((size_t)fi.size); + uint8_t* buf_ptr = ctx->rom; + size_t read = 0; + while(read < fi.size) { + size_t to_read = fi.size - read; + if(to_read > UINT16_MAX) to_read = UINT16_MAX; + uint16_t now_read = storage_file_read(rom_file, buf_ptr, (uint16_t)to_read); + read += now_read; + buf_ptr += now_read; + } + + // Reorder endianess of ROM + for(size_t i = 0; i < fi.size; i += 2) { + uint8_t b = ctx->rom[i]; + ctx->rom[i] = ctx->rom[i + 1]; + ctx->rom[i + 1] = b & 0xF; + } + } + + storage_file_close(rom_file); + storage_file_free(rom_file); + } + furi_record_close(RECORD_STORAGE); + + if(ctx->rom != NULL) { + // Init TIM2 + // 64KHz + LL_TIM_InitTypeDef tim_init = { + .Prescaler = 999, + .CounterMode = LL_TIM_COUNTERMODE_UP, + .Autoreload = 0xFFFFFFFF, + }; + LL_TIM_Init(TIM2, &tim_init); + LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableCounter(TIM2); + LL_TIM_SetCounter(TIM2, 0); + + // Init TamaLIB + tamalib_register_hal(&ctx->hal); + tamalib_init((u12_t*)ctx->rom, NULL, 64000); + tamalib_set_speed(1); + + // TODO: implement fast forwarding + ctx->fast_forward_done = true; + + // Start stepping thread + ctx->thread = furi_thread_alloc(); + furi_thread_set_name(ctx->thread, "TamaLIB"); + furi_thread_set_stack_size(ctx->thread, 1024); + furi_thread_set_callback(ctx->thread, tama_p1_worker); + furi_thread_set_context(ctx->thread, g_state_mutex); + furi_thread_start(ctx->thread); + } +} + +static void tama_p1_deinit(TamaApp* const ctx) { + if(ctx->rom != NULL) { + tamalib_release(); + furi_thread_free(ctx->thread); + free(ctx->rom); + } +} + +int32_t tama_p1_app(void* p) { + UNUSED(p); + + TamaApp* ctx = malloc(sizeof(TamaApp)); + g_state_mutex = furi_mutex_alloc(FuriMutexTypeRecursive); + tama_p1_init(ctx); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TamaEvent)); + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, tama_p1_draw_callback, g_state_mutex); + view_port_input_callback_set(view_port, tama_p1_input_callback, event_queue); + + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + FuriTimer* timer = + furi_timer_alloc(tama_p1_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30); + + for(bool running = true; running;) { + TamaEvent event; + FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever); + if(event_status == FuriStatusOk) { + // Local override with acquired context + if(furi_mutex_acquire(g_state_mutex, FuriWaitForever) != FuriStatusOk) continue; + + if(event.type == EventTypeTick) { + // FURI_LOG_D(TAG, "EventTypeTick"); + view_port_update(view_port); + } else if(event.type == EventTypeInput) { + FURI_LOG_D( + TAG, + "EventTypeInput: %d %d %d", + event.input.sequence, + event.input.key, + event.input.type); + InputType input_type = event.input.type; + if(input_type == InputTypePress || input_type == InputTypeRelease) { + btn_state_t tama_btn_state = 0; + if(input_type == InputTypePress) + tama_btn_state = BTN_STATE_PRESSED; + else if(input_type == InputTypeRelease) + tama_btn_state = BTN_STATE_RELEASED; + + if(event.input.key == InputKeyLeft) { + tamalib_set_button(BTN_LEFT, tama_btn_state); + } else if(event.input.key == InputKeyOk) { + tamalib_set_button(BTN_MIDDLE, tama_btn_state); + } else if(event.input.key == InputKeyRight) { + tamalib_set_button(BTN_RIGHT, tama_btn_state); + } + } + + if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) { + furi_timer_stop(timer); + running = false; + } + } + + furi_mutex_release(g_state_mutex); + } else { + // Timeout + // FURI_LOG_D(TAG, "Timed out"); + } + } + + if(ctx->rom != NULL) { + furi_thread_flags_set(furi_thread_get_id(ctx->thread), 1); + furi_thread_join(ctx->thread); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(g_state_mutex); + tama_p1_deinit(ctx); + free(ctx); + + return 0; +} diff --git a/applications/plugins/tama_p1/tamalib b/applications/plugins/tama_p1/tamalib new file mode 160000 index 000000000..694664b8b --- /dev/null +++ b/applications/plugins/tama_p1/tamalib @@ -0,0 +1 @@ +Subproject commit 694664b8b455df75d420808de417565cc0d244fe diff --git a/applications/plugins/tanksgame/application.fam b/applications/plugins/tanksgame/application.fam new file mode 100644 index 000000000..8998d626f --- /dev/null +++ b/applications/plugins/tanksgame/application.fam @@ -0,0 +1,12 @@ +App( + appid="GAME_Tanks", + name="Tanks", + apptype=FlipperAppType.EXTERNAL, + entry_point="tanks_game_app", + cdefines=["APP_TANKS_GAME"], + requires=["gui", "subghz"], + stack_size=4 * 1024, + order=230, + fap_icon="tanksIcon.png", + fap_category="Games", +) diff --git a/applications/plugins/tanksgame/constants.h b/applications/plugins/tanksgame/constants.h new file mode 100644 index 000000000..d65dd32ea --- /dev/null +++ b/applications/plugins/tanksgame/constants.h @@ -0,0 +1,19 @@ +#ifndef FLIPPERZERO_FIRMWARE_CONSTANTS_H +#define FLIPPERZERO_FIRMWARE_CONSTANTS_H + +const uint8_t SCREEN_WIDTH_TANKS = 128; +const uint8_t SCREEN_HEIGHT_TANKS = 64; + +const uint8_t FIELD_WIDTH = 16; +const uint8_t FIELD_HEIGHT = 11; + +const uint16_t TURN_LENGTH = 300; +const uint16_t LONG_PRESS_LENGTH = 10; + +const uint8_t SHOT_COOLDOWN = 5; +const uint8_t RESPAWN_COOLDOWN = 8; +const uint8_t PLAYER_RESPAWN_COOLDOWN = 1; + +const uint8_t CELL_LENGTH_PIXELS = 6; + +#endif diff --git a/applications/plugins/tanksgame/tanksIcon.png b/applications/plugins/tanksgame/tanksIcon.png new file mode 100644 index 000000000..485f70e5c Binary files /dev/null and b/applications/plugins/tanksgame/tanksIcon.png differ diff --git a/applications/plugins/tanksgame/tanks_game.c b/applications/plugins/tanksgame/tanks_game.c new file mode 100644 index 000000000..47fa348c6 --- /dev/null +++ b/applications/plugins/tanksgame/tanks_game.c @@ -0,0 +1,1808 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" + +#include + +const uint8_t _I_HappyFlipper_128x64_0[] = { + 0x01, 0x00, 0xc2, 0x03, 0xc9, 0x49, 0x29, 0x20, 0x2f, 0x01, 0x78, 0x0b, 0xc0, 0x59, 0xa4, 0xa5, + 0x65, 0x65, 0x55, 0x5d, 0x55, 0x53, 0x53, 0x52, 0xd2, 0x82, 0x16, 0x53, 0x51, 0xd1, 0x51, 0x31, + 0x31, 0x29, 0x2d, 0x2b, 0x29, 0x28, 0x04, 0x36, 0x2a, 0x29, 0x29, 0xa9, 0x69, 0x29, 0x19, 0x19, + 0x15, 0x14, 0x10, 0xba, 0x41, 0x54, 0x82, 0x12, 0x13, 0x21, 0xa1, 0x61, 0x21, 0x11, 0x19, 0x15, + 0x13, 0x11, 0x10, 0x04, 0x2d, 0x4a, 0x95, 0xf5, 0xff, 0xd4, 0xc9, 0xf5, 0xa4, 0xc9, 0x66, 0x24, + 0x9b, 0xf2, 0x8d, 0x52, 0x6a, 0x95, 0x6a, 0x4f, 0xa4, 0xa3, 0x57, 0xec, 0x4c, 0x95, 0x06, 0x39, + 0x3c, 0x55, 0x2a, 0x55, 0x2a, 0xd4, 0xea, 0x34, 0x9a, 0x2c, 0x96, 0x2b, 0xa9, 0xd3, 0xa5, 0x49, + 0x69, 0x3a, 0xea, 0x86, 0x4a, 0x85, 0x49, 0x85, 0x49, 0x65, 0x39, 0x4d, 0x25, 0x43, 0x7b, 0x56, + 0xc8, 0xd2, 0x3a, 0x53, 0xdc, 0xac, 0x93, 0x33, 0x92, 0xb2, 0x52, 0xb2, 0x52, 0x9a, 0x4c, 0x9a, + 0x41, 0xbd, 0x92, 0x65, 0x6e, 0x97, 0x99, 0x15, 0x26, 0x45, 0x24, 0xc4, 0xe4, 0x24, 0x94, 0x2c, + 0x95, 0x22, 0xeb, 0xfe, 0xa5, 0x48, 0xab, 0xf6, 0xaa, 0xae, 0x4a, 0xa9, 0x49, 0xa9, 0x0d, 0xc4, + 0x4c, 0x39, 0x5e, 0x56, 0xa6, 0xbf, 0xff, 0xc9, 0x7b, 0x31, 0x15, 0x4c, 0x48, 0x38, 0x50, 0x03, + 0x28, 0xaa, 0xc8, 0xbe, 0x14, 0xff, 0x94, 0x92, 0x53, 0x97, 0xd2, 0xa5, 0xc7, 0x15, 0x48, 0x94, + 0xc8, 0xb4, 0xa1, 0x71, 0x9e, 0xe7, 0xf2, 0x94, 0xae, 0x16, 0x96, 0x53, 0x95, 0xc5, 0x55, 0x25, + 0x55, 0x2d, 0x4d, 0x20, 0x2e, 0x34, 0xef, 0x9c, 0x8b, 0x23, 0x7c, 0xe4, 0xe4, 0xe9, 0x12, 0x49, + 0x0e, 0x47, 0x09, 0x44, 0x89, 0x44, 0xa9, 0x3f, 0x4e, 0x5f, 0xff, 0xef, 0x27, 0x8a, 0xc9, 0x74, + 0xac, 0x71, 0xa5, 0x4a, 0x65, 0x3a, 0x5d, 0x26, 0x52, 0xcb, 0x15, 0xa4, 0xd7, 0xa9, 0x5a, 0x0a, + 0x4c, 0x4b, 0x4b, 0x92, 0xc9, 0x04, 0x46, 0x54, 0x12, 0x1a, 0xc6, 0xa4, 0xf0, 0x3a, 0x88, 0xb6, + 0x4f, 0x49, 0x08, 0xd2, 0xc9, 0x2a, 0x49, 0x91, 0xa2, 0xd0, 0xb2, 0xb4, 0x3a, 0x55, 0x4b, 0x0b, + 0xa8, 0xd2, 0xcf, 0xea, 0xb2, 0x8a, 0x4e, 0x26, 0x55, 0x25, 0x77, 0x8e, 0xab, 0x28, 0x96, 0x19, + 0xf7, 0x17, 0xd3, 0x72, 0xd0, 0x85, 0x86, 0xa5, 0x44, 0xa4, 0xc4, 0x72, 0xbc, 0x44, 0x38, 0xd5, + 0x6f, 0xfa, 0x9d, 0x97, 0x11, 0x78, 0x39, 0x44, 0x38, 0xba, 0x46, 0x90, 0x3e, 0x1d, 0x56, 0x22, + 0x4f, 0xf2, 0xaf, 0xea, 0x95, 0x43, 0x49, 0x58, 0x9b, 0xf0, 0x44, 0x63, 0xa5, 0xaa, 0xff, 0xe5, + 0x79, 0x1a, 0x89, 0x88, 0x5f, 0x83, 0x48, 0x95, 0x08, 0x44, 0x02, 0x31, 0x2d, 0xcc, 0x17, 0x23, + 0xca, 0x0c, 0x0e, 0xaa, 0x55, 0x51, 0xd5, 0x51, 0x25, 0x3c, 0x96, 0x90, 0xdf, 0xe9, 0xda, 0x9a, + 0x57, 0xf3, 0x2b, 0x54, 0xa2, 0x51, 0x31, 0x51, 0x29, 0x4d, 0x0a, 0xa4, 0xd2, 0x1e, 0x4c, 0x42, + 0xa5, 0x24, 0xff, 0xb2, 0xc7, 0x25, 0x52, 0xa8, 0xe9, 0x01, 0xa3, 0x64, 0xc4, 0xf5, 0x13, 0x63, + 0x84, 0xe9, 0x66, 0xea, 0x81, 0xb1, 0x8a, 0xea, 0xa9, 0x58, 0x4c, 0x41, 0x50, 0x71, 0x20, 0x61, + 0x94, 0xff, 0xe5, 0xd8, 0x9d, 0x25, 0x13, 0x91, 0x04, 0xc8, 0xca, 0xaa, 0xbe, 0xa9, 0x27, 0x4a, + 0x89, 0x28, 0xf5, 0x6a, 0xe9, 0x75, 0x2a, 0x4e, 0xaa, 0x25, 0x69, 0x28, 0x8c, 0x89, 0x26, 0x3a, + 0x5d, 0x4e, 0x4b, 0xe5, 0xff, 0xca, 0xa3, 0xc6, 0x20, 0x41, 0x12, 0x88, 0xe5, 0x2d, 0x39, 0x2a, + 0x45, 0xd7, 0x4b, 0x7f, 0xc9, 0x7d, 0xf2, 0x32, 0x4d, 0x2d, 0x22, 0x49, 0xa9, 0xd2, 0x48, 0xba, + 0x52, 0x48, 0xaf, 0x0b, 0x93, 0x4f, 0xd4, 0xfb, 0x2a, 0xe7, 0x31, 0x11, 0x89, 0x4c, 0x75, 0x36, + 0x4d, 0x4f, 0x22, 0xa9, 0xef, 0x3b, 0x0e, 0xea, 0x19, 0x0a, 0x88, 0xc8, 0xb2, 0x29, 0xc1, 0x1f, + 0x0c, 0x2a, 0x4b, 0x89, 0xfe, 0x64, 0xba, 0x93, 0xfb, 0xea, 0x70, 0x6a, 0x92, 0xaa, 0x4b, 0x48, + 0x7a, 0x5f, 0x42, 0xd0, 0xe4, 0x7f, 0xf7, 0x5f, 0x4f, 0xc3, 0x2f, 0x44, 0xc4, 0xd1, 0x06, 0x23, + 0x4a, 0xb2, 0x52, 0x6f, 0xd2, 0x52, 0xd8, 0xef, 0xf4, 0x9c, 0x59, 0x74, 0xa7, 0x4b, 0x52, 0x18, + 0x8e, 0x52, 0x93, 0x92, 0xe9, 0x65, 0xf4, 0x92, 0x45, 0x98, 0xf5, 0x2b, 0x34, 0x58, 0x96, 0x24, + 0x62, 0x32, 0x1b, 0x53, 0xcc, 0x6b, 0x8f, 0x91, 0xa4, 0xf4, 0xa2, 0x52, 0x3a, 0x96, 0xa6, 0xa8, + 0x31, 0x1a, 0x51, 0x40, 0x51, 0xa2, 0xf9, 0x1c, 0xa5, 0xd3, 0x4b, 0xee, 0x8a, 0x48, 0xb1, 0x28, + 0x01, 0xa9, 0x23, 0xc5, 0xe0, 0x3a, 0xbf, 0xf4, 0x9f, 0x7f, 0x48, 0xcd, 0x68, 0xea, 0x95, 0x2a, + 0x94, 0x4b, 0x2b, 0xb2, 0x92, 0x52, 0x74, 0x9e, 0xea, 0xbf, 0xf6, 0x5f, 0x95, 0x9a, 0x53, 0x70, + 0xaa, 0x21, 0xd2, 0x48, 0x36, 0x89, 0x61, 0x89, 0x7e, 0x75, 0xbe, 0xfd, 0xdc, 0x82, 0xf3, 0x9a, + 0x94, 0x52, 0x61, 0x11, 0x5a, 0x6c, 0xe6, 0xa3, 0xa2, 0xd2, 0x7a, 0xbf, 0xf5, 0x79, 0xbd, 0x36, + 0xc5, 0x31, 0xaa, 0xe5, 0x75, 0x54, 0x8c, 0x95, 0x4b, 0x15, 0x14, 0xa4, 0xe4, 0x2f, 0x3f, 0xfa, + 0x56, 0x46, 0xe9, 0x6a, 0x90, 0xe8, 0x81, 0xa3, 0x29, 0xaa, 0x54, 0x5d, 0xc3, 0xa6, 0x93, 0x8e, + 0x47, 0x4b, 0xe2, 0xe2, 0x53, 0x65, 0x32, 0x49, 0x2d, 0x0b, 0x33, 0x14, 0xa1, 0x55, 0x29, 0x07, + 0xf1, 0xa9, 0x4b, 0xa6, 0xb2, 0x9c, 0x99, 0xec, 0x69, 0x15, 0x59, 0x96, 0x97, 0x29, 0x22, 0x8a, + 0xe5, 0x72, 0x58, 0x4d, 0xd5, 0xe3, 0x25, 0x2c, 0x94, 0xe5, 0x65, 0x59, 0x2a, 0xa6, 0x4a, 0x55, + 0x95, 0xd1, 0x2e, 0x87, 0x55, 0xe0, 0xcd, 0xc9, 0x2b, 0x34, 0xad, 0x15, 0x0a, 0x93, 0x99, 0x92, + 0x68, 0x69, 0x1f, 0x4a, 0x15, 0x22, 0x21, 0x15, 0x97, 0xd5, 0x4d, 0x03, 0x14, 0xcc, 0x87, 0x46, + 0x92, 0xdd, 0x1b, 0xf0, 0x9c, 0x51, 0x23, 0x54, 0xc1, 0x75, 0x22, 0x5a, 0x99, 0x24, 0x93, 0x58, + 0x62, 0x19, 0x2b, 0xb4, 0x68, 0x95, 0x5a, 0x4e, 0x06, 0x68, 0x63, 0x1a, 0x15, 0x48, 0xb2, 0x32, + 0x4c, 0x97, 0xa2, 0x5f, 0x29, 0x94, 0xc8, 0xa2, 0x55, 0xaa, 0x4c, 0x2b, 0x2f, 0x94, 0xc2, 0x6a, + 0xb3, 0x54, 0xa9, 0x2a, 0x74, 0x50, 0xe3, 0x49, 0xd2, 0xc8, 0xb2, 0x55, 0x03, 0x00, 0xca, 0x62, + 0x32, 0xcc, 0x8f, 0xff, 0xff, 0x84, 0x92, 0x61, 0x32, 0x8b, 0xc1, 0x92, 0xb6, 0xc6, 0xa5, 0x4a, + 0xa5, 0x52, 0x57, 0x83, 0xbf, 0x29, 0x8d, 0x56, 0x45, 0x45, 0x95, 0x52, 0xa5, 0x36, 0x9c, 0x25, + 0x4a, 0xa3, 0x9e, 0xb2, 0x7f, 0xf2, 0x75, 0x0d, 0x56, 0xa2, 0xa9, 0xaa, 0xa2, 0x64, 0x68, 0x58, + 0x22, 0xf8, 0xe2, 0xa5, 0x94, 0x99, 0x14, 0x97, 0x4b, 0x04, 0x82, 0xe2, 0x62, 0x06, 0x61, 0xaa, + 0x4d, 0x69, 0x62, 0x31, 0xaa, 0xe4, 0xb2, 0x94, 0x88, 0x55, 0xa7, 0x55, 0x52, 0xd2, 0xd4, 0xaa, + 0x92, 0x5a, 0x26, 0x57, 0x21, 0x52, +}; +const uint8_t* const _I_HappyFlipper_128x64[] = {_I_HappyFlipper_128x64_0}; + +const uint8_t _I_TanksSplashScreen_128x64_0[] = { + 0x01, 0x00, 0xbe, 0x03, 0xc9, 0x49, 0x29, 0x20, 0x2f, 0x01, 0x78, 0x0b, 0xc0, 0x59, 0xa4, 0xa5, + 0x65, 0x65, 0x55, 0x5d, 0x55, 0x53, 0x53, 0x52, 0xd2, 0x82, 0x16, 0x53, 0x51, 0xd1, 0x51, 0x31, + 0x31, 0x29, 0x2d, 0x2b, 0x29, 0x28, 0x04, 0x36, 0x2a, 0x29, 0x29, 0xa9, 0x69, 0x29, 0x19, 0x19, + 0x15, 0x14, 0x10, 0xba, 0x41, 0x54, 0x82, 0x12, 0x13, 0x21, 0xa1, 0x61, 0x21, 0x11, 0x19, 0x15, + 0x13, 0x11, 0x10, 0x04, 0x2d, 0x4a, 0xb4, 0x4f, 0xdd, 0x25, 0xeb, 0xd1, 0xff, 0xd3, 0x94, 0x6a, + 0x93, 0xcc, 0xb9, 0x64, 0xa3, 0xc5, 0x1a, 0xc9, 0x56, 0xb3, 0xb2, 0x4f, 0x4e, 0x4b, 0x15, 0x4a, + 0x95, 0x4a, 0xb5, 0x3a, 0x8d, 0x26, 0x88, 0x10, 0x35, 0x4a, 0x64, 0x99, 0x28, 0x34, 0xbd, 0x26, + 0x42, 0x85, 0x49, 0x85, 0x49, 0x65, 0x39, 0x4d, 0x25, 0x43, 0x13, 0xea, 0xa5, 0xa8, 0xc7, 0x2d, + 0x12, 0xaa, 0xd5, 0x32, 0x56, 0x4a, 0x56, 0x4a, 0x53, 0x49, 0x93, 0x54, 0xa5, 0x99, 0x19, 0x24, + 0xa6, 0xbf, 0xa9, 0x91, 0x62, 0x64, 0x98, 0x9c, 0x84, 0x92, 0x85, 0x92, 0xa4, 0x79, 0x3d, 0x66, + 0x51, 0xa9, 0x4f, 0xf2, 0x59, 0x4a, 0x95, 0x26, 0xa5, 0x25, 0xa4, 0xe5, 0x64, 0x99, 0x59, 0xcf, + 0xfe, 0x8c, 0x32, 0x1f, 0x48, 0x88, 0x64, 0xa0, 0xe1, 0x40, 0x0c, 0xa2, 0xab, 0x22, 0xd1, 0x6a, + 0x33, 0xfa, 0x4d, 0xe7, 0xea, 0xab, 0x42, 0xa4, 0x65, 0x69, 0x12, 0x99, 0x16, 0x97, 0x11, 0x54, + 0xba, 0x6e, 0xbf, 0xdf, 0xfa, 0x7e, 0x73, 0x07, 0x94, 0x95, 0x48, 0x72, 0xb5, 0x2d, 0x4d, 0x23, + 0x4b, 0x22, 0xf2, 0xcf, 0xe4, 0xf4, 0x99, 0x45, 0x5e, 0x6f, 0xa9, 0xa2, 0x52, 0xa4, 0x58, 0x4a, + 0x24, 0x4a, 0x25, 0x49, 0xc4, 0x7b, 0x0d, 0xe3, 0xf2, 0x92, 0xd2, 0xe2, 0x5a, 0x9c, 0xa5, 0x52, + 0x53, 0x29, 0xd2, 0xe9, 0x32, 0x95, 0x5c, 0x57, 0xfb, 0xdf, 0x9f, 0xa4, 0x64, 0xe9, 0x54, 0x59, + 0x4e, 0x26, 0xa4, 0xa4, 0x10, 0x90, 0xc8, 0xaa, 0xb9, 0x2b, 0xe5, 0x4b, 0x2b, 0x4a, 0x92, 0x44, + 0xa8, 0x4c, 0x31, 0xca, 0x52, 0x28, 0xb4, 0x2c, 0xa6, 0x52, 0x49, 0x41, 0x84, 0xc8, 0xb2, 0x44, + 0xb1, 0xaa, 0x49, 0x62, 0xb2, 0x19, 0x2b, 0xbc, 0x69, 0x28, 0x51, 0xcd, 0x55, 0xea, 0xb2, 0x7a, + 0x2b, 0x24, 0x69, 0x14, 0x9a, 0x55, 0x26, 0x23, 0x95, 0x89, 0x6a, 0xb2, 0x5e, 0x4d, 0x3e, 0x22, + 0x97, 0xa8, 0x92, 0x54, 0x65, 0x42, 0x41, 0x64, 0x8d, 0x23, 0x48, 0xd6, 0x1f, 0x4d, 0x36, 0xa5, + 0x53, 0x89, 0x87, 0x07, 0x22, 0x3f, 0x19, 0x20, 0xb4, 0x75, 0x35, 0x2a, 0x47, 0xd7, 0x3f, 0x94, + 0x93, 0xe9, 0x2a, 0x59, 0x11, 0x68, 0xc8, 0xb5, 0x31, 0x2c, 0x94, 0x4b, 0x4f, 0x52, 0xf9, 0x4b, + 0xef, 0xb4, 0xfa, 0x4c, 0x52, 0x54, 0xcb, 0x1a, 0xa4, 0x4b, 0xa9, 0x24, 0xd2, 0x52, 0xb1, 0x59, + 0x4e, 0x26, 0x5a, 0xb1, 0x2a, 0xd2, 0xb8, 0x05, 0xe6, 0x3a, 0x48, 0x4d, 0x26, 0x93, 0x92, 0xaa, + 0x7f, 0x77, 0x32, 0x2f, 0xeb, 0xe0, 0x70, 0x80, 0x81, 0x79, 0x91, 0x48, 0x64, 0xb2, 0x19, 0x36, + 0x4e, 0xa9, 0xa2, 0xaf, 0xe5, 0x65, 0x52, 0xac, 0x4c, 0x90, 0x4a, 0x2b, 0x51, 0xaa, 0xd2, 0xa9, + 0x72, 0x4c, 0x4e, 0x52, 0xe9, 0x22, 0xa8, 0xe8, 0x43, 0xc3, 0x91, 0x41, 0x8e, 0x27, 0x12, 0x77, + 0x1a, 0x50, 0x24, 0x7a, 0x55, 0x4c, 0x55, 0x30, 0x3c, 0x34, 0xa2, 0xf8, 0x9e, 0x86, 0x89, 0xa7, + 0xd4, 0x67, 0x34, 0x96, 0x41, 0x60, 0x9c, 0x07, 0x23, 0x92, 0xd5, 0x19, 0x44, 0xb0, 0x39, 0x68, + 0x95, 0xde, 0x13, 0x79, 0x60, 0x0c, 0xa8, 0x20, 0x32, 0x5c, 0x8d, 0x02, 0x93, 0x09, 0xc4, 0xf4, + 0xb4, 0xbc, 0xaa, 0x97, 0x7b, 0x17, 0x12, 0xa0, 0xca, 0x9b, 0xe3, 0x4a, 0xd6, 0xe4, 0xaa, 0x75, + 0x29, 0x24, 0x5f, 0xa7, 0xab, 0xd3, 0x55, 0xb4, 0x9a, 0xca, 0x24, 0x87, 0x2a, 0xf6, 0x1a, 0xa1, + 0x88, 0x69, 0x57, 0xe9, 0x77, 0xfa, 0xab, 0xbd, 0x25, 0x8e, 0xa7, 0x55, 0xa0, 0x2c, 0x0e, 0x26, + 0x93, 0x29, 0xc8, 0xfa, 0x3d, 0xb9, 0xfb, 0x27, 0xd5, 0x9c, 0x31, 0x28, 0x95, 0x37, 0x08, 0xc7, + 0x1e, 0xad, 0x3e, 0x55, 0x25, 0xdf, 0xc9, 0x37, 0x3c, 0x49, 0xfd, 0x27, 0x49, 0xa4, 0xc8, 0x4a, + 0xa9, 0x2e, 0x21, 0xf9, 0x51, 0x40, 0x85, 0x3e, 0xd4, 0xdd, 0xf2, 0xae, 0x01, 0x92, 0xd4, 0x62, + 0x54, 0x9c, 0x9e, 0x52, 0xbf, 0x92, 0xe5, 0x7f, 0xc0, 0x63, 0xd6, 0x90, 0x44, 0x62, 0xba, 0xa1, + 0x10, 0xaf, 0xc6, 0x93, 0xf4, 0x92, 0x7c, 0x76, 0x7a, 0xf9, 0x26, 0xb7, 0x57, 0xa4, 0xd4, 0x51, + 0x21, 0x55, 0x5a, 0xa3, 0x80, 0x72, 0x5f, 0xb9, 0xfd, 0x16, 0x9d, 0x49, 0xcd, 0x42, 0xe5, 0x34, + 0x59, 0x4d, 0x45, 0x7c, 0x39, 0x31, 0xe0, 0xd3, 0xaf, 0xfa, 0xa8, 0xf6, 0xaa, 0xc9, 0x6e, 0x38, + 0x8d, 0x16, 0x55, 0x52, 0xe9, 0x69, 0x29, 0x59, 0x29, 0xd6, 0x26, 0x21, 0x59, 0x88, 0xfa, 0x73, + 0x43, 0xc1, 0x18, 0x0c, 0x97, 0xc9, 0x4a, 0xca, 0x69, 0x3c, 0x92, 0xfd, 0x2f, 0xd3, 0x4b, 0xd2, + 0xb2, 0x42, 0x65, 0x32, 0x5d, 0x4d, 0x23, 0x93, 0x28, 0x90, 0x90, 0x46, 0xde, 0x23, 0x1a, 0xff, + 0x13, 0x53, 0x52, 0xa2, 0x8c, 0x47, 0x2b, 0x52, 0xc9, 0xea, 0xb2, 0x5a, 0x4c, 0xb0, 0x8c, 0x7e, + 0x9a, 0x9a, 0x2c, 0x97, 0x25, 0x90, 0xd2, 0x48, 0x2a, 0x84, 0xe1, 0xaa, 0x4a, 0x65, 0xa2, 0x31, + 0x89, 0x62, 0xe5, 0x35, 0x77, 0x90, 0xc5, 0x69, 0xbc, 0x91, 0x50, 0xc8, 0xb2, 0x5d, 0x4b, 0xfe, + 0x57, 0x49, 0xfe, 0xab, 0xd3, 0x0b, 0xe3, 0xa2, 0xc9, 0x54, 0xb2, 0x32, 0x47, 0x28, 0xe9, 0x74, + 0x92, 0x82, 0x20, 0xe3, 0x36, 0x45, 0xf1, 0xca, 0x49, 0x2c, 0x8c, 0x21, 0xa4, 0x52, 0x7d, 0xdc, + 0xbb, 0xff, 0xf6, 0x4f, 0x2f, 0xb4, 0x8d, 0x06, 0x53, 0x49, 0xe4, 0xd1, 0x09, 0xe3, 0xd2, 0x99, + 0xe4, 0x68, 0x59, 0x2d, 0x76, 0xa1, 0x50, 0x34, 0xa0, 0x30, 0xcc, 0xb5, 0x31, 0x29, 0x2f, 0xd3, + 0x8b, 0x7f, 0xd4, 0xc9, 0x3f, 0xb1, 0x7d, 0x24, 0xae, 0xa2, 0x3a, 0x1b, 0x23, 0x84, 0x69, 0x5f, + 0xdd, 0xd6, 0x92, 0x17, 0xc9, 0xc5, 0xdf, 0x22, 0xba, 0x69, 0x25, 0x4b, 0x53, 0xa9, 0x52, 0x8f, + 0x43, 0x7f, 0x14, 0xa4, 0xd5, 0xaa, 0xb5, 0x2a, 0x16, 0x86, 0x39, 0x49, 0x20, 0x89, 0x38, 0x43, + 0xc3, 0x5f, 0xf2, 0xea, 0xa4, 0x96, 0x2d, 0x5d, 0x6a, 0x95, 0x71, 0x95, 0x26, 0x86, 0xc9, 0x92, + 0xfe, 0x7f, 0xe5, 0x7b, 0xed, 0xd5, 0x3e, 0x93, 0xab, 0xed, 0x45, 0x39, 0x59, 0x20, 0xe8, 0xea, + 0x6a, 0x9f, 0xff, 0xfd, 0x0a, 0x93, 0x45, 0xa9, 0x6a, 0xb5, 0x52, 0xad, 0x4e, 0x4b, 0x4b, 0x90, + 0x2f, 0x8e, 0x17, 0xfb, 0x4f, 0xca, 0x7d, 0x3f, 0xf2, 0x5c, 0x8f, 0xfe, 0xbf, 0x22, 0x94, 0xc2, + 0xe4, 0xb2, 0xab, 0xa6, 0xca, 0x24, 0xd8, 0x1c, 0x93, 0x10, 0x47, 0x45, 0x54, 0xa1, 0x5b, 0x29, + 0x55, 0x10, +}; +const uint8_t* const _I_TanksSplashScreen_128x64[] = {_I_TanksSplashScreen_128x64_0}; + +const uint8_t _I_enemy_down_0[] = { + 0x00, + 0x21, + 0x3f, + 0x33, + 0x3f, + 0x2d, + 0x0c, +}; +const uint8_t* const _I_enemy_down[] = {_I_enemy_down_0}; + +const uint8_t _I_enemy_left_0[] = { + 0x00, + 0x3e, + 0x1c, + 0x17, + 0x17, + 0x1c, + 0x3e, +}; +const uint8_t* const _I_enemy_left[] = {_I_enemy_left_0}; + +const uint8_t _I_enemy_right_0[] = { + 0x00, + 0x1f, + 0x0e, + 0x3a, + 0x3a, + 0x0e, + 0x1f, +}; +const uint8_t* const _I_enemy_right[] = {_I_enemy_right_0}; + +const uint8_t _I_enemy_up_0[] = { + 0x00, + 0x0c, + 0x2d, + 0x3f, + 0x33, + 0x3f, + 0x21, +}; +const uint8_t* const _I_enemy_up[] = {_I_enemy_up_0}; + +const uint8_t _I_projectile_down_0[] = { + 0x00, + 0x00, + 0x12, + 0x1e, + 0x1e, + 0x0c, + 0x00, +}; +const uint8_t* const _I_projectile_down[] = {_I_projectile_down_0}; + +const uint8_t _I_projectile_left_0[] = { + 0x00, + 0x00, + 0x1c, + 0x0e, + 0x0e, + 0x1c, + 0x00, +}; +const uint8_t* const _I_projectile_left[] = {_I_projectile_left_0}; + +const uint8_t _I_projectile_right_0[] = { + 0x00, + 0x00, + 0x0e, + 0x1c, + 0x1c, + 0x0e, + 0x00, +}; +const uint8_t* const _I_projectile_right[] = {_I_projectile_right_0}; + +const uint8_t _I_projectile_up_0[] = { + 0x00, + 0x00, + 0x0c, + 0x1e, + 0x1e, + 0x12, + 0x00, +}; +const uint8_t* const _I_projectile_up[] = {_I_projectile_up_0}; + +const uint8_t _I_tank_base_0[] = { + 0x00, + 0x21, + 0x33, + 0x0c, + 0x1e, + 0x0c, + 0x3f, +}; +const uint8_t* const _I_tank_base[] = {_I_tank_base_0}; + +const uint8_t _I_tank_down_0[] = { + 0x00, + 0x21, + 0x3f, + 0x3f, + 0x3f, + 0x2d, + 0x0c, +}; +const uint8_t* const _I_tank_down[] = {_I_tank_down_0}; + +const uint8_t _I_tank_explosion_0[] = { + 0x00, + 0x1a, + 0x25, + 0x16, + 0x29, + 0x15, + 0x2a, +}; +const uint8_t* const _I_tank_explosion[] = {_I_tank_explosion_0}; + +const uint8_t _I_tank_hedgehog_0[] = { + 0x00, + 0x21, + 0x12, + 0x0c, + 0x0c, + 0x12, + 0x21, +}; +const uint8_t* const _I_tank_hedgehog[] = {_I_tank_hedgehog_0}; + +const uint8_t _I_tank_left_0[] = { + 0x00, + 0x3e, + 0x1c, + 0x1f, + 0x1f, + 0x1c, + 0x3e, +}; +const uint8_t* const _I_tank_left[] = {_I_tank_left_0}; + +const uint8_t _I_tank_right_0[] = { + 0x00, + 0x1f, + 0x0e, + 0x3e, + 0x3e, + 0x0e, + 0x1f, +}; +const uint8_t* const _I_tank_right[] = {_I_tank_right_0}; + +const uint8_t _I_tank_stone_0[] = { + 0x00, + 0x12, + 0x3f, + 0x1e, + 0x1e, + 0x3f, + 0x12, +}; +const uint8_t* const _I_tank_stone[] = {_I_tank_stone_0}; + +const uint8_t _I_tank_up_0[] = { + 0x00, + 0x0c, + 0x2d, + 0x3f, + 0x3f, + 0x3f, + 0x21, +}; +const uint8_t* const _I_tank_up[] = {_I_tank_up_0}; + +const uint8_t _I_tank_wall_0[] = { + 0x00, + 0x3f, + 0x2d, + 0x3f, + 0x3f, + 0x2d, + 0x3f, +}; +const uint8_t* const _I_tank_wall[] = {_I_tank_wall_0}; + +const Icon I_HappyFlipper_128x64 = { + .width = 128, + .height = 64, + .frame_count = 1, + .frame_rate = 0, + .frames = _I_HappyFlipper_128x64}; +const Icon I_TanksSplashScreen_128x64 = { + .width = 128, + .height = 64, + .frame_count = 1, + .frame_rate = 0, + .frames = _I_TanksSplashScreen_128x64}; +const Icon I_enemy_down = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_enemy_down}; +const Icon I_enemy_left = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_enemy_left}; +const Icon I_enemy_right = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_enemy_right}; +const Icon I_enemy_up = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_enemy_up}; +const Icon I_projectile_down = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_projectile_down}; +const Icon I_projectile_left = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_projectile_left}; +const Icon I_projectile_right = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_projectile_right}; +const Icon I_projectile_up = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_projectile_up}; +const Icon I_tank_base = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_base}; +const Icon I_tank_down = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_down}; +const Icon I_tank_explosion = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_explosion}; +const Icon I_tank_hedgehog = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_hedgehog}; +const Icon I_tank_left = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_left}; +const Icon I_tank_right = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_right}; +const Icon I_tank_stone = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_stone}; +const Icon I_tank_up = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_up}; +const Icon I_tank_wall = + {.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_tank_wall}; + +typedef struct { + // +-----x + // | + // | + // y + uint8_t x; + uint8_t y; +} Point; + +typedef enum { + CellEmpty = 1, + CellWall, + CellExplosion, + CellTankUp, + CellTankRight, + CellTankDown, + CellTankLeft, + CellEnemyUp, + CellEnemyRight, + CellEnemyDown, + CellEnemyLeft, + CellProjectileUp, + CellProjectileRight, + CellProjectileDown, + CellProjectileLeft, +} GameCellState; + +typedef enum { + MenuStateSingleMode, + MenuStateCooperativeServerMode, + MenuStateCooperativeClientMode, +} MenuState; + +typedef enum { + GameStateMenu, + GameStateSingle, + GameStateCooperativeServer, + GameStateCooperativeClient, + GameStateGameOver, +} GameState; + +typedef enum { + DirectionUp, + DirectionRight, + DirectionDown, + DirectionLeft, +} Direction; + +typedef enum { + ModeSingle, + ModeCooperative, +} Mode; + +typedef struct { + Point coordinates; + Direction direction; + bool explosion; + bool is_p1; + bool is_p2; +} ProjectileState; + +typedef struct { + Point coordinates; + uint16_t score; + uint8_t lives; + Direction direction; + bool moving; + bool shooting; + bool live; + uint8_t cooldown; + uint8_t respawn_cooldown; +} PlayerState; + +typedef struct { + // char map[FIELD_WIDTH][FIELD_HEIGHT]; + char thisMap[16][11]; + Point team_one_respawn_points[3]; + Point team_two_respawn_points[3]; + Mode mode; + bool server; + GameState state; + MenuState menu_state; + ProjectileState* projectiles[100]; + PlayerState* bots[6]; + uint8_t enemies_left; + uint8_t enemies_live; + uint8_t enemies_respawn_cooldown; + uint8_t received; + uint8_t sent; + PlayerState* p1; + PlayerState* p2; +} TanksState; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} TanksEvent; + +typedef enum { + GoesUp, + GoesRight, + GoesDown, + GoesLeft, + Shoots, +} ClientAction; + +//char map[FIELD_HEIGHT][FIELD_WIDTH + 1] = { +char thisMap[11][16 + 1] = { + "* - * -", + " - - = ", + " - - 2", + "1 = - -- ", + "-- = - -- ", + "a-1 = - = 2", + "-- = - -- ", + "1 = - -- ", + " - - 2", + " - - = ", + "* - * -", +}; + +static void tanks_game_write_cell(unsigned char* data, int8_t x, int8_t y, GameCellState cell) { + uint8_t index = y * 16 + x; + data[index] = cell; + // if (x % 2) { + // data[index] = (data[index] & 0b00001111) + (cell << 4); + // } else { + // data[index] = (data[index] & 0b0000) + cell; + // } +} + +// Enum with < 16 items => 4 bits in cell, 2 cells in byte +unsigned char* tanks_game_serialize(const TanksState* const tanks_state) { + static unsigned char result[11 * 16 + 1]; + + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + result[(y * FIELD_WIDTH + x)] = 0; + + GameCellState cell = CellEmpty; + + if(tanks_state->thisMap[x][y] == '-') { + cell = CellWall; + + tanks_game_write_cell(result, x, y, cell); + } + } + } + + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + GameCellState cell = CellEmpty; + + switch(tanks_state->bots[i]->direction) { + case DirectionUp: + cell = CellEnemyUp; + break; + case DirectionDown: + cell = CellEnemyDown; + break; + case DirectionRight: + cell = CellEnemyRight; + break; + case DirectionLeft: + cell = CellEnemyLeft; + break; + } + + tanks_game_write_cell( + result, + tanks_state->bots[i]->coordinates.x, + tanks_state->bots[i]->coordinates.y, + cell); + } + } + + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + GameCellState cell = CellEmpty; + + switch(tanks_state->projectiles[x]->direction) { + case DirectionUp: + cell = CellProjectileUp; + break; + case DirectionDown: + cell = CellProjectileDown; + break; + case DirectionRight: + cell = CellProjectileRight; + break; + case DirectionLeft: + cell = CellProjectileLeft; + break; + } + + tanks_game_write_cell( + result, + tanks_state->projectiles[x]->coordinates.x, + tanks_state->projectiles[x]->coordinates.y, + cell); + } + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live) { + GameCellState cell = CellEmpty; + + switch(tanks_state->p1->direction) { + case DirectionUp: + cell = CellTankUp; + break; + case DirectionDown: + cell = CellTankDown; + break; + case DirectionRight: + cell = CellTankRight; + break; + case DirectionLeft: + cell = CellTankLeft; + break; + } + + tanks_game_write_cell( + result, tanks_state->p1->coordinates.x, tanks_state->p1->coordinates.y, cell); + } + + if(tanks_state->p2 != NULL && tanks_state->p2->live) { + GameCellState cell = CellEmpty; + + switch(tanks_state->p2->direction) { + case DirectionUp: + cell = CellTankUp; + break; + case DirectionDown: + cell = CellTankDown; + break; + case DirectionRight: + cell = CellTankRight; + break; + case DirectionLeft: + cell = CellTankLeft; + break; + } + + tanks_game_write_cell( + result, tanks_state->p2->coordinates.x, tanks_state->p2->coordinates.y, cell); + } + + return result; +} + +static void + tanks_game_render_cell(GameCellState cell, uint8_t x, uint8_t y, Canvas* const canvas) { + const Icon* icon; + + if(cell == CellEmpty) { + return; + } + + switch(cell) { + case CellWall: + icon = &I_tank_wall; + break; + case CellExplosion: + icon = &I_tank_explosion; + break; + case CellTankUp: + icon = &I_tank_up; + break; + case CellTankRight: + icon = &I_tank_right; + break; + case CellTankDown: + icon = &I_tank_down; + break; + case CellTankLeft: + icon = &I_tank_left; + break; + case CellEnemyUp: + icon = &I_enemy_up; + break; + case CellEnemyRight: + icon = &I_enemy_right; + break; + case CellEnemyDown: + icon = &I_enemy_down; + break; + case CellEnemyLeft: + icon = &I_enemy_left; + break; + case CellProjectileUp: + icon = &I_projectile_up; + break; + case CellProjectileRight: + icon = &I_projectile_right; + break; + case CellProjectileDown: + icon = &I_projectile_down; + break; + case CellProjectileLeft: + icon = &I_projectile_left; + break; + default: + return; + break; + } + + canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, icon); +} + +static void tanks_game_render_constant_cells(Canvas* const canvas) { + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + char cell = thisMap[y][x]; + + if(cell == '=') { + canvas_draw_icon( + canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_stone); + continue; + } + + if(cell == '*') { + canvas_draw_icon( + canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_hedgehog); + continue; + } + + if(cell == 'a') { + canvas_draw_icon( + canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_base); + continue; + } + } + } +} + +void tanks_game_deserialize_and_write_to_state(unsigned char* data, TanksState* const tanks_state) { + for(uint8_t i = 0; i < 11 * 16; i++) { + uint8_t x = i % 16; + uint8_t y = i / 16; + tanks_state->thisMap[x][y] = data[i]; + } +} + +void tanks_game_deserialize_and_render(unsigned char* data, Canvas* const canvas) { + //for (uint8_t i = 0; i < 11 * 16 / 2; i++) { + for(uint8_t i = 0; i < 11 * 16; i++) { + char cell = data[i]; + uint8_t x = i % 16; // One line (16 cells) = 8 bytes + uint8_t y = i / 16; + + // GameCellState first = cell >> 4; + // GameCellState second = cell & 0b00001111; + + tanks_game_render_cell(cell, x, y, canvas); + // tanks_game_render_cell(second, x + 1, y, canvas); + } + + tanks_game_render_constant_cells(canvas); +} + +static void tanks_game_render_callback(Canvas* const canvas, void* ctx) { + const TanksState* tanks_state = acquire_mutex((ValueMutex*)ctx, 25); + if(tanks_state == NULL) { + return; + } + + // Before the function is called, the state is set with the canvas_reset(canvas) + if(tanks_state->state == GameStateMenu) { + canvas_draw_icon(canvas, 0, 0, &I_TanksSplashScreen_128x64); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 124, 10, AlignRight, AlignBottom, "Single"); + canvas_draw_str_aligned(canvas, 124, 25, AlignRight, AlignBottom, "Co-op S"); + canvas_draw_str_aligned(canvas, 124, 40, AlignRight, AlignBottom, "Co-op C"); + + switch(tanks_state->menu_state) { + case MenuStateSingleMode: + canvas_draw_icon(canvas, 74, 3, &I_tank_right); + break; + case MenuStateCooperativeServerMode: + canvas_draw_icon(canvas, 74, 18, &I_tank_right); + break; + case MenuStateCooperativeClientMode: + canvas_draw_icon(canvas, 74, 33, &I_tank_right); + break; + } + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + release_mutex((ValueMutex*)ctx, tanks_state); + return; + } + + // Field right border + canvas_draw_box(canvas, FIELD_WIDTH * CELL_LENGTH_PIXELS, 0, 2, SCREEN_HEIGHT_TANKS); + + // Cooperative client + if(tanks_state->mode == ModeCooperative && !tanks_state->server) { + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + tanks_game_render_cell(tanks_state->thisMap[x][y], x, y, canvas); + } + } + + tanks_game_render_constant_cells(canvas); + + release_mutex((ValueMutex*)ctx, tanks_state); + return; + } + + // Player + // Point coordinates = tanks_state->p1->coordinates; + // const Icon *icon; + // switch (tanks_state->p1->direction) { + // case DirectionUp: + // icon = &I_tank_up; + // break; + // case DirectionDown: + // icon = &I_tank_down; + // break; + // case DirectionRight: + // icon = &I_tank_right; + // break; + // case DirectionLeft: + // icon = &I_tank_left; + // break; + // default: + // icon = &I_tank_explosion; + // } + + // if (tanks_state->p1->live) { + // canvas_draw_icon(canvas, coordinates.x * CELL_LENGTH_PIXELS, coordinates.y * CELL_LENGTH_PIXELS - 1, icon); + // } + // + // for(int8_t x = 0; x < FIELD_WIDTH; x++) { + // for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + // switch (tanks_state->thisMap[x][y]) { + // case '-': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_wall); + // break; + // + // case '=': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_stone); + // break; + // + // case '*': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_hedgehog); + // break; + // + // case 'a': + // canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_base); + // break; + // } + // } + // } + + // for ( + // uint8_t i = 0; + // i < 6; + // i++ + // ) { + // if (tanks_state->bots[i] != NULL) { + // const Icon *icon; + // + // switch(tanks_state->bots[i]->direction) { + // case DirectionUp: + // icon = &I_enemy_up; + // break; + // case DirectionDown: + // icon = &I_enemy_down; + // break; + // case DirectionRight: + // icon = &I_enemy_right; + // break; + // case DirectionLeft: + // icon = &I_enemy_left; + // break; + // default: + // icon = &I_tank_explosion; + // } + // + // canvas_draw_icon( + // canvas, + // tanks_state->bots[i]->coordinates.x * CELL_LENGTH_PIXELS, + // tanks_state->bots[i]->coordinates.y * CELL_LENGTH_PIXELS - 1, + // icon); + // } + // } + + // for(int8_t x = 0; x < 100; x++) { + // if (tanks_state->projectiles[x] != NULL) { + // ProjectileState *projectile = tanks_state->projectiles[x]; + // + // if (projectile->explosion) { + // canvas_draw_icon( + // canvas, + // projectile->coordinates.x * CELL_LENGTH_PIXELS, + // projectile->coordinates.y * CELL_LENGTH_PIXELS - 1, + // &I_tank_explosion); + // continue; + // } + // + // const Icon *icon; + // + // switch(projectile->direction) { + // case DirectionUp: + // icon = &I_projectile_up; + // break; + // case DirectionDown: + // icon = &I_projectile_down; + // break; + // case DirectionRight: + // icon = &I_projectile_right; + // break; + // case DirectionLeft: + // icon = &I_projectile_left; + // break; + // default: + // icon = &I_tank_explosion; + // } + // + // canvas_draw_icon( + // canvas, + // projectile->coordinates.x * CELL_LENGTH_PIXELS, + // projectile->coordinates.y * CELL_LENGTH_PIXELS - 1, + // icon); + // } + // } + + // Info + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + char buffer1[13]; + snprintf(buffer1, sizeof(buffer1), "live: %u", tanks_state->enemies_live); + canvas_draw_str_aligned(canvas, 127, 8, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "left: %u", tanks_state->enemies_left); + canvas_draw_str_aligned(canvas, 127, 18, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "p1 l: %u", tanks_state->p1->lives); + canvas_draw_str_aligned(canvas, 127, 28, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "p1 s: %u", tanks_state->p1->score); + canvas_draw_str_aligned(canvas, 127, 38, AlignRight, AlignBottom, buffer1); + + if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2) { + snprintf(buffer1, sizeof(buffer1), "rec: %u", tanks_state->received); + canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1); + + snprintf(buffer1, sizeof(buffer1), "snt: %u", tanks_state->sent); + canvas_draw_str_aligned(canvas, 127, 58, AlignRight, AlignBottom, buffer1); + // snprintf(buffer1, sizeof(buffer1), "p2 l: %u", tanks_state->p2->lives); + // canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1); + // + // snprintf(buffer1, sizeof(buffer1), "p2 s: %u", tanks_state->p2->score); + // canvas_draw_str_aligned(canvas, 127, 58, AlignRight, AlignBottom, buffer1); + } + + if(tanks_state->state == GameStateCooperativeClient) { + snprintf(buffer1, sizeof(buffer1), "rec: %u", tanks_state->received); + canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1); + } + + // Game Over banner + if(tanks_state->state == GameStateGameOver) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 34, 20, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 34, 20, 62, 24); + canvas_set_font(canvas, FontPrimary); + + if(tanks_state->enemies_left == 0 && tanks_state->enemies_live == 0) { + canvas_draw_str(canvas, 37, 31, "You win!"); + } else { + canvas_draw_str(canvas, 37, 31, "Game Over"); + } + + canvas_set_font(canvas, FontSecondary); + char buffer[13]; + snprintf(buffer, sizeof(buffer), "Score: %u", tanks_state->p1->score); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer); + } + + // TEST start + unsigned char* data = tanks_game_serialize(tanks_state); + tanks_game_deserialize_and_render(data, canvas); + // TEST enf + + release_mutex((ValueMutex*)ctx, tanks_state); +} + +static void tanks_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TanksEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void tanks_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + TanksEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +static bool tanks_get_cell_is_free(TanksState* const tanks_state, Point point) { + // Tiles + if(tanks_state->thisMap[point.x][point.y] != ' ') { + return false; + } + + // Projectiles + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + if(tanks_state->projectiles[x]->coordinates.x == point.x && + tanks_state->projectiles[x]->coordinates.y == point.y) { + return false; + } + } + } + + // Player 1 + if(tanks_state->p1 != NULL) { + if(tanks_state->p1->coordinates.x == point.x && + tanks_state->p1->coordinates.y == point.y) { + return false; + } + } + + // Player 2 + if(tanks_state->p2 != NULL) { + if(tanks_state->p2->coordinates.x == point.x && + tanks_state->p2->coordinates.y == point.y) { + return false; + } + } + + // Bots + for(int8_t x = 0; x < 6; x++) { + if(tanks_state->bots[x] != NULL) { + if(tanks_state->bots[x]->coordinates.x == point.x && + tanks_state->bots[x]->coordinates.y == point.y) { + return false; + } + } + } + + return true; +} + +static uint8_t tanks_get_random_free_respawn_point_index( + TanksState* const tanks_state, + Point respawn_points[3]) { + uint8_t first = rand() % 3; + int8_t add = rand() % 2 ? +1 : -1; + int8_t second = first + add; + uint8_t third; + + if(second == 4) { + second = 0; + } else if(second == -1) { + second = 3; + } + + for(uint8_t i = 0; i < 3; i++) { + if(i != first && i != second) { + third = i; + } + } + + if(tanks_get_cell_is_free(tanks_state, respawn_points[first])) { + return first; + } + + if(tanks_get_cell_is_free(tanks_state, respawn_points[second])) { + return second; + } + + if(tanks_get_cell_is_free(tanks_state, respawn_points[third])) { + return third; + } + + return -1; +} + +static void tanks_game_init_game(TanksState* const tanks_state, GameState type) { + srand(DWT->CYCCNT); + + tanks_state->state = type; + + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + free(tanks_state->projectiles[x]); + tanks_state->projectiles[x] = NULL; + } + } + + int8_t team_one_respawn_points_counter = 0; + int8_t team_two_respawn_points_counter = 0; + + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + tanks_state->thisMap[x][y] = ' '; + + if(thisMap[y][x] == '1') { + Point respawn = {x, y}; + tanks_state->team_one_respawn_points[team_one_respawn_points_counter++] = respawn; + } + + if(thisMap[y][x] == '2') { + Point respawn = {x, y}; + tanks_state->team_two_respawn_points[team_two_respawn_points_counter++] = respawn; + } + + if(thisMap[y][x] == '-') { + tanks_state->thisMap[x][y] = '-'; + } + + if(thisMap[y][x] == '=') { + tanks_state->thisMap[x][y] = '='; + } + + if(thisMap[y][x] == '*') { + tanks_state->thisMap[x][y] = '*'; + } + + if(thisMap[y][x] == 'a') { + tanks_state->thisMap[x][y] = 'a'; + } + } + } + + uint8_t index1 = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + Point c = { + tanks_state->team_one_respawn_points[index1].x, + tanks_state->team_one_respawn_points[index1].y}; + + PlayerState p1 = { + c, + 0, + 4, + DirectionRight, + 0, + 0, + 1, + SHOT_COOLDOWN, + PLAYER_RESPAWN_COOLDOWN, + }; + + PlayerState* p1_state = malloc(sizeof(PlayerState)); + *p1_state = p1; + + tanks_state->p1 = p1_state; + + if(type == GameStateCooperativeServer) { + int8_t index2 = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + Point c = { + tanks_state->team_one_respawn_points[index2].x, + tanks_state->team_one_respawn_points[index2].y}; + + PlayerState p2 = { + c, + 0, + 4, + DirectionRight, + 0, + 0, + 1, + SHOT_COOLDOWN, + PLAYER_RESPAWN_COOLDOWN, + }; + + PlayerState* p2_state = malloc(sizeof(PlayerState)); + *p2_state = p2; + + tanks_state->p2 = p2_state; + } + + tanks_state->enemies_left = 5; + tanks_state->enemies_live = 0; + tanks_state->enemies_respawn_cooldown = RESPAWN_COOLDOWN; + tanks_state->received = 0; + tanks_state->sent = 0; + + if(type == GameStateCooperativeClient) { + for(int8_t x = 0; x < FIELD_WIDTH; x++) { + for(int8_t y = 0; y < FIELD_HEIGHT; y++) { + tanks_state->thisMap[x][y] = CellEmpty; + } + } + } +} + +static bool + tanks_game_collision(Point const next_step, bool shoot, TanksState const* const tanks_state) { + if(next_step.x < 0 || next_step.y < 0) { + return true; + } + + if(next_step.x >= FIELD_WIDTH || next_step.y >= FIELD_HEIGHT) { + return true; + } + + char tile = tanks_state->thisMap[next_step.x][next_step.y]; + + if(tile == '*' && !shoot) { + return true; + } + + if(tile == '-' || tile == '=' || tile == 'a') { + return true; + } + + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + if(tanks_state->bots[i]->coordinates.x == next_step.x && + tanks_state->bots[i]->coordinates.y == next_step.y) { + return true; + } + } + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live && + tanks_state->p1->coordinates.x == next_step.x && + tanks_state->p1->coordinates.y == next_step.y) { + return true; + } + + if(tanks_state->p2 != NULL && tanks_state->p2->live && + tanks_state->p2->coordinates.x == next_step.x && + tanks_state->p2->coordinates.y == next_step.y) { + return true; + } + + return false; +} + +static Point tanks_game_get_next_step(Point coordinates, Direction direction) { + Point next_step = {coordinates.x, coordinates.y}; + + switch(direction) { + // +-----x + // | + // | + // y + case DirectionUp: + next_step.y--; + break; + case DirectionRight: + next_step.x++; + break; + case DirectionDown: + next_step.y++; + break; + case DirectionLeft: + next_step.x--; + break; + } + return next_step; +} + +static uint8_t tanks_game_get_free_projectile_index(TanksState* const tanks_state) { + uint8_t freeProjectileIndex; + for(freeProjectileIndex = 0; freeProjectileIndex < 100; freeProjectileIndex++) { + if(tanks_state->projectiles[freeProjectileIndex] == NULL) { + return freeProjectileIndex; + } + } + + return 0; +} + +static void tanks_game_shoot( + TanksState* const tanks_state, + PlayerState* tank_state, + bool is_p1, + bool is_p2) { + tank_state->cooldown = SHOT_COOLDOWN; + + uint8_t freeProjectileIndex = tanks_game_get_free_projectile_index(tanks_state); + + ProjectileState* projectile_state = malloc(sizeof(ProjectileState)); + Point next_step = tanks_game_get_next_step(tank_state->coordinates, tank_state->direction); + + projectile_state->direction = tank_state->direction; + projectile_state->coordinates = next_step; + projectile_state->is_p1 = is_p1; + projectile_state->is_p2 = is_p2; + + bool crush = tanks_game_collision(projectile_state->coordinates, true, tanks_state); + projectile_state->explosion = crush; + + tanks_state->projectiles[freeProjectileIndex] = projectile_state; +} + +static void tanks_game_process_game_step(TanksState* const tanks_state) { + if(tanks_state->state == GameStateMenu) { + return; + } + + if(tanks_state->enemies_left == 0 && tanks_state->enemies_live == 0) { + tanks_state->state = GameStateGameOver; + } + + if(!tanks_state->p1->live && tanks_state->p1->lives == 0) { + tanks_state->state = GameStateGameOver; + } + + if(tanks_state->state == GameStateGameOver) { + return; + } + + if(tanks_state->p1 != NULL) { + if(!tanks_state->p1->live && tanks_state->p1->respawn_cooldown > 0) { + tanks_state->p1->respawn_cooldown--; + } + } + + // Player 1 spawn + if(tanks_state->p1 && !tanks_state->p1->live && tanks_state->p1->lives > 0) { + int8_t index = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + + if(index != -1) { + Point point = tanks_state->team_one_respawn_points[index]; + Point c = {point.x, point.y}; + tanks_state->p1->coordinates = c; + tanks_state->p1->live = true; + tanks_state->p1->direction = DirectionRight; + tanks_state->p1->cooldown = SHOT_COOLDOWN; + tanks_state->p1->respawn_cooldown = SHOT_COOLDOWN; + } + } + + // Player 2 spawn + if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2 && + !tanks_state->p2->live && tanks_state->p2->lives > 0) { + int8_t index = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_one_respawn_points); + + if(index != -1) { + Point point = tanks_state->team_one_respawn_points[index]; + Point c = {point.x, point.y}; + tanks_state->p2->coordinates = c; + tanks_state->p2->live = true; + tanks_state->p2->direction = DirectionRight; + tanks_state->p2->cooldown = SHOT_COOLDOWN; + tanks_state->p2->respawn_cooldown = SHOT_COOLDOWN; + } + } + + // Bot turn + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + PlayerState* bot = tanks_state->bots[i]; + if(bot->cooldown) { + bot->cooldown--; + } + + // Rotate + if(rand() % 3 == 0) { + bot->direction = (rand() % 4); + } + + // Move + if(rand() % 2 == 0) { + Point next_step = tanks_game_get_next_step(bot->coordinates, bot->direction); + bool crush = tanks_game_collision(next_step, false, tanks_state); + + if(!crush) { + bot->coordinates = next_step; + } + } + + // Shoot + if(bot->cooldown == 0 && rand() % 3 != 0) { + tanks_game_shoot(tanks_state, bot, false, false); + } + } + } + + // Bot spawn + if(tanks_state->enemies_respawn_cooldown) { + tanks_state->enemies_respawn_cooldown--; + } + + if(tanks_state->enemies_left > 0 && tanks_state->enemies_live <= 4 && + tanks_state->enemies_respawn_cooldown == 0) { + int8_t index = tanks_get_random_free_respawn_point_index( + tanks_state, tanks_state->team_two_respawn_points); + + if(index != -1) { + tanks_state->enemies_left--; + tanks_state->enemies_live++; + tanks_state->enemies_respawn_cooldown = RESPAWN_COOLDOWN; + Point point = tanks_state->team_two_respawn_points[index]; + + Point c = {point.x, point.y}; + + PlayerState bot = { + c, + 0, + 0, + DirectionLeft, + 0, + 0, + 1, + SHOT_COOLDOWN, + PLAYER_RESPAWN_COOLDOWN, + }; + + uint8_t freeEnemyIndex; + for(freeEnemyIndex = 0; freeEnemyIndex < 6; freeEnemyIndex++) { + if(tanks_state->bots[freeEnemyIndex] == NULL) { + break; + } + } + + PlayerState* bot_state = malloc(sizeof(PlayerState)); + *bot_state = bot; + + tanks_state->bots[freeEnemyIndex] = bot_state; + } + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live && tanks_state->p1->moving) { + Point next_step = + tanks_game_get_next_step(tanks_state->p1->coordinates, tanks_state->p1->direction); + bool crush = tanks_game_collision(next_step, false, tanks_state); + + if(!crush) { + tanks_state->p1->coordinates = next_step; + } + } + + // Player 2 spawn + if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2 && + tanks_state->p2->live && tanks_state->p2->moving) { + Point next_step = + tanks_game_get_next_step(tanks_state->p2->coordinates, tanks_state->p2->direction); + bool crush = tanks_game_collision(next_step, false, tanks_state); + + if(!crush) { + tanks_state->p2->coordinates = next_step; + } + } + + for(int8_t x = 0; x < 100; x++) { + if(tanks_state->projectiles[x] != NULL) { + ProjectileState* projectile = tanks_state->projectiles[x]; + Point c = projectile->coordinates; + + if(projectile->explosion) { + // Break a wall + if(tanks_state->thisMap[c.x][c.y] == '-') { + tanks_state->thisMap[c.x][c.y] = ' '; + } + + // Kill a bot + for(uint8_t i = 0; i < 6; i++) { + if(tanks_state->bots[i] != NULL) { + if(tanks_state->bots[i]->coordinates.x == c.x && + tanks_state->bots[i]->coordinates.y == c.y) { + if(projectile->is_p1) { + tanks_state->p1->score++; + } + + if(projectile->is_p2) { + tanks_state->p2->score++; + } + + // No friendly fire + if(projectile->is_p1 || projectile->is_p2) { + tanks_state->enemies_live--; + free(tanks_state->bots[i]); + tanks_state->bots[i] = NULL; + } + } + } + } + + // Destroy the flag + if(tanks_state->thisMap[c.x][c.y] == 'a') { + tanks_state->state = GameStateGameOver; + return; + } + + // Kill a player 1 + if(tanks_state->p1 != NULL) { + if(tanks_state->p1->live && tanks_state->p1->coordinates.x == c.x && + tanks_state->p1->coordinates.y == c.y) { + tanks_state->p1->live = false; + tanks_state->p1->lives--; + tanks_state->p1->respawn_cooldown = PLAYER_RESPAWN_COOLDOWN; + } + } + + // Kill a player 2 + if(tanks_state->p2 != NULL) { + if(tanks_state->p2->live && tanks_state->p2->coordinates.x == c.x && + tanks_state->p2->coordinates.y == c.y) { + tanks_state->p2->live = false; + tanks_state->p2->lives--; + tanks_state->p2->respawn_cooldown = PLAYER_RESPAWN_COOLDOWN; + } + } + + // Delete projectile + free(tanks_state->projectiles[x]); + tanks_state->projectiles[x] = NULL; + continue; + } + + Point next_step = + tanks_game_get_next_step(projectile->coordinates, projectile->direction); + bool crush = tanks_game_collision(next_step, true, tanks_state); + projectile->coordinates = next_step; + + if(crush) { + projectile->explosion = true; + } + } + } + + if(tanks_state->p1->cooldown > 0) { + tanks_state->p1->cooldown--; + } + + if(tanks_state->p2 != NULL && tanks_state->p2->cooldown > 0) { + tanks_state->p2->cooldown--; + } + + if(tanks_state->p1 != NULL && tanks_state->p1->live && tanks_state->p1->shooting && + tanks_state->p1->cooldown == 0) { + tanks_game_shoot(tanks_state, tanks_state->p1, true, false); + } + + tanks_state->p1->moving = false; + tanks_state->p1->shooting = false; + + if(tanks_state->p2 != NULL) { + tanks_state->p2->moving = false; + tanks_state->p2->shooting = false; + } +} + +int32_t tanks_game_app(void* p) { + srand(DWT->CYCCNT); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TanksEvent)); + + TanksState* tanks_state = malloc(sizeof(TanksState)); + + tanks_state->state = GameStateMenu; + tanks_state->menu_state = MenuStateSingleMode; + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tanks_state, sizeof(TanksState))) { + FURI_LOG_E("Tanks", "cannot create mutex\r\n"); + furi_message_queue_free(event_queue); + free(tanks_state); + return 255; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, tanks_game_render_callback, &state_mutex); + view_port_input_callback_set(view_port, tanks_game_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(tanks_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + TanksEvent event; + + // Initialize network thing. + uint32_t frequency = 433920000; + size_t message_max_len = 180; + uint8_t incomingMessage[180] = {0}; + SubGhzTxRxWorker* subghz_txrx = subghz_tx_rx_worker_alloc(); + subghz_tx_rx_worker_start(subghz_txrx, frequency); + furi_hal_power_suppress_charge_enter(); + + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + TanksState* tanks_state = (TanksState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + if(tanks_state->state == GameStateMenu) { + if(tanks_state->menu_state == MenuStateCooperativeServerMode) { + tanks_state->menu_state = MenuStateSingleMode; + } else if(tanks_state->menu_state == MenuStateCooperativeClientMode) { + tanks_state->menu_state = MenuStateCooperativeServerMode; + } + } else if(tanks_state->state == GameStateCooperativeClient) { + string_t goesUp; + char arr[2]; + arr[0] = GoesUp; + arr[1] = 0; + string_set(goesUp, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)string_get_cstr(goesUp), + strlen(string_get_cstr(goesUp))); + + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionUp; + } + break; + case InputKeyDown: + if(tanks_state->state == GameStateMenu) { + if(tanks_state->menu_state == MenuStateSingleMode) { + tanks_state->menu_state = MenuStateCooperativeServerMode; + } else if(tanks_state->menu_state == MenuStateCooperativeServerMode) { + tanks_state->menu_state = MenuStateCooperativeClientMode; + } + } else if(tanks_state->state == GameStateCooperativeClient) { + string_t goesDown; + char arr[2]; + arr[0] = GoesDown; + arr[1] = 0; + string_set(goesDown, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)string_get_cstr(goesDown), + strlen(string_get_cstr(goesDown))); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionDown; + } + break; + case InputKeyRight: + if(tanks_state->state == GameStateCooperativeClient) { + string_t goesRight; + char arr[2]; + arr[0] = GoesRight; + arr[1] = 0; + string_set(goesRight, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)string_get_cstr(goesRight), + strlen(string_get_cstr(goesRight))); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionRight; + } + break; + case InputKeyLeft: + if(tanks_state->state == GameStateCooperativeClient) { + string_t goesLeft; + char arr[2]; + arr[0] = GoesLeft; + arr[1] = 0; + string_set(goesLeft, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)string_get_cstr(goesLeft), + strlen(string_get_cstr(goesLeft))); + } else { + tanks_state->p1->moving = true; + tanks_state->p1->direction = DirectionLeft; + } + break; + case InputKeyOk: + if(tanks_state->state == GameStateMenu) { + if(tanks_state->menu_state == MenuStateSingleMode) { + tanks_state->server = true; + tanks_game_init_game(tanks_state, GameStateSingle); + break; + } else if(tanks_state->menu_state == MenuStateCooperativeServerMode) { + tanks_state->server = true; + tanks_game_init_game(tanks_state, GameStateCooperativeServer); + break; + } else if(tanks_state->menu_state == MenuStateCooperativeClientMode) { + tanks_state->server = false; + tanks_game_init_game(tanks_state, GameStateCooperativeClient); + break; + } + } else if(tanks_state->state == GameStateGameOver) { + tanks_game_init_game(tanks_state, tanks_state->state); + } else if(tanks_state->state == GameStateCooperativeClient) { + string_t shoots; + char arr[2]; + arr[0] = Shoots; + arr[1] = 0; + string_set(shoots, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)string_get_cstr(shoots), + strlen(string_get_cstr(shoots))); + } else { + tanks_state->p1->shooting = true; + } + break; + case InputKeyBack: + processing = false; + break; + } + } + } else if(event.type == EventTypeTick) { + if(tanks_state->state == GameStateCooperativeServer) { + if(subghz_tx_rx_worker_available(subghz_txrx)) { + memset(incomingMessage, 0x00, message_max_len); + subghz_tx_rx_worker_read(subghz_txrx, incomingMessage, message_max_len); + + if(incomingMessage != NULL) { + tanks_state->received++; + + switch(incomingMessage[0]) { + case GoesUp: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionUp; + break; + case GoesRight: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionRight; + break; + case GoesDown: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionDown; + break; + case GoesLeft: + tanks_state->p2->moving = true; + tanks_state->p2->direction = DirectionLeft; + break; + case Shoots: + tanks_state->p2->shooting = true; + break; + default: + break; + } + } + } + + tanks_game_process_game_step(tanks_state); + + string_t serializedData; + unsigned char* data = tanks_game_serialize(tanks_state); + char arr[11 * 16 + 1]; + + for(uint8_t i = 0; i < 11 * 16; i++) { + arr[i] = data[i]; + } + + arr[11 * 16] = 0; + + string_set(serializedData, (char*)&arr); + + subghz_tx_rx_worker_write( + subghz_txrx, + (uint8_t*)string_get_cstr(serializedData), + strlen(string_get_cstr(serializedData))); + + tanks_state->sent++; + } else if(tanks_state->state == GameStateSingle) { + tanks_game_process_game_step(tanks_state); + } else if(tanks_state->state == GameStateCooperativeClient) { + if(subghz_tx_rx_worker_available(subghz_txrx)) { + memset(incomingMessage, 0x00, message_max_len); + subghz_tx_rx_worker_read(subghz_txrx, incomingMessage, message_max_len); + + tanks_state->received++; + + tanks_game_deserialize_and_write_to_state( + (unsigned char*)incomingMessage, tanks_state); + } + } + } + } else { + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, tanks_state); + furi_delay_ms(1); + } + + furi_delay_ms(10); + furi_hal_power_suppress_charge_exit(); + + if(subghz_tx_rx_worker_is_running(subghz_txrx)) { + subghz_tx_rx_worker_stop(subghz_txrx); + subghz_tx_rx_worker_free(subghz_txrx); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + + if(tanks_state->p1 != NULL) { + free(tanks_state->p1); + } + + if(tanks_state->p2 != NULL) { + free(tanks_state->p2); + } + + free(tanks_state); + + return 0; +} diff --git a/applications/plugins/videopoker/application.fam b/applications/plugins/videopoker/application.fam new file mode 100644 index 000000000..7885dadfc --- /dev/null +++ b/applications/plugins/videopoker/application.fam @@ -0,0 +1,12 @@ +App( + appid="GAME_VideoPoker", + name="Video Poker", + apptype=FlipperAppType.EXTERNAL, + entry_point="video_poker_app", + cdefines=["APP_VIDEOPOKER_GAME"], + requires=["gui"], + stack_size=2 * 1024, + order=270, + fap_icon="pokerIcon.png", + fap_category="Games", +) diff --git a/applications/plugins/videopoker/poker.c b/applications/plugins/videopoker/poker.c new file mode 100644 index 000000000..664057b43 --- /dev/null +++ b/applications/plugins/videopoker/poker.c @@ -0,0 +1,818 @@ +#include +#include +#include +#include +#include +#include +#include +#include "assets_icons.h" +#include + +/* Core game logic from +https://github.com/Yaoir/VideoPoker-C +*/ + +/* KNOWN BUGS +This has been converted from a standalone PC console app to flipper +All of the original input/output handing code has been ripped out +Original code also used TONS of defines and everything was a global. +As is, it does what I wanted and doesn't seem to have major issues, so that's pretty good. +Game logic is handled during input and this is a bit of a mess of nested ifs. +Sometimes duplicate cards will show up. there is a function to test this. I should use it better. + +*/ + +#define TAG "Video Poker" + +static void Shake(void) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_single_vibro); + furi_record_close(RECORD_NOTIFICATION); +} + +typedef struct { + int index; /* cards value, minus 1 */ + char* sym; /* text appearance */ + int suit; /* card's suit (see just below) */ + int gone; /* true if it's been dealt */ + int held; /* for hand */ +} PokerPlayer_card; + +typedef struct { + FuriMutex** model_mutex; + FuriMessageQueue* event_queue; + ViewPort* view_port; + Gui* gui; + PokerPlayer_card hand[5]; + PokerPlayer_card shand[5]; + PokerPlayer_card deck[52]; + int GameType; /* What rules are we using */ + int held[5]; + int score; + int highscore; + int pot; + int GameState; + int selected; + int bet; + int minbet; +} PokerPlayer; + +/* GameState +0=Splash/help, OK button (later on up/down for rules or settings) +1=cards down, betting enabled, left/right to change bet, OK to confirm +2=first hand, holding enabled, left/right to pick card, OK to hold/unhold card, down to confirm +3=second hand, only confirm to claim rewards +4=game over/won +5=WIP saving gamestate +*/ + +/* +#define AllAmerican 0 +#define TensOrBetter 1 +#define BonusPoker 2 +#define DoubleBonus 3 +#define DoubleBonusBonus 4 +#define JacksOrBetter 5 +#define JacksOrBetter95 6 +#define JacksOrBetter86 7 +#define JacksOrBetter85 8 +#define JacksOrBetter75 9 +#define JacksOrBetter65 10 +#define NUMGAMES 11 +*/ +/* + The game in play. Default is Jacks or Better, + which is coded into initialization of static data +*/ +const char* gamenames[11] = { + "All American", + "Tens or Better", + "Bonus Poker", + "Double Bonus", + "Double Bonus Bonus", + "Jacks or Better", + "9/5 Jacks or Better", + "8/6 Jacks or Better", + "8/5 Jacks or Better", + "7/5 Jacks or Better", + "6/5 Jacks or Better"}; + +PokerPlayer_card deck[52] = { + /* index, card name, suit, gone */ + /* Clubs:0 Diamonds:1 Hearts: 2 Spades: 3 */ + {1, "2", 0, 0, 0}, {2, "3", 0, 0, 0}, {3, "4", 0, 0, 0}, {4, "5", 0, 0, 0}, + {5, "6", 0, 0, 0}, {6, "7", 0, 0, 0}, {7, "8", 0, 0, 0}, {8, "9", 0, 0, 0}, + {9, "10", 0, 0, 0}, {10, "J", 0, 0, 0}, {11, "Q", 0, 0, 0}, {12, "K", 0, 0, 0}, + {13, "A", 0, 0, 0}, + + {1, "2", 1, 0, 0}, {2, "3", 1, 0, 0}, {3, "4", 1, 0, 0}, {4, "5", 1, 0, 0}, + {5, "6", 1, 0, 0}, {6, "7", 1, 0, 0}, {7, "8", 1, 0, 0}, {8, "9", 1, 0, 0}, + {9, "10", 1, 0, 0}, {10, "J", 1, 0, 0}, {11, "Q", 1, 0, 0}, {12, "K", 1, 0, 0}, + {13, "A", 1, 0, 0}, + + {1, "2", 2, 0, 0}, {2, "3", 2, 0, 0}, {3, "4", 2, 0, 0}, {4, "5", 2, 0, 0}, + {5, "6", 2, 0, 0}, {6, "7", 2, 0, 0}, {7, "8", 2, 0, 0}, {8, "9", 2, 0, 0}, + {9, "10", 2, 0, 0}, {10, "J", 2, 0, 0}, {11, "Q", 2, 0, 0}, {12, "K", 2, 0, 0}, + {13, "A", 2, 0, 0}, + + {1, "2", 3, 0, 0}, {2, "3", 3, 0, 0}, {3, "4", 3, 0, 0}, {4, "5", 3, 0, 0}, + {5, "6", 3, 0, 0}, {6, "7", 3, 0, 0}, {7, "8", 3, 0, 0}, {8, "9", 3, 0, 0}, + {9, "10", 3, 0, 0}, {10, "J", 3, 0, 0}, {11, "Q", 3, 0, 0}, {12, "K", 3, 0, 0}, + {13, "A", 3, 0, 0}, +}; + +/* +Image Format +0x01 = Compressed +0x00 = Reserved Section +0xa4,0x01 = 0x1a4, or, 420 - the size of the compressed array, minus this header. +Rest of the data is char array output from heatshrink of the original XBM char array. +Calculated Header: 0x01,0x00,0xa4,0x01 +from furi_hal_compress.c: +typedef struct { + uint8_t is_compressed; + uint8_t reserved; + uint16_t compressed_buff_size; +} FuriHalCompressHeader; +*/ + +const uint8_t _I_Splash_128x64_0[] = { + 0x01, 0x00, 0x8a, 0x02, 0x00, 0x78, 0x02, 0x60, 0xe0, 0x54, 0xc0, 0x03, 0x9f, 0xc0, 0x0f, 0x5a, + 0x04, 0x04, 0x1e, 0xdf, 0x08, 0x78, 0x0c, 0x60, 0xc0, 0x21, 0x90, 0x40, 0xa3, 0x00, 0xf5, 0xfe, + 0x61, 0xc1, 0xe9, 0x1e, 0x8e, 0x59, 0xf0, 0x02, 0x24, 0x9f, 0x70, 0xc0, 0x63, 0x03, 0x01, 0x0c, + 0x0b, 0xc1, 0x80, 0xbc, 0x83, 0xd3, 0x3f, 0x63, 0x98, 0x03, 0xcf, 0x88, 0x02, 0x1c, 0x31, 0x5d, + 0x38, 0xf6, 0x19, 0xc0, 0xa0, 0xfc, 0x93, 0x13, 0x12, 0xf0, 0x38, 0x76, 0x08, 0xc7, 0x00, 0x1e, + 0x5e, 0x8b, 0xcc, 0x32, 0x86, 0x0f, 0x4f, 0x0c, 0x80, 0x06, 0x20, 0x72, 0xe4, 0x5e, 0x33, 0xd4, + 0x73, 0xf2, 0x5d, 0xe2, 0x10, 0xef, 0xe6, 0x02, 0x0f, 0x07, 0x84, 0x4c, 0x33, 0xd2, 0x70, 0x79, + 0xd8, 0x2e, 0x11, 0x88, 0x3d, 0xff, 0xc1, 0xc7, 0x83, 0xc4, 0x20, 0x10, 0xc9, 0x18, 0x3d, 0x27, + 0x18, 0x8c, 0x3c, 0xde, 0xe1, 0xe6, 0x87, 0x7e, 0x0c, 0x62, 0x12, 0x10, 0x01, 0xce, 0x31, 0x9c, + 0x39, 0x9c, 0x62, 0x67, 0x0f, 0x83, 0x7f, 0x27, 0xe0, 0xf5, 0x8c, 0x71, 0xbc, 0x31, 0x8c, 0xc4, + 0xe2, 0x1e, 0x62, 0x1e, 0x02, 0xe0, 0x80, 0x05, 0x1c, 0xe1, 0xdc, 0x23, 0x97, 0xc8, 0xe4, 0x5c, + 0x12, 0x50, 0x40, 0x7a, 0x43, 0x38, 0x77, 0x88, 0xf4, 0x36, 0x3d, 0x1f, 0x04, 0x94, 0x20, 0x1e, + 0x98, 0xce, 0x0d, 0xbe, 0x37, 0x0d, 0xcd, 0xbd, 0x0c, 0x7e, 0xbe, 0xce, 0x07, 0x1f, 0xf3, 0xfc, + 0xf8, 0xb2, 0x8d, 0x30, 0x20, 0x53, 0xbe, 0x60, 0x06, 0x03, 0x78, 0xf0, 0x06, 0x4c, 0x1e, 0x34, + 0x10, 0x29, 0x5e, 0x05, 0x0f, 0x00, 0xa0, 0x40, 0x24, 0x20, 0x52, 0x76, 0x88, 0x01, 0xc1, 0xe3, + 0x11, 0x05, 0xc3, 0xe9, 0x20, 0x10, 0x97, 0x01, 0xcf, 0xc1, 0xf2, 0x81, 0x3f, 0xe7, 0xfc, 0x66, + 0xf4, 0x02, 0xf1, 0xc0, 0x3f, 0xdf, 0xf0, 0x30, 0xc6, 0x1e, 0xe5, 0xff, 0x81, 0xf0, 0x3f, 0xe5, + 0xb2, 0x80, 0x7f, 0xc1, 0xe5, 0x1c, 0x03, 0x0f, 0xe3, 0xff, 0x1f, 0xf8, 0x02, 0x48, 0x00, 0x31, + 0xfe, 0x0b, 0xa4, 0x61, 0xcc, 0x62, 0xfc, 0x4f, 0xe3, 0x0f, 0x31, 0x41, 0x0e, 0x02, 0x07, 0x01, + 0x07, 0x8a, 0xb4, 0xa3, 0x84, 0x71, 0x8f, 0xff, 0x20, 0x77, 0x00, 0x78, 0x95, 0x46, 0x06, 0x13, + 0x10, 0x78, 0xef, 0x3f, 0x5f, 0xfc, 0xff, 0xea, 0x07, 0xf0, 0x37, 0x90, 0x3c, 0x78, 0x00, 0xf2, + 0xae, 0x7f, 0x77, 0xf7, 0xaf, 0xec, 0x0f, 0x88, 0x41, 0x1b, 0x06, 0x02, 0x03, 0xc0, 0x02, 0x8c, + 0x08, 0x5c, 0x37, 0xff, 0xa9, 0x3c, 0x7b, 0xcc, 0x52, 0xe0, 0x70, 0x7c, 0x31, 0x89, 0xe4, 0xff, + 0xfb, 0xff, 0xdf, 0x8c, 0x46, 0x03, 0x1f, 0x34, 0x17, 0x83, 0xe1, 0x71, 0x8f, 0x6f, 0xe7, 0xe0, + 0xc1, 0x8f, 0xfd, 0x20, 0x18, 0x65, 0x59, 0x47, 0xaf, 0x9b, 0x8b, 0x9e, 0x6f, 0xe7, 0x1f, 0x16, + 0x0c, 0x3e, 0x3d, 0x00, 0xe4, 0x43, 0xd1, 0xe5, 0x3f, 0xe6, 0x6e, 0xfb, 0x39, 0x88, 0x67, 0xea, + 0xff, 0xc5, 0x22, 0x8f, 0xc0, 0xf0, 0x41, 0x71, 0xe7, 0x76, 0xf9, 0x98, 0x48, 0x64, 0x17, 0x59, + 0x38, 0x05, 0x8f, 0xc0, 0xd0, 0x5f, 0xe8, 0x0f, 0x1a, 0xdb, 0xe6, 0xb1, 0xd1, 0xa0, 0x50, 0x85, + 0x59, 0x7e, 0x16, 0x05, 0x06, 0x80, 0x71, 0xbf, 0xf7, 0x19, 0x85, 0x99, 0x74, 0x6d, 0x31, 0x02, + 0x10, 0x88, 0x7c, 0xdd, 0xdb, 0x84, 0x62, 0x7c, 0x0f, 0x38, 0xe5, 0xf0, 0x1e, 0x97, 0xce, 0x67, + 0xbc, 0xb6, 0x40, 0xa3, 0x98, 0x00, 0xc5, 0x76, 0x53, 0x8c, 0x67, 0x1e, 0x07, 0x0e, 0x63, 0x0a, + 0xe4, 0x9c, 0x62, 0x0f, 0x11, 0x41, 0x95, 0x88, 0x1e, 0x41, 0xd1, 0x8c, 0x49, 0x80, 0xe6, 0x00, + 0x50, 0xb8, 0xa3, 0x07, 0xf1, 0x7f, 0x06, 0xb8, 0x00, 0x61, 0xce, 0xb2, 0x9c, 0x53, 0x01, 0xf3, + 0xf0, 0x55, 0x97, 0xd0, 0x3f, 0x40, 0x03, 0xfd, 0x33, 0xc8, 0x01, 0x71, 0x92, 0x78, 0x80, 0x2f, + 0x80, 0x6f, 0x20, 0x03, 0xff, 0x23, 0xe7, 0x02, 0x02, 0x18, 0x01, 0xa3, 0x91, 0x00, 0x18, 0xc3, + 0x20, 0x91, 0xc0, 0x7c, 0x7f, 0x83, 0x42, 0xaa, 0x1f, 0xe0, 0xbe, 0x60, 0x46, 0xa2, 0x81, 0xe2, + 0x24, 0x21, 0xf9, 0x54, 0x14, 0x18, 0x9e, 0x3f, 0xe4, 0x29, 0x00, 0x12, 0x0e, 0xb0, 0x28, 0x50, + 0x3c, 0x60, 0x50, 0x85, 0xf4, 0x7f, 0xb8, 0x3f, 0xf3, 0xf8, 0x83, 0xe0, 0x00, 0x38, 0x6e, 0x0c, + 0xc3, 0xf2, 0x2f, 0x94, 0x09, 0x07, 0xc7, 0xf7, 0x3f, 0xfe, 0x0d, 0xc4, 0x00, 0xfc, 0x4c, 0x05, + 0x86, 0x15, 0x23, 0x92, 0x03, 0xe7, 0xf9, 0x80, 0x0f, 0x97, 0x52, 0x0c, 0x2f, 0xb1, 0xf8, 0xe3, + 0x01, 0xf3, 0x82, 0x27, 0x8d, 0xe6, 0x41, 0x1c, 0x17, 0xcf, 0xfc, 0x3e, 0x64, 0xf8, +}; +const uint8_t* _I_Splash_128x64[] = {_I_Splash_128x64_0}; +const Icon I_Splash_128x64 = + {.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_Splash_128x64}; + +/* +const uint8_t _I_BadEnd_128x64_0[] = { + 0x01, 0x00, 0xDF, 0x01, 0x00, 0x2c, 0x12, 0x01, 0x02, 0x80, 0x40, 0x70, 0x10, 0x0a, 0x04, 0x02, + 0x41, 0x3e, 0xcf, 0x63, 0xfb, 0xfe, 0xc8, 0x18, 0x3e, 0x6f, 0xdb, 0xfc, 0xf8, 0x3c, 0x60, 0xe0, + 0xf9, 0xb3, 0x6c, 0xf3, 0x3c, 0x1b, 0x6c, 0x18, 0x5f, 0x40, 0xf1, 0xe7, 0xdb, 0xc1, 0xf4, 0x2f, + 0x10, 0x78, 0xdb, 0xbc, 0xdf, 0xf0, 0x04, 0x59, 0x81, 0xe3, 0xc1, 0xb6, 0x41, 0x83, 0xd1, 0x00, + 0xbf, 0x6c, 0xc9, 0xe6, 0x0f, 0x91, 0xf8, 0x9b, 0xcc, 0x1f, 0x20, 0x06, 0x07, 0xf8, 0x3e, 0x0b, + 0x32, 0x00, 0x50, 0x88, 0xc4, 0x20, 0x10, 0x85, 0xfd, 0x03, 0xfc, 0x1f, 0xe0, 0xff, 0x07, 0xf9, + 0x7f, 0xc3, 0xdc, 0x89, 0x10, 0x7d, 0x00, 0x04, 0x1f, 0xe0, 0xfd, 0xfc, 0x40, 0xc1, 0xfb, 0x07, + 0x8e, 0x2f, 0xf3, 0x9f, 0x00, 0xb0, 0x7f, 0x97, 0xf6, 0x0a, 0x11, 0x10, 0xa3, 0xec, 0x10, 0x21, + 0x32, 0x07, 0xd0, 0x18, 0x40, 0xa2, 0x0f, 0xb0, 0x20, 0x81, 0xc4, 0x1f, 0xeb, 0xfa, 0xbf, 0x84, + 0x86, 0x01, 0xc8, 0x5f, 0xd0, 0x0c, 0x81, 0xe2, 0x05, 0x10, 0x7e, 0xdc, 0xc1, 0xf5, 0x01, 0xe0, + 0x41, 0xf2, 0x17, 0xf0, 0x7d, 0xaf, 0x0a, 0x7e, 0x0f, 0xbf, 0x84, 0x7f, 0x21, 0x1f, 0x2b, 0x8e, + 0x3c, 0xbe, 0xd3, 0xf0, 0x78, 0xc4, 0xfa, 0x0b, 0xf2, 0x00, 0x08, 0x81, 0xa1, 0xf3, 0x08, 0x9f, + 0xc0, 0x1e, 0x57, 0x00, 0x7b, 0x60, 0x60, 0x3e, 0x08, 0x4f, 0x80, 0x1e, 0x59, 0x05, 0xc1, 0x03, + 0xce, 0xc3, 0x00, 0x2f, 0x88, 0x3c, 0xe2, 0x10, 0x20, 0x78, 0xbd, 0xc6, 0xff, 0x7c, 0x8c, 0x0e, + 0x48, 0x1e, 0x90, 0x48, 0x47, 0xe2, 0x06, 0x1b, 0x1e, 0x3c, 0x1c, 0x1e, 0x80, 0x01, 0x93, 0xad, + 0x06, 0x1e, 0x0a, 0x28, 0x04, 0x18, 0x1e, 0x81, 0xe1, 0x90, 0x20, 0x46, 0x49, 0xa9, 0x91, 0x3e, + 0x46, 0xf8, 0x0f, 0xac, 0x48, 0x3c, 0xb0, 0x82, 0x52, 0x07, 0xa1, 0x08, 0x43, 0xe5, 0x72, 0x93, + 0x41, 0x7e, 0x01, 0x01, 0x07, 0xc7, 0x8a, 0x97, 0xa9, 0x39, 0x88, 0xa0, 0x7f, 0x00, 0xf2, 0x08, + 0x0c, 0x03, 0x25, 0x54, 0x88, 0xe9, 0x66, 0x11, 0xc2, 0x99, 0x9e, 0x07, 0xff, 0x13, 0x90, 0x7f, + 0xb2, 0x60, 0xf2, 0xaa, 0x79, 0x1b, 0xe5, 0x01, 0xfe, 0x1f, 0xca, 0x41, 0x08, 0xb0, 0xd4, 0xe2, + 0x33, 0x9c, 0x9f, 0x13, 0xff, 0x07, 0xc0, 0x0c, 0x04, 0x1e, 0x54, 0x08, 0x40, 0x64, 0x80, 0x03, + 0x84, 0xff, 0xc0, 0x68, 0x10, 0x0f, 0x80, 0x3d, 0x13, 0xc2, 0x00, 0x28, 0x25, 0xfa, 0x00, 0x0f, + 0x76, 0x60, 0x83, 0xcc, 0x04, 0x20, 0xc1, 0x07, 0xaf, 0xc8, 0x52, 0x52, 0x00, 0x7a, 0x2f, 0xcc, + 0x16, 0x31, 0x30, 0x49, 0x48, 0x17, 0xe5, 0x20, 0xc0, 0x23, 0xce, 0x81, 0x80, 0x88, 0xe6, 0x24, + 0x7c, 0x69, 0xc0, 0xd0, 0xa2, 0x1c, 0x00, 0x79, 0x85, 0x07, 0xe3, 0xa4, 0xb0, 0x4a, 0x64, 0xa0, + 0xf3, 0x57, 0x9d, 0x82, 0x01, 0x80, 0x84, 0x54, 0xb2, 0x19, 0x48, 0x91, 0x90, 0xa2, 0x1f, 0x00, + 0x79, 0x0f, 0x87, 0x80, 0x0f, 0x44, 0x21, 0x03, 0xd0, 0x3e, 0x26, 0x01, 0xa6, 0x44, 0x2c, 0x79, + 0xc0, 0x79, 0xb3, 0xc4, 0xbe, 0x5e, 0x01, 0x08, 0x80, 0x09, 0x56, 0x20, 0x01, 0x98, 0x03, 0xc4, + 0xfe, 0x51, 0x0b, 0xf8, 0x3c, 0xf8, 0x00, 0x32, 0x9c, 0x7f, 0x01, 0xe8, 0x1f, 0x40, 0x21, 0xd7, + 0x81, 0xfb, 0x80, 0xcf, 0x8f, 0x44, 0x1e, 0x7c, 0x88, 0x38, 0x28, 0x70, 0xe4, 0x92, 0xff, 0xc7, + 0xef, 0x1f, 0x80, +}; +const uint8_t* _I_BadEnd_128x64[] = {_I_BadEnd_128x64_0}; +const Icon I_BadEnd_128x64 = + {.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_BadEnd_128x64}; +*/ /* space savings until external apps are possible */ +const uint8_t _I_Hand_12x10_0[] = { + 0x01, 0x00, 0x11, 0x00, 0x8c, 0x40, 0x25, 0x00, 0x16, 0xb4, 0x40, + 0x35, 0x10, 0x1d, 0x5c, 0x1b, 0x5b, 0x0a, 0x84, 0xc2, 0x80, +}; +const uint8_t* _I_Hand_12x10[] = {_I_Hand_12x10_0}; +const Icon I_Hand_12x10 = + {.width = 12, .height = 10, .frame_count = 1, .frame_rate = 0, .frames = _I_Hand_12x10}; + +const uint8_t _I_CardBack_22x35_0[] = { + 0x01, 0x00, 0x23, 0x00, 0xfe, 0x7f, 0xe1, 0xf0, 0x28, 0x04, 0x43, 0xe3, 0xff, + 0x91, 0xea, 0x75, 0x52, 0x6a, 0xad, 0x56, 0x5b, 0xad, 0xd5, 0x4a, 0x80, 0xbe, + 0x05, 0xf0, 0x2f, 0x81, 0x7c, 0x0b, 0x45, 0x32, 0x2c, 0x91, 0x7c, 0x8c, 0xa4, +}; +const uint8_t* _I_CardBack_22x35[] = {_I_CardBack_22x35_0}; +const Icon I_CardBack_22x35 = + {.width = 22, .height = 35, .frame_count = 1, .frame_rate = 0, .frames = _I_CardBack_22x35}; + +//uncompressed but lol +const uint8_t _I_club_7x8_0[] = {0x00, 0x08, 0x1c, 0x1c, 0x6b, 0x7f, 0x36, 0x08, 0x1c}; +const uint8_t* _I_club_7x8[] = {_I_club_7x8_0}; +const Icon I_club_7x8 = + {.width = 7, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_club_7x8}; + +//uncompressed but lol +const uint8_t _I_diamond_7x8_0[] = {0x00, 0x00, 0x08, 0x1c, 0x3e, 0x7f, 0x3e, 0x1c, 0x08}; +const uint8_t* _I_diamond_7x8[] = {_I_diamond_7x8_0}; +const Icon I_diamond_7x8 = + {.width = 7, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_diamond_7x8}; + +//uncompressed +const uint8_t _I_hearts_7x8_0[] = {0x00, 0x00, 0x36, 0x7f, 0x7f, 0x7f, 0x3e, 0x1c, 0x08}; +const uint8_t* _I_hearts_7x8[] = {_I_hearts_7x8_0}; +const Icon I_hearts_7x8 = + {.width = 7, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_hearts_7x8}; + +//uncompressed +const uint8_t _I_spade_7x8_0[] = {0x00, 0x08, 0x1c, 0x3e, 0x7f, 0x7f, 0x36, 0x08, 0x1c}; +const uint8_t* _I_spade_7x8[] = {_I_spade_7x8_0}; +const Icon I_spade_7x8 = + {.width = 7, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_spade_7x8}; + +// They only included Numeric Profont22 glyphs and I don't want to fuck up the font embeds right now sooo.. + +const uint8_t _I_King_7x8_0[] = { + 0x01, 0x00, 0x1a, 0x00, 0xc1, 0xc0, 0xf8, 0x70, 0x1f, 0x1c, 0x02, 0xe7, 0x00, 0x9d, 0xc0, + 0x23, 0xf0, 0x08, 0x78, 0x0c, 0x80, 0xe2, 0x0b, 0x10, 0x78, 0x84, 0xc4, 0x2e, 0x20, 0x01, +}; +const uint8_t* _I_King_7x8[] = {_I_King_7x8_0}; +const Icon I_King_7x8 = + {.width = 10, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_King_7x8}; + +const uint8_t _I_Queen_7x8_0[] = { + 0x01, 0x00, 0x13, 0x00, 0xfe, 0x40, 0x3f, 0xd0, 0x1c, 0x3c, 0x0c, 0x01, + 0x76, 0x38, 0x1f, 0x8e, 0x07, 0xc7, 0x81, 0x85, 0x47, 0xf9, 0x01, +}; +const uint8_t* _I_Queen_7x8[] = {_I_Queen_7x8_0}; +const Icon I_Queen_7x8 = + {.width = 10, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_Queen_7x8}; + +const uint8_t _I_Jack_7x8_0[] = { + 0x01, + 0x00, + 0x0D, + 0x00, + 0x80, + 0x40, + 0xc0, + 0x3a, + 0x00, + 0x5c, + 0x3c, + 0x0f, + 0xfd, + 0x01, + 0xfe, + 0x40, + 0x00, +}; +const uint8_t* _I_Jack_7x8[] = {_I_Jack_7x8_0}; +const Icon I_Jack_7x8 = + {.width = 10, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_Jack_7x8}; + +const uint8_t _I_Ace_7x8_0[] = { + 0x01, 0x00, 0x13, 0x00, 0x98, 0x40, 0x2f, 0x00, 0x12, 0xe6, 0x00, 0x4b, + 0x0d, 0x01, 0x00, 0x8c, 0x0e, 0x07, 0xff, 0x00, 0x90, 0x01, 0xc0, +}; +const uint8_t* _I_Ace_7x8[] = {_I_Ace_7x8_0}; +const Icon I_Ace_7x8 = + {.width = 10, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_Ace_7x8}; + +const uint8_t _I_Ten_7x8_0[] = { + 0x01, 0x00, 0x29, 0x00, 0x86, 0x7f, 0x00, 0x43, 0xfe, 0x80, 0xc3, 0xf0, 0xf0, 0x38, 0x7e, + 0x0e, 0x07, 0x0c, 0xe1, 0x80, 0x87, 0xc6, 0x02, 0x1b, 0x98, 0x08, 0x67, 0x60, 0x21, 0x8f, + 0x80, 0x86, 0x1e, 0x02, 0x18, 0x38, 0x08, 0x43, 0x43, 0x7f, 0x10, 0x0d, 0xfc, 0x4c, 0x20, +}; +const uint8_t* _I_Ten_7x8[] = {_I_Ten_7x8_0}; +const Icon I_Ten_7x8 = + {.width = 18, .height = 14, .frame_count = 1, .frame_rate = 0, .frames = _I_Ten_7x8}; + +const Icon card_suit[4] = {I_diamond_7x8, I_club_7x8, I_hearts_7x8, I_spade_7x8}; + +const Icon card_face[5] = {I_Ten_7x8, I_Jack_7x8, I_Queen_7x8, I_King_7x8, I_Ace_7x8}; + +/* Sanity check: check that there are no duplicate cards in hand */ + +static void playcard(PokerPlayer* app) { + int i, c, crd; + + int hold[5]; + hold[5] = 2; + // int digit; + c = 1; + c++; + c = hold[5]; /* FIX for unused-but-set-variable */ + /* initialize deck */ + for(i = 0; i < 52; i++) deck[i].gone = 0; + + /* initialize hold[] */ + for(i = 0; i < 5; i++) hold[i] = 1; + + /* app->score -= bet; */ + if(app->score > app->highscore) { + app->highscore = app->score; + } /* record high water mark */ + + for(i = 0; i < 5; i++) { + /* find a card not already dealt */ + do crd = random() % 52; + while(deck[crd].gone); + hold[i] = 1; + deck[crd].gone = 1; + if(!app->held[i]) { + app->hand[i] = deck[crd]; + } + } +} + +static int check_for_dupes(PokerPlayer* app) { + int i, j; + + for(i = 0; i < 5; i++) { + for(j = i + 1; j < 5; j++) { + if(app->hand[i].index == app->hand[j].index && app->hand[i].suit == app->hand[j].suit) + return 0; + } + } + + return 1; +} + +/* Functions that recognize winning hands */ + +/* + Flush: + returns 1 if the sorted hand is a flush +*/ + +static int flush(PokerPlayer* app) { + if(app->shand[0].suit == app->shand[1].suit && app->shand[1].suit == app->shand[2].suit && + app->shand[2].suit == app->shand[3].suit && app->shand[3].suit == app->shand[4].suit) + return 1; + + return 0; +} + +/* + Straight: + returns 1 if the sorted hand is a straight +*/ + +static int straight(PokerPlayer* app) { + if(app->shand[1].index == app->shand[0].index + 1 && + app->shand[2].index == app->shand[1].index + 1 && + app->shand[3].index == app->shand[2].index + 1 && + app->shand[4].index == app->shand[3].index + 1) + return 1; + + /* Ace low straight: Ace, 2, 3, 4, 5 */ + + if(app->shand[4].index == 13 && app->shand[0].index == 1 && app->shand[1].index == 2 && + app->shand[2].index == 3 && app->shand[3].index == 4) + return 1; + + return 0; +} + +/* + Four of a kind: + the middle 3 all match, and the first or last matches those +*/ + +static int four(PokerPlayer* app) { + if((app->shand[1].index == app->shand[2].index && + app->shand[2].index == app->shand[3].index) && + (app->shand[0].index == app->shand[2].index || app->shand[4].index == app->shand[2].index)) + return 1; + + return 0; +} + +/* + Full house: + 3 of a kind and a pair +*/ + +static int full(PokerPlayer* app) { + if(app->shand[0].index == app->shand[1].index && + (app->shand[2].index == app->shand[3].index && app->shand[3].index == app->shand[4].index)) + return 1; + + if(app->shand[3].index == app->shand[4].index && + (app->shand[0].index == app->shand[1].index && app->shand[1].index == app->shand[2].index)) + return 1; + + return 0; +} + +/* + Three of a kind: + it can appear 3 ways +*/ + +static int three(PokerPlayer* app) { + if(app->shand[0].index == app->shand[1].index && app->shand[1].index == app->shand[2].index) + return 1; + + if(app->shand[1].index == app->shand[2].index && app->shand[2].index == app->shand[3].index) + return 1; + + if(app->shand[2].index == app->shand[3].index && app->shand[3].index == app->shand[4].index) + return 1; + + return 0; +} + +/* + Two pair: + it can appear in 3 ways +*/ + +static int twopair(PokerPlayer* app) { + if(((app->shand[0].index == app->shand[1].index) && + (app->shand[2].index == app->shand[3].index)) || + ((app->shand[0].index == app->shand[1].index) && + (app->shand[3].index == app->shand[4].index)) || + ((app->shand[1].index == app->shand[2].index) && + (app->shand[3].index == app->shand[4].index))) + return 1; + + return 0; +} + +/* + Two of a kind (pair), jacks or better + or if the game is Tens or Better, 10s or better. +*/ + +static int two(PokerPlayer* app) { + int min = 10; + + if(app->GameType == 1) min = 9; + + if(app->shand[0].index == app->shand[1].index && app->shand[1].index >= min) return 1; + if(app->shand[1].index == app->shand[2].index && app->shand[2].index >= min) return 1; + if(app->shand[2].index == app->shand[3].index && app->shand[3].index >= min) return 1; + if(app->shand[3].index == app->shand[4].index && app->shand[4].index >= min) return 1; + + return 0; +} + +static int paytable[10] = { + 800, /* royal flush: 800 */ + 50, /* straight flush: 50 */ + 25, /* 4 of a kind: 25 */ + 9, /* full house: 9 */ + 6, /* flush: 6 */ + 4, /* straight: 4 */ + 3, /* 3 of a kind: 3 */ + 2, /* two pair: 2 */ + 1, /* jacks or better: 1 */ + 0 /* nothing */ +}; + +static const char* poker_handname[10] = { + "Royal Flush", + "Straight Flush", + "Four of a Kind", + "Full House", + "Flush", + "Straight", + "Three of a Kind", + "Two Pair", + "Pair", + "Nothing", +}; + +static int recognize(PokerPlayer* app) { + int i, j, f = 0; + int min = 100; + PokerPlayer_card tmp[5]; + int st = 0, fl = 0; + + /* Sort hand into sorted hand (app->shand) */ + + /* make copy of hand */ + for(i = 0; i < 5; i++) tmp[i] = app->hand[i]; + + for(i = 0; i < 5; i++) { + /* put lowest card in hand into next place in app->shand */ + + for(j = 0; j < 5; j++) + if(tmp[j].index <= min) { + min = tmp[j].index; + f = j; + } + + app->shand[i] = tmp[f]; + tmp[f].index = 100; /* larger than any card */ + min = 100; + } + + /* royal and straight flushes, strait, and flush */ + + fl = flush(app); + st = straight(app); + + if(st && fl && app->shand[0].index == 9) return 0; + if(st && fl) return 1; + if(four(app)) return 2; + if(full(app)) return 3; + if(fl) return 4; + if(st) return 5; + if(three(app)) return 6; + if(twopair(app)) return 7; + if(two(app)) return 8; + + /* Nothing */ + + return 9; +} + +void poker_draw_callback(Canvas* canvas, void* ctx) { + PokerPlayer* poker_player = ctx; + furi_check(furi_mutex_acquire(poker_player->model_mutex, FuriWaitForever) == FuriStatusOk); + canvas_clear(canvas); + char buffer[30]; + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + /* Magic Begins */ + + /* Status Info */ + if(poker_player->GameState != 0 && poker_player->GameState != 4) { + snprintf(buffer, sizeof(buffer), "%d", poker_player->score); + canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, buffer); + } + + /* Start of game. Cards are face down, bet can be changed */ + if(poker_player->GameState == 1) { + snprintf(buffer, sizeof(buffer), "Bet:%d", poker_player->bet); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, buffer); + snprintf(buffer, sizeof(buffer), "<*> Place Bet"); + canvas_draw_str_aligned(canvas, 0, 9, AlignLeft, AlignTop, buffer); + + for(int i = 0; i < 5; ++i) { + canvas_draw_icon(canvas, 5 + (i * 24), 18, &I_CardBack_22x35); /* 5, 29, 53, 77, 101 */ + } + } + /* Cards are turned face up. Bet is deducted and put in th pot. Show the selector hand */ + else if(poker_player->GameState == 2 || poker_player->GameState == 3) { + snprintf(buffer, sizeof(buffer), "Pot:%d", poker_player->bet); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, buffer); + snprintf(buffer, sizeof(buffer), "<*> Select Hold"); + canvas_draw_str_aligned(canvas, 0, 9, AlignLeft, AlignTop, buffer); + + /* Normal or inverse to indicate selection - cards*/ + for(int i = 0; i < 5; ++i) { + poker_player->held[i] ? canvas_draw_rbox(canvas, 5 + (i * 24), 18, 22, 35, 3) : + canvas_draw_rframe(canvas, 5 + (i * 24), 18, 22, 35, 3); + } + + /* Normal or inverse to indicate selection - card suit and value */ + + for(int i = 0; i < 5; ++i) { + poker_player->held[i] ? canvas_set_color(canvas, ColorWhite) : + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 18 + (i * 24), 43, &card_suit[poker_player->hand[i].suit]); + } + + /* Card Value. Profont_22 does not include letters (AJQK), and "10" is too big. These are bitmaps. */ + canvas_set_font(canvas, FontBigNumbers); + + for(int i = 0; i < 5; ++i) { + poker_player->held[i] ? canvas_set_color(canvas, ColorWhite) : + canvas_set_color(canvas, ColorBlack); + if(poker_player->hand[i].index >= 1 && poker_player->hand[i].index <= 8) { + snprintf(buffer, sizeof(buffer), "%s", poker_player->hand[i].sym); + canvas_draw_str_aligned(canvas, 8 + (i * 24), 21, AlignLeft, AlignTop, buffer); + } else { + if(poker_player->hand[i].index >= 9 && poker_player->hand[i].index <= 13) { + canvas_draw_icon( + canvas, 7 + (i * 24), 21, &card_face[poker_player->hand[i].index - 9]); + } + } + } + + /* Draw the Select hand */ + if(poker_player->GameState == 2) { + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 11 + (poker_player->selected * 24), 54, &I_Hand_12x10); + } + } // GameState 2 or 3 + + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + if(poker_player->GameState == 3) { + snprintf( + buffer, + sizeof(buffer), + "%s:%ix", + poker_handname[recognize(poker_player)], + paytable[recognize(poker_player)]); + canvas_draw_str_aligned(canvas, 63, 61, AlignCenter, AlignBottom, buffer); + } + if(poker_player->GameState == 0) { + canvas_draw_icon(canvas, 0, 0, &I_Splash_128x64); /* Initial launch */ + } + if(poker_player->GameState == 4) { + /* canvas_draw_icon(canvas, 0, 0, &I_BadEnd_128x64); Just Lost The Game - disabled for now :( */ + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + snprintf(buffer, sizeof(buffer), "%s", "You have run out of money!"); + canvas_draw_str_aligned(canvas, 63, 22, AlignCenter, AlignCenter, buffer); + snprintf(buffer, sizeof(buffer), "%s", "At one point, you had"); + canvas_draw_str_aligned(canvas, 63, 32, AlignCenter, AlignCenter, buffer); + snprintf(buffer, sizeof(buffer), "%d dollars", poker_player->highscore); + canvas_draw_str_aligned(canvas, 63, 42, AlignCenter, AlignCenter, buffer); + } + + furi_mutex_release(poker_player->model_mutex); +} + +void poker_input_callback(InputEvent* input, void* ctx) { + PokerPlayer* poker_player = ctx; + furi_message_queue_put(poker_player->event_queue, input, FuriWaitForever); +} + +PokerPlayer* poker_player_alloc() { + PokerPlayer* poker_player = malloc(sizeof(PokerPlayer)); + + poker_player->score = 1000; + poker_player->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + poker_player->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + poker_player->view_port = view_port_alloc(); + poker_player->selected = 0; + poker_player->GameState = 0; + poker_player->bet = 10; + poker_player->minbet = 10; + poker_player->highscore = 1000; + + playcard( + poker_player); /* Get things rolling before the player gets into the game. This will preload the hand. */ + view_port_draw_callback_set(poker_player->view_port, poker_draw_callback, poker_player); + + view_port_input_callback_set(poker_player->view_port, poker_input_callback, poker_player); + + poker_player->gui = furi_record_open(RECORD_GUI); + gui_add_view_port(poker_player->gui, poker_player->view_port, GuiLayerFullscreen); + + return poker_player; +} + +void poker_player_free(PokerPlayer* poker_player) { + view_port_enabled_set(poker_player->view_port, false); + gui_remove_view_port(poker_player->gui, poker_player->view_port); + furi_record_close(RECORD_GUI); + view_port_free(poker_player->view_port); + furi_message_queue_free(poker_player->event_queue); + furi_mutex_free(poker_player->model_mutex); + + free(poker_player); +} + +int32_t video_poker_app(void* p) { + UNUSED(p); + PokerPlayer* poker_player = poker_player_alloc(); + + InputEvent event; + for(bool processing = true; processing;) { + FuriStatus status = furi_message_queue_get(poker_player->event_queue, &event, 100); + furi_check(furi_mutex_acquire(poker_player->model_mutex, FuriWaitForever) == FuriStatusOk); + if(status == FuriStatusOk) { + if(event.type == InputTypePress) { + switch(event.key) { + case InputKeyUp: + Shake(); + break; + case InputKeyDown: + if(poker_player->GameState == 2) { + playcard(poker_player); + if(check_for_dupes(poker_player) == 0) { + playcard(poker_player); + } + + poker_player->GameState = 3; + } + break; + case InputKeyLeft: + if(poker_player->GameState == 1) { + if(poker_player->bet >= poker_player->minbet + 10) { + poker_player->bet -= 10; + } + } else if(poker_player->selected > 0 && poker_player->GameState == 2) { + poker_player->selected--; + } // Move hand left/right + else if(poker_player->selected == 0 && poker_player->GameState == 2) { + poker_player->selected = 4; //wraparound + } + break; + case InputKeyRight: + if(poker_player->GameState == 1) { + if(poker_player->bet < poker_player->score + 10) { + poker_player->bet += 10; + } + } + if(poker_player->selected < 4 && poker_player->GameState == 2) { + poker_player->selected++; + } // Move hand left/right + else if(poker_player->selected == 4 && poker_player->GameState == 2) { + poker_player->selected = 0; //wraparound + } + break; + case InputKeyOk: + /* close splash screen */ + if(poker_player->GameState == 0) { + poker_player->GameState = 1; + } else if(poker_player->GameState == 1) { + /* Pledge bet. Bet is subtracted here. Original code subtracts it during playcard + but playcard is called multiple times which would otherwise subtract bet + multiple times */ + poker_player->score -= poker_player->bet; + poker_player->GameState = 2; + } else if(poker_player->GameState == 2) { + /* Select or un-select card to be held */ + poker_player->held[poker_player->selected] = + !poker_player + ->held[poker_player->selected]; //cursed and bad pls replace + } else if(poker_player->GameState == 3) { + /* accept your fate */ + if(recognize(poker_player) != 9) { + poker_player->score += + poker_player->bet * paytable[recognize(poker_player)]; + } + poker_player->GameState = 1; + if(poker_player->bet > poker_player->score) { + poker_player->bet = poker_player->score; + } + poker_player->held[0] = 0; + poker_player->held[1] = 0; + poker_player->held[2] = 0; + poker_player->held[3] = 0; + poker_player->held[4] = 0; + if(poker_player->score <= 0) { + /* lost the game */ + poker_player->GameState = 4; + } + playcard(poker_player); // shuffle shuffle + } else if(poker_player->GameState == 4) { + /* escape the summary, return to splash */ + Shake(); + poker_player->selected = 0; + poker_player->GameState = 0; + poker_player->bet = 10; + poker_player->minbet = 10; + poker_player->highscore = 1000; + poker_player->score = 1000; + poker_player->GameState = 0; + } + break; + case InputKeyBack: + /* if game is not over, we should store the game state. */ + processing = false; + break; + } + } + } + furi_mutex_release(poker_player->model_mutex); + view_port_update(poker_player->view_port); + } + + poker_player_free(poker_player); + return 0; +} diff --git a/applications/plugins/videopoker/pokerIcon.png b/applications/plugins/videopoker/pokerIcon.png new file mode 100644 index 000000000..485f70e5c Binary files /dev/null and b/applications/plugins/videopoker/pokerIcon.png differ