/* Canabalt clone for sunos4 sunview
   written by claude, public domain
   cc canabalt.c -o canabalt -lsuntool -lsunwindow -lpixrect */

#include <suntool/sunview.h>
#include <suntool/canvas.h>
#include <sunwindow/cms.h>
#include <sunwindow/pixwin.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#define WINDOW_WIDTH 600
#define WINDOW_HEIGHT 400
#define PLAYER_WIDTH 20
#define PLAYER_HEIGHT 40
#define BUILDING_WIDTH 200
#define GAP_WIDTH 35
#define MAX_BUILDINGS 3
#define NUM_COLORS 4
#define JUMP_VELOCITY -25
#define GRAVITY 1.6
#define INITIAL_SPEED 25
#define SPEED_INCREASE 0.1
#define PIXELS_PER_METER 4
#define JUMP_INDICATOR_DURATION 5

#define COLOR_LIGHT_GREY 0
#define COLOR_BLACK 1
#define COLOR_GREY 2
#define COLOR_WHITE 3

#define PLAYER_STYLE_STICK 0
#define PLAYER_STYLE_BLOCK 1

Frame frame;
Canvas canvas;
Pixwin *pw;

int player_x, player_y;
float player_velocity;
float player_speed;
float display_speed;
float distance;
float world_offset;
int buildings[MAX_BUILDINGS][3]; /* [x, width, height] */
int game_over;
int cms_size;
int player_style;
int player_on_ground;
int jump_indicator_timer;
unsigned char red[NUM_COLORS], green[NUM_COLORS], blue[NUM_COLORS];

/* Function prototypes */
main();
setup_colors();
init_game();
draw_game();
update_game();
handle_input();
Notify_value game_timer();
add_building();
draw_stick_figure();
draw_block_figure();
check_collision();
attempt_jump();

main(argc, argv)
int argc;
char **argv;
{
    struct itimerval timer_value;

    srand(time(0));
    
    frame = window_create(NULL, FRAME,
                          FRAME_LABEL, "Canabalt SunView",
                          WIN_WIDTH, WINDOW_WIDTH,
                          WIN_HEIGHT, WINDOW_HEIGHT,
                          0);
    if (frame == NULL) {
        fprintf(stderr, "Failed to create frame\n");
        exit(1);
    }

    canvas = window_create(frame, CANVAS,
                           WIN_WIDTH, WINDOW_WIDTH,
                           WIN_HEIGHT, WINDOW_HEIGHT,
                           WIN_EVENT_PROC, handle_input,
                           WIN_CONSUME_KBD_EVENTS, WIN_UP_EVENTS | WIN_ASCII_EVENTS,
                           WIN_CONSUME_PICK_EVENTS, WIN_MOUSE_BUTTONS,
                           0);
    if (canvas == NULL) {
        fprintf(stderr, "Failed to create canvas\n");
        exit(1);
    }

    pw = canvas_pixwin(canvas);
    if (pw == NULL) {
        fprintf(stderr, "Failed to get pixwin\n");
        exit(1);
    }

    setup_colors();
    init_game();
    window_fit(frame);

    /* Set up the timer */
    timer_value.it_value.tv_sec = 0;
    timer_value.it_value.tv_usec = 33333; /* Approx. 30 FPS */
    timer_value.it_interval = timer_value.it_value;
    notify_set_itimer_func(frame, game_timer, ITIMER_REAL, &timer_value, NULL);

    window_main_loop(frame);
    exit(0);
}

setup_colors()
{
    cms_size = NUM_COLORS;
    red[COLOR_LIGHT_GREY] = 220; green[COLOR_LIGHT_GREY] = 220; blue[COLOR_LIGHT_GREY] = 220;
    red[COLOR_BLACK] = 0; green[COLOR_BLACK] = 0; blue[COLOR_BLACK] = 0;
    red[COLOR_GREY] = 128; green[COLOR_GREY] = 128; blue[COLOR_GREY] = 128;
    red[COLOR_WHITE] = 255; green[COLOR_WHITE] = 255; blue[COLOR_WHITE] = 255;
    
    pw_setcmsname(pw, "canabalt_cms");
    pw_putcolormap(pw, 0, NUM_COLORS, red, green, blue);
}

init_game()
{
    int i;
    
    player_speed = INITIAL_SPEED;
    display_speed = player_speed;
    distance = 0;
    game_over = 0;
    player_style = PLAYER_STYLE_BLOCK;
    world_offset = 0;
    player_on_ground = 1;
    jump_indicator_timer = 0;

    /* Initialize buildings */
    for (i = 0; i < MAX_BUILDINGS; i++) {
        buildings[i][0] = i * (BUILDING_WIDTH + GAP_WIDTH) * PIXELS_PER_METER;
        buildings[i][1] = BUILDING_WIDTH * PIXELS_PER_METER;
        buildings[i][2] = (WINDOW_HEIGHT / 3) + rand() % (WINDOW_HEIGHT / 6);
    }

    /* Position player on the first building */
    player_x = PLAYER_WIDTH;
    player_y = WINDOW_HEIGHT - buildings[0][2] - PLAYER_HEIGHT;
    player_velocity = 0;
}

draw_stick_figure(x, y)
int x, y;
{
    /* Head */
    pw_vector(pw, x + PLAYER_WIDTH/2 - 5, y, x + PLAYER_WIDTH/2 + 5, y, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);
    pw_vector(pw, x + PLAYER_WIDTH/2 - 5, y, x + PLAYER_WIDTH/2 - 5, y + 10, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);
    pw_vector(pw, x + PLAYER_WIDTH/2 + 5, y, x + PLAYER_WIDTH/2 + 5, y + 10, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);
    pw_vector(pw, x + PLAYER_WIDTH/2 - 5, y + 10, x + PLAYER_WIDTH/2 + 5, y + 10, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);

    /* Body */
    pw_vector(pw, x + PLAYER_WIDTH/2, y + 10, x + PLAYER_WIDTH/2, y + 30, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);

    /* Arms */
    pw_vector(pw, x + PLAYER_WIDTH/2, y + 15, x + PLAYER_WIDTH/2 - 10, y + 25, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);
    pw_vector(pw, x + PLAYER_WIDTH/2, y + 15, x + PLAYER_WIDTH/2 + 10, y + 25, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);

    /* Legs */
    pw_vector(pw, x + PLAYER_WIDTH/2, y + 30, x + PLAYER_WIDTH/2 - 10, y + PLAYER_HEIGHT, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);
    pw_vector(pw, x + PLAYER_WIDTH/2, y + 30, x + PLAYER_WIDTH/2 + 10, y + PLAYER_HEIGHT, PIX_SRC | PIX_COLOR(COLOR_BLACK), 2);
}

draw_block_figure(x, y)
int x, y;
{
    /* Head */
    pw_rop(pw, x + PLAYER_WIDTH/2 - 5, y, 10, 10, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, 0, 0);

    /* Body */
    pw_rop(pw, x + PLAYER_WIDTH/2 - 7, y + 10, 14, 20, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, 0, 0);

    /* Arms */
    pw_rop(pw, x + PLAYER_WIDTH/2 - 15, y + 12, 8, 5, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, 0, 0);
    pw_rop(pw, x + PLAYER_WIDTH/2 + 7, y + 12, 8, 5, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, 0, 0);

    /* Legs */
    pw_rop(pw, x + PLAYER_WIDTH/2 - 7, y + 30, 5, 10, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, 0, 0);
    pw_rop(pw, x + PLAYER_WIDTH/2 + 2, y + 30, 5, 10, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, 0, 0);
}

draw_game()
{
    int i;
    char info_str[60];
    int draw_x;

    /* Clear screen */
    pw_writebackground(pw, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, PIX_SRC | PIX_COLOR(COLOR_LIGHT_GREY));

    /* Draw buildings */
    for (i = 0; i < MAX_BUILDINGS; i++) {
        draw_x = buildings[i][0] - (int)world_offset;
        while (draw_x > WINDOW_WIDTH) {
            draw_x -= MAX_BUILDINGS * (BUILDING_WIDTH + GAP_WIDTH) * PIXELS_PER_METER;
        }
        pw_rop(pw, draw_x, WINDOW_HEIGHT - buildings[i][2], buildings[i][1], buildings[i][2], PIX_SRC | PIX_COLOR(COLOR_GREY), 0, 0, 0);
    }

    /* Draw player */
    if (player_style == PLAYER_STYLE_STICK) {
        draw_stick_figure(player_x, player_y);
    } else {
        draw_block_figure(player_x, player_y);
    }

    /* Draw jump indicator */
    if (jump_indicator_timer > 0) {
        pw_text(pw, player_x, player_y - 20, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, "JUMP!");
    }

    /* Draw distance and speed on the same line */
    sprintf(info_str, "Distance: %.0f Speed: %.0f ", distance, display_speed);
    pw_text(pw, 10, 30, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, info_str);

    if (game_over) {
        pw_text(pw, WINDOW_WIDTH/2 - 40, WINDOW_HEIGHT/2, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, "Game Over!");
        pw_text(pw, WINDOW_WIDTH/2 - 80, WINDOW_HEIGHT/2 + 30, PIX_SRC | PIX_COLOR(COLOR_BLACK), 0, "Press SPACE to restart");
    }
}

check_collision()
{
    int i;
    int draw_x;
    int roof_y;
    int was_on_ground = player_on_ground;

    player_on_ground = 0;

    for (i = 0; i < MAX_BUILDINGS; i++) {
        draw_x = buildings[i][0] - (int)world_offset;
        while (draw_x > WINDOW_WIDTH) {
            draw_x -= MAX_BUILDINGS * (BUILDING_WIDTH + GAP_WIDTH) * PIXELS_PER_METER;
        }
        
        roof_y = WINDOW_HEIGHT - buildings[i][2];
        
        if (player_x + PLAYER_WIDTH > draw_x && player_x < draw_x + buildings[i][1]) {
            if (player_y + PLAYER_HEIGHT <= roof_y && player_y + PLAYER_HEIGHT + player_velocity > roof_y) {
                /* Player is landing on top of a building */
                player_y = roof_y - PLAYER_HEIGHT;
                player_velocity = 0;
                player_on_ground = 1;
            } else if (player_y + PLAYER_HEIGHT > roof_y && player_y < roof_y) {
                /* Player is on top of a building */
                player_y = roof_y - PLAYER_HEIGHT;
                player_velocity = 0;
                player_on_ground = 1;
            }
        }
    }

    /* Check if player fell off the screen */
    if (player_y + PLAYER_HEIGHT >= WINDOW_HEIGHT) {
        game_over = 1;
    }
}

update_game()
{
    int i;
    float pixel_move;

    if (game_over) return;

    /* Increase speed and distance */
    player_speed += SPEED_INCREASE;
    display_speed = player_speed;
    distance += player_speed / 30.0;  /* Divide by frame rate for correct distance */

    /* Calculate world offset */
    world_offset += player_speed * PIXELS_PER_METER / 30.0;
    if (world_offset >= (BUILDING_WIDTH + GAP_WIDTH) * PIXELS_PER_METER) {
        world_offset -= (BUILDING_WIDTH + GAP_WIDTH) * PIXELS_PER_METER;
    }

    /* Update player position */
    player_velocity += GRAVITY;
    player_y += player_velocity;

    /* Check for collision */
    check_collision();

    /* Update jump indicator timer */
    if (jump_indicator_timer > 0) {
        jump_indicator_timer--;
    }

    /* Redraw the game */
    draw_game();
}


handle_input(window, event, arg)
Window window;
Event *event;
caddr_t arg;
{
    unsigned short key_id;

    if (event_is_ascii(event)) {
        key_id = event_id(event);
        switch (key_id) {
            case 'q':
            case 'Q':
                exit(0);
                break;
            case ' ':
                if (game_over) {
                    init_game();  /* Completely reset the game */
                } else {
                    attempt_jump();
                }
                break;
            case 's':
                player_style = PLAYER_STYLE_STICK;
                break;
            case 'b':
                player_style = PLAYER_STYLE_BLOCK;
                break;
        }
    } else if (event_is_button(event) && event_is_down(event)) {
        /* Handle mouse click */
        if (game_over) {
            init_game();
        } else {
            attempt_jump();
        }
    }
}

attempt_jump()
{
    if (player_on_ground) {
        player_velocity = JUMP_VELOCITY;
        player_on_ground = 0;
        player_y -= 1; /* Ensure player is not touching the ground next frame */
        jump_indicator_timer = JUMP_INDICATOR_DURATION;
    } else {
        /* Even if we can't jump, show an indicator that the attempt was registered */
        jump_indicator_timer = JUMP_INDICATOR_DURATION;
    }
}

Notify_value
game_timer(client, itimer_type)
    Notify_client client;
    int itimer_type;
{
    update_game();
    return NOTIFY_DONE;
}

add_building(start_x)
int start_x;
{
    int i;
    int height;
    int prev_height = 0;

    for (i = 0; i < MAX_BUILDINGS; i++) {
        if (buildings[i][0] + buildings[i][1] < 0) {
            buildings[i][0] = start_x;
            buildings[i][1] = BUILDING_WIDTH * PIXELS_PER_METER;
            
            /* Ensure the height difference is not too large */
            if (i > 0) {
                prev_height = buildings[(i-1+MAX_BUILDINGS)%MAX_BUILDINGS][2];
            }
            do {
                height = (WINDOW_HEIGHT / 3) + rand() % (WINDOW_HEIGHT / 6);
            } while (abs(height - prev_height) > WINDOW_HEIGHT / 6);
            
            buildings[i][2] = height;
            return;
        }
    }
}