demo/snake/snake.c

changeset 274
ba7f043f9fdf
parent 273
966bfca56b9d
--- a/demo/snake/snake.c	Thu Aug 21 22:13:51 2025 +0200
+++ b/demo/snake/snake.c	Fri Aug 22 23:27:36 2025 +0200
@@ -72,8 +72,9 @@
     /**
      * The new position of the player when @c reset_position is @c true.
      */
-    asc_vec2i new_position;
+    asc_vec2u new_position;
     unsigned health;
+    bool alive;
     bool reset_position;
     uint8_t number;
 } Player;
@@ -272,25 +273,46 @@
     asc_mesh_draw_triangle_strip(&sprite->mesh);
 }
 
-static void player_set_health(Player *player, unsigned health) {
-    player->health = health;
-    // TODO: probably we want to add more effects when the health changes
+static void player_position(Player *pl, unsigned x, unsigned y) {
+    pl->new_position.x = x;
+    pl->new_position.y = y;
+    pl->reset_position = true;
+}
+
+static void player_position_random(Player *pl) {
+    // TODO: check if the spawn location is viable when there is more action on the board
+    player_position(
+        pl,
+        4u + asc_util_rand(GAME_FIELD_SIZE - 8u),
+        4u + asc_util_rand(GAME_FIELD_SIZE - 8u)
+    );
 }
 
-static unsigned player_get_health(Player *player) {
-    return player->health;
+static void player_main_behavior(AscBehavior *behavior) {
+    AscSceneNode *node = behavior->node;
+    Player *player = node->user_data;
+
+    if (player->alive && player->health == 0) {
+        player->alive = false;
+        asc_scene_node_hide(node);
+        // TODO: probably we don't want the entire trace to disappear instantly
+        cxListClear(player->trace);
+        // TODO: this should be controlled by another behavior that watches all players
+        game.state = GAME_STATE_GAME_OVER;
+    }
+
+    // TODO: replace with respawn event
+    if (!player->alive && player->health > 0) {
+        player_position_random(player);
+        player->alive = true;
+        asc_scene_node_show(node);
+    }
 }
 
 static void player_move(AscBehavior *behavior) {
     AscSceneNode *node = behavior->node;
     Player *player = node->user_data;
 
-    // TODO: instead of skipping this behavior, it should be disabled when health is zero
-    if (player_get_health(player) == 0) return;
-
-    // TODO: move this to a different behavior
-    asc_scene_node_show(node);
-
     const float ts = (float) GAME_FIELD_TILE_SIZE;
 
     // check if the position is set programmatically
@@ -354,12 +376,8 @@
     if (node->position.x < 0 || node->position.y < 0 ||
         node->position.x > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE ||
         node->position.y > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE) {
-        // TODO: add fancy death animation
-        asc_scene_node_hide(node);
-        player_set_health(player, 0);
-        // TODO: remove the trace gradually (dequeuing the trace should be a different behavior)
-        cxListClear(player->trace);
-        game.state = GAME_STATE_GAME_OVER;
+        // TODO: replace setting health to zero with a "kill" event
+        player->health = 0;
         return;
     }
 
@@ -383,9 +401,6 @@
 }
 
 static void player_controls(AscBehavior *behavior) {
-    // TODO: instead of checking the game state, disable the behavior when the player is dead
-    if (game.state != GAME_STATE_PLAYING) return;
-
     Player *player = behavior->node->user_data;
     // TODO: different key bindings for different player number in hot seat mode
     if (asc_key_pressed(ASC_KEY(LEFT))) {
@@ -410,21 +425,6 @@
     }
 }
 
-static void player_position(Player *pl, int x, int y) {
-    pl->new_position.x = x;
-    pl->new_position.y = y;
-    pl->reset_position = true;
-}
-
-static void player_position_random(Player *pl) {
-    // TODO: check if the spawn location is viable when there is more action on the board
-    player_position(
-        pl,
-        4 + asc_util_rand(GAME_FIELD_SIZE - 8),
-        4 + asc_util_rand(GAME_FIELD_SIZE - 8)
-    );
-}
-
 static void player_destroy(CxAllocator *allocator, Player *player) {
     cxListFree(player->trace);
     cxFree(allocator, player);
@@ -444,7 +444,6 @@
     );
     asc_scene_add_node(MAIN_SCENE, node);
     Player *player = asc_scene_node_allocate_data(node, sizeof(Player));
-    player_position_random(player);
     player->speed = 3.f; // start with 3 tiles/sec
     player->number = 1;
     player->health = 100;
@@ -453,9 +452,13 @@
     cxDefineDestructor(player->trace, player_trace_release_tile);
     node->draw_func = player_draw;
     node->user_data_free_func = (cx_destructor_func2)player_destroy;
-    // add behaviors (the order is important!)
+
+    // add behaviors
+    asc_behavior_add(node, player_main_behavior, .always_enabled = true);
     asc_behavior_add(node, player_controls);
     asc_behavior_add(node, player_move);
+    asc_behavior_pause_all_while_hidden(node);
+
     return player;
 }
 
@@ -580,9 +583,9 @@
         if (game.state == GAME_STATE_GAME_OVER) {
             asc_scene_node_show(text_game_over);
             if (asc_key_pressed(ASC_KEY(R))) {
+                // TODO: instead of setting the health, send a "respawn" event to the behavior
+                game.players[0]->health = 100;
                 // TODO: re-load the "level"
-                player_position_random(game.players[0]);
-                player_set_health(game.players[0], 100);
                 game.state = GAME_STATE_PLAYING;
                 asc_scene_node_hide(text_game_over);
             }

mercurial