mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-14 22:28:36 -07:00
more games
This commit is contained in:
@@ -2,9 +2,6 @@
|
||||
|
||||
To Be Added/To Verify Present:
|
||||
|
||||
- [Game of Life (Updated to work by tgxn) (By itsyourbedtime)](https://github.com/tgxn/flipperzero-firmware/blob/dev/applications/game_of_life/game_of_life.c)
|
||||
- [Mandelbrot Set (By Possibly-Matt)](https://github.com/Possibly-Matt/flipperzero-firmware-wPlugins)
|
||||
- [Monty Hall (By DevMilanIan)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/203)
|
||||
- [TAMA P1 (By GMMan)](https://github.com/GMMan/flipperzero-firmware/tree/tama-p1) requires [this rom](https://tinyurl.com/tamap1) IN `tama_p1` on SD as `rom.bin` to make it work.
|
||||
- [Tanks (By Alexgr13)](https://github.com/alexgr13/flipperzero-firmware/tree/fork/dev/applications/tanks-game)
|
||||
- [Video Poker (By PixlEmly)](https://github.com/PixlEmly/flipperzero-firmware-testing/blob/420/applications/VideoPoker/poker.c)
|
||||
@@ -94,6 +91,9 @@ Implemented:
|
||||
- [Dice Roller Including SEX/WAR/8BALL/WEED DICE (By RogueMaster)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/applications/dice/dice.c)
|
||||
- [Flappy Bird (By DroomOne)](https://github.com/DroomOne/flipperzero-firmware/tree/dev/applications/flappy_bird)
|
||||
- Snake [OFW]
|
||||
- [Game of Life (Updated to work by tgxn) (By itsyourbedtime)](https://github.com/tgxn/flipperzero-firmware/blob/dev/applications/game_of_life/game_of_life.c)
|
||||
- [Mandelbrot Set (By Possibly-Matt)](https://github.com/Possibly-Matt/flipperzero-firmware-wPlugins)
|
||||
- [Monty Hall (By DevMilanIan)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/203)
|
||||
- [Tetris (By jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game)
|
||||
- [Tic Tac Toe (By gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
|
||||
- [Zombiez (Reworked By DevMilanIan)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/240) [(Original By Dooskington)](https://github.com/Dooskington/flipperzero-zombiez)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 159 B |
12
applications/plugins/game_of_life/application.fam
Normal file
12
applications/plugins/game_of_life/application.fam
Normal file
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="GAME_GameOfLife",
|
||||
name="Game of Life",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="game_of_life_app",
|
||||
cdefines=["APP_GAMEOFLIFE_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=110,
|
||||
fap_icon="golIcon.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
160
applications/plugins/game_of_life/game_of_life.c
Normal file
160
applications/plugins/game_of_life/game_of_life.c
Normal file
@@ -0,0 +1,160 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define TOTAL_PIXELS SCREEN_WIDTH* SCREEN_HEIGHT
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} AppEvent;
|
||||
|
||||
typedef struct {
|
||||
bool revive;
|
||||
int evo;
|
||||
} State;
|
||||
|
||||
unsigned char new[TOTAL_PIXELS] = {};
|
||||
unsigned char old[TOTAL_PIXELS] = {};
|
||||
unsigned char* fields[] = {new, old};
|
||||
|
||||
int current = 0;
|
||||
int next = 1;
|
||||
|
||||
unsigned char get_cell(int x, int y) {
|
||||
if(x <= 0 || x >= SCREEN_WIDTH) return 0;
|
||||
if(y <= 0 || y >= SCREEN_HEIGHT) return 0;
|
||||
|
||||
int pix = (y * SCREEN_WIDTH) + x;
|
||||
return fields[current][pix];
|
||||
}
|
||||
|
||||
int count_neightbors(int x, int y) {
|
||||
return get_cell(x + 1, y - 1) + get_cell(x - 1, y - 1) + get_cell(x - 1, y + 1) +
|
||||
get_cell(x + 1, y + 1) + get_cell(x + 1, y) + get_cell(x - 1, y) + get_cell(x, y - 1) +
|
||||
get_cell(x, y + 1);
|
||||
}
|
||||
|
||||
static void update_field(State* state) {
|
||||
if(state->revive) {
|
||||
for(int i = 0; i < TOTAL_PIXELS; ++i) {
|
||||
if((random() % 100) == 1) {
|
||||
fields[current][i] = 1;
|
||||
}
|
||||
state->revive = false;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < TOTAL_PIXELS; ++i) {
|
||||
int x = i % SCREEN_WIDTH;
|
||||
int y = (int)(i / SCREEN_WIDTH);
|
||||
|
||||
int v = get_cell(x, y);
|
||||
int n = count_neightbors(x, y);
|
||||
|
||||
if(v && n == 3) {
|
||||
++state->evo;
|
||||
} else if(v && (n < 2 || n > 3)) {
|
||||
++state->evo;
|
||||
v = 0;
|
||||
} else if(!v && n == 3) {
|
||||
++state->evo;
|
||||
v = 1;
|
||||
}
|
||||
|
||||
fields[next][i] = v;
|
||||
}
|
||||
|
||||
next ^= current;
|
||||
current ^= next;
|
||||
next ^= current;
|
||||
|
||||
if(state->evo < TOTAL_PIXELS) {
|
||||
state->revive = true;
|
||||
state->evo = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
AppEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* canvas, void* ctx) {
|
||||
State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25);
|
||||
canvas_clear(canvas);
|
||||
|
||||
for(int i = 0; i < TOTAL_PIXELS; ++i) {
|
||||
int x = i % SCREEN_WIDTH;
|
||||
int y = (int)(i / SCREEN_WIDTH);
|
||||
if(fields[current][i] == 1) canvas_draw_dot(canvas, x, y);
|
||||
}
|
||||
release_mutex((ValueMutex*)ctx, state);
|
||||
}
|
||||
|
||||
int32_t game_of_life_app(void* p) {
|
||||
UNUSED(p);
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(1, sizeof(AppEvent));
|
||||
furi_check(event_queue);
|
||||
|
||||
State* _state = malloc(sizeof(State));
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, _state, sizeof(State))) {
|
||||
printf("cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
AppEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
State* state = (State*)acquire_mutex_block(&state_mutex);
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 25);
|
||||
|
||||
if(event_status == FuriStatusOk && event.type == EventTypeKey &&
|
||||
event.input.type == InputTypePress) {
|
||||
if(event.input.key == InputKeyBack) {
|
||||
// furiac_exit(NULL);
|
||||
processing = false;
|
||||
release_mutex(&state_mutex, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
update_field(state);
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
free(_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
applications/plugins/game_of_life/golIcon.png
Normal file
BIN
applications/plugins/game_of_life/golIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
applications/plugins/mandelbrot/Mandelbrot.png
Normal file
BIN
applications/plugins/mandelbrot/Mandelbrot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
12
applications/plugins/mandelbrot/application.fam
Normal file
12
applications/plugins/mandelbrot/application.fam
Normal file
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="GAME_MandelbrotSet",
|
||||
name="Mandelbrot Set",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="mandelbrot_app",
|
||||
cdefines=["APP_MANDELBROT_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_icon="Mandelbrot.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
170
applications/plugins/mandelbrot/mandelbrot.c
Normal file
170
applications/plugins/mandelbrot/mandelbrot.c
Normal file
@@ -0,0 +1,170 @@
|
||||
#include <furi.h>
|
||||
#include <math.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
float xZoom;
|
||||
float yZoom;
|
||||
float xOffset;
|
||||
float yOffset;
|
||||
float zoom;
|
||||
} PluginState;
|
||||
|
||||
bool mandelbrot_pixel(int x, int y, float xZoom, float yZoom, float xOffset, float yOffset) {
|
||||
float ratio = 128.0 / 64.0;
|
||||
//x0 := scaled x coordinate of pixel (scaled to lie in the Mandelbrot X scale (-2.00, 0.47))
|
||||
float x0 = (((x / 128.0) * ratio * xZoom)) - xOffset;
|
||||
//y0 := scaled y coordinate of pixel (scaled to lie in the Mandelbrot Y scale (-1.12, 1.12))
|
||||
float y0 = ((y / 64.0) * yZoom) - yOffset;
|
||||
float x1 = 0.0;
|
||||
float y1 = 0.0;
|
||||
float x2 = 0.0;
|
||||
float y2 = 0.0;
|
||||
|
||||
int iteration = 0;
|
||||
int max_iteration = 50;
|
||||
|
||||
while(x2 + y2 <= 4.0 && iteration < max_iteration) {
|
||||
y1 = 2.0 * x1 * y1 + y0;
|
||||
x1 = x2 - y2 + x0;
|
||||
x2 = x1 * x1;
|
||||
y2 = y1 * y1;
|
||||
iteration++;
|
||||
}
|
||||
|
||||
if(iteration > 49) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) {
|
||||
return;
|
||||
}
|
||||
// border around the edge of the screen
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
for(int y = 0; y < 64; y++) {
|
||||
for(int x = 0; x < 128; x++) {
|
||||
// did you know if you just pass the indivdiual bits of plugin_state instead of plugin_state
|
||||
// you dont get any compiler warnings :)
|
||||
if(mandelbrot_pixel(
|
||||
x,
|
||||
y,
|
||||
plugin_state->xZoom,
|
||||
plugin_state->yZoom,
|
||||
plugin_state->xOffset,
|
||||
plugin_state->yOffset)) {
|
||||
canvas_draw_dot(canvas, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void mandelbrot_state_init(PluginState* const plugin_state) {
|
||||
plugin_state->xOffset = 3.0;
|
||||
plugin_state->yOffset = 1.12;
|
||||
plugin_state->xZoom = 2.47;
|
||||
plugin_state->yZoom = 2.24;
|
||||
plugin_state->zoom = 1; // this controls the camera when
|
||||
}
|
||||
|
||||
int32_t mandelbrot_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
mandelbrot_state_init(plugin_state);
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
|
||||
FURI_LOG_E("mandelbrot", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)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:
|
||||
plugin_state->yOffset += 0.1 / plugin_state->zoom;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
plugin_state->yOffset += -0.1 / plugin_state->zoom;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
plugin_state->xOffset += -0.1 / plugin_state->zoom;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
plugin_state->xOffset += 0.1 / plugin_state->zoom;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
plugin_state->xZoom -= (2.47 / 10) / plugin_state->zoom;
|
||||
plugin_state->yZoom -= (2.24 / 10) / plugin_state->zoom;
|
||||
// used to make camera control finer the more zoomed you are
|
||||
// this needs to be some sort of curve
|
||||
plugin_state->zoom += 0.15;
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("mandelbrot", "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
applications/plugins/montyhall/Monty.png
Normal file
BIN
applications/plugins/montyhall/Monty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
12
applications/plugins/montyhall/application.fam
Normal file
12
applications/plugins/montyhall/application.fam
Normal file
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="GAME_MontyHall",
|
||||
name="Monty Hall",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="montyhall_game_app",
|
||||
cdefines=["APP_MONTYHALL_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=185,
|
||||
fap_icon="Monty.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
448
applications/plugins/montyhall/monteyhall.c
Normal file
448
applications/plugins/montyhall/monteyhall.c
Normal file
@@ -0,0 +1,448 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/elements.h>
|
||||
#include <gui/icon_i.h>
|
||||
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
|
||||
//AUTHOR: https://github.com/DevMilanIan
|
||||
//I_DoorClosed_22x35 sourced from VideoPoker/poker.c -> I_CardBack22x35
|
||||
//PRs for syntax, formatting, etc can get you listed as a contributor :)
|
||||
|
||||
// CONCEPT: one of three doors will have a car while the other two have only a goat
|
||||
// randomize a winning door each round, let the player choose a first selection
|
||||
// reveal a goat door and allow the player to keep or switch their selection
|
||||
// based on the Monty Hall problem from Let's Make a Deal
|
||||
|
||||
//void draw_goat(Canvas* canvas, int x, int y) { TODO }
|
||||
|
||||
void draw_car(Canvas* canvas, int x, int y) {
|
||||
// x -> leftmost pixel, y -> topmost pixel
|
||||
// could be in another file or a pixel array but idk how to so feel free to PR
|
||||
|
||||
canvas_draw_dot(canvas, x + 1, y + 4);
|
||||
canvas_draw_dot(canvas, x + 1, y + 5);
|
||||
canvas_draw_dot(canvas, x + 2, y + 3);
|
||||
canvas_draw_dot(canvas, x + 2, y + 6);
|
||||
canvas_draw_dot(canvas, x + 3, y + 3);
|
||||
canvas_draw_dot(canvas, x + 3, y + 6);
|
||||
|
||||
canvas_draw_dot(canvas, x + 4, y + 2);
|
||||
canvas_draw_dot(canvas, x + 4, y + 3);
|
||||
canvas_draw_dot(canvas, x + 4, y + 6);
|
||||
canvas_draw_dot(canvas, x + 4, y + 7);
|
||||
|
||||
canvas_draw_dot(canvas, x + 5, y + 1);
|
||||
canvas_draw_dot(canvas, x + 5, y + 2);
|
||||
canvas_draw_dot(canvas, x + 5, y + 3);
|
||||
canvas_draw_dot(canvas, x + 5, y + 5);
|
||||
canvas_draw_dot(canvas, x + 5, y + 8);
|
||||
|
||||
canvas_draw_dot(canvas, x + 6, y);
|
||||
canvas_draw_dot(canvas, x + 6, y + 1);
|
||||
canvas_draw_dot(canvas, x + 6, y + 3);
|
||||
canvas_draw_dot(canvas, x + 6, y + 5);
|
||||
canvas_draw_dot(canvas, x + 6, y + 8);
|
||||
|
||||
canvas_draw_dot(canvas, x + 7, y);
|
||||
canvas_draw_dot(canvas, x + 7, y + 3);
|
||||
canvas_draw_dot(canvas, x + 7, y + 6);
|
||||
canvas_draw_dot(canvas, x + 7, y + 7);
|
||||
|
||||
canvas_draw_dot(canvas, x + 8, y);
|
||||
canvas_draw_dot(canvas, x + 8, y + 3);
|
||||
canvas_draw_dot(canvas, x + 8, y + 6);
|
||||
|
||||
canvas_draw_dot(canvas, x + 9, y);
|
||||
canvas_draw_dot(canvas, x + 9, y + 3);
|
||||
canvas_draw_dot(canvas, x + 9, y + 6);
|
||||
|
||||
canvas_draw_dot(canvas, x + 10, y);
|
||||
canvas_draw_dot(canvas, x + 10, y + 3);
|
||||
canvas_draw_dot(canvas, x + 10, y + 6);
|
||||
|
||||
canvas_draw_dot(canvas, x + 11, y);
|
||||
canvas_draw_dot(canvas, x + 11, y + 1);
|
||||
canvas_draw_dot(canvas, x + 11, y + 3);
|
||||
canvas_draw_dot(canvas, x + 11, y + 6);
|
||||
|
||||
canvas_draw_dot(canvas, x + 12, y + 1);
|
||||
canvas_draw_dot(canvas, x + 12, y + 2);
|
||||
canvas_draw_dot(canvas, x + 12, y + 3);
|
||||
canvas_draw_dot(canvas, x + 12, y + 6);
|
||||
canvas_draw_dot(canvas, x + 12, y + 7);
|
||||
|
||||
canvas_draw_dot(canvas, x + 13, y + 2);
|
||||
canvas_draw_dot(canvas, x + 13, y + 3);
|
||||
canvas_draw_dot(canvas, x + 13, y + 5);
|
||||
canvas_draw_dot(canvas, x + 13, y + 8);
|
||||
|
||||
canvas_draw_dot(canvas, x + 14, y + 1);
|
||||
canvas_draw_dot(canvas, x + 14, y + 2);
|
||||
canvas_draw_dot(canvas, x + 14, y + 5);
|
||||
canvas_draw_dot(canvas, x + 14, y + 8);
|
||||
|
||||
canvas_draw_dot(canvas, x + 15, y);
|
||||
canvas_draw_dot(canvas, x + 15, y + 1);
|
||||
canvas_draw_dot(canvas, x + 15, y + 6);
|
||||
canvas_draw_dot(canvas, x + 15, y + 7);
|
||||
|
||||
canvas_draw_dot(canvas, x + 16, y);
|
||||
canvas_draw_dot(canvas, x + 16, y + 1);
|
||||
canvas_draw_dot(canvas, x + 16, y + 2);
|
||||
canvas_draw_dot(canvas, x + 16, y + 3);
|
||||
canvas_draw_dot(canvas, x + 16, y + 4);
|
||||
canvas_draw_dot(canvas, x + 16, y + 5);
|
||||
}
|
||||
|
||||
const uint8_t _I_DoorClosed_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_DoorClosed_22x35[] = {_I_DoorClosed_22x35_0};
|
||||
const Icon I_DoorClosed_22x35 =
|
||||
{.width = 22, .height = 35, .frame_count = 1, .frame_rate = 0, .frames = _I_DoorClosed_22x35};
|
||||
|
||||
typedef struct {
|
||||
bool isOpen;
|
||||
bool isSelected; // picked in RoundOne, RoundThree
|
||||
bool isWinningDoor; // randomized in RoundOne
|
||||
} Door;
|
||||
|
||||
typedef struct {
|
||||
Door doors[3];
|
||||
bool didSelect; // false in RoundOne -> RoundTwo when true
|
||||
bool didSwitch; // determined in RoundFour
|
||||
} DoorState;
|
||||
|
||||
typedef enum {
|
||||
RoundOne, // all doors closed, player selects a door when ready
|
||||
RoundTwo, // door selected, reveal one of the remaining two (can go straight to GameOver)
|
||||
RoundThree, // player can keep or switch their selection
|
||||
RoundFour, // reveal all doors
|
||||
GameOver // score has been updated, allow restart
|
||||
} GameState;
|
||||
|
||||
typedef struct {
|
||||
GameState game_state;
|
||||
DoorState door_state;
|
||||
uint16_t score;
|
||||
} MontyState;
|
||||
|
||||
static void montyhall_game_init_state(MontyState* monty_state) {
|
||||
if(!monty_state->score) {
|
||||
monty_state->score = 0;
|
||||
}
|
||||
monty_state->door_state.didSelect = false;
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
monty_state->door_state.doors[i].isOpen = false;
|
||||
monty_state->door_state.doors[i].isSelected = false;
|
||||
monty_state->door_state.doors[i].isWinningDoor = false;
|
||||
}
|
||||
|
||||
monty_state->game_state = RoundOne;
|
||||
int doorIndex = random() % 3;
|
||||
monty_state->door_state.doors[doorIndex].isWinningDoor = true;
|
||||
}
|
||||
|
||||
void selectDoor(MontyState* monty_state, int doorIndex) {
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
monty_state->door_state.doors[doorIndex].isSelected = true;
|
||||
if(monty_state->door_state.doors[doorIndex].isSelected) {
|
||||
monty_state->door_state.didSelect = true;
|
||||
monty_state->game_state = RoundTwo;
|
||||
}
|
||||
} else if(monty_state->game_state == RoundThree) {
|
||||
for(int i = 0; i < 3; i++) {
|
||||
monty_state->door_state.doors[i].isSelected = false;
|
||||
}
|
||||
|
||||
monty_state->door_state.doors[doorIndex].isSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
int getRandomDoorIndex() {
|
||||
int randomDoorIndex = random() % 3;
|
||||
return randomDoorIndex;
|
||||
}
|
||||
|
||||
void revealBadDoor(MontyState* monty_state) {
|
||||
int doorToReveal = getRandomDoorIndex();
|
||||
while(!monty_state->door_state.doors[doorToReveal].isOpen) {
|
||||
if(!(monty_state->door_state.doors[doorToReveal].isSelected ||
|
||||
monty_state->door_state.doors[doorToReveal].isWinningDoor)) {
|
||||
monty_state->door_state.doors[doorToReveal].isOpen = true;
|
||||
} else {
|
||||
doorToReveal = getRandomDoorIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void revealDoors_updateScore(MontyState* monty_state) {
|
||||
for(int i = 0; i < 3; i++) {
|
||||
monty_state->door_state.doors[i].isOpen = true;
|
||||
|
||||
if(monty_state->door_state.doors[i].isWinningDoor &&
|
||||
monty_state->door_state.doors[i].isSelected) {
|
||||
monty_state->score++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_top(Canvas* canvas, const MontyState* monty_state) {
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "Cars: %u", monty_state->score);
|
||||
canvas_draw_str_aligned(canvas, 2, 8, AlignLeft, AlignBottom, buffer);
|
||||
|
||||
if(monty_state->game_state == RoundThree) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, SCREEN_WIDTH - 5, 8, AlignRight, AlignBottom, "Opened a decoy door");
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_doors(Canvas* canvas, const MontyState* monty_state) {
|
||||
// {| 16 | <22> | 15 | <22> | 15 | <22> | 16 |} = SCREEN_WIDTH
|
||||
if(monty_state->door_state.doors[0].isOpen) {
|
||||
if(monty_state->door_state.doors[0].isWinningDoor) {
|
||||
canvas_draw_frame(canvas, 16, 12, 22, 35);
|
||||
draw_car(canvas, 18, 26);
|
||||
} else {
|
||||
canvas_draw_frame(canvas, 16, 12, 22, 35);
|
||||
canvas_draw_str(canvas, 18, 34, "Goat");
|
||||
}
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 16, 12, &I_DoorClosed_22x35);
|
||||
}
|
||||
|
||||
if(monty_state->door_state.doors[1].isOpen) {
|
||||
if(monty_state->door_state.doors[1].isWinningDoor) {
|
||||
canvas_draw_frame(canvas, 53, 12, 22, 35);
|
||||
draw_car(canvas, 55, 26);
|
||||
} else {
|
||||
canvas_draw_frame(canvas, 53, 12, 22, 35);
|
||||
canvas_draw_str(canvas, 55, 34, "Goat");
|
||||
}
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 53, 12, &I_DoorClosed_22x35);
|
||||
}
|
||||
|
||||
if(monty_state->door_state.doors[2].isOpen) {
|
||||
if(monty_state->door_state.doors[2].isWinningDoor) {
|
||||
canvas_draw_frame(canvas, 90, 12, 22, 35);
|
||||
draw_car(canvas, 92, 26);
|
||||
} else {
|
||||
canvas_draw_frame(canvas, 90, 12, 22, 35);
|
||||
canvas_draw_str(canvas, 92, 34, "Goat");
|
||||
}
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 90, 12, &I_DoorClosed_22x35);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_bottom(Canvas* canvas, const MontyState* monty_state) {
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
elements_button_left(canvas, "Left");
|
||||
elements_button_center(canvas, "Center");
|
||||
elements_button_right(canvas, "Right");
|
||||
}
|
||||
|
||||
if(monty_state->game_state == RoundThree) {
|
||||
if(monty_state->door_state.doors[0].isSelected) {
|
||||
elements_button_left(canvas, "Keep");
|
||||
if(!monty_state->door_state.doors[1].isOpen) {
|
||||
elements_button_center(canvas, "Switch");
|
||||
} else {
|
||||
elements_button_right(canvas, "Switch");
|
||||
}
|
||||
} else if(monty_state->door_state.doors[1].isSelected) {
|
||||
elements_button_center(canvas, "Keep");
|
||||
if(!monty_state->door_state.doors[0].isOpen) {
|
||||
elements_button_left(canvas, "Switch");
|
||||
} else {
|
||||
elements_button_right(canvas, "Switch");
|
||||
}
|
||||
} else if(monty_state->door_state.doors[2].isSelected) {
|
||||
elements_button_right(canvas, "Keep");
|
||||
if(!monty_state->door_state.doors[0].isOpen) {
|
||||
elements_button_left(canvas, "Switch");
|
||||
} else {
|
||||
elements_button_center(canvas, "Switch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(monty_state->game_state == RoundFour) {
|
||||
elements_button_center(canvas, "Reveal");
|
||||
}
|
||||
|
||||
if(monty_state->game_state == GameOver) {
|
||||
canvas_draw_str(canvas, 16, SCREEN_HEIGHT - 5, "Hold center to restart");
|
||||
}
|
||||
}
|
||||
|
||||
static void montyhall_render_callback(Canvas* const canvas, void* ctx) {
|
||||
const MontyState* monty_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(monty_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
draw_top(canvas, monty_state);
|
||||
draw_doors(canvas, monty_state);
|
||||
draw_bottom(canvas, monty_state);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, monty_state);
|
||||
}
|
||||
|
||||
static void montyhall_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
int32_t montyhall_game_app(void* p) {
|
||||
UNUSED(p);
|
||||
int32_t return_code = 0;
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
MontyState* monty_state = malloc(sizeof(MontyState));
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, monty_state, sizeof(MontyState))) {
|
||||
return_code = 255;
|
||||
goto free_and_exit;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, montyhall_render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, montyhall_input_callback, event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
// Start the game
|
||||
montyhall_game_init_state(monty_state);
|
||||
|
||||
InputEvent event;
|
||||
for(bool loop = true; loop;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
MontyState* monty_state = (MontyState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == InputTypeShort) {
|
||||
switch(event.key) {
|
||||
case InputKeyUp: /* <debug>
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
monty_state->score++;
|
||||
} else if(monty_state->game_state == RoundTwo) {
|
||||
monty_state->score += 2;
|
||||
} else if(monty_state->game_state == RoundThree) {
|
||||
monty_state->score += 3;
|
||||
} else if(monty_state->game_state == RoundFour) {
|
||||
monty_state->score += 4;
|
||||
} else if(monty_state->game_state == GameOver) {
|
||||
monty_state->score += 5;
|
||||
} </debug> */
|
||||
break;
|
||||
case InputKeyDown: /* <debug>
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
monty_state->score--;
|
||||
} else if(monty_state->game_state == RoundTwo) {
|
||||
monty_state->score -= 2;
|
||||
} else if(monty_state->game_state == RoundThree) {
|
||||
monty_state->score -= 3;
|
||||
} else if(monty_state->game_state == RoundFour) {
|
||||
monty_state->score -= 4;
|
||||
} else if(monty_state->game_state == GameOver) {
|
||||
monty_state->score -= 5;
|
||||
} </dubug> */
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
selectDoor(monty_state, 0);
|
||||
if(monty_state->game_state == RoundTwo) {
|
||||
revealBadDoor(monty_state);
|
||||
monty_state->game_state = RoundThree;
|
||||
}
|
||||
} else if(monty_state->game_state == RoundThree) {
|
||||
if(monty_state->door_state.doors[0].isSelected) {
|
||||
monty_state->door_state.didSwitch = false;
|
||||
} else if(!monty_state->door_state.doors[0].isOpen) {
|
||||
monty_state->door_state.didSwitch = true;
|
||||
selectDoor(monty_state, 0);
|
||||
}
|
||||
monty_state->game_state = RoundFour;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
selectDoor(monty_state, 1);
|
||||
if(monty_state->game_state == RoundTwo) {
|
||||
revealBadDoor(monty_state);
|
||||
monty_state->game_state = RoundThree;
|
||||
}
|
||||
} else if(monty_state->game_state == RoundThree) {
|
||||
if(monty_state->door_state.doors[1].isSelected) {
|
||||
monty_state->door_state.didSwitch = false;
|
||||
} else if(!monty_state->door_state.doors[1].isOpen) {
|
||||
monty_state->door_state.didSwitch = true;
|
||||
selectDoor(monty_state, 1);
|
||||
}
|
||||
monty_state->game_state = RoundFour;
|
||||
} else if(monty_state->game_state == RoundFour) {
|
||||
revealDoors_updateScore(monty_state);
|
||||
monty_state->game_state = GameOver;
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(monty_state->game_state == RoundOne) {
|
||||
selectDoor(monty_state, 2);
|
||||
if(monty_state->game_state == RoundTwo) {
|
||||
revealBadDoor(monty_state);
|
||||
monty_state->game_state = RoundThree;
|
||||
}
|
||||
} else if(monty_state->game_state == RoundThree) {
|
||||
if(monty_state->door_state.doors[2].isSelected) {
|
||||
monty_state->door_state.didSwitch = false;
|
||||
} else if(!monty_state->door_state.doors[2].isOpen) {
|
||||
monty_state->door_state.didSwitch = true;
|
||||
selectDoor(monty_state, 2);
|
||||
}
|
||||
monty_state->game_state = RoundFour;
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
loop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == InputTypeLong) {
|
||||
if(event.key == InputKeyOk && monty_state->game_state == GameOver) {
|
||||
montyhall_game_init_state(monty_state);
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, monty_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
free_and_exit:
|
||||
free(monty_state);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return return_code;
|
||||
}
|
||||
Reference in New Issue
Block a user