Mon, 04 Aug 2025 23:07:06 +0200
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();