From e1c1fdf608f475af9ac6ba652ebf38e43945a952 Mon Sep 17 00:00:00 2001 From: RogueMaster Date: Fri, 16 Sep 2022 18:30:05 -0400 Subject: [PATCH] more games --- ReadMe.md | 6 +- applications/plugins/game2048/2048.png | Bin 181 -> 159 bytes .../plugins/game_of_life/application.fam | 12 + .../plugins/game_of_life/game_of_life.c | 160 +++++++ applications/plugins/game_of_life/golIcon.png | Bin 0 -> 1921 bytes .../plugins/mandelbrot/Mandelbrot.png | Bin 0 -> 1918 bytes .../plugins/mandelbrot/application.fam | 12 + applications/plugins/mandelbrot/mandelbrot.c | 170 +++++++ applications/plugins/montyhall/Monty.png | Bin 0 -> 1919 bytes .../plugins/montyhall/application.fam | 12 + applications/plugins/montyhall/monteyhall.c | 448 ++++++++++++++++++ 11 files changed, 817 insertions(+), 3 deletions(-) create mode 100644 applications/plugins/game_of_life/application.fam create mode 100644 applications/plugins/game_of_life/game_of_life.c create mode 100644 applications/plugins/game_of_life/golIcon.png create mode 100644 applications/plugins/mandelbrot/Mandelbrot.png create mode 100644 applications/plugins/mandelbrot/application.fam create mode 100644 applications/plugins/mandelbrot/mandelbrot.c create mode 100644 applications/plugins/montyhall/Monty.png create mode 100644 applications/plugins/montyhall/application.fam create mode 100644 applications/plugins/montyhall/monteyhall.c diff --git a/ReadMe.md b/ReadMe.md index 081fcb719..422524b1b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -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) diff --git a/applications/plugins/game2048/2048.png b/applications/plugins/game2048/2048.png index 517a3056418da79b38bd254c55b5dbd7751a909d..6f46d4de543e8ee590f58b4cd9988ee95a7196f6 100644 GIT binary patch delta 96 zcmdnWIG=HXTOu@>wf@e9i)o$YKTtzJnmlxMO1WTcDtfr;B5V wM0m0TTTf2_ONvorMp{}23s1nZhDJsPt}hHG!q;aW0IFl~boFyt=akR{03y30KmY&$ delta 118 zcmbQwxRr5&TmJw5{}~t<*x1;BWXnHq9U#S0666;Q5_iyA`31=5EbxddW? z8eR=RK_yQY#}J9|WCyk$0fwiIKLXf{x;alB(Cc0!$+RFwAt3>1XcU8NgW_wDkqn-$ KelF{r5}E*5dLqXF diff --git a/applications/plugins/game_of_life/application.fam b/applications/plugins/game_of_life/application.fam new file mode 100644 index 000000000..306373524 --- /dev/null +++ b/applications/plugins/game_of_life/application.fam @@ -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", +) diff --git a/applications/plugins/game_of_life/game_of_life.c b/applications/plugins/game_of_life/game_of_life.c new file mode 100644 index 000000000..65be8cb72 --- /dev/null +++ b/applications/plugins/game_of_life/game_of_life.c @@ -0,0 +1,160 @@ +#include +#include + +#include +#include + +#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; +} \ No newline at end of file diff --git a/applications/plugins/game_of_life/golIcon.png b/applications/plugins/game_of_life/golIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..df14f812c2d4bd216dfb21ffc46b47813ce58cf0 GIT binary patch literal 1921 zcmcIlO>g5w7F}B;J zJs{W{r|*oVjpALgEh~mDh2S4N~dmLZoCq^33yoJ@3pX2m24# z*WOrD6lJ~LY97Lv&)04&!|ykLZa#%!DLs6+r9As-;}2N8(rtJRMfvLKtAEoKSg#)J zb#^W)Y=%HRN^e$n>xmZ;M{ox=6&Mi~SC}IAIRgHXp37)kZEb3`9oN z(V!ot?x?PfiCviI+q$NXA=yb?YvdNyWA8xSmWfd95gy`7P zjWRN9w_>?QMV)*!u&1iEga__nb9Z77R&}kHWwER4!{M+rESF@`)eXmSbWC(Y5Fk)` z9%XcdqV!%tGNEaTlqF%Dg)&leTI$QwtgeB}MJ$7O#vP@TghA@`5sh`Dg!9;@K+n4# zIvC8*X|{6)RwhngNt|}hW1%04RGubG?3{@xyEio!_hcs1p8OBrW`F<7L(XOfAQgB4{R)j+Fgr#SW$xRZ=dQ>RU%)#N{x(pL;YpAeuB2buF(R z!VWUbj$ygjbj##zTy`-opdJW<(~K?#mOKp37lC@7+m6zVMohGubq$;SDHE2!#JIC4x}Mzm}MwqbIcRoK`sINy})G|xnSzR)k13^-=lbS;DUhEIG{wJd>5 z8`!F5U?M^sUzi33W7d@I-6Vt_qWyW++$#=@f>dqG5ENmzcFUT|nH$JtKtVs@fOdr*N8PEULDXObf@y^*RPcP(QkwZ-ctV^7;#|O}4`;61!Lk1?{jWfb<$&6tGkV*^nlp(S;(sTzC2c zfu!{8`Y;yL$Nw2d!OeO!>I%4Np#$dwtLgcjG+(uveszIsFXS?nTmzaRS3x?Lsj&*S zXm@UFrh*4keNtNH4q+78R<(>AQn67rz&>I;Ae`6+vjeN}tRK!16m$>+Czyn#RZZ27tR^Glnz5nDN_OLgi literal 0 HcmV?d00001 diff --git a/applications/plugins/mandelbrot/Mandelbrot.png b/applications/plugins/mandelbrot/Mandelbrot.png new file mode 100644 index 0000000000000000000000000000000000000000..485f70e5c3425761f3fe2a489f785f4fff5e0d84 GIT binary patch literal 1918 zcmcIlJ&zkj7~YT&C5H~Bpg;-5vXKyo%zk=5W#{mnkJ#e&I9-hH=x%mq)?S@=XW895 z`>vp%ql!dB0WA$k5wuVsK?4+_pr%1INJ#tvq|3XzwqvBr#SND1`S3H(`}MptAMb46 zTV1)iA_&52uiM#$v7E15y9~eI{=N19hD-VGy>;Q?qgVcd#g#$ZZwtaV4_^2eKM(7x zJ6rwDv$HdpzW2j#AHr}w?Y^H2f-IHaOJDx+`zL_PmCs0Yl6R zEKcH4l6#YuR1teHFSiv*tRTf9Cw+QCdVi5ldPe@S~z7fy;F)aXRNt@@c{#b;<;%iYBXNY%`$m zUkr`oIXW*kkHN~+>2rzm{z=M|U6%8sjIhmPmK3*V#?m1#cs}G$@ooP1e>|jQZZJK{ zMpcd|QP_yZz?}nDtNcs7jf~-fXMN5`vx@D^EQ-1zo8s$xQ9}7Rf1~thdVqCs!CF#T zJ*2uwGy9t5sitS>Z>ol;sx{OHK~P-a<-nXq;mI;k-}ib+Uf_hVUZ*92Q*snhPYoKT z-f%E-%%FiBw?UC>ss_S_7TTIgZPFl>UvR#|$x)e!@_emd$_a4Hv*}ry9%z9cAjh&8 zGHqaU%+N%-?gq@%Fc`CDZ0}|f^bj5`vX)*^U}U;uTN=Yi)hru@tf3+|a6)8-s^eO= zMm4HS;uE_Ye8e-KQ^>9%iIjLD&teSviDEopN}3F2k_J(O5eTLmE^1iL!(xmx)*e6+o~=7& zi9m9Ed~q1d>Er(lqvj?SO#y +#include +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/applications/plugins/montyhall/Monty.png b/applications/plugins/montyhall/Monty.png new file mode 100644 index 0000000000000000000000000000000000000000..7305817b26263cd4f92d0e8cc3c59b1d7d42cc13 GIT binary patch literal 1919 zcmcIl&5I*N6z{v-PPUcPm&!nlbKCmdmLu7%y_f9syb;V>1w+> zlgu1s(UYeI{|to%y$E_(P!Lag@a)B#Aovdmp6|DUmLz@s@a2EV3vhn% z@NRGK^70b4_kZ~96IgDfoey(KQkC+%@#P=Ce*s#*#=Vo`B)IESF;WQ=Ay=oP6tpF& zu{})*J>`WQ@_w8&mA}7#tjIBID#uoUgLIb<;?70J?_C`9=*21Zn6iC)v(}jUfH2|( zk*A|!lKa!9G6VKuUy3zFo~ab4O{G;@luv>~c~@jyc2omVjIr!^svg=FalH3s0~>~h zZA~|k?)Y`v*X#29qkuhGZe%R-kJ|fldvIzhgQ7@%O`A+6>cmt<*4K2;^E7N|hJio> z<>yI3rYOm8Rv>ejHqU7mr$sChxr8O57#B?iT&`jnrAzE2pC=4br%g$!=_)Q`TPOy> z^{S)MQadm9&cMpt=_`Tr-g(NkBc6+KM)}?uPl}rhW9dK?A|HtV@NN0`Qywz9G?T&V^?2u2FBp0Z$ZbiR&i5cu6BFGxapx7}30DK(CnkHflU z)LnvHE36~et25+T*hIwCBS*KGL+f`;TjvB}-F8sK>lk^V8zDQwu4g+s z)0v^jPweiBp~wQkAiJg_GwMeo8xi17JR*IrrAdE5X&^NifnYju4i!9~wM*4Iv!=Pn zKVA{dTVNQ=Gd}u+mTk~fXjy;Gl({&*BC+d*P{B?H96+z(MWvRDsF;w9xB5_oXY)>5 zBaob&T_47J{P;h^sIcjPBz+DqEp*^&U=6K&lUA$N(5^0U;|X0BlxsmVlq|?sGBsnt z4(YFmmMplv;3pv_^9)LmW4k8ujJku|2#1KW$Y6$}Qzx=3&xY{|P$gOgZ)nfpY4@m9-~YCBu%+pl5_Gw{A7=ifd?<;?5u9kd_qeDvwR#Edtx literal 0 HcmV?d00001 diff --git a/applications/plugins/montyhall/application.fam b/applications/plugins/montyhall/application.fam new file mode 100644 index 000000000..8b5868765 --- /dev/null +++ b/applications/plugins/montyhall/application.fam @@ -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", +) diff --git a/applications/plugins/montyhall/monteyhall.c b/applications/plugins/montyhall/monteyhall.c new file mode 100644 index 000000000..4d91895c5 --- /dev/null +++ b/applications/plugins/montyhall/monteyhall.c @@ -0,0 +1,448 @@ +#include +#include +#include +#include + +#include +#include + +#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: /* + 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; + } */ + break; + case InputKeyDown: /* + 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; + } */ + 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; +} \ No newline at end of file