Sat, 16 Aug 2025 22:59:49 +0200
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