add player's trace and removes the origin offsets from the game field's tiles

Mon, 04 Aug 2025 23:07:06 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 04 Aug 2025 23:07:06 +0200
changeset 247
3547254742a7
parent 246
718b9bc3a6cd
child 248
793a0108c6c4

add player's trace and removes the origin offsets from the game field's tiles

test/snake/snake.c file | annotate | diff | comparison | revisions
--- a/test/snake/snake.c	Mon Aug 04 23:06:37 2025 +0200
+++ b/test/snake/snake.c	Mon Aug 04 23:07:06 2025 +0200
@@ -32,6 +32,7 @@
 #include <ascension/shader.h>
 
 #include <cx/printf.h>
+#include <cx/linked_list.h>
 
 #define TEXTURE_2D_COUNT 3
 static AscTexture tex2d[TEXTURE_2D_COUNT];
@@ -64,8 +65,16 @@
      * The speed in tiles per second.
      */
     float speed;
+    /**
+     * A linked list of vec2u elements describing the current trace.
+     */
+    CxList *trace;
+    /**
+     * The new position of the player when @c reset_position is @c true.
+     */
     asc_vec2i new_position;
     bool reset_position;
+    uint8_t number;
 } Player;
 
 #define GAME_FIELD_SIZE 32
@@ -169,6 +178,51 @@
     asc_ui_add_node(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;
+    if (player == NULL) {
+        asc_clear_flag(game_field->tile_data[x][y], GAME_FIELD_TILE_OWNER_MASK);
+        tile->color = asc_col_itof(ASC_RGB(16, 50, 160));
+    } else {
+        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;
+    return old_owner != new_owner;
+}
+
+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));
+    for (unsigned x = 0; x < GAME_FIELD_SIZE; x++) {
+        for (unsigned y = 0; y < GAME_FIELD_SIZE; y++) {
+            AscSceneNode *tile = asc_rectangle(
+                .x = x*GAME_FIELD_TILE_SIZE, .y = y*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2,
+                .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE,
+                .color = ASC_RGB(16, 50, 160),
+                .border_color = ASC_RGB(20, 84, 128),
+            );
+
+            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);
+}
+
+static asc_vec2u game_field_tile_at_position(asc_vec3f position) {
+    return ASC_VEC2U((int)position.x / GAME_FIELD_TILE_SIZE, (int)position.y / GAME_FIELD_TILE_SIZE);
+}
+
 typedef struct {
     AscShaderProgram program;
     asc_uniform_loc map_albedo;
@@ -212,23 +266,24 @@
 static void player_move(AscBehavior *behavior) {
     AscSceneNode *node = behavior->node;
     Player *player = node->user_data;
+    const float ts = (float) GAME_FIELD_TILE_SIZE;
 
     // check if the position is set programmatically
     if (player->reset_position) {
         asc_scene_node_set_position2f(node,
             ASC_VEC2F(
-                GAME_FIELD_TILE_SIZE * player->new_position.x,
-                GAME_FIELD_TILE_SIZE * player->new_position.y));
+                ts * (player->new_position.x + .5f),
+                ts * (player->new_position.y + .5f)
+            ));
         player->reset_position = false;
         return;
     }
 
     // normal movement
-    const int ts = (int) GAME_FIELD_TILE_SIZE;
-    const float fts = (float) ts;
-    const float speed = fts * player->speed * asc_context.frame_factor;
+    const float speed = ts * player->speed * asc_context.frame_factor;
     const asc_vec2i dir = directions[player->direction];
     const asc_vec2f movement = asc_vec2f_scale(asc_vec2_itof(dir), speed);
+
     // check if we are supposed to change the direction
     if (player->direction == player->target_direction) {
         // move without changing the direction
@@ -240,19 +295,28 @@
         bool rotate = false;
         if (movement.x == 0) {
             // vertical movement
-            const int y0 = (int)node->position.y / ts;
-            const int y1 = (int)(node->position.y+movement.y) / ts;
-            rotate = y0 != y1 && asc_sgn(y1-y0) == dir.y;
+            const float y_0 = floorf(node->position.y / ts);
+            const float y_curr = node->position.y / ts - y_0;
+            const float y_next = (node->position.y+movement.y) / ts - y_0;
+            const bool side_curr = y_curr > 0.5f;
+            const bool side_next = y_next > 0.5f;
+            rotate = side_curr ^ side_next;
         } else {
             // horizontal movement
-            const int x0 = (int)node->position.x / ts;
-            const int x1 = (int)(node->position.x+movement.x) / ts;
-            rotate = x0 != x1 && asc_sgn(x1-x0) == dir.x;
+            const float x0 = floorf(node->position.x / ts);
+            const float x_curr = node->position.x / ts - x0;
+            const float x_next = (node->position.x+movement.x) / ts - x0;
+            const bool side_curr = x_curr > 0.5f;
+            const bool side_next = x_next > 0.5f;
+            rotate = side_curr ^ side_next;
         }
         if (rotate) {
             // snap position to the center of the tile
-            node->position.x = roundf(node->position.x / fts) * fts;
-            node->position.y = roundf(node->position.y / fts) * fts;
+            asc_scene_node_set_position2f(node,
+            ASC_VEC2F(
+                (.5f+floorf(node->position.x / ts)) * ts,
+                (.5f+floorf(node->position.y / ts)) * ts
+            ));
             player->direction = player->target_direction;
             asc_scene_node_set_rotation(node, rotations[player->direction]);
         } else {
@@ -260,6 +324,26 @@
             asc_scene_node_move2f(node, movement);
         }
     }
+
+    // TODO: collision detection
+
+    // update the trace, if necessary.
+    // remark: some calculations are repeated here, but they are cheap enough
+    {
+        const asc_vec2u tile_coords = game_field_tile_at_position(node->position);
+        // TODO: player should have been destroyed before leaving the field
+        if (tile_coords.x > GAME_FIELD_SIZE || tile_coords.y > GAME_FIELD_SIZE) return;
+        if (game_field_tile_chown(tile_coords, player)) {
+            // new owner of the tile!
+            asc_vec2u p = tile_coords;
+            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);
+            }
+        }
+    }
 }
 
 static void player_position(Player *pl, int x, int y) {
@@ -268,48 +352,32 @@
     pl->reset_position = true;
 }
 
+static void player_destroy(CxAllocator *allocator, Player *player) {
+    cxListFree(player->trace);
+    cxFree(allocator, player);
+}
+
 static Player *player_create(void) {
-    AscSceneNode *sprite = asc_sprite(
+    AscSceneNode *node = asc_sprite(
         .name = "Player",
         .width = GAME_FIELD_TILE_SIZE,
         .height = GAME_FIELD_TILE_SIZE,
         .origin_x = GAME_FIELD_TILE_SIZE / 2,
         .origin_y = GAME_FIELD_TILE_SIZE / 2,
     );
-    asc_scene_add_node(MAIN_SCENE, sprite);
-    Player *player = asc_scene_node_allocate_data(sprite, sizeof(Player));
+    asc_scene_add_node(MAIN_SCENE, node);
+    Player *player = asc_scene_node_allocate_data(node, sizeof(Player));
     player_position(player, 12, 8);
     player->speed = 3.f; // start with 3 tiles/sec
+    player->number = 1;
     player->color = ASC_RGB_F(1, 0, 0);
-    sprite->draw_func = player_draw;
-    asc_behavior_add(sprite, player_move);
+    player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2i));
+    node->draw_func = player_draw;
+    node->user_data_free_func = (cx_destructor_func2)player_destroy;
+    asc_behavior_add(node, player_move);
     return player;
 }
 
-static void gamefield_create() {
-    // TODO: create a proper data structure and a more interesting map than just a basic grid
-    AscSceneNode *node = asc_scene_node_empty();
-    GameField *data = 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(
-                .x = (x+1)*GAME_FIELD_TILE_SIZE, .y = (y+1)*GAME_FIELD_TILE_SIZE, .filled = true, .thickness = 2,
-                .origin_x = GAME_FIELD_TILE_SIZE / 2, .origin_y = GAME_FIELD_TILE_SIZE / 2,
-                .width = GAME_FIELD_TILE_SIZE, .height = GAME_FIELD_TILE_SIZE,
-                .color = ASC_RGB(16, 50, 160),
-                .border_color = ASC_RGB(20, 84, 128),
-            );
-
-            data->tile_data[x][y] = GAME_FIELD_TILE_EXISTS_FLAG;
-            data->nodes[x][y] = tile;
-
-            asc_scene_node_link(node, tile);
-        }
-    }
-    asc_scene_node_set_zindex(node, -2);
-    asc_scene_add_node(MAIN_SCENE, node);
-}
-
 static asc_rect main_scene_viewport_update(asc_vec2u window_size) {
 
     // margins
@@ -377,9 +445,11 @@
     );
     asc_scene_init(MAIN_SCENE, "main",
         .type = ASC_CAMERA_ORTHO,
-        .ortho.rect = ASC_RECT(0, 0,
-            (GAME_FIELD_SIZE+1)*GAME_FIELD_TILE_SIZE,
-            (GAME_FIELD_SIZE+1)*GAME_FIELD_TILE_SIZE
+        .ortho.rect = ASC_RECT(
+            -GAME_FIELD_TILE_SIZE,
+            -GAME_FIELD_TILE_SIZE,
+            (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE,
+            (GAME_FIELD_SIZE+2)*GAME_FIELD_TILE_SIZE
         ),
         .viewport_clear = true,
         .clear_color = ASC_RGB(0, 32, 16),
@@ -390,7 +460,7 @@
     backdrop_create();
 
     // the game field
-    gamefield_create();
+    game_field_create();
 
     // create UI elements
     fps_counter_create();

mercurial