test/snake/snake.c

Sun, 13 Jul 2025 17:17:15 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 13 Jul 2025 17:17:15 +0200
changeset 205
d1e44c861426
parent 204
be5cf64b5c29
permissions
-rw-r--r--

hack a quick example for both rotation directions

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * Copyright 2023 Mike Becker. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <ascension/core.h>
#include <ascension/ui.h>
#include <ascension/sprite.h>
#include <ascension/2d.h>

#include <cx/printf.h>

enum Textures2d {
    TEX_SHIP = 0,
    TEX_BACKDROP,
    TEX2D_COUNT
};
static AscTexture tex2d[TEX2D_COUNT];
#define TEXTURE_SHIP &tex2d[TEX_SHIP]
#define TEXTURE_BACKDROP &tex2d[TEX_BACKDROP]

#define BACKDROP_SCENE asc_window_scene(0)
#define MAIN_SCENE asc_window_scene(1)

static void destroy_textures(void) {
    asc_texture_destroy(tex2d, TEX2D_COUNT);
}

static void init_textures(void) {
    asc_texture_init_2d(tex2d, TEX2D_COUNT);
    asc_texture_from_file(TEXTURE_SHIP, "ship.png");
    asc_texture_from_file(TEXTURE_BACKDROP, "backdrop.png");
    asc_gl_context_add_cleanup_func(asc_active_glctx, destroy_textures);
}

static void update_fps_counter(AscBehavior *behavior) {
    asc_ptr_cast(AscText, node, behavior->node);
    static float last_fps = 0.f;
    if (fabsf(asc_context.frame_rate - last_fps) > 1) {
        last_fps = asc_context.frame_rate;
        asc_text_printf(node, "%.2f FPS", asc_context.frame_rate);
    }
}

static void tie_fps_counter_to_corner(AscBehavior *behavior) {
    // TODO: this should be replaced with some sort of UI layout manager
    AscSceneNode *node = behavior->node;
    if (asc_test_flag(node->flags, ASC_SCENE_NODE_GRAPHICS_UPDATED) || asc_active_window->resized) {
        asc_vec2u bottom_right = asc_active_window->dimensions;
        asc_vec2u text_size = ((AscText*)node)->dimension;
        asc_node_set_position2f(node, ASC_VEC2F(
                (int) bottom_right.x - (int) text_size.width - 10,
                (int) bottom_right.y - (int) text_size.height - 10
        ));
    }
}

static void scale_backdrop(AscBehavior *behavior) {
    // scale the backdrop to the size of the window
    if (asc_active_window->resized) {
        asc_ptr_cast(AscSprite, sprite, behavior->node);
        asc_vec2u window_size = asc_active_window->dimensions;
        asc_sprite_set_size(sprite, window_size);
    }
}

static void create_backdrop(void) {
    AscSceneNode *node = asc_sprite(
        .texture = TEXTURE_BACKDROP,
        .texture_scale_mode = ASC_TEXTURE_SCALE_REPEAT
    );
    asc_behavior_add(node, .func = scale_backdrop);
    asc_scene_add_node(BACKDROP_SCENE, node);
}

static void create_fps_counter(void) {
    asc_font(ASC_FONT_REGULAR, 12);
    asc_ink_rgb(255, 255, 255);
    AscSceneNode *node = asc_text(.name = "FPS Counter");
    asc_behavior_add(node, .func = update_fps_counter, .interval = asc_seconds(1));
    asc_behavior_add(node, tie_fps_counter_to_corner);
    asc_ui_add_node(node);
}

static void create_score_counter(void) {
    asc_font(ASC_FONT_BOLD, 16);
    asc_ink_rgb(0, 255, 0);
    AscSceneNode *node = asc_text(
        .name = "Score Counter",
        .x = 10, .y = 10,
        .text = "Score: 0"
    );
    asc_ui_add_node(node);
}

enum MoveDirection {
    MOVE_UP,
    MOVE_LEFT,
    MOVE_DOWN,
    MOVE_RIGHT
};

static asc_transform rotations[4];
static asc_vec2f directions[4];

typedef struct {
    AscSceneNode *sprite;
    enum MoveDirection direction;
} Spaceship;

static void turn_left(Spaceship *spaceship) {
    spaceship->direction = (spaceship->direction + 1) % 4;
    asc_node_set_rotation(spaceship->sprite, rotations[spaceship->direction]);
}

static void turn_right(Spaceship *spaceship) {
    spaceship->direction = (spaceship->direction + 3) % 4;
    asc_node_set_rotation(spaceship->sprite, rotations[spaceship->direction]);
}

static Spaceship *create_spaceship(void) {
    // TODO: this all belongs somewhere else, this is just quickly hacked into here for testing
    asc_transform_identity(rotations[MOVE_UP]);
    asc_transform_roll(rotations[MOVE_LEFT], asc_rad(-90));
    asc_transform_roll(rotations[MOVE_RIGHT], asc_rad(90));
    asc_transform_roll(rotations[MOVE_DOWN], asc_rad(180));
    directions[MOVE_UP] = ASC_VEC2F(0, -1);
    directions[MOVE_LEFT] = ASC_VEC2F(-1, 0);
    directions[MOVE_DOWN] = ASC_VEC2F(0, 1);
    directions[MOVE_RIGHT] = ASC_VEC2F(1, 0);
    AscSceneNode *sprite = asc_sprite(
        .name = "Player",
        .texture = TEXTURE_SHIP,
        .x = 250,
        .y = 300,
        .width = 64,
        .height = 64,
        .origin_x = 32,
        .origin_y = 32,
    );
    asc_scene_add_node(MAIN_SCENE, sprite);
    // TODO: this is never freed at the moment
    Spaceship *ship = cxZallocDefault(sizeof(Spaceship));
    ship->sprite = sprite;
    return ship;
}

static asc_rect update_viewport_for_window_resize(asc_vec2u window_size) {
    // Compute scaling and offsets
    unsigned viewport_size, offset_x = 0, offset_y = 0;
    if (window_size.width > window_size.height) {
        // Wider window: letterbox (black bars on top/bottom)
        offset_x = (window_size.width - window_size.height) / 2;
        viewport_size = window_size.height;
    } else {
        // Taller window: pillarbox (black bars on sides)
        offset_y = (window_size.height - window_size.width) / 2;
        viewport_size = window_size.width;
    }

    // Set the viewport to the scaled and centered region
    return ASC_RECT(offset_x, offset_y, viewport_size, viewport_size);
}

int main(void) {
    asc_context_initialize();
    if (asc_has_error()) {
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
                "Fatal Error",asc_get_error(), NULL);
        return 1;
    }
#ifdef TEST_BUILD
    asc_set_font_path("../../fonts");
    asc_set_shader_path("../../shader");
    asc_set_texture_path("../../test/snake/textures");
#endif

    // create window
    AscWindowSettings settings;
    asc_window_settings_init_defaults(&settings);
    settings.title = "Snake";
    asc_window_initialize(0, &settings);
    asc_ui_scale_auto();

    // load textures
    init_textures();

    // initialize the scenes
    const int game_field_size = 500;
    asc_scene_init(BACKDROP_SCENE,
        .type = ASC_CAMERA_ORTHO,
        .projection_update_func = asc_camera_ortho_update_size
    );
    asc_ink_rgb(0, 128, 90);
    asc_scene_init(MAIN_SCENE,
        .type = ASC_CAMERA_ORTHO,
        .ortho.rect = ASC_RECT(0, 0, game_field_size, game_field_size),
        .viewport_clear = true,
        .viewport_update_func = update_viewport_for_window_resize
    );

    // backdrop for letterbox/pillarbox
    create_backdrop();

    // create UI elements
    create_fps_counter();
    create_score_counter();

    // create spaceship
    Spaceship *spaceship = create_spaceship();

    // Main Loop
    do {
        // quit application on any error
        if (asc_has_error()) {
            SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
                    "Fatal Error", asc_get_error(),
                    asc_active_window->window);
            asc_clear_error();
            asc_context_quit();
        }

        // player rotation
        if (asc_key_pressed(ASC_KEY(LEFT))) {
            turn_left(spaceship);
        }
        if (asc_key_pressed(ASC_KEY(RIGHT))) {
            turn_right(spaceship);
        }

        // debug-key for clearing the shader registry
        if (asc_key_pressed(ASC_KEY(S))) {
            asc_shader_clear_registry();
            asc_dprintf("Shader cache cleared.");
        }

        // quit application on ESC key press
        if (asc_key_pressed(ASC_KEY(ESCAPE))) {
            asc_context_quit();
        }
    } while (asc_loop_next());

    // cleanup
    asc_context_destroy();
    return 0;
}

mercurial