add simple game over screen default tip

Sat, 16 Aug 2025 22:59:49 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 16 Aug 2025 22:59:49 +0200
changeset 265
5c915d01bdc0
parent 264
653869cd653f

add simple game over screen

src/ascension/behavior.h file | annotate | diff | comparison | revisions
src/ascension/text.h file | annotate | diff | comparison | revisions
src/behavior.c file | annotate | diff | comparison | revisions
src/text.c file | annotate | diff | comparison | revisions
test/snake/snake.c file | annotate | diff | comparison | revisions
--- a/src/ascension/behavior.h	Wed Aug 13 23:55:55 2025 +0200
+++ b/src/ascension/behavior.h	Sat Aug 16 22:59:49 2025 +0200
@@ -127,4 +127,8 @@
     behavior->enabled = false;
 }
 
+void asc_behavior_enable_all(AscSceneNode *node);
+
+void asc_behavior_disable_all(AscSceneNode *node);
+
 #endif
--- a/src/ascension/text.h	Wed Aug 13 23:55:55 2025 +0200
+++ b/src/ascension/text.h	Sat Aug 16 22:59:49 2025 +0200
@@ -55,7 +55,7 @@
 
 enum asc_text_alignment {
     ASC_TEXT_ALIGN_LEFT = 0x00,
-    ASC_TEXT_ALIGN_CENTERED = 0x01,
+    ASC_TEXT_ALIGN_CENTER = 0x01,
     ASC_TEXT_ALIGN_RIGHT = 0x02
 };
 #define ASC_TEXT_ALIGNMENT_MASK 0x03
--- a/src/behavior.c	Wed Aug 13 23:55:55 2025 +0200
+++ b/src/behavior.c	Sat Aug 16 22:59:49 2025 +0200
@@ -98,3 +98,19 @@
     assert(behavior != NULL);
     return cx_strcast(behavior->name);
 }
+
+// TODO: add asc_behavior_find() and asc_behavior_find_global()
+
+void asc_behavior_enable_all(AscSceneNode *node) {
+    CxIterator iter = cxListIterator(node->behaviors);
+    cx_foreach(AscBehavior*, behavior, iter) {
+        behavior->enabled = true;
+    }
+}
+
+void asc_behavior_disable_all(AscSceneNode *node) {
+    CxIterator iter = cxListIterator(node->behaviors);
+    cx_foreach(AscBehavior*, behavior, iter) {
+        behavior->enabled = false;
+    }
+}
\ No newline at end of file
--- a/src/text.c	Wed Aug 13 23:55:55 2025 +0200
+++ b/src/text.c	Sat Aug 16 22:59:49 2025 +0200
@@ -192,9 +192,9 @@
 void asc_text_centered(AscText *node, bool centered) {
     asc_ptr_cast(AscSceneNode, snode, node);
     if (centered) {
-        asc_clear_flag(snode->flags, ASC_TEXT_ALIGN_CENTERED);
+        asc_clear_flag(snode->flags, ASC_TEXT_ALIGN_CENTER);
     } else {
-        asc_set_flag(snode->flags, ASC_TEXT_ALIGN_CENTERED);
+        asc_set_flag(snode->flags, ASC_TEXT_ALIGN_CENTER);
     }
     asc_scene_node_update(snode);
 }
--- a/test/snake/snake.c	Wed Aug 13 23:55:55 2025 +0200
+++ b/test/snake/snake.c	Sat Aug 16 22:59:49 2025 +0200
@@ -72,6 +72,7 @@
      * The new position of the player when @c reset_position is @c true.
      */
     asc_vec2i new_position;
+    unsigned health;
     bool reset_position;
     uint8_t number;
 } Player;
@@ -89,6 +90,19 @@
     int8_t tile_data[GAME_FIELD_SIZE][GAME_FIELD_SIZE];
 } GameField;
 
+
+#define GAME_STATE_MENU 0
+#define GAME_STATE_PLAYING 1
+#define GAME_STATE_GAME_OVER 2
+
+typedef struct {
+    int state;
+    GameField *field;
+    Player *players[4];
+} GameState;
+
+GameState game = {0};
+
 static void globals_init(void) {
     asc_transform_identity(rotations[MOVE_UP]);
     asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90));
@@ -174,28 +188,25 @@
     return node;
 }
 
-
-static GameField *game_field;
-
 static bool game_field_tile_chown(asc_vec2u coords, Player *player) {
     unsigned x = coords.x, y = coords.y;
-    asc_ptr_cast(AscRectangle, tile, game_field->nodes[x][y]);
-    int old_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
+    asc_ptr_cast(AscRectangle, tile, game.field->nodes[x][y]);
+    int old_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
     if (player == NULL) {
-        asc_clear_flag(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK);
+        asc_clear_flag(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK);
         tile->color = ASC_RGBi(16, 50, 160);
     } else {
-        asc_set_flag_masked(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number);
+        asc_set_flag_masked(game.field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK, player->number);
         tile->color = player->color;
     }
-    int new_owner = game_field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
+    int new_owner = game.field->tile_data[x][y] & GAME_FIELD_TILE_OWNER_MASK;
     return old_owner != new_owner;
 }
 
-static GameField *game_field_create() {
+static void game_field_create() {
     // TODO: create a more interesting map than just a basic grid
     AscSceneNode *node = asc_scene_node_empty();
-    game_field = asc_scene_node_allocate_data(node, sizeof(GameField));
+    game.field = asc_scene_node_allocate_data(node, sizeof(GameField));
     for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) {
         for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) {
             AscSceneNode *tile = asc_rectangle(
@@ -205,15 +216,14 @@
                 .border_color = ASC_RGBi(20, 84, 128),
             );
 
-            game_field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG;
-            game_field->nodes[x][y] = tile;
+            game.field->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG;
+            game.field->nodes[x][y] = tile;
 
             asc_scene_node_link(node, tile);
         }
     }
     asc_scene_node_set_zindex(node, -2);
     asc_scene_add_node(MAIN_SCENE, node);
-    return game_field;
 }
 
 static asc_vec2u game_field_tile_at_position(asc_vec3f position) {
@@ -260,9 +270,25 @@
     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 unsigned player_get_health(Player *player) {
+    return player->health;
+}
+
 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
@@ -322,6 +348,19 @@
         }
     }
 
+    // die when leaving the game field
+    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;
+        return;
+    }
+
     // TODO: collision detection
 
     // update the trace, if necessary.
@@ -336,8 +375,7 @@
             cxListAdd(player->trace, &p);
             if (cxListSize(player->trace) > 7) {
                 // TODO: implement power-up which makes the trace longer
-                cxListPopFront(player->trace, &p);
-                game_field_tile_chown(p, NULL);
+                cxListRemove(player->trace, 0);
             }
         }
     }
@@ -377,6 +415,10 @@
     cxFree(allocator, player);
 }
 
+static void player_trace_release_tile(asc_vec2u *coords) {
+    game_field_tile_chown(*coords, NULL);
+}
+
 static Player *player_create(void) {
     AscSceneNode *node = asc_sprite(
         .name = "Player",
@@ -390,8 +432,10 @@
     player_position(player, 12, 8);
     player->speed = 3.f; // start with 3 tiles/sec
     player->number = 1;
+    player->health = 100;
     player->color = ASC_RGB(1, 0, 0);
-    player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2i));
+    player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2u));
+    cxDefineDestructor(player->trace, player_trace_release_tile);
     node->draw_func = player_draw;
     node->user_data_free_func = (cx_destructor_func2)player_destroy;
     asc_behavior_add(node, player_move);
@@ -435,16 +479,6 @@
     return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size);
 }
 
-#define GAME_STATE_MENU 0
-#define GAME_STATE_PLAYING 1
-#define GAME_STATE_GAME_OVER 2
-
-typedef struct {
-    int state;
-    GameField *field;
-    Player *players[4];
-} GameState;
-
 int main(void) {
     asc_context_initialize();
     if (asc_has_error()) {
@@ -491,12 +525,27 @@
     AscSceneNode *fps_counter = fps_counter_create();
     asc_scene_node_hide(fps_counter);
 
-    // create the game state
-    GameState game = {0};
+    // create game over text
+    AscSceneNode *text_game_over = asc_text(
+        .name = "game_over_text",
+        .text = "Game Over\nPress R to Restart",
+        .color = ASC_RGB(1, 1, 1),
+        .font = asc_font(ASC_FONT_REGULAR, 36),
+        .alignment = ASC_TEXT_ALIGN_CENTER,
+        .centered = true,
+        .x = (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE/2,
+        .y = (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE/2 - 60,
+    );
+    asc_scene_node_hide(text_game_over);
+    // TODO: add as a UI node and add a behavior which centers the node in the main scenes viewport
+    //       otherwise we won't be able to implement a moving camera in the future
+    asc_scene_add_node(MAIN_SCENE, text_game_over);
+
+    // initialize the game state
     // TODO: add a main menu and start with the menu
     game.state = GAME_STATE_PLAYING;
-    game.field = game_field_create();
     game.players[0] = player_create();
+    game_field_create();
 
     // Main Loop
     do {
@@ -510,9 +559,19 @@
         }
 
         // game states
+        // TODO: move all this into behaviors
         if (game.state == GAME_STATE_PLAYING) {
             // TODO: implement hot seat 1on1 multiplayer
             player_controls(game.players[0]);
+        } else if (game.state == GAME_STATE_GAME_OVER) {
+            asc_scene_node_show(text_game_over);
+            if (asc_key_pressed(ASC_KEY(R))) {
+                // TODO: re-load the "level"
+                player_position(game.players[0], 12, 8);
+                player_set_health(game.players[0], 100);
+                game.state = GAME_STATE_PLAYING;
+                asc_scene_node_hide(text_game_over);
+            }
         }
 
         // debug-key for clearing the shader registry

mercurial