From 3cca0c71cfda12e9881f2d6f2e0e7c9caa0f7b4c Mon Sep 17 00:00:00 2001 From: RocketGod <57732082+RocketGod-git@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:01:03 -0700 Subject: [PATCH] Make the Laser Tag game. It's built, no errors in VSCode, no errors with uFBT, but still crashes. --- .gitignore | 6 ++ README.md | 5 +- application.fam | 20 +++++ docs/CHANGELOG.md | 3 + docs/README.md | 3 + game_state.c | 99 ++++++++++++++++++++++ game_state.h | 44 ++++++++++ icons/laser_tag_10px.png | Bin 0 -> 806 bytes icons/toolkit.png | Bin 0 -> 4586 bytes infrared_controller.c | 112 +++++++++++++++++++++++++ infrared_controller.h | 42 ++++++++++ laser_tag_app.c | 174 +++++++++++++++++++++++++++++++++++++++ laser_tag_app.h | 31 +++++++ laser_tag_icons.c | 123 +++++++++++++++++++++++++++ laser_tag_icons.h | 17 ++++ laser_tag_view.c | 91 ++++++++++++++++++++ laser_tag_view.h | 16 ++++ manifest.yml | 19 +++++ screenshots/todo.png | Bin 0 -> 3710 bytes 19 files changed, 804 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 application.fam create mode 100644 docs/CHANGELOG.md create mode 100644 docs/README.md create mode 100644 game_state.c create mode 100644 game_state.h create mode 100644 icons/laser_tag_10px.png create mode 100644 icons/toolkit.png create mode 100644 infrared_controller.c create mode 100644 infrared_controller.h create mode 100644 laser_tag_app.c create mode 100644 laser_tag_app.h create mode 100644 laser_tag_icons.c create mode 100644 laser_tag_icons.h create mode 100644 laser_tag_view.c create mode 100644 laser_tag_view.h create mode 100644 manifest.yml create mode 100644 screenshots/todo.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..81a8981f739 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +dist/* +.vscode +.clang-format +.editorconfig +.env +.ufbt diff --git a/README.md b/README.md index 04b906fe7a5..218097c178c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # Flipper-Zero-Laser-Tag - Laser Tag game for Flipper Zero + + Laser Tag game for Flipper Zero. ---> Not working yet, so please help! + + ![rocketgod_logo](https://github.com/RocketGod-git/shodanbot/assets/57732082/7929b554-0fba-4c2b-b22d-6772d23c4a18) diff --git a/application.fam b/application.fam new file mode 100644 index 00000000000..1ca784a2492 --- /dev/null +++ b/application.fam @@ -0,0 +1,20 @@ +App( + appid="laser_tag", + name="Laser Tag", + apptype=FlipperAppType.EXTERNAL, + entry_point="laser_tag_app", + cdefines=["APP_LASER_TAG"], + fap_category="Games", + fap_author="@RocketGod-git", + fap_version="0.1", + fap_description="Laser Tag game for Flipper Zero", + fap_icon="icons/laser_tag_10px.png", + fap_libs=["assets"], + fap_weburl="https://github.com/RocketGod-Git/Flipper-Zero-Laser-Tag", + requires=[ + "gui", + "infrared", + ], + stack_size=2 * 1024, + order=10, +) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 00000000000..9be3b2d20a2 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,3 @@ +## v1.0 + +- Initial release. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..2fb6d451b8d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Flipper Zero - Laser Tag + +Not working, yet. diff --git a/game_state.c b/game_state.c new file mode 100644 index 00000000000..c492f94ce39 --- /dev/null +++ b/game_state.c @@ -0,0 +1,99 @@ +#include "game_state.h" +#include + +struct GameState { + LaserTagTeam team; + uint8_t health; + uint16_t score; + uint16_t ammo; + uint32_t game_time; + bool game_over; +}; + +GameState* game_state_alloc() { + GameState* state = malloc(sizeof(GameState)); + state->team = TeamRed; + state->health = 100; + state->score = 0; + state->ammo = 100; + state->game_time = 0; + state->game_over = false; + return state; +} + +void game_state_free(GameState* state) { + free(state); +} + +void game_state_reset(GameState* state) { + state->health = 100; + state->score = 0; + state->ammo = 100; + state->game_time = 0; + state->game_over = false; +} + +void game_state_set_team(GameState* state, LaserTagTeam team) { + state->team = team; +} + +LaserTagTeam game_state_get_team(GameState* state) { + return state->team; +} + +void game_state_decrease_health(GameState* state, uint8_t amount) { + if(state->health > amount) { + state->health -= amount; + } else { + state->health = 0; + state->game_over = true; + } +} + +void game_state_increase_health(GameState* state, uint8_t amount) { + state->health = (state->health + amount > 100) ? 100 : state->health + amount; +} + +uint8_t game_state_get_health(GameState* state) { + return state->health; +} + +void game_state_increase_score(GameState* state, uint16_t points) { + state->score += points; +} + +uint16_t game_state_get_score(GameState* state) { + return state->score; +} + +void game_state_decrease_ammo(GameState* state, uint16_t amount) { + if(state->ammo > amount) { + state->ammo -= amount; + } else { + state->ammo = 0; + } +} + +void game_state_increase_ammo(GameState* state, uint16_t amount) { + state->ammo += amount; +} + +uint16_t game_state_get_ammo(GameState* state) { + return state->ammo; +} + +void game_state_update_time(GameState* state, uint32_t delta_time) { + state->game_time += delta_time; +} + +uint32_t game_state_get_time(GameState* state) { + return state->game_time; +} + +bool game_state_is_game_over(GameState* state) { + return state->game_over; +} + +void game_state_set_game_over(GameState* state, bool game_over) { + state->game_over = game_over; +} \ No newline at end of file diff --git a/game_state.h b/game_state.h new file mode 100644 index 00000000000..c1c5f52e319 --- /dev/null +++ b/game_state.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +typedef enum { + TeamRed, + TeamBlue +} LaserTagTeam; + +typedef enum { + LaserTagStateTeamSelect, + LaserTagStateGame, +} LaserTagState; + +typedef struct GameState GameState; + +GameState* game_state_alloc(); +void game_state_free(GameState* state); +void game_state_reset(GameState* state); + +void game_state_set_team(GameState* state, LaserTagTeam team); +LaserTagTeam game_state_get_team(GameState* state); + +void game_state_decrease_health(GameState* state, uint8_t amount); +void game_state_increase_health(GameState* state, uint8_t amount); +uint8_t game_state_get_health(GameState* state); + +void game_state_increase_score(GameState* state, uint16_t points); +uint16_t game_state_get_score(GameState* state); + +void game_state_decrease_ammo(GameState* state, uint16_t amount); +void game_state_increase_ammo(GameState* state, uint16_t amount); +uint16_t game_state_get_ammo(GameState* state); + +void game_state_update_time(GameState* state, uint32_t delta_time); +uint32_t game_state_get_time(GameState* state); + +bool game_state_is_game_over(GameState* state); +void game_state_set_game_over(GameState* state, bool game_over); + +#define INITIAL_HEALTH 100 +#define INITIAL_AMMO 100 +#define MAX_HEALTH 100 \ No newline at end of file diff --git a/icons/laser_tag_10px.png b/icons/laser_tag_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..9d043a84049389bfd8773899e3c905f756f9ee99 GIT binary patch literal 806 zcmV+>1KIqEP)EX>4Tx04R}tkv&MmKpe$iQ^g_`g?12e2w0u$q9Tr^ibb$c+6t{Ym|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1le0HIlBs@W3*RLwHd ziMW`{uZn%I2w(^yj3Fv9Q%`0Vv+x{W_we!cF3PjK&;2WUHMh6+K29HiEOoVf0~{Oz z6D7)C_jq@pySIPOwEO!3@cnYx=@ptZ00009a7bBm000XU000XU0RWnu7ytkPNJ&IN zR2b7$kIO4WVF1N{-`sh`V#q76J1Z6v$|L#=vk)PQ{Um>YrBY%`$wt{&Sh2RTV5iA+ z7xJhPQRH#4Fdj2Bw=wtLxfZVCw>qbD>U4yZ-EBird#a;1H;K#JvvRjO7kF{)mZrrQ zl&Y$l?aPz=aD{5&^IK$%JVIE-oqf6L<}SPX>fF=vn#_E84I}evnwEW$u=q%M6mY;1 zI0777P*9XE1LnQKV9{26Yo76m?0};DY<;E$XGa0dJX|LXN=i!C1iKEFm%IRjO@|LshBts0@Qw~e&0|oy+%r!Ap5LcJICnw_0i<>| zeL--R-Dnm-@CPZwJ(?EZ2Z%%>g{EoW0_8xzhkrQ!UlohR4B*(cmCE0muIo2ahR0Mj kbg%38rmBVtfk$BF8!<_B%|Int&j0`b07*qoM6N<$g5S4o!vFvP literal 0 HcmV?d00001 diff --git a/icons/toolkit.png b/icons/toolkit.png new file mode 100644 index 0000000000000000000000000000000000000000..2f747cb5f66af2a806f83264a68f777c38ec775e GIT binary patch literal 4586 zcma)92{=^k+do-DS=uZW(-b9UAG5tAF_vtDNmSGoUG(HBks=J zfk^Z8ZmoDXNe>V)Lw`$zG2rBBTeZ0~Y^G-Y-Fs1)OGI*;6=n>CdR>+iK4`{8xF}C% zo%HVBvAZh;&^msetZS;J9ZfQHj6+s59Z1njHs$CTa;~hf(z+W%X2j24HD-D)H|5D` z^NQ%3Doo|6x_9~mVKVNV9imi4dS>)i@)FGZbM^Umk3HhS+X`0oSH|#v2f?O?N zWRhCXec;pL{vh@8B49!PxHq=*X$zHFfiJK48;{3fCkknkfkT3|&!164b-z|}HP1{Q z>^(c)q6chgh}n96=pdp*HKGv#+yR4?+(Dal{5(!Ofcem{A$azRYXS1lUrP(*9s%GKe$ z$}3d0VE4yv`b_2uu6#wq!b3V%CLR<|_7f+#*YY|IBaaV|8x2?NZJaZR)BbX!Y0ilm zGwkJ7#wp)fy|@do-l9|-ferDZjv8;dF$?xz+lQ6;!P-k5xt@w$haK ztZ;scbymwsij9$uuk#?CrLGwN5B^S*bBf?bTwT;d8(s>aSNr17=?d=Bk#F?=>Sdwe)fh=)h;)Q2b<< z6BoSqNU`04IMtIgc+{%+Rd0;22O3}kH=TKkR6E@qf49s6+B~&#rE>jsdkSqA+XkKp zbtAg53cOqG@VaNB+w7j5sBYHkQjjdD()8KQUvnWBl_SVq-h39LGi!EkY}xf-CLH6C zQl2uWVU^RucZ)chxWlQn%(6kRoyCjW4DQ}gEsc7-vd68*yGK(hH>PBFE#unk4x{aA!N!-3UMlq;;l36RLHte$m#bMFXXH1xXXcW<%dQ)( zG0Z+Dj30;?NU&d4sC6c0w+(Xp(z1l$L&2#e+Z~PxOS-neW|;+WggFix2v1pJ*RtV%leb~>{9FoEFbHiDwi6G)n?x^ zo=MH+ zzg!lcs@|CM_YIy3w|>O%jN`tfhP#Mk;-jx`&#y19e~}cn+%-8U(KV^vu-$l)HY4vu zzGgvOUg_qJoa+tlAKahi73WbMkD#jaXPSI4Rx&PGXI}95EGhqF@tf?UCGrxsMMKkq8`b=+vOXGt}*hdFhfk$J}q+s{1|JzZP7 zvaPUf>s~8R0liiy`ON*c#tEl&F`rFvMb-k@U zDbml<+!4u`*O>fBu6m)m(|)D>b%#G3_OCOlb6D<^zbrr3r76QXeYCN+aR^C3e%#bp z2`|wvp_a(=vHWcQL;jKH51-F{zVSKN!qmJiG=0b>>|1c(;G$3NgUk@KuzR5!L(UHM zeO@xEFh(CUA9*^*7z(>vZaV>FU*_7<-&6()>(yr2XrK7?}1Lp?r&wL#nsPzawA6^;n zdxKf-`3-eyZfbs2(H#G z!d5BJ?>EtFXm%H=q$FOJcjBm#iRnk13JC#q$@sC)`ydF$B`d-=Utd}IEw-C@tbNB*ts2&e)^n0c zmj;)YjU7Jfm(-ITlHVr9_K!S!;pT!ut=v_ssY)y)5lAh&$J zfqdQGy3BBcv5A50S+6aY>nz`}t9d-TBBRB(bw=PDNrTi#dRR)8&dwZO_2faovlriU z?;qLkayqk+j0me5p4H)WE!CAPZfeYMv?PjGb&LnQIyFtGX=(F6D~+gc8mD)8pJJM~3}iZULVo(&w{IDBD}{mEl2;_d3Xz@m>n#A1O&18HE6dxMsI zKlSmR&A0~zsM@%R7g`O727qwmVaYR}#&AReQ^7cUtx4UTp zM|Ib+@8u(vjY9_E;_)-P2ux|G;^fWzc1PbkHRi)xpA=wDMGr)&AGu36s|ub>27nJ+0DzJL0NTRzDmQb8v6ALQr$To{MMY(0Wi>T591a&37gt+b`~Lm= z-rnB6zP`_&KaY-%PE1TpPEOV=jq`-iDnfg2F#sT!%KkHg^DLzRpmdQ(_mX%yJ5gAI z03?GgV1mft03n110CUS=A%nFIl)#uEm&dn&_g|`k!+2~9_KwHpx}tGpFcQcPQ>O=+-+=s z8iSrJ;C>Q`kb*)51qC64a7clOi^7n}WE2{U!eS8+0wE6JOBlfjzIf>`4K|>dCE^Js zJOLjj)5u^70wor3$kiV)1PGm-rxo+XKQjg83>C}}qA*A_Dj+}>*c4hUp-Lg)pKkmS zEvAPEL6kcv76gh|AXN(TB};#avsu%!!a$M#Q~+!i3iJm9Aeb1khM6{H=ip46M#}=` z@&bfY5>OGQQP6rC^hbAonHKs9Bm%LY;QxS6v!^2dB_!c-{z>}(P%q<5L!k^(Yy_-8 zSr863znX$AVDZ?LX~h%<9wd>-SQY}!B7z7G7SBeI7)%m^z+_=rOfrjwXLDqRW!fD0 zVhMxK0%h7D3X%tDVw#zu&Db0^0tA^L0>t832nL?XMzDz>q=HBW87#~%c~=n+>T8Do z?_SCDupvD}5}t`8U`YrzfrvxkIYcuAlgvRQNMt-4!+|`(5FY5oj}3<`}DkIxnaiGR}Rya3Qc!jP2>Lm(1J zI2@K}h9Tjxm|v2fphyfgt4tXNjl|-oVxm}!K!!vhq6-B67I2vbn2h}!542%OKu8Fh zDFhn&L?zO(XbRShg2p1y(Df(OS-|FTLVkx55jZp*jiKPoC^$&+Hz+jGY=(sK|APN1 zv9-WoAc9QD{D8y$p*uid&>{hc=MORd^(765$(o4*l?-YhhHOru)X1jyM=RL##8BKJ zKlcL;8 z2&gS^2olSTh#;^?Xcm^iMx$9!-hRl71sq8bLj+oJAvd8CK+`x?0@#Y51vdVL4DthI z)tf2=7J(tl3IQb;V+Ke4SHAzr)Bmpde|Y-4{XbUsj{*JIuxY=x2^bcSB>X$J-|{L8 z1|{23f7Ar^zsohn`)&9A1&6kD*>d_YfgYxhBajc3OavV^x7Zp?=%6xmu(6`ge8hSc zC9ky1Nb_o{CRqs$n8=B*&a;_!XKz6irYubjXt*U`QIvx9l^tykR2Q3)HCy)l2V^*R ATmS$7 literal 0 HcmV?d00001 diff --git a/infrared_controller.c b/infrared_controller.c new file mode 100644 index 00000000000..6c32dd38119 --- /dev/null +++ b/infrared_controller.c @@ -0,0 +1,112 @@ +#include "infrared_controller.h" +#include +#include +#include +#include +#include + +#define TAG "LaserTagInfrared" + +#define IR_COMMAND_RED_TEAM 0xA1 +#define IR_COMMAND_BLUE_TEAM 0xB2 + +struct InfraredController { + LaserTagTeam team; + InfraredWorker* worker; + FuriThread* rx_thread; + volatile bool rx_running; + volatile bool hit_received; +}; + +static void infrared_rx_callback(void* context, InfraredWorkerSignal* received_signal) { + InfraredController* controller = (InfraredController*)context; + + const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); + if (message != NULL) { + uint32_t received_command = message->address; + if((controller->team == TeamRed && received_command == IR_COMMAND_BLUE_TEAM) || + (controller->team == TeamBlue && received_command == IR_COMMAND_RED_TEAM)) { + controller->hit_received = true; + } + } +} + +static int32_t infrared_rx_thread(void* context) { + InfraredController* controller = (InfraredController*)context; + + while(controller->rx_running) { + infrared_worker_rx_start(controller->worker); + furi_thread_flags_wait(0, FuriFlagWaitAny, 10); + } + + return 0; +} + +InfraredController* infrared_controller_alloc() { + InfraredController* controller = malloc(sizeof(InfraredController)); + controller->team = TeamRed; + controller->worker = infrared_worker_alloc(); + controller->rx_running = true; + controller->hit_received = false; + + infrared_worker_rx_set_received_signal_callback(controller->worker, infrared_rx_callback, controller); + + controller->rx_thread = furi_thread_alloc(); + furi_thread_set_name(controller->rx_thread, "IR_Rx_Thread"); + furi_thread_set_stack_size(controller->rx_thread, 1024); + furi_thread_set_context(controller->rx_thread, controller); + furi_thread_set_callback(controller->rx_thread, infrared_rx_thread); + furi_thread_start(controller->rx_thread); + + infrared_worker_rx_start(controller->worker); + + return controller; +} + +void infrared_controller_free(InfraredController* controller) { + furi_assert(controller); + + controller->rx_running = false; + furi_thread_join(controller->rx_thread); + furi_thread_free(controller->rx_thread); + + infrared_worker_rx_stop(controller->worker); + infrared_worker_free(controller->worker); + free(controller); +} + +void infrared_controller_set_team(InfraredController* controller, LaserTagTeam team) { + furi_assert(controller); + controller->team = team; +} + +void infrared_controller_send(InfraredController* controller) { + furi_assert(controller); + uint32_t command = (controller->team == TeamRed) ? IR_COMMAND_RED_TEAM : IR_COMMAND_BLUE_TEAM; + InfraredMessage message = { + .protocol = InfraredProtocolNEC, + .address = 0x00, + .command = command, + .repeat = false + }; + + infrared_worker_set_decoded_signal(controller->worker, &message); + + infrared_worker_tx_set_get_signal_callback( + controller->worker, + infrared_worker_tx_get_signal_steady_callback, + NULL); + + infrared_worker_tx_start(controller->worker); + + furi_delay_ms(250); // Delay to ensure the signal is sent + + infrared_worker_tx_stop(controller->worker); +} + +bool infrared_controller_receive(InfraredController* controller) { + furi_assert(controller); + bool hit = controller->hit_received; + controller->hit_received = false; + return hit; +} \ No newline at end of file diff --git a/infrared_controller.h b/infrared_controller.h new file mode 100644 index 00000000000..391e26ee12a --- /dev/null +++ b/infrared_controller.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include "game_state.h" // For LaserTagTeam enum + +typedef struct InfraredController InfraredController; + +/** + * Allocate and initialize an InfraredController. + * @return Pointer to the newly allocated InfraredController. + */ +InfraredController* infrared_controller_alloc(); + +/** + * Free an InfraredController and its resources. + * @param controller Pointer to the InfraredController to free. + */ +void infrared_controller_free(InfraredController* controller); + +/** + * Set the team for the InfraredController. + * @param controller Pointer to the InfraredController. + * @param team The team to set (TeamRed or TeamBlue). + */ +void infrared_controller_set_team(InfraredController* controller, LaserTagTeam team); + +/** + * Send an infrared signal corresponding to the controller's team. + * @param controller Pointer to the InfraredController. + */ +void infrared_controller_send(InfraredController* controller); + +/** + * Check if a hit has been received from the opposite team. + * @param controller Pointer to the InfraredController. + * @return true if a hit was received, false otherwise. + */ +bool infrared_controller_receive(InfraredController* controller); + +// IR command definitions +#define IR_COMMAND_RED_TEAM 0xA1 +#define IR_COMMAND_BLUE_TEAM 0xB2 \ No newline at end of file diff --git a/laser_tag_app.c b/laser_tag_app.c new file mode 100644 index 00000000000..099f1a55093 --- /dev/null +++ b/laser_tag_app.c @@ -0,0 +1,174 @@ +#include "laser_tag_app.h" +#include "laser_tag_view.h" +#include "infrared_controller.h" +#include "game_state.h" +#include +#include +#include +#include +#include +#include + +#define TAG "LaserTag" + +struct LaserTagApp { + Gui* gui; + ViewPort* view_port; + LaserTagView* view; + FuriMessageQueue* event_queue; + FuriTimer* timer; + NotificationApp* notifications; + InfraredController* ir_controller; + GameState* game_state; + LaserTagState state; +}; + +const NotificationSequence sequence_vibro_1 = { + &message_vibro_on, + &message_vibro_off, + NULL +}; + +static void laser_tag_app_timer_callback(void* context) { + furi_assert(context); + LaserTagApp* app = context; + game_state_update_time(app->game_state, 1); + laser_tag_view_update(app->view, app->game_state); +} + +static void laser_tag_app_input_callback(InputEvent* input_event, void* context) { + furi_assert(context); + LaserTagApp* app = context; + furi_message_queue_put(app->event_queue, input_event, FuriWaitForever); +} + +static void laser_tag_app_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + LaserTagApp* app = context; + if(app->state == LaserTagStateTeamSelect) { + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 32, 32, "Select Team:"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 32, 48, "LEFT: Red RIGHT: Blue"); + } else { + laser_tag_view_draw(laser_tag_view_get_view(app->view), canvas); + } +} + +LaserTagApp* laser_tag_app_alloc() { + LaserTagApp* app = malloc(sizeof(LaserTagApp)); + + app->gui = furi_record_open(RECORD_GUI); + app->view_port = view_port_alloc(); + app->view = laser_tag_view_alloc(); + app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->ir_controller = infrared_controller_alloc(); + app->game_state = game_state_alloc(); + app->state = LaserTagStateTeamSelect; + + view_port_draw_callback_set(app->view_port, laser_tag_app_draw_callback, app); + view_port_input_callback_set(app->view_port, laser_tag_app_input_callback, app); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + + app->timer = furi_timer_alloc(laser_tag_app_timer_callback, FuriTimerTypePeriodic, app); + furi_timer_start(app->timer, furi_kernel_get_tick_frequency()); + + return app; +} + +void laser_tag_app_free(LaserTagApp* app) { + furi_assert(app); + + furi_timer_free(app->timer); + view_port_enabled_set(app->view_port, false); + gui_remove_view_port(app->gui, app->view_port); + view_port_free(app->view_port); + laser_tag_view_free(app->view); + furi_message_queue_free(app->event_queue); + infrared_controller_free(app->ir_controller); + game_state_free(app->game_state); + + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + + free(app); +} + +void laser_tag_app_fire(LaserTagApp* app) { + infrared_controller_send(app->ir_controller); + game_state_decrease_ammo(app->game_state, 1); + notification_message(app->notifications, &sequence_blink_blue_100); +} + +void laser_tag_app_handle_hit(LaserTagApp* app) { + game_state_decrease_health(app->game_state, 10); + notification_message(app->notifications, &sequence_vibro_1); +} + +void laser_tag_app_enter_game_state(LaserTagApp* app) { + app->state = LaserTagStateGame; + game_state_reset(app->game_state); + laser_tag_view_update(app->view, app->game_state); +} + +int32_t laser_tag_app(void* p) { + UNUSED(p); + LaserTagApp* app = laser_tag_app_alloc(); + + InputEvent event; + bool running = true; + while(running) { + FuriStatus status = furi_message_queue_get(app->event_queue, &event, 100); + if(status == FuriStatusOk) { + if(event.type == InputTypePress) { + if(app->state == LaserTagStateTeamSelect) { + switch(event.key) { + case InputKeyLeft: + infrared_controller_set_team(app->ir_controller, TeamRed); + game_state_set_team(app->game_state, TeamRed); + laser_tag_app_enter_game_state(app); + break; + case InputKeyRight: + infrared_controller_set_team(app->ir_controller, TeamBlue); + game_state_set_team(app->game_state, TeamBlue); + laser_tag_app_enter_game_state(app); + break; + default: + break; + } + } else { + switch(event.key) { + case InputKeyBack: + running = false; + break; + case InputKeyOk: + laser_tag_app_fire(app); + break; + default: + break; + } + } + } + } + + if(app->state == LaserTagStateGame) { + if(infrared_controller_receive(app->ir_controller)) { + laser_tag_app_handle_hit(app); + } + + if(game_state_is_game_over(app->game_state)) { + notification_message(app->notifications, &sequence_error); + running = false; + } + + laser_tag_view_update(app->view, app->game_state); + } + + view_port_update(app->view_port); + } + + laser_tag_app_free(app); + return 0; +} diff --git a/laser_tag_app.h b/laser_tag_app.h new file mode 100644 index 00000000000..c932c4ae499 --- /dev/null +++ b/laser_tag_app.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FRAME_WIDTH 128 +#define FRAME_HEIGHT 64 + +typedef struct LaserTagApp LaserTagApp; + +LaserTagApp* laser_tag_app_alloc(); + +void laser_tag_app_free(LaserTagApp* app); + +int32_t laser_tag_app(void* p); + +void laser_tag_app_set_view_port(LaserTagApp* app, View* view); + +void laser_tag_app_switch_to_next_scene(LaserTagApp* app); + +void laser_tag_app_fire(LaserTagApp* app); + +void laser_tag_app_handle_hit(LaserTagApp* app); \ No newline at end of file diff --git a/laser_tag_icons.c b/laser_tag_icons.c new file mode 100644 index 00000000000..c6912c71730 --- /dev/null +++ b/laser_tag_icons.c @@ -0,0 +1,123 @@ +#include "laser_tag_icons.h" +#include + +const uint8_t laser_gun_icon_data[] = { + 0b00000000, 0b00000000, + 0b00000001, 0b10000000, + 0b00000011, 0b11000000, + 0b00000111, 0b11100000, + 0b00001111, 0b11110000, + 0b00011111, 0b11111000, + 0b11111111, 0b11111110, + 0b11111111, 0b11111111, +}; + +const uint8_t health_icon_data[] = { + 0b00001100, 0b00110000, + 0b00011110, 0b01111000, + 0b00111111, 0b11111100, + 0b01111111, 0b11111110, + 0b01111111, 0b11111110, + 0b00111111, 0b11111100, + 0b00011111, 0b11111000, + 0b00000111, 0b11100000, +}; + +const uint8_t ammo_icon_data[] = { + 0b00011000, 0b00011000, + 0b00111100, 0b00111100, + 0b01111110, 0b01111110, + 0b11111111, 0b11111111, + 0b11111111, 0b11111111, + 0b01111110, 0b01111110, + 0b00111100, 0b00111100, + 0b00011000, 0b00011000, +}; + +const uint8_t team_red_icon_data[] = { + 0b00011000, 0b00011000, + 0b00111100, 0b00111100, + 0b01111110, 0b01111110, + 0b11111111, 0b11111111, + 0b11111111, 0b11111111, + 0b01111110, 0b01111110, + 0b00111100, 0b00111100, + 0b00011000, 0b00011000, +}; + +const uint8_t team_blue_icon_data[] = { + 0b11100111, 0b11100111, + 0b11000011, 0b11000011, + 0b10000001, 0b10000001, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b10000001, 0b10000001, + 0b11000011, 0b11000011, + 0b11100111, 0b11100111, +}; + +const uint8_t game_over_icon_data[] = { + 0b11111111, 0b11111111, + 0b10000000, 0b00000001, + 0b10111101, 0b10111101, + 0b10100001, 0b10100001, + 0b10100001, 0b10100001, + 0b10111101, 0b10111101, + 0b10000000, 0b00000001, + 0b11111111, 0b11111111, +}; + +const uint8_t* const laser_gun_icon_frames[] = {laser_gun_icon_data}; +const uint8_t* const health_icon_frames[] = {health_icon_data}; +const uint8_t* const ammo_icon_frames[] = {ammo_icon_data}; +const uint8_t* const team_red_icon_frames[] = {team_red_icon_data}; +const uint8_t* const team_blue_icon_frames[] = {team_blue_icon_data}; +const uint8_t* const game_over_icon_frames[] = {game_over_icon_data}; + +const Icon I_laser_gun_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = laser_gun_icon_frames, +}; + +const Icon I_health_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = health_icon_frames, +}; + +const Icon I_ammo_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = ammo_icon_frames, +}; + +const Icon I_team_red_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = team_red_icon_frames, +}; + +const Icon I_team_blue_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = team_blue_icon_frames, +}; + +const Icon I_game_over_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = game_over_icon_frames, +}; \ No newline at end of file diff --git a/laser_tag_icons.h b/laser_tag_icons.h new file mode 100644 index 00000000000..d1026367399 --- /dev/null +++ b/laser_tag_icons.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +extern const Icon I_laser_gun_icon; +extern const Icon I_health_icon; +extern const Icon I_ammo_icon; +extern const Icon I_team_red_icon; +extern const Icon I_team_blue_icon; +extern const Icon I_game_over_icon; + +#define LASER_GUN_ICON (&I_laser_gun_icon) +#define HEALTH_ICON (&I_health_icon) +#define AMMO_ICON (&I_ammo_icon) +#define TEAM_RED_ICON (&I_team_red_icon) +#define TEAM_BLUE_ICON (&I_team_blue_icon) +#define GAME_OVER_ICON (&I_game_over_icon) \ No newline at end of file diff --git a/laser_tag_view.c b/laser_tag_view.c new file mode 100644 index 00000000000..7c1ec65e53e --- /dev/null +++ b/laser_tag_view.c @@ -0,0 +1,91 @@ +#include "laser_tag_view.h" +#include "laser_tag_icons.h" +#include +#include + +struct LaserTagView { + View* view; +}; + +typedef struct { + LaserTagTeam team; + uint8_t health; + uint16_t ammo; + uint16_t score; + uint32_t game_time; + bool game_over; +} LaserTagViewModel; + +static void laser_tag_view_draw_callback(Canvas* canvas, void* model) { + LaserTagViewModel* m = model; + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_icon(canvas, 0, 0, m->team == TeamRed ? TEAM_RED_ICON : TEAM_BLUE_ICON); + canvas_draw_icon(canvas, 0, 10, HEALTH_ICON); + canvas_draw_str_aligned(canvas, 20, 14, AlignLeft, AlignBottom, furi_string_get_cstr(furi_string_alloc_printf("%d", m->health))); + canvas_draw_icon(canvas, 0, 20, AMMO_ICON); + canvas_draw_str_aligned(canvas, 20, 24, AlignLeft, AlignBottom, furi_string_get_cstr(furi_string_alloc_printf("%d", m->ammo))); + canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignBottom, "Score:"); + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignBottom, furi_string_get_cstr(furi_string_alloc_printf("%d", m->score))); + uint32_t minutes = m->game_time / 60; + uint32_t seconds = m->game_time % 60; + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, furi_string_get_cstr(furi_string_alloc_printf("%02ld:%02ld", minutes, seconds))); + canvas_draw_icon(canvas, 112, 0, LASER_GUN_ICON); + + if(m->game_over) { + canvas_draw_icon(canvas, 56, 28, GAME_OVER_ICON); + } +} + +static bool laser_tag_view_input_callback(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + return false; +} + +LaserTagView* laser_tag_view_alloc() { + LaserTagView* laser_tag_view = malloc(sizeof(LaserTagView)); + laser_tag_view->view = view_alloc(); + view_set_context(laser_tag_view->view, laser_tag_view); + view_allocate_model(laser_tag_view->view, ViewModelTypeLocking, sizeof(LaserTagViewModel)); + view_set_draw_callback(laser_tag_view->view, laser_tag_view_draw_callback); + view_set_input_callback(laser_tag_view->view, laser_tag_view_input_callback); + return laser_tag_view; +} + +void laser_tag_view_draw(View* view, Canvas* canvas) { + LaserTagViewModel* model = view_get_model(view); + laser_tag_view_draw_callback(canvas, model); + view_commit_model(view, false); +} + +void laser_tag_view_free(LaserTagView* laser_tag_view) { + furi_assert(laser_tag_view); + view_free(laser_tag_view->view); + free(laser_tag_view); +} + +View* laser_tag_view_get_view(LaserTagView* laser_tag_view) { + furi_assert(laser_tag_view); + return laser_tag_view->view; +} + +void laser_tag_view_update(LaserTagView* laser_tag_view, GameState* game_state) { + furi_assert(laser_tag_view); + furi_assert(game_state); + + with_view_model( + laser_tag_view->view, + LaserTagViewModel * model, + { + model->team = game_state_get_team(game_state); + model->health = game_state_get_health(game_state); + model->ammo = game_state_get_ammo(game_state); + model->score = game_state_get_score(game_state); + model->game_time = game_state_get_time(game_state); + model->game_over = game_state_is_game_over(game_state); + }, + true); +} diff --git a/laser_tag_view.h b/laser_tag_view.h new file mode 100644 index 00000000000..f7079f49ad3 --- /dev/null +++ b/laser_tag_view.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "game_state.h" + +typedef struct LaserTagView LaserTagView; + +LaserTagView* laser_tag_view_alloc(); + +void laser_tag_view_draw(View* view, Canvas* canvas); + +void laser_tag_view_free(LaserTagView* laser_tag_view); + +View* laser_tag_view_get_view(LaserTagView* laser_tag_view); + +void laser_tag_view_update(LaserTagView* laser_tag_view, GameState* game_state); \ No newline at end of file diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 00000000000..f77eab31ded --- /dev/null +++ b/manifest.yml @@ -0,0 +1,19 @@ +# Manifest required for the Flipper Application Catalog +# https://github.com/flipperdevices/flipper-application-catalog/blob/main/documentation/Manifest.md +author: "@CodyTolene" +category: "Sub-GHz" +changelog: "@./docs/CHANGELOG.md" +description: "@./docs/README.md" +icon: "icons/toolkit.png" +id: "toolkit" +name: "Development Toolkit" +screenshots: + - "screenshots/todo.png" +short_description: "A Flipper Zero Development Toolkit for all to use!" +sourcecode: + location: + commit_sha: ... # Update this with the latest commit sha + origin: https://github.com/CodyTolene/Flipper-Zero-Development-Toolkit.git + subdir: src-fap + type: git +version: 1.0 diff --git a/screenshots/todo.png b/screenshots/todo.png new file mode 100644 index 0000000000000000000000000000000000000000..092b044d5e81f62ba2d228a3a1d773a90b855f2c GIT binary patch literal 3710 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 YsS;IPeEQt=GyeiT9m88yT6U5D19jI2J^%m! literal 0 HcmV?d00001