Просмотр исходного кода

Fixed some stuff. Added tons of logging for cli log, but ir still crashes.

RocketGod 1 год назад
Родитель
Сommit
e1d90b0245
12 измененных файлов с 439 добавлено и 173 удалено
  1. 1 0
      .gitignore
  2. 2 2
      docs/README.md
  3. 26 10
      game_state.c
  4. 3 3
      game_state.h
  5. 121 25
      infrared_controller.c
  6. 3 30
      infrared_controller.h
  7. 134 26
      laser_tag_app.c
  8. 3 8
      laser_tag_app.h
  9. 98 49
      laser_tag_icons.c
  10. 5 4
      laser_tag_icons.h
  11. 40 10
      laser_tag_view.c
  12. 3 6
      laser_tag_view.h

+ 1 - 0
.gitignore

@@ -1,6 +1,7 @@
 dist/*
 .vscode
 .clang-format
+.clangd
 .editorconfig
 .env
 .ufbt

+ 2 - 2
docs/README.md

@@ -1,3 +1,3 @@
-# Flipper Zero - Laser Tag
+# Flipper Zero - Development Toolkit
 
-Not working, yet. 
+Short description.

+ 26 - 10
game_state.c

@@ -1,5 +1,7 @@
+
 #include "game_state.h"
 #include <furi.h>
+#include <stdlib.h>
 
 struct GameState {
     LaserTagTeam team;
@@ -12,36 +14,39 @@ struct GameState {
 
 GameState* game_state_alloc() {
     GameState* state = malloc(sizeof(GameState));
+    if(!state) {
+        return NULL;
+    }
     state->team = TeamRed;
-    state->health = 100;
+    state->health = INITIAL_HEALTH;
     state->score = 0;
-    state->ammo = 100;
+    state->ammo = INITIAL_AMMO;
     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;
+    furi_assert(state);
+    state->health = INITIAL_HEALTH;
     state->score = 0;
-    state->ammo = 100;
+    state->ammo = INITIAL_AMMO;
     state->game_time = 0;
     state->game_over = false;
 }
 
 void game_state_set_team(GameState* state, LaserTagTeam team) {
+    furi_assert(state);
     state->team = team;
 }
 
 LaserTagTeam game_state_get_team(GameState* state) {
+    furi_assert(state);
     return state->team;
 }
 
 void game_state_decrease_health(GameState* state, uint8_t amount) {
+    furi_assert(state);
     if(state->health > amount) {
         state->health -= amount;
     } else {
@@ -51,22 +56,27 @@ void game_state_decrease_health(GameState* state, uint8_t amount) {
 }
 
 void game_state_increase_health(GameState* state, uint8_t amount) {
-    state->health = (state->health + amount > 100) ? 100 : state->health + amount;
+    furi_assert(state);
+    state->health = (state->health + amount > MAX_HEALTH) ? MAX_HEALTH : state->health + amount;
 }
 
 uint8_t game_state_get_health(GameState* state) {
+    furi_assert(state);
     return state->health;
 }
 
 void game_state_increase_score(GameState* state, uint16_t points) {
+    furi_assert(state);
     state->score += points;
 }
 
 uint16_t game_state_get_score(GameState* state) {
+    furi_assert(state);
     return state->score;
 }
 
 void game_state_decrease_ammo(GameState* state, uint16_t amount) {
+    furi_assert(state);
     if(state->ammo > amount) {
         state->ammo -= amount;
     } else {
@@ -75,25 +85,31 @@ void game_state_decrease_ammo(GameState* state, uint16_t amount) {
 }
 
 void game_state_increase_ammo(GameState* state, uint16_t amount) {
+    furi_assert(state);
     state->ammo += amount;
 }
 
 uint16_t game_state_get_ammo(GameState* state) {
+    furi_assert(state);
     return state->ammo;
 }
 
 void game_state_update_time(GameState* state, uint32_t delta_time) {
+    furi_assert(state);
     state->game_time += delta_time;
 }
 
 uint32_t game_state_get_time(GameState* state) {
+    furi_assert(state);
     return state->game_time;
 }
 
 bool game_state_is_game_over(GameState* state) {
+    furi_assert(state);
     return state->game_over;
 }
 
 void game_state_set_game_over(GameState* state, bool game_over) {
+    furi_assert(state);
     state->game_over = game_over;
-}
+}

+ 3 - 3
game_state.h

@@ -1,3 +1,4 @@
+
 #pragma once
 
 #include <stdint.h>
@@ -16,7 +17,6 @@ typedef enum {
 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);
@@ -40,5 +40,5 @@ 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
+#define INITIAL_AMMO   100
+#define MAX_HEALTH     100

+ 121 - 25
infrared_controller.c

@@ -5,10 +5,7 @@
 #include <infrared_worker.h>
 #include <stdlib.h>
 
-#define TAG "LaserTagInfrared"
-
-#define IR_COMMAND_RED_TEAM 0xA1
-#define IR_COMMAND_BLUE_TEAM 0xB2
+#define TAG "InfraredController"
 
 struct InfraredController {
     LaserTagTeam team;
@@ -16,97 +13,196 @@ struct InfraredController {
     FuriThread* rx_thread;
     volatile bool rx_running;
     volatile bool hit_received;
+    FuriMutex* mutex;
 };
 
 static void infrared_rx_callback(void* context, InfraredWorkerSignal* received_signal) {
+    FURI_LOG_D(TAG, "RX callback triggered");
+    furi_assert(context);
+    furi_assert(received_signal);
+
     InfraredController* controller = (InfraredController*)context;
-    
+    furi_mutex_acquire(controller->mutex, FuriWaitForever);
+
+    FURI_LOG_D(TAG, "Context and received signal validated");
+
     const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
-    if (message != NULL) {
+    if(message != NULL) {
         uint32_t received_command = message->address;
+        FURI_LOG_D(TAG, "Received command: 0x%lx", (unsigned long)received_command);
+
         if((controller->team == TeamRed && received_command == IR_COMMAND_BLUE_TEAM) ||
            (controller->team == TeamBlue && received_command == IR_COMMAND_RED_TEAM)) {
             controller->hit_received = true;
+            FURI_LOG_D(
+                TAG, "Hit detected for team: %s", controller->team == TeamRed ? "Red" : "Blue");
         }
+    } else {
+        FURI_LOG_E(TAG, "Received NULL message");
     }
+
+    furi_mutex_release(controller->mutex);
 }
 
 static int32_t infrared_rx_thread(void* context) {
+    FURI_LOG_D(TAG, "RX thread started");
+    furi_assert(context);
+
     InfraredController* controller = (InfraredController*)context;
-    
+
     while(controller->rx_running) {
-        infrared_worker_rx_start(controller->worker);
-        furi_thread_flags_wait(0, FuriFlagWaitAny, 10);
+        furi_mutex_acquire(controller->mutex, FuriWaitForever);
+        FURI_LOG_D(TAG, "Starting infrared_worker_rx_start");
+
+        // Check for worker validity before starting
+        if(controller->worker) {
+            infrared_worker_rx_start(controller->worker);
+            FURI_LOG_D(TAG, "infrared_worker_rx_start succeeded");
+        } else {
+            FURI_LOG_E(TAG, "InfraredWorker is NULL");
+            furi_mutex_release(controller->mutex);
+            continue;
+        }
+
+        furi_mutex_release(controller->mutex);
+
+        FURI_LOG_D(TAG, "Waiting for thread flags");
+        FuriStatus status = furi_thread_flags_wait(0, FuriFlagWaitAny, 10);
+
+        if(status == FuriStatusErrorTimeout) {
+            FURI_LOG_D(TAG, "RX loop timeout, continuing");
+        } else {
+            FURI_LOG_D(TAG, "RX loop received flag: %d", status);
+        }
     }
 
+    FURI_LOG_D(TAG, "RX thread stopping");
     return 0;
 }
 
 InfraredController* infrared_controller_alloc() {
+    FURI_LOG_D(TAG, "Allocating InfraredController");
+
     InfraredController* controller = malloc(sizeof(InfraredController));
+    if(!controller) {
+        FURI_LOG_E(TAG, "Failed to allocate InfraredController struct");
+        return NULL;
+    }
+    FURI_LOG_D(TAG, "InfraredController struct allocated");
+
     controller->team = TeamRed;
+    FURI_LOG_D(TAG, "Team initialized to Red");
+
+    FURI_LOG_D(TAG, "Allocating InfraredWorker");
     controller->worker = infrared_worker_alloc();
+    if(!controller->worker) {
+        FURI_LOG_E(TAG, "Failed to allocate InfraredWorker");
+        free(controller);
+        return NULL;
+    }
+    FURI_LOG_D(TAG, "InfraredWorker allocated");
+
     controller->rx_running = true;
     controller->hit_received = false;
 
-    infrared_worker_rx_set_received_signal_callback(controller->worker, infrared_rx_callback, controller);
+    FURI_LOG_D(TAG, "Creating mutex");
+    controller->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!controller->mutex) {
+        FURI_LOG_E(TAG, "Failed to create mutex");
+        infrared_worker_free(controller->worker);
+        free(controller);
+        return NULL;
+    }
 
+    FURI_LOG_D(TAG, "Setting up RX callback");
+    infrared_worker_rx_set_received_signal_callback(
+        controller->worker, infrared_rx_callback, controller);
+
+    FURI_LOG_D(TAG, "Allocating RX thread");
     controller->rx_thread = furi_thread_alloc();
+    if(!controller->rx_thread) {
+        FURI_LOG_E(TAG, "Failed to allocate RX thread");
+        furi_mutex_free(controller->mutex);
+        infrared_worker_free(controller->worker);
+        free(controller);
+        return NULL;
+    }
+
     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_LOG_D(TAG, "Starting RX thread");
     furi_thread_start(controller->rx_thread);
 
+    FURI_LOG_D(TAG, "Starting InfraredWorker RX");
     infrared_worker_rx_start(controller->worker);
 
+    FURI_LOG_D(TAG, "InfraredController allocated successfully");
     return controller;
 }
 
 void infrared_controller_free(InfraredController* controller) {
+    FURI_LOG_D(TAG, "Freeing InfraredController");
     furi_assert(controller);
 
     controller->rx_running = false;
+    FURI_LOG_D(TAG, "Stopping RX thread");
     furi_thread_join(controller->rx_thread);
     furi_thread_free(controller->rx_thread);
 
+    FURI_LOG_D(TAG, "Stopping InfraredWorker RX");
+    furi_mutex_acquire(controller->mutex, FuriWaitForever);
     infrared_worker_rx_stop(controller->worker);
     infrared_worker_free(controller->worker);
+    furi_mutex_release(controller->mutex);
+
+    furi_mutex_free(controller->mutex);
     free(controller);
+    FURI_LOG_D(TAG, "InfraredController freed");
 }
 
 void infrared_controller_set_team(InfraredController* controller, LaserTagTeam team) {
     furi_assert(controller);
+    furi_mutex_acquire(controller->mutex, FuriWaitForever);
     controller->team = team;
+    furi_mutex_release(controller->mutex);
+    FURI_LOG_D(TAG, "Team set to %s", (team == TeamRed) ? "Red" : "Blue");
 }
 
 void infrared_controller_send(InfraredController* controller) {
+    FURI_LOG_D(TAG, "Sending IR signal");
     furi_assert(controller);
+
+    furi_mutex_acquire(controller->mutex, FuriWaitForever);
+
     uint32_t command = (controller->team == TeamRed) ? IR_COMMAND_RED_TEAM : IR_COMMAND_BLUE_TEAM;
     InfraredMessage message = {
-        .protocol = InfraredProtocolNEC,
-        .address = 0x00,
-        .command = command,
-        .repeat = false
-    };
-    
+        .protocol = InfraredProtocolNEC, .address = 0x00, .command = command, .repeat = false};
+
     infrared_worker_set_decoded_signal(controller->worker, &message);
-    
+
+    FURI_LOG_D(TAG, "Starting IR transmission");
     infrared_worker_tx_set_get_signal_callback(
-        controller->worker,
-        infrared_worker_tx_get_signal_steady_callback,
-        NULL);
-        
+        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
-    
+
+    furi_delay_ms(250);
+
     infrared_worker_tx_stop(controller->worker);
+    FURI_LOG_D(TAG, "IR signal sent");
+
+    furi_mutex_release(controller->mutex);
 }
 
 bool infrared_controller_receive(InfraredController* controller) {
     furi_assert(controller);
+    furi_mutex_acquire(controller->mutex, FuriWaitForever);
     bool hit = controller->hit_received;
     controller->hit_received = false;
+    furi_mutex_release(controller->mutex);
+    FURI_LOG_D(TAG, "Checking for hit: %s", hit ? "Hit received" : "No hit");
     return hit;
-}
+}

+ 3 - 30
infrared_controller.h

@@ -1,42 +1,15 @@
 #pragma once
 
 #include <stdbool.h>
-#include "game_state.h"  // For LaserTagTeam enum
+#include "game_state.h"
 
 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
+#define IR_COMMAND_RED_TEAM  0xA1
+#define IR_COMMAND_BLUE_TEAM 0xB2

+ 134 - 26
laser_tag_app.c

@@ -4,12 +4,10 @@
 #include "game_state.h"
 #include <furi.h>
 #include <gui/gui.h>
-#include <gui/view.h>
 #include <input/input.h>
 #include <notification/notification.h>
-#include <notification/notification_messages.h>
 
-#define TAG "LaserTag"
+#define TAG "LaserTagApp"
 
 struct LaserTagApp {
     Gui* gui;
@@ -21,64 +19,105 @@ struct LaserTagApp {
     InfraredController* ir_controller;
     GameState* game_state;
     LaserTagState state;
+    bool need_redraw;
 };
 
-const NotificationSequence sequence_vibro_1 = {
-    &message_vibro_on,
-    &message_vibro_off,
-    NULL
-};
+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);
+    FURI_LOG_D(TAG, "Timer callback triggered");
+    if(app->game_state) {
+        game_state_update_time(app->game_state, 1);
+        FURI_LOG_D(TAG, "Updated game time by 1");
+        if(app->view) {
+            FURI_LOG_D(TAG, "Updating view with the latest game state");
+            laser_tag_view_update(app->view, app->game_state);
+            app->need_redraw = true;
+        }
+    }
 }
 
 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);
+    FURI_LOG_D(
+        TAG, "Input callback triggered: type=%d, key=%d", input_event->type, input_event->key);
+    furi_message_queue_put(app->event_queue, input_event, 0);
+    FURI_LOG_D(TAG, "Input event queued successfully");
 }
 
 static void laser_tag_app_draw_callback(Canvas* canvas, void* context) {
     furi_assert(context);
     LaserTagApp* app = context;
+    FURI_LOG_D(TAG, "Entering draw callback");
+
     if(app->state == LaserTagStateTeamSelect) {
+        FURI_LOG_D(TAG, "Drawing team selection screen");
         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 {
+    } else if(app->view) {
+        FURI_LOG_D(TAG, "Drawing game view");
         laser_tag_view_draw(laser_tag_view_get_view(app->view), canvas);
     }
+    FURI_LOG_D(TAG, "Exiting draw callback");
 }
 
 LaserTagApp* laser_tag_app_alloc() {
+    FURI_LOG_D(TAG, "Allocating Laser Tag App");
     LaserTagApp* app = malloc(sizeof(LaserTagApp));
+    if(!app) {
+        FURI_LOG_E(TAG, "Failed to allocate LaserTagApp");
+        return NULL;
+    }
+    FURI_LOG_D(TAG, "LaserTagApp struct allocated");
+
+    memset(app, 0, 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->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+
+    if(!app->gui || !app->view_port || !app->view || !app->notifications || !app->game_state ||
+       !app->event_queue) {
+        FURI_LOG_E(TAG, "Failed to allocate resources");
+        laser_tag_app_free(app);
+        return NULL;
+    }
+
     app->state = LaserTagStateTeamSelect;
+    app->need_redraw = true;
+    FURI_LOG_D(TAG, "Initial state set");
 
     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);
+    FURI_LOG_D(TAG, "ViewPort callbacks set and added to GUI");
 
     app->timer = furi_timer_alloc(laser_tag_app_timer_callback, FuriTimerTypePeriodic, app);
+    if(!app->timer) {
+        FURI_LOG_E(TAG, "Failed to allocate timer");
+        laser_tag_app_free(app);
+        return NULL;
+    }
+    FURI_LOG_D(TAG, "Timer allocated");
+
     furi_timer_start(app->timer, furi_kernel_get_tick_frequency());
+    FURI_LOG_D(TAG, "Timer started");
 
+    FURI_LOG_D(TAG, "Laser Tag App allocated successfully");
     return app;
 }
 
 void laser_tag_app_free(LaserTagApp* app) {
+    FURI_LOG_D(TAG, "Freeing Laser Tag App");
     furi_assert(app);
 
     furi_timer_free(app->timer);
@@ -87,53 +126,107 @@ void laser_tag_app_free(LaserTagApp* app) {
     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);
-
+    if(app->ir_controller) {
+        infrared_controller_free(app->ir_controller);
+    }
+    free(app->game_state);
     furi_record_close(RECORD_GUI);
     furi_record_close(RECORD_NOTIFICATION);
 
     free(app);
+    FURI_LOG_D(TAG, "Laser Tag App freed");
 }
 
 void laser_tag_app_fire(LaserTagApp* app) {
+    furi_assert(app);
+    FURI_LOG_D(TAG, "Firing laser");
+
+    if(!app->ir_controller) {
+        FURI_LOG_E(TAG, "IR controller is NULL in laser_tag_app_fire");
+        return;
+    }
+
+    FURI_LOG_D(TAG, "Sending infrared signal");
     infrared_controller_send(app->ir_controller);
+    FURI_LOG_D(TAG, "Decreasing ammo by 1");
     game_state_decrease_ammo(app->game_state, 1);
+    FURI_LOG_D(TAG, "Notifying with blink blue");
     notification_message(app->notifications, &sequence_blink_blue_100);
+    app->need_redraw = true;
 }
 
 void laser_tag_app_handle_hit(LaserTagApp* app) {
+    furi_assert(app);
+    FURI_LOG_D(TAG, "Handling hit");
+
+    FURI_LOG_D(TAG, "Decreasing health by 10");
     game_state_decrease_health(app->game_state, 10);
+    FURI_LOG_D(TAG, "Notifying with vibration");
     notification_message(app->notifications, &sequence_vibro_1);
+    app->need_redraw = true;
 }
 
-void laser_tag_app_enter_game_state(LaserTagApp* app) {
+static bool laser_tag_app_enter_game_state(LaserTagApp* app) {
+    furi_assert(app);
+    FURI_LOG_D(TAG, "Entering game state");
+
     app->state = LaserTagStateGame;
+    FURI_LOG_D(TAG, "Resetting game state");
     game_state_reset(app->game_state);
+    FURI_LOG_D(TAG, "Updating view with new game state");
     laser_tag_view_update(app->view, app->game_state);
+
+    FURI_LOG_D(TAG, "Allocating IR controller");
+    app->ir_controller = infrared_controller_alloc();
+    if(!app->ir_controller) {
+        FURI_LOG_E(TAG, "Failed to allocate IR controller");
+        return false;
+    }
+    FURI_LOG_D(TAG, "IR controller allocated");
+
+    FURI_LOG_D(TAG, "Setting IR controller team");
+    infrared_controller_set_team(app->ir_controller, game_state_get_team(app->game_state));
+    app->need_redraw = true;
+    return true;
 }
 
 int32_t laser_tag_app(void* p) {
     UNUSED(p);
+    FURI_LOG_I(TAG, "Laser Tag app starting");
+
     LaserTagApp* app = laser_tag_app_alloc();
+    if(!app) {
+        FURI_LOG_E(TAG, "Failed to allocate application");
+        return -1;
+    }
+    FURI_LOG_D(TAG, "LaserTagApp allocated successfully");
 
+    FURI_LOG_D(TAG, "Entering main loop");
     InputEvent event;
     bool running = true;
     while(running) {
+        FURI_LOG_D(TAG, "Start of main loop iteration");
+
         FuriStatus status = furi_message_queue_get(app->event_queue, &event, 100);
         if(status == FuriStatusOk) {
-            if(event.type == InputTypePress) {
+            FURI_LOG_D(TAG, "Received input event: type=%d, key=%d", event.type, event.key);
+            if(event.type == InputTypePress || event.type == InputTypeRepeat) {
+                FURI_LOG_D(TAG, "Processing input event");
                 if(app->state == LaserTagStateTeamSelect) {
                     switch(event.key) {
                     case InputKeyLeft:
-                        infrared_controller_set_team(app->ir_controller, TeamRed);
+                        FURI_LOG_D(TAG, "Selected Red Team");
                         game_state_set_team(app->game_state, TeamRed);
-                        laser_tag_app_enter_game_state(app);
+                        if(!laser_tag_app_enter_game_state(app)) {
+                            running = false;
+                        }
                         break;
                     case InputKeyRight:
-                        infrared_controller_set_team(app->ir_controller, TeamBlue);
+                        FURI_LOG_D(TAG, "Selected Blue Team");
                         game_state_set_team(app->game_state, TeamBlue);
-                        laser_tag_app_enter_game_state(app);
+                        if(!laser_tag_app_enter_game_state(app)) {
+                            running = false;
+                        }
                         break;
                     default:
                         break;
@@ -141,9 +234,11 @@ int32_t laser_tag_app(void* p) {
                 } else {
                     switch(event.key) {
                     case InputKeyBack:
+                        FURI_LOG_D(TAG, "Exiting game");
                         running = false;
                         break;
                     case InputKeyOk:
+                        FURI_LOG_D(TAG, "Firing laser");
                         laser_tag_app_fire(app);
                         break;
                     default:
@@ -151,24 +246,37 @@ int32_t laser_tag_app(void* p) {
                     }
                 }
             }
+        } else if(status == FuriStatusErrorTimeout) {
+            FURI_LOG_D(TAG, "No input event, continuing");
+        } else {
+            FURI_LOG_E(TAG, "Failed to get input event, status: %d", status);
         }
 
-        if(app->state == LaserTagStateGame) {
+        if(app->state == LaserTagStateGame && app->ir_controller) {
+            FURI_LOG_D(TAG, "Game is active");
             if(infrared_controller_receive(app->ir_controller)) {
+                FURI_LOG_D(TAG, "Hit received");
                 laser_tag_app_handle_hit(app);
             }
 
             if(game_state_is_game_over(app->game_state)) {
+                FURI_LOG_D(TAG, "Game over");
                 notification_message(app->notifications, &sequence_error);
                 running = false;
             }
+        }
 
-            laser_tag_view_update(app->view, app->game_state);
+        if(app->need_redraw) {
+            FURI_LOG_D(TAG, "Updating view port");
+            view_port_update(app->view_port);
+            app->need_redraw = false;
         }
 
-        view_port_update(app->view_port);
+        FURI_LOG_D(TAG, "End of main loop iteration");
+        furi_delay_ms(10);
     }
 
+    FURI_LOG_I(TAG, "Laser Tag app exiting");
     laser_tag_app_free(app);
     return 0;
 }

+ 3 - 8
laser_tag_app.h

@@ -1,3 +1,4 @@
+
 #pragma once
 
 #include <furi.h>
@@ -11,21 +12,15 @@
 #include <gui/modules/variable_item_list.h>
 #include <gui/modules/button_menu.h>
 
-#define FRAME_WIDTH 128
+#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);
+void laser_tag_app_handle_hit(LaserTagApp* app);

+ 98 - 49
laser_tag_icons.c

@@ -1,70 +1,119 @@
+
 #include "laser_tag_icons.h"
 #include <gui/icon_i.h>
 
 const uint8_t laser_gun_icon_data[] = {
-    0b00000000, 0b00000000,
-    0b00000001, 0b10000000,
-    0b00000011, 0b11000000,
-    0b00000111, 0b11100000,
-    0b00001111, 0b11110000,
-    0b00011111, 0b11111000,
-    0b11111111, 0b11111110,
-    0b11111111, 0b11111111,
+    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,
+    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,
+    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,
+    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,
+    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,
+    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};
@@ -120,4 +169,4 @@ const Icon I_game_over_icon = {
     .frame_count = 1,
     .frame_rate = 0,
     .frames = game_over_icon_frames,
-};
+};

+ 5 - 4
laser_tag_icons.h

@@ -1,3 +1,4 @@
+
 #pragma once
 
 #include <gui/icon.h>
@@ -10,8 +11,8 @@ 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 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)
+#define GAME_OVER_ICON (&I_game_over_icon)

+ 40 - 10
laser_tag_view.c

@@ -1,3 +1,4 @@
+
 #include "laser_tag_view.h"
 #include "laser_tag_icons.h"
 #include <furi.h>
@@ -18,25 +19,40 @@ typedef struct {
 
 static void laser_tag_view_draw_callback(Canvas* canvas, void* model) {
     LaserTagViewModel* m = model;
+    furi_assert(m);
+    furi_assert(canvas);
 
     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)));
+    FuriString* str = furi_string_alloc_printf("%d", m->health);
+    canvas_draw_str_aligned(canvas, 20, 14, AlignLeft, AlignBottom, furi_string_get_cstr(str));
+
     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)));
+    furi_string_reset(str);
+    furi_string_printf(str, "%d", m->ammo);
+    canvas_draw_str_aligned(canvas, 20, 24, AlignLeft, AlignBottom, furi_string_get_cstr(str));
+
     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)));
+    furi_string_reset(str);
+    furi_string_printf(str, "%d", m->score);
+    canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignBottom, furi_string_get_cstr(str));
+
     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)));
+    furi_string_reset(str);
+    furi_string_printf(str, "%02ld:%02ld", minutes, seconds);
+    canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, furi_string_get_cstr(str));
+
     canvas_draw_icon(canvas, 112, 0, LASER_GUN_ICON);
 
     if(m->game_over) {
         canvas_draw_icon(canvas, 56, 28, GAME_OVER_ICON);
     }
+
+    furi_string_free(str);
 }
 
 static bool laser_tag_view_input_callback(InputEvent* event, void* context) {
@@ -47,26 +63,40 @@ static bool laser_tag_view_input_callback(InputEvent* event, void* context) {
 
 LaserTagView* laser_tag_view_alloc() {
     LaserTagView* laser_tag_view = malloc(sizeof(LaserTagView));
+    if(!laser_tag_view) {
+        return NULL;
+    }
+
     laser_tag_view->view = view_alloc();
+    if(!laser_tag_view->view) {
+        free(laser_tag_view);
+        return NULL;
+    }
+
     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_free(LaserTagView* laser_tag_view) {
+    if(!laser_tag_view) return;
+    if(laser_tag_view->view) {
+        view_free(laser_tag_view->view);
+    }
+    free(laser_tag_view);
+}
+
 void laser_tag_view_draw(View* view, Canvas* canvas) {
+    furi_assert(view);
+    furi_assert(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;

+ 3 - 6
laser_tag_view.h

@@ -1,3 +1,4 @@
+
 #pragma once
 
 #include <gui/view.h>
@@ -6,11 +7,7 @@
 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);
-
+void laser_tag_view_draw(View* view, Canvas* canvas);
 View* laser_tag_view_get_view(LaserTagView* laser_tag_view);
-
-void laser_tag_view_update(LaserTagView* laser_tag_view, GameState* game_state);
+void laser_tag_view_update(LaserTagView* laser_tag_view, GameState* game_state);