secret_toggle.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /* @file secret_toggle.c
  2. * @author @nostrumuva, Isomer Programming LLC
  3. * @license GNU General Public License Version 3
  4. * @note yup it's open source for the flipper zero
  5. */
  6. #include <dialogs/dialogs.h>
  7. #include <furi.h>
  8. #include <furi_hal.h>
  9. #include <gui/gui.h>
  10. #include <input/input.h>
  11. #include <notification/notification_messages.h>
  12. #include <stdbool.h>
  13. #include <stdlib.h>
  14. #include "util.h"
  15. #define BOARD_HEIGHT 7
  16. #define BOARD_WIDTH 7
  17. #define BOARD_NUM_SQUARES (BOARD_HEIGHT * BOARD_WIDTH)
  18. #define MAX_LEVELS 10
  19. typedef enum {
  20. EventTypeTick,
  21. EventTypeKey,
  22. } EventType;
  23. typedef struct {
  24. EventType type;
  25. InputEvent input;
  26. } PluginEvent;
  27. typedef enum { SquareActiveOff, SquareActiveOn } SquareActive;
  28. typedef struct {
  29. SquareActive mySquareActive;
  30. int myConnectedSquares[BOARD_NUM_SQUARES]; //0 if not connected, 1 if connected
  31. } Square;
  32. typedef struct {
  33. Square myBoard[BOARD_NUM_SQUARES];
  34. int myLevel;
  35. int myCursorX;
  36. int myCursorY;
  37. int myNumToggles;
  38. bool myWin;
  39. } Game;
  40. static void showStartupScreen() {
  41. const char* tempTitle = "Secret Toggle";
  42. const char* tempMessage =
  43. "Toggle all squares to be bright.\nSome squares are connected.\ngithub.com/nostrumuva";
  44. const char* tempConfirmButtonText = "Play";
  45. utilShowDialog(tempTitle, tempMessage, tempConfirmButtonText);
  46. }
  47. static void showLevelScreen(Game* paramGame) {
  48. const char* tempTitle = "Secret Toggle";
  49. FuriString* tempMessage;
  50. tempMessage = furi_string_alloc();
  51. furi_string_printf(tempMessage, "LEVEL %d!", paramGame->myLevel);
  52. const char* tempConfirmButtonText = "Play";
  53. utilShowDialog(tempTitle, furi_string_get_cstr(tempMessage), tempConfirmButtonText);
  54. furi_string_free(tempMessage);
  55. }
  56. static void showExitScreen(Game* paramGame) {
  57. const char* tempExit = "Never give up!";
  58. const char* tempMessage = "Thanks for playing!\ngithub.com/nostrumuva";
  59. const char* tempConfirmButtonText = "Bye";
  60. FuriString* tempWin;
  61. tempWin = furi_string_alloc();
  62. furi_string_printf(tempWin, "You Win! T: %d", paramGame->myNumToggles);
  63. if(paramGame->myWin == true) {
  64. utilShowDialog(furi_string_get_cstr(tempWin), tempMessage, tempConfirmButtonText);
  65. } else {
  66. utilShowDialog(tempExit, tempMessage, tempConfirmButtonText);
  67. }
  68. furi_string_free(tempWin);
  69. }
  70. static bool gameCheckWinLevel(Game* paramGame) {
  71. bool tempWinLevel = true;
  72. int tempIterateSquares = 0;
  73. for(tempIterateSquares = 0; tempIterateSquares < BOARD_NUM_SQUARES; tempIterateSquares++) {
  74. if(paramGame->myBoard[tempIterateSquares].mySquareActive == SquareActiveOff) {
  75. tempWinLevel = false;
  76. break;
  77. }
  78. }
  79. return tempWinLevel;
  80. }
  81. static void gameToggleSquare(Game* paramGame, int paramSquareIndex) {
  82. int tempIterateConnected = 0;
  83. for(tempIterateConnected = 0; tempIterateConnected < BOARD_NUM_SQUARES;
  84. tempIterateConnected++) {
  85. if(paramGame->myBoard[paramSquareIndex].myConnectedSquares[tempIterateConnected] == 1 &&
  86. tempIterateConnected != paramSquareIndex) {
  87. paramGame->myBoard[tempIterateConnected].mySquareActive =
  88. (SquareActive)(!((bool)paramGame->myBoard[tempIterateConnected].mySquareActive));
  89. }
  90. }
  91. paramGame->myBoard[paramSquareIndex].mySquareActive =
  92. (SquareActive)(!((bool)paramGame->myBoard[paramSquareIndex].mySquareActive));
  93. }
  94. static void gameSetup(Game* paramGame, int paramLevel) {
  95. int tempIterateLevel = 0;
  96. int tempIterateSquares = 0;
  97. int tempIterateConnected = 0;
  98. int tempRandomSquare = 0;
  99. paramGame->myLevel = paramLevel;
  100. paramGame->myCursorX = 0;
  101. paramGame->myCursorY = 0;
  102. //turn them all on and set to no connections
  103. for(tempIterateSquares = 0; tempIterateSquares < BOARD_NUM_SQUARES; tempIterateSquares++) {
  104. paramGame->myBoard[tempIterateSquares].mySquareActive = SquareActiveOn;
  105. for(tempIterateConnected = 0; tempIterateConnected < BOARD_NUM_SQUARES;
  106. tempIterateConnected++) {
  107. paramGame->myBoard[tempIterateSquares].myConnectedSquares[tempIterateConnected] = 0;
  108. }
  109. }
  110. //based on level, connect more squares
  111. for(tempIterateSquares = 0; tempIterateSquares < BOARD_NUM_SQUARES; tempIterateSquares++) {
  112. for(tempIterateLevel = 0; tempIterateLevel < paramGame->myLevel; tempIterateLevel++) {
  113. tempRandomSquare = furi_hal_random_get() % BOARD_NUM_SQUARES;
  114. paramGame->myBoard[tempIterateSquares].myConnectedSquares[tempRandomSquare] = 1;
  115. }
  116. }
  117. //based on level, toggle more squares to scramble
  118. for(tempIterateLevel = 0; tempIterateLevel < paramGame->myLevel; tempIterateLevel++) {
  119. tempRandomSquare = rand() % BOARD_NUM_SQUARES;
  120. gameToggleSquare(paramGame, tempRandomSquare);
  121. }
  122. }
  123. static void render_callback(Canvas* const canvas, void* ctx) {
  124. Game* tempGame = (Game*)ctx;
  125. FuriString* tempStatusStr;
  126. tempStatusStr = furi_string_alloc();
  127. furi_string_printf(
  128. tempStatusStr, "L: %d/%d T: %d", tempGame->myLevel, MAX_LEVELS, tempGame->myNumToggles);
  129. canvas_set_font(canvas, FontSecondary);
  130. canvas_draw_str_aligned(
  131. canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(tempStatusStr));
  132. static uint8_t staticMinX = 40;
  133. static uint8_t staticMinY = 8;
  134. static uint8_t staticSquareActiveOnBits[] = {
  135. 0x00,
  136. 0x7E,
  137. 0x42,
  138. 0x42,
  139. 0x42,
  140. 0x42,
  141. 0x7E,
  142. 0x00,
  143. }; //1 is black, 0 is bright
  144. static uint8_t staticSquareActiveOnBitsSelected[] = {
  145. 0xFF,
  146. 0xFF,
  147. 0xC3,
  148. 0xC3,
  149. 0xC3,
  150. 0xC3,
  151. 0xFF,
  152. 0xFF,
  153. }; //1 is black, 0 is bright
  154. static uint8_t staticSquareActiveOffBits[] = {
  155. 0x00,
  156. 0x7E,
  157. 0x7E,
  158. 0x7E,
  159. 0x7E,
  160. 0x7E,
  161. 0x7E,
  162. 0x00,
  163. }; //1 is black, 0 is bright
  164. static uint8_t staticSquareActiveOffBitsSelected[] = {
  165. 0xFF,
  166. 0xFF,
  167. 0xFF,
  168. 0xFF,
  169. 0xFF,
  170. 0xFF,
  171. 0xFF,
  172. 0xFF,
  173. }; //1 is black, 0 is bright
  174. int tempIterateSquares = 0;
  175. int tempSquareX = 0;
  176. int tempSquareY = 0;
  177. for(tempIterateSquares = 0; tempIterateSquares < BOARD_NUM_SQUARES; tempIterateSquares++) {
  178. tempSquareX = tempIterateSquares % BOARD_WIDTH;
  179. tempSquareY = (tempIterateSquares - tempSquareX) / BOARD_HEIGHT;
  180. if(tempGame->myBoard[tempIterateSquares].mySquareActive == SquareActiveOn) {
  181. if(tempSquareX == tempGame->myCursorX && tempSquareY == tempGame->myCursorY) {
  182. canvas_draw_xbm(
  183. canvas,
  184. staticMinX + (tempSquareX * 8), //x position
  185. staticMinY + (tempSquareY * 8), //y position
  186. 8, //width
  187. 8, //height
  188. staticSquareActiveOnBitsSelected);
  189. } else {
  190. canvas_draw_xbm(
  191. canvas,
  192. staticMinX + (tempSquareX * 8), //x position
  193. staticMinY + (tempSquareY * 8), //y position
  194. 8, //width
  195. 8, //height
  196. staticSquareActiveOnBits);
  197. }
  198. } else {
  199. if(tempSquareX == tempGame->myCursorX && tempSquareY == tempGame->myCursorY) {
  200. canvas_draw_xbm(
  201. canvas,
  202. staticMinX + (tempSquareX * 8), //x position
  203. staticMinY + (tempSquareY * 8), //y position
  204. 8, //width
  205. 8, //height
  206. staticSquareActiveOffBitsSelected);
  207. } else {
  208. canvas_draw_xbm(
  209. canvas,
  210. staticMinX + (tempSquareX * 8), //x position
  211. staticMinY + (tempSquareY * 8), //y position
  212. 8, //width
  213. 8, //height
  214. staticSquareActiveOffBits);
  215. }
  216. }
  217. }
  218. furi_string_free(tempStatusStr);
  219. }
  220. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  221. furi_assert(event_queue);
  222. PluginEvent event = {.type = EventTypeKey, .input = *input_event};
  223. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  224. }
  225. static void clampCursor(Game* paramGame) {
  226. if(paramGame->myCursorX < 0) {
  227. paramGame->myCursorX = 0;
  228. } else if(paramGame->myCursorX > BOARD_WIDTH - 1) {
  229. paramGame->myCursorX = BOARD_WIDTH - 1;
  230. }
  231. if(paramGame->myCursorY < 0) {
  232. paramGame->myCursorY = 0;
  233. } else if(paramGame->myCursorY > BOARD_HEIGHT - 1) {
  234. paramGame->myCursorY = BOARD_HEIGHT - 1;
  235. }
  236. }
  237. static int getCursorSquareIndex(Game* paramGame) {
  238. return paramGame->myCursorY * BOARD_HEIGHT + paramGame->myCursorX;
  239. }
  240. int32_t secret_toggle_app(void* p) {
  241. UNUSED(p);
  242. showStartupScreen();
  243. Game* newGame = malloc(sizeof(Game));
  244. newGame->myWin = false;
  245. newGame->myNumToggles = 0;
  246. int tempLevel = 1;
  247. bool tempContinueGame = true;
  248. while(tempContinueGame == true) {
  249. gameSetup(newGame, tempLevel);
  250. showLevelScreen(newGame);
  251. FuriMessageQueue* newFuriMessageQueue = furi_message_queue_alloc(8, sizeof(PluginEvent));
  252. Gui* newGui = furi_record_open("gui");
  253. ViewPort* newViewPort = view_port_alloc();
  254. view_port_draw_callback_set(newViewPort, render_callback, newGame);
  255. view_port_input_callback_set(newViewPort, input_callback, newFuriMessageQueue);
  256. gui_add_view_port(newGui, newViewPort, GuiLayerFullscreen);
  257. PluginEvent tempPluginEvent;
  258. for(bool tempProcessing = true; tempProcessing;) {
  259. FuriStatus tempFuriStatus =
  260. furi_message_queue_get(newFuriMessageQueue, &tempPluginEvent, 100);
  261. if(tempFuriStatus == FuriStatusOk) //otherwise, event timed out
  262. {
  263. if(tempPluginEvent.type == EventTypeKey) {
  264. if(tempPluginEvent.input.type == InputTypeShort) //short press
  265. {
  266. switch(tempPluginEvent.input.key) {
  267. case InputKeyUp:
  268. newGame->myCursorY -= 1;
  269. clampCursor(newGame);
  270. break;
  271. case InputKeyDown:
  272. newGame->myCursorY += 1;
  273. clampCursor(newGame);
  274. break;
  275. case InputKeyRight:
  276. newGame->myCursorX += 1;
  277. clampCursor(newGame);
  278. break;
  279. case InputKeyLeft:
  280. newGame->myCursorX -= 1;
  281. clampCursor(newGame);
  282. break;
  283. case InputKeyOk:
  284. gameToggleSquare(newGame, getCursorSquareIndex(newGame));
  285. newGame->myNumToggles += 1;
  286. break;
  287. case InputKeyBack:
  288. tempProcessing = false;
  289. tempContinueGame = false;
  290. break;
  291. case InputKeyMAX:
  292. break;
  293. default:
  294. break;
  295. }
  296. } else if(tempPluginEvent.input.type == InputTypeLong) //long press
  297. {
  298. switch(tempPluginEvent.input.key) {
  299. case InputKeyUp:
  300. case InputKeyDown:
  301. case InputKeyRight:
  302. case InputKeyLeft:
  303. break;
  304. case InputKeyOk:
  305. //test win condition
  306. ///newGame->myWin=true;
  307. ///tempProcessing=false;
  308. ///tempContinueGame=false;
  309. break;
  310. case InputKeyBack:
  311. tempProcessing = false;
  312. tempContinueGame = false;
  313. break;
  314. case InputKeyMAX:
  315. break;
  316. default:
  317. break;
  318. }
  319. }
  320. }
  321. if(gameCheckWinLevel(newGame) == true) {
  322. tempLevel += 1;
  323. if(tempLevel > MAX_LEVELS) {
  324. newGame->myWin = true;
  325. tempContinueGame = false;
  326. }
  327. tempProcessing = false;
  328. }
  329. }
  330. view_port_update(newViewPort);
  331. }
  332. view_port_enabled_set(newViewPort, false);
  333. gui_remove_view_port(newGui, newViewPort);
  334. view_port_free(newViewPort);
  335. furi_record_close("gui");
  336. furi_message_queue_free(newFuriMessageQueue);
  337. }
  338. showExitScreen(newGame);
  339. free(newGame);
  340. return 0;
  341. }