/* * Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * aquatinspitz.c * Aqua tin spitz! */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Utility global variables every game will need. uint32_t window_id = 0; static size_t framesize; static uint32_t* fb; static bool game_running = true; static size_t game_width = 800; static size_t game_height = 512; #define MAX_KEY_NUMBER 512 static bool keys_down[MAX_KEY_NUMBER]; static bool keys_pending[MAX_KEY_NUMBER]; static struct timespec key_handled_last[MAX_KEY_NUMBER]; // Utility functions every game will need. bool pop_is_key_just_down(int abskbkey); static inline uint32_t make_color(uint8_t r, uint8_t g, uint8_t b); // Your game is customized from here ... struct player { float x; float y; int size; }; struct player player; struct enemy { float x; float y; float vx; float vy; int size; int shift; }; #define NUM_ENEMIES 256 static struct enemy enemies[NUM_ENEMIES]; // Prepare the game state for the first round. void init(void) { player.x = game_width / 2; player.y = game_height / 2; player.size = 24.0; for ( size_t i = 0; i < NUM_ENEMIES; i++ ) { enemies[i].x = (float) arc4random_uniform(game_width); enemies[i].y = (float) arc4random_uniform(game_height); enemies[i].vx = (float) ((int) arc4random_uniform(96) - 48); enemies[i].vy = (float) ((int) arc4random_uniform(96) - 48); enemies[i].size = arc4random_uniform(8) + 8; enemies[i].shift = (int) arc4random_uniform(6) - 3; if ( enemies[i].shift <= 0 ) enemies[i].shift -= 1; } } // Calculate the game state of the next round. void update(float deltatime) { float player_speed = 64.0f; float player_velocity_x = 0.0f; float player_velocity_y = 0.0f; if ( keys_down[KBKEY_UP] ) player_velocity_y -= player_speed; if ( keys_down[KBKEY_DOWN] ) player_velocity_y += player_speed; if ( keys_down[KBKEY_LEFT] ) player_velocity_x -= player_speed; if ( keys_down[KBKEY_RIGHT] ) player_velocity_x += player_speed; player.x += deltatime * player_velocity_x; player.y += deltatime * player_velocity_y; if ( pop_is_key_just_down(KBKEY_SPACE) ) player.size = 192 - player.size; float total_speed = 0.0; for ( size_t i = 0; i < NUM_ENEMIES; i++ ) { struct enemy* enemy = &enemies[i]; float g = 10000.0; float dist_sq = (player.x - enemy->x) * (player.x - enemy->x) + (player.y - enemy->y) * (player.y - enemy->y); if ( dist_sq < 0.1 ) dist_sq = 0.1; float dist = sqrtf(dist_sq); float f = g * enemy->size * player.size / dist_sq; float f_x = (player.x - enemy->x) / dist * f; float f_y = (player.y - enemy->y) / dist * f; float a_x = f_x / enemy->size; float a_y = f_y / enemy->size; enemy->vx += deltatime * a_x; enemy->vy += deltatime * a_y; float speed = sqrtf(enemy->vx * enemy->vx + enemy->vy * enemy->vy); total_speed += speed; } float average_speed = total_speed / NUM_ENEMIES; float mid_game = game_width / 2.0; for ( size_t i = 0; i < NUM_ENEMIES; i++ ) { struct enemy* enemy = &enemies[i]; float speed = sqrtf(enemy->vx * enemy->vx + enemy->vy * enemy->vy); float ox = enemy->x; float oy = enemy->y; float nx = ox + deltatime * enemy->vx; float ny = oy + deltatime * enemy->vy; if ( mid_game + enemy->size / 2 < ox && nx <= mid_game + enemy->size / 2 ) { if ( speed < average_speed ) { if ( enemy->vx < 0.0 ) enemy->vx = -enemy->vx; continue; } } else if ( ox <= mid_game - enemy->size / 2 && mid_game - enemy->size / 2 < nx ) { if ( speed >= average_speed ) { if ( enemy->vx > 0.0 ) enemy->vx = -enemy->vx; continue; } } enemy->x = nx; enemy->y = ny; } for ( size_t i = 0; i < NUM_ENEMIES; i++ ) { struct enemy* enemy = &enemies[i]; if ( enemy->x - enemy->size / 2 < 0 ) { enemy->x = 0.0f + enemy->size / 2; if ( enemy->vx < 0.0 ) enemy->vx = -0.9 * enemy->vx; } else if ( game_width < (size_t) (enemy->x + enemy->size / 2) ) { enemy->x = (float) game_width - enemy->size / 2; if ( 0.0 < enemy->vx ) enemy->vx = -0.9 * enemy->vx; } if ( enemy->y - enemy->size / 2 < 0 ) { enemy->y = 0.0f + enemy->size / 2; if ( enemy->vy < 0.0 ) enemy->vy = -0.9 * enemy->vy; } else if ( game_height < (size_t) (enemy->y + enemy->size / 2) ) { enemy->y = (float) game_height - enemy->size / 2; if ( 0.0 < enemy->vy ) enemy->vy = -0.9 * enemy->vy; } } } // Render the game into the framebuffer. void render(struct display_connection* connection) { size_t old_framesize = framesize; size_t xres = game_width; size_t yres = game_height; size_t pitch = xres; framesize = xres * yres * sizeof(uint32_t); if ( old_framesize != framesize && !(fb = realloc(fb, framesize)) ) err(1, "malloc"); // Render a colorful background. for ( size_t y = 0; y < yres; y++ ) { for ( size_t x = 0; x < xres; x++ ) { uint32_t color = make_color(x * y, y ? x / y : 255, x ^ y); fb[y * pitch + x] = color; } } // Render the player. for ( int t = -player.size / 2; t < player.size / 2; t++ ) { if ( player.y + t < 0 ) continue; size_t y = (size_t) (player.y + t); if ( yres <= y ) continue; for ( int l = -player.size / 2; l < player.size / 2; l++ ) { if ( player.x + l < 0 ) continue; size_t x = (size_t) (player.x + l); if ( xres <= x ) continue; uint32_t background = fb[y * pitch + x]; uint32_t color = ~background; fb[y * pitch + x] = color; } } // Render the enemies. for ( size_t i = 0; i < NUM_ENEMIES; i++ ) { struct enemy* enemy = &enemies[i]; for ( int t = -enemy->size / 2; t < enemy->size / 2; t++ ) { if ( enemy->y + t < 0 ) continue; size_t y = (size_t) (enemy->y + t); if ( yres <= y ) continue; for ( int l = -enemy->size / 2; l < enemy->size / 2; l++ ) { if ( enemy->x + l < 0 ) continue; size_t x = (size_t) (enemy->x + l); if ( xres <= x ) continue; uint32_t background = fb[y * pitch + x]; uint32_t color = enemy->shift < 0 ? background >> -enemy->shift : background << enemy->shift; color = ~color; fb[y * pitch + x] = color; } } } display_render_window(connection, window_id, 0, 0, game_width, game_height, fb); display_show_window(connection, window_id); } // ... to here. No need to edit stuff below. // Create a color from rgb values. static inline uint32_t make_color(uint8_t r, uint8_t g, uint8_t b) { return b << 0UL | g << 8UL | r << 16UL; } // Return if a keystroke is pending. For instance, if you press A on your // keyboard and keep pressing it, a new A character will appear every time a // small interval has passed, not just every time the code checks if A is down. bool pop_is_key_just_down(int abskbkey) { assert(0 <= abskbkey); if ( MAX_KEY_NUMBER <= (size_t) abskbkey ) return false; if ( keys_pending[abskbkey] ) { keys_pending[abskbkey] = false; clock_gettime(CLOCK_MONOTONIC, &key_handled_last[abskbkey]); return true; } if ( !keys_down[abskbkey] ) return false; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec elapsed = timespec_sub(now, key_handled_last[abskbkey]); struct timespec repress_delay = timespec_make(0, 100 * 1000 * 1000); if ( timespec_lt(elapsed, repress_delay) ) return false; clock_gettime(CLOCK_MONOTONIC, &key_handled_last[abskbkey]); return true; } // When the connection to the display server has disconnected. void on_disconnect(void* ctx) { (void) ctx; exit(0); } // When the window is asked to quit. void on_quit(void* ctx, uint32_t window_id) { (void) ctx; (void) window_id; exit(0); } // When the window has been resized. void on_resize(void* ctx, uint32_t window_id, uint32_t width, uint32_t height) { (void) ctx; if ( window_id != window_id ) return; game_width = width; game_height = height; } // When a key has been pressed. void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint) { (void) ctx; if ( window_id != window_id ) return; int kbkey = KBKEY_DECODE(codepoint); if ( !kbkey ) return; int abskbkey = (kbkey < 0) ? -kbkey : kbkey; if ( MAX_KEY_NUMBER <= (size_t) abskbkey ) return; bool is_key_down_event = 0 < kbkey; if ( !keys_down[abskbkey] && is_key_down_event ) keys_pending[abskbkey] = true; keys_down[abskbkey] = is_key_down_event; } // Run the game until no longer needed. void mainloop(struct display_connection* connection) { struct display_event_handlers handlers = {0}; handlers.disconnect_handler = on_disconnect; handlers.quit_handler = on_quit; handlers.resize_handler = on_resize; handlers.keyboard_handler = on_keyboard; init(); struct timespec last_frame_time; clock_gettime(CLOCK_MONOTONIC, &last_frame_time); render(connection); while ( game_running ) { struct timespec current_frame_time; clock_gettime(CLOCK_MONOTONIC, ¤t_frame_time); struct timespec deltatime_ts = timespec_sub(current_frame_time, last_frame_time); float deltatime = deltatime_ts.tv_sec + deltatime_ts.tv_nsec / 1E9f; while ( display_poll_event(connection, &handlers) == 0 ); update(deltatime); render(connection); last_frame_time = current_frame_time; } } // Create a display context, run the game, and then cleanly exit. int main(int argc, char* argv[]) { struct display_connection* connection = display_connect_default(); if ( !connection && errno == ECONNREFUSED ) display_spawn(argc, argv); if ( !connection ) error(1, errno, "Could not connect to display server"); display_create_window(connection, window_id); display_resize_window(connection, window_id, game_width, game_height); display_title_window(connection, window_id, "Aquatinspitz"); mainloop(connection); display_disconnect(connection); return 0; }