1
- /*
1
+ /*
2
2
* Copyright 2023 Alexander Taran
3
+ *
3
4
* Use of this source code is governed by an MIT-style
4
5
* license that can be found in the LICENSE file or at
5
6
* https://opensource.org/licenses/MIT
6
- *
7
+ *
7
8
* Thanks to:
8
9
* - Eugene Kirzhanov: https://github.com/eugene-kirzhanov/flipper-zero-2048-game
9
10
*/
20
21
#define SCREEN_WIDTH 128
21
22
#define SCREEN_HIGHT 64
22
23
24
+ #define TAG "connect_wires" // For logging
25
+
23
26
enum AppStatus {
24
27
ST_PLAYING ,
25
28
ST_MAIN_MENU ,
@@ -83,11 +86,34 @@ static const int8_t DY[4] = { 0, -1, 0, 1};
83
86
static const int8_t OPP [4 ] = {DIR_RIGHT , DIR_BOTTOM , DIR_LEFT , DIR_TOP };
84
87
85
88
static const char * WINNING_MESSAGE = "Congratulations!" ;
89
+ static const char * SCORE_MESSAGE = "Moves: %u" ; // Will show the user how many "rotations" they made
90
+
91
+ const NotificationSequence sequence_winning = {
92
+ & message_vibro_on ,
93
+ & message_delay_50 ,
94
+ & message_vibro_off ,
95
+ NULL ,
96
+ };
97
+
98
+ // Give a notification when the user changes direction
99
+ // show green led for clockwise.
100
+ const NotificationSequence sequence_clockwise = {
101
+ & message_green_255 ,
102
+ & message_vibro_on ,
103
+ & message_delay_50 ,
104
+ & message_vibro_off ,
105
+ & message_green_0 ,
106
+ NULL ,
107
+ };
86
108
87
- const NotificationSequence sequence_winning = {
109
+ // Give a notification when the user changes direction
110
+ // show red led for counter-clockwise.
111
+ const NotificationSequence sequence_counter_clockwise = {
112
+ & message_red_255 ,
88
113
& message_vibro_on ,
89
114
& message_delay_50 ,
90
115
& message_vibro_off ,
116
+ & message_red_0 ,
91
117
NULL ,
92
118
};
93
119
@@ -103,12 +129,22 @@ GridElement createGridElement() {
103
129
return g ;
104
130
}
105
131
106
- void rotate_grid_element (GridElement * elem ) {
107
- bool tmp = elem -> edges [0 ];
108
- for (int8_t i = 0 ; i < 3 ; ++ i ) {
109
- elem -> edges [i ] = elem -> edges [i + 1 ];
132
+
133
+ void rotate_grid_element (GridElement * elem , bool clockwise ) {
134
+ //FURI_LOG_I(TAG, "Attempt Rotation: clockwise = %d", clockwise);
135
+ if (!clockwise ) { // Counter-clockwise logic
136
+ bool tmp = elem -> edges [0 ];
137
+ for (int8_t i = 0 ; i < 3 ; ++ i ) {
138
+ elem -> edges [i ] = elem -> edges [i + 1 ];
139
+ }
140
+ elem -> edges [3 ] = tmp ;
141
+ } else { // Clockwise logic
142
+ bool tmp = elem -> edges [3 ];
143
+ for (int8_t i = 3 ; i > 0 ; -- i ) {
144
+ elem -> edges [i ] = elem -> edges [i - 1 ];
145
+ }
146
+ elem -> edges [0 ] = tmp ;
110
147
}
111
- elem -> edges [3 ] = tmp ;
112
148
}
113
149
114
150
uint8_t count_edges (GridElement * elem ) {
@@ -141,6 +177,8 @@ typedef struct{
141
177
Coord startingPoint ;
142
178
GridElement elements [MAX_FIELD_WIDTH ][MAX_FIELD_HEIGHT ];
143
179
Coord currentSelection ;
180
+ uint16_t gameMoves ; // 65535 theres no check for this, but if they go over....
181
+ bool clockwise ; // CLOCKWISE(true) || COUNTERCLOCKWISE(false)
144
182
145
183
// calculated properties
146
184
bool reachable [MAX_FIELD_WIDTH ][MAX_FIELD_HEIGHT ];
@@ -157,7 +195,10 @@ GameState createNewGameState(Coord fieldSize) {
157
195
GameState gs ;
158
196
gs .fieldSize = fieldSize ;
159
197
Coord start = createCoord (1 + random () % (gs .fieldSize .x - 2 ), 1 + random () % (gs .fieldSize .y - 2 ));
160
-
198
+
199
+ gs .gameMoves = 0 ; // Initialize beginning moves
200
+ gs .clockwise = false; // This was this games original default, we'll keep it the same.
201
+
161
202
gs .startingPoint = start ;
162
203
163
204
gs .currentSelection = start ;
@@ -199,7 +240,7 @@ GameState createNewGameState(Coord fieldSize) {
199
240
dirs [num_dirs ] = i ;
200
241
num_dirs ++ ;
201
242
}
202
- }
243
+ }
203
244
if (num_dirs != 0 ) break ;
204
245
candidates [cand_id ] = candidates [num_candidates - 1 ]; num_candidates -- ;
205
246
}
@@ -235,7 +276,7 @@ GameState createNewGameState(Coord fieldSize) {
235
276
}
236
277
}
237
278
free (candidates );
238
-
279
+
239
280
return gs ;
240
281
}
241
282
@@ -246,7 +287,7 @@ void shuffle(GameState* gs) {
246
287
for (uint8_t j = 0 ; j < gs -> fieldSize .y ; ++ j ) {
247
288
uint8_t rounds = rand () % 4 ;
248
289
for (uint8_t r = 0 ; r < rounds ; ++ r ) {
249
- rotate_grid_element (& gs -> elements [i ][j ]);
290
+ rotate_grid_element (& gs -> elements [i ][j ], false );
250
291
shuffles ++ ;
251
292
}
252
293
}
@@ -266,7 +307,9 @@ void moveSelection(GameState* gs, uint8_t dir) {
266
307
267
308
void rotateSelection (GameState * gs ) {
268
309
Coord * cs = & gs -> currentSelection ;
269
- rotate_grid_element (& gs -> elements [cs -> x ][cs -> y ]);
310
+ rotate_grid_element (& gs -> elements [cs -> x ][cs -> y ], gs -> clockwise );
311
+
312
+ gs -> gameMoves ++ ; // Increment moves counter
270
313
}
271
314
272
315
void recalculateReachables (GameState * gs ) {
@@ -311,13 +354,13 @@ bool checkIsWinning(GameState* gs) {
311
354
static void draw_selection (Canvas * canvas , Coord at , uint8_t cellSize , uint8_t cornerSize ) {
312
355
canvas_draw_line (canvas , at .x , at .y , at .x + cornerSize - 1 , at .y );
313
356
canvas_draw_line (canvas , at .x , at .y , at .x , at .y + cornerSize - 1 );
314
-
357
+
315
358
canvas_draw_line (canvas , at .x + cellSize - 1 , at .y , at .x + cellSize - cornerSize , at .y );
316
359
canvas_draw_line (canvas , at .x + cellSize - 1 , at .y , at .x + cellSize - 1 , at .y + cornerSize - 1 );
317
-
360
+
318
361
canvas_draw_line (canvas , at .x , at .y + cellSize - 1 , at .x , at .y + cellSize - cornerSize );
319
362
canvas_draw_line (canvas , at .x , at .y + cellSize - 1 , at .x + cornerSize - 1 , at .y + cellSize - 1 );
320
-
363
+
321
364
canvas_draw_line (canvas , at .x + cellSize - 1 , at .y + cellSize - 1 , at .x + cellSize - 1 , at .y + cellSize - cornerSize );
322
365
canvas_draw_line (canvas , at .x + cellSize - 1 , at .y + cellSize - 1 , at .x + cellSize - cornerSize , at .y + cellSize - 1 );
323
366
}
@@ -333,7 +376,7 @@ static void draw_interleaved_dots(Canvas* canvas, Coord at, uint8_t size) {
333
376
334
377
static void draw_grid_element_size12 (Canvas * canvas , Coord at , Coord element , GameState * gs ) {
335
378
GridElement * elem = & gs -> elements [element .x ][element .y ];
336
-
379
+
337
380
canvas_set_color (canvas , ColorWhite );
338
381
canvas_draw_box (canvas , at .x , at .y , 12 , 12 );
339
382
@@ -463,7 +506,7 @@ void draw_menu(Canvas* canvas, const MenuEntry* menu, uint8_t selectedIndex) {
463
506
int w = canvas_string_width (canvas , menu [i ].text );
464
507
if (w > max_width ) {
465
508
max_width = w ;
466
- }
509
+ }
467
510
}
468
511
max_width += 2 ;
469
512
for (uint8_t i = 0 ; i < nitems ; ++ i ) {
@@ -488,7 +531,7 @@ void draw_about(Canvas* canvas) {
488
531
int w = canvas_string_width (canvas , AboutStrings [i ]);
489
532
if (w > max_width ) {
490
533
max_width = w ;
491
- }
534
+ }
492
535
}
493
536
canvas_set_color (canvas , ColorBlack );
494
537
for (uint8_t i = 0 ; i < nitems ; ++ i ) {
@@ -497,31 +540,42 @@ void draw_about(Canvas* canvas) {
497
540
}
498
541
}
499
542
500
- void draw_winning (Canvas * canvas ) {
543
+ void draw_winning (Canvas * canvas , GameState * gs ) {
501
544
canvas_set_font (canvas , FontPrimary );
545
+
546
+ size_t s = snprintf (NULL , 0 , "%s:%u" , SCORE_MESSAGE , gs -> gameMoves );
547
+ char moves [s ];
548
+ // Use snprintf to combine the score message and the actual score
549
+ snprintf (moves , s , SCORE_MESSAGE , gs -> gameMoves );
550
+
502
551
int w = canvas_string_width (canvas , WINNING_MESSAGE );
503
- int h = canvas_current_font_height (canvas );
552
+ int h = canvas_current_font_height (canvas ) * 2 ;
504
553
const int paddingV = 2 ;
505
554
const int paddingH = 4 ;
506
-
555
+
507
556
canvas_set_color (canvas , ColorWhite );
508
- canvas_draw_box (canvas , SCREEN_WIDTH /2 - w /2 - paddingH , SCREEN_HIGHT /2 - h /2 - paddingV , w + paddingH * 2 , h + paddingV * 2 );
557
+ canvas_draw_box (canvas , SCREEN_WIDTH /2 - w /2 - paddingH , SCREEN_HIGHT /2 - h /2 - paddingV , w + paddingH * 2 , h + paddingV * 2 + canvas_current_font_height ( canvas ) );
509
558
canvas_set_color (canvas , ColorBlack );
510
- canvas_draw_rframe (canvas , SCREEN_WIDTH /2 - w /2 - paddingH , SCREEN_HIGHT /2 - h /2 - paddingV , w + paddingH * 2 , h + paddingV * 2 , 2 );
559
+ canvas_draw_rframe (canvas , SCREEN_WIDTH /2 - w /2 - paddingH , SCREEN_HIGHT /2 - h /2 - paddingV , w + paddingH * 2 , h + paddingV * 2 + canvas_current_font_height ( canvas ) , 2 );
511
560
canvas_draw_str_aligned (canvas , SCREEN_WIDTH /2 , SCREEN_HIGHT /2 , AlignCenter , AlignCenter , WINNING_MESSAGE );
561
+ canvas_draw_str_aligned (canvas , SCREEN_WIDTH /2 , (SCREEN_HIGHT /2 ) + canvas_current_font_height (canvas ), AlignCenter , AlignCenter , moves );
512
562
}
513
563
564
+
565
+
566
+
567
+
514
568
static void game_draw_callback (Canvas * canvas , void * ctx ) {
515
569
AppState * appState = (AppState * )ctx ;
516
570
furi_mutex_acquire (appState -> mutex , FuriWaitForever );
517
571
GameState * gs = & appState -> gameState ;
518
572
519
573
canvas_clear (canvas );
520
- if (appState -> status == ST_PLAYING ) {
574
+ if (appState -> status == ST_PLAYING ) {
521
575
draw_grid (canvas , gs , determine_element_size (gs -> fieldSize ));
522
576
} else if (appState -> status == ST_WINNING ) {
523
577
draw_grid (canvas , gs , determine_element_size (gs -> fieldSize ));
524
- draw_winning (canvas );
578
+ draw_winning (canvas , gs );
525
579
} else if (appState -> status == ST_MAIN_MENU ) {
526
580
draw_menu (canvas , MainMenu , appState -> currentMenuSelection );
527
581
} else if (appState -> status == ST_SELECTION_MENU ) {
@@ -564,42 +618,49 @@ int32_t flipper_game_connect_wires(void* p) {
564
618
UNUSED (p );
565
619
furi_assert ((uint16_t )MAX_FIELD_WIDTH * MAX_FIELD_HEIGHT < 256 );
566
620
567
- dolphin_deed (DolphinDeedPluginGameStart );
568
-
569
621
InputEvent event ;
570
622
FuriMessageQueue * event_queue = furi_message_queue_alloc (8 , sizeof (InputEvent ));
571
623
572
- // Configure view port
573
- ViewPort * view_port = view_port_alloc ();
574
-
575
- AppState * appState = malloc (sizeof (AppState ));
624
+ AppState * appState = malloc (sizeof (AppState ));
576
625
appState -> gameState .fieldSize = createCoord (0 , 0 );
577
626
appState -> status = ST_MAIN_MENU ;
578
627
579
- // temp
580
- //appState->status = ST_MAIN_MENU;
581
- //appState->currentMenuSelection = 0;
582
- // temp end
628
+ // Allocate our appState->mutex
629
+ appState -> mutex = furi_mutex_alloc (FuriMutexTypeNormal );
583
630
631
+ // Configure view port
632
+ ViewPort * view_port = view_port_alloc ();
584
633
view_port_draw_callback_set (view_port , game_draw_callback , appState );
585
634
view_port_input_callback_set (view_port , game_input_callback , event_queue );
586
635
587
636
// Register view port in GUI
588
637
Gui * gui = furi_record_open (RECORD_GUI );
589
- gui_add_view_port (gui , view_port , GuiLayerFullscreen );
638
+ gui_add_view_port (gui , view_port , GuiLayerFullscreen );
639
+
590
640
NotificationApp * notification = furi_record_open (RECORD_NOTIFICATION );
591
641
642
+ dolphin_deed (DolphinDeedPluginGameStart );
643
+
644
+
592
645
while (1 ) {
593
646
furi_check (furi_message_queue_get (event_queue , & event , FuriWaitForever ) == FuriStatusOk );
594
647
595
- if ( event .type != InputTypePress ) continue ;
648
+ if (( event .type != InputTypeShort ) && ( event . type != InputTypeLong ) ) continue ;
596
649
597
650
furi_mutex_acquire (appState -> mutex , FuriWaitForever );
598
651
599
652
if (appState -> status == ST_PLAYING ) {
600
653
if (event .key == InputKeyBack ) {
601
- appState -> currentMenuSelection = 0 ;
602
- appState -> status = ST_MAIN_MENU ;
654
+ if (event .type == InputTypeShort ) {
655
+ appState -> currentMenuSelection = 0 ;
656
+ appState -> status = ST_MAIN_MENU ;
657
+ }
658
+ if (event .type == InputTypeLong ) {
659
+ // Change rotation to default: counter-clockwise
660
+ appState -> gameState .clockwise = false;
661
+ notification_message_block (notification , & sequence_counter_clockwise );
662
+ //FURI_LOG_I(TAG, "Rotation: counter-clockwise > clockwise = %d", appState->gameState.clockwise);
663
+ }
603
664
} else if (event .key == InputKeyLeft ) {
604
665
moveSelection (& appState -> gameState , DIR_LEFT );
605
666
} else if (event .key == InputKeyRight ) {
@@ -609,11 +670,18 @@ int32_t flipper_game_connect_wires(void* p) {
609
670
} else if (event .key == InputKeyDown ) {
610
671
moveSelection (& appState -> gameState , DIR_BOTTOM );
611
672
} else if (event .key == InputKeyOk ) {
612
- rotateSelection (& appState -> gameState );
613
- recalculateReachables (& appState -> gameState );
614
- if (checkIsWinning (& appState -> gameState )) {
615
- appState -> status = ST_WINNING ;
616
- notification_message_block (notification , & sequence_winning );
673
+ if (event .type == InputTypeShort ) {
674
+ rotateSelection (& appState -> gameState );
675
+ recalculateReachables (& appState -> gameState );
676
+ if (checkIsWinning (& appState -> gameState )) {
677
+ appState -> status = ST_WINNING ;
678
+ notification_message_block (notification , & sequence_winning );
679
+ }
680
+ } else if (event .type == InputTypeLong ) {
681
+ // Change rotation direction to clockwise
682
+ appState -> gameState .clockwise = true;
683
+ notification_message_block (notification , & sequence_clockwise );
684
+ // FURI_LOG_I(TAG, "Rotation: clockwise > clockwise = %d", appState->gameState.clockwise);
617
685
}
618
686
}
619
687
} else if (appState -> status == ST_WINNING ) {
@@ -665,6 +733,7 @@ int32_t flipper_game_connect_wires(void* p) {
665
733
}
666
734
667
735
furi_mutex_release (appState -> mutex );
736
+ view_port_update (view_port );
668
737
}
669
738
670
739
free (appState );
0 commit comments