xlib_playground

Xlib playground for experiments.
Log | Files | Refs

commit 3f9b4e6f1a3cb716f36c7770eadb9294567a000f
parent 44a75937ae887d6ef3d08be3165e4fc609560d71
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Tue, 27 Dec 2022 09:53:40 +0900

copy to ex7

Diffstat:
Aex7/Makefile | 14++++++++++++++
Aex7/ex7.c | 761+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 775 insertions(+), 0 deletions(-)

diff --git a/ex7/Makefile b/ex7/Makefile @@ -0,0 +1,14 @@ +INCS=-I/usr/X11R6/include +CFLAGS=-Wall -W -Wextra -Wpointer-arith -Wbad-function-cast -std=c11 +LIBS=-L/usr/X11R6/lib -lX11 -lXext -lm +IN=*.c +OUT=ex7 + +all: $(IN) + $(CC) $(INCS) $(CFLAGS) -o $(OUT) $(IN) $(LIBS) + +run: all + ./$(OUT) + +clean: + rm -f $(OUT) diff --git a/ex7/ex7.c b/ex7/ex7.c @@ -0,0 +1,761 @@ +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <string.h> +#include <math.h> + +#include <X11/Xlib.h> + +/* macros */ +#define INIT_WIDTH (800) +#define INIT_HEIGHT (600) +#define FPS (60) +#define SUB_TICK (4) +#define NUM_RECT (200) +#define GRAVITY (1000) +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define WORLD_WIDTH (80) +#define WORLD_HEIGHT (60) +#define BLOCK_SIZE (10) + +// #define COUNT_FPS + +enum keys { + KEY_D, + KEY_S, + KEY_A, + KEY_W, + KEY_Q, + KEY_SPACE, + NUM_KEY, //number of keys in this enum +}; +enum key_state { + KEY_UP, + KEY_DOWN, +}; +enum next_menu { + START_MENU, + GAME_PLAY, + GAME_OVER, + QUIT, +}; + +struct rect { + float ppx, ppy; // previous position + float px, py; // top left corner + float vx, vy; + float ax, ay; + int w, h; + int m; +}; + +struct circle { + float ppx, ppy; + float px, py; + float vx, vy; + float ax, ay; + int r; + int m; +}; + +char world_map[WORLD_WIDTH * WORLD_HEIGHT + 1] = +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"........b......................................................................." +"................................................................................" +"................................................................................" +"....b..........................................................................." +"................................................................................" +"................b..............................................................." +"..........................................................b..........b.........." +"................................................................................" +".......................b........................................................" +"...........................................b...................................." +"...........................................b...................................." +"................................................................................" +"..................b............................................................." +"................................................................................" +"...........................................b...................................." +"................................................................................" +"................................................................................" +"...........................b...................................................." +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"....................................bbbbbbbbbb.................................." +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"..............................................bbbbbbbbbb........................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"....................................bbbbbbbbbb.................................." +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"..........................bbbbbbbbbb............................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................................................................................" +"................bbbbbbbbbb......................................................" +"................................................................................" +"................................................................................" +"...p............................................................................" +"bbbbbbbbbbbbbbbbbbbbbbbbb.......bbbbbbbbbbbbbbbbbbbbbbbb...bbbbbbbbbbbbbbbbbbbbb" +"........................b.......b......................b...b...................." +"........................b.......b......................b...b...................." +"........................b.......b......................b...b...................." +"........................b.......b......................b...b...................."; + +/* variables */ +Display *display; +Window window; +unsigned int win_width = INIT_WIDTH, win_height = INIT_HEIGHT; +GC gc, sgc[NUM_RECT]; +Atom wm_delete_window; +struct rect block[NUM_RECT]; +struct rect player; +int player_is_falling; +int next_menu = START_MENU; + + +/* function prototypes */ +void setup(void); +void cleanup(void); +void start_menu(void); +void game_play(void); +void receive_events(int[]); +void handle_inputs(int[]); +void rect_next_tick(struct rect *, long); +int rect_test_collision(struct rect *, struct rect *); +void rect_handle_collision_mf(struct rect *, struct rect *); +void rect_handle_collision(struct rect *, struct rect *); +void rect_handle_collision_elastic(struct rect *, struct rect *); +void circle_next_tick(struct circle *, long); +int circle_test_collision(struct circle *, struct circle *); +void update_falling_status(struct rect *, long); +void game_over(void); + + +void +setup(void) +{ + if ((display = XOpenDisplay(NULL)) == NULL){ + fprintf(stderr, "ERROR: could not open display\n"); + exit(1); + } + window = XCreateSimpleWindow( + display, + XDefaultRootWindow(display), + 0, 0, + win_width, win_height, + 0, 0, + 0); + XStoreName(display, window, "UNKO"); + gc = XCreateGC(display, window, 0, NULL); + XSetForeground(display, gc, 0x00FFFF); + for (int i = 0; i < NUM_RECT; i++){ + sgc[i] = XCreateGC(display, window, 0, NULL); + XSetForeground(display, sgc[i], 0x00FF00); + } + + wm_delete_window = XInternAtom(display, + "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, window, &wm_delete_window, 1); + + XSelectInput(display, window, + ExposureMask | KeyPressMask | KeyReleaseMask); + + XMapWindow(display, window); +} + +void +start_menu(void) +{ + XEvent event; + char *menu_char_q = "press q to quit."; + char *menu_char_s = "press <space> to start."; + + XClearArea(display, window, + 0, 0, // position + win_width, win_height, // width and height + False); + XDrawString(display, window, gc, + win_width/2 - strlen(menu_char_q)/2, win_height/2, + menu_char_q, strlen(menu_char_q)); + XDrawString(display, window, gc, + win_width/2 - strlen(menu_char_s)/2, win_height/2 + 20, + menu_char_s, strlen(menu_char_s)); + + while (next_menu == START_MENU) { + XNextEvent(display, &event); + switch (event.type) { + case Expose: { + XDrawString(display, window, gc, + win_width/2 - strlen(menu_char_q)/2, + win_height/2, + menu_char_q, strlen(menu_char_q)); + XDrawString(display, window, gc, + win_width/2 - strlen(menu_char_s)/2, + win_height/2 + 20, + menu_char_s, strlen(menu_char_s)); + + } break; + case KeyPress: { + switch (XLookupKeysym(&event.xkey, 0)) { + case 'q': + next_menu = QUIT; + break; + case ' ': + next_menu = GAME_PLAY; + break; + default: + break; + } + } break; + case ClientMessage: { + if ((Atom) event.xclient.data.l[0] == wm_delete_window) { + next_menu = QUIT; + } + } break; + default: + break; + } + } +} + +void +receive_events(int key_state[]) +{ + XEvent event; + XWindowAttributes wattr; + + while (XPending(display) > 0) { + XNextEvent(display, &event); + switch (event.type) { + case Expose: { + XGetWindowAttributes(display, window, &wattr); + win_width = wattr.width; + win_height = wattr.height; + } break; + case KeyPress: { + switch (XLookupKeysym(&event.xkey, 0)) { + case 'q': + //next_menu = GAME_OVER; + key_state[KEY_Q] = KEY_DOWN; + break; + case 'd': + key_state[KEY_D] = KEY_DOWN; + break; + case 'a': + key_state[KEY_A] = KEY_DOWN; + break; + case 'w': + key_state[KEY_W] = KEY_DOWN; + break; + case 's': + key_state[KEY_S] = KEY_DOWN; + break; + case ' ': + key_state[KEY_SPACE] = KEY_DOWN; + break; + default: + break; + } + } break; + case KeyRelease: { + switch (XLookupKeysym(&event.xkey, 0)) { + case 'q': + key_state[KEY_Q] = KEY_UP; + break; + case 'd': + key_state[KEY_D] = KEY_UP; + break; + case 'a': + key_state[KEY_A] = KEY_UP; + break; + case 'w': + key_state[KEY_W] = KEY_UP; + break; + case 's': + key_state[KEY_S] = KEY_UP; + break; + case ' ': + key_state[KEY_SPACE] = KEY_UP; + break; + default: + break; + } + } break; + case ClientMessage: { + if ((Atom) event.xclient.data.l[0] == wm_delete_window) { + next_menu = QUIT; + } + } break; + default: + break; + } + } +} + +void +handle_inputs(int key_state[]) +{ + if (key_state[KEY_Q] == KEY_DOWN){ + next_menu = GAME_OVER; + return; + } + if (key_state[KEY_D] == KEY_DOWN) { + if (player.vx > 0) { + player.ax = 500; + } else { + player.ax = 1000; + } + } else if (key_state[KEY_A] == KEY_DOWN) { + if (player.vx > 0) { + player.ax = -1000; + } else { + player.ax = -500; + } + } else { + if (player_is_falling) + player.ax = -player.vx; + else + player.ax = -3 * player.vx; + } + + if (player.vx < -200) player.vx = -200; + if (player.vx > 200) player.vx = 200; + /* + if (key_state[KEY_S] == KEY_DOWN) + player.vy += 300; + if (key_state[KEY_W] == KEY_DOWN) + player.vy += -300; + */ + if (!player_is_falling && key_state[KEY_SPACE] == KEY_DOWN) + player.vy = -450; +} + +void +rect_next_tick(struct rect *s, long ndt) // nano second +{ + s->ppx = s->px; + s->ppy = s->py; + s->vx += s->ax * ndt / 1000 / 1000 / 1000; + s->vy += s->ay * ndt / 1000 / 1000 / 1000; + s->px += s->vx * ndt / 1000 / 1000 / 1000; + s->py += s->vy * ndt / 1000 / 1000 / 1000; + + // bind within the window + if (s->px < 0) { + s->px = 0; + //s->vx *= -1; + } + if (win_width < s->px + s->w) { + s->px = win_width - s->w; + //s->vx *= -1; + } + /* + if (s->py < 0) { + s->py = 0; + s->vy *= -1; + } + if (win_height < s->py + s->h) { + s->py = win_height - s->h; + s->vy *= -1; + } + */ + // game over when fall out of the screen + if (s->py > win_height) + next_menu = GAME_OVER; +} + +void +circle_next_tick(struct circle *c, long ndt) +{ + c->ppx = c->px; + c->ppy = c->py; + c->vx += c->ax * ndt / 1000 / 1000 / 1000; + c->vy += c->ay * ndt / 1000 / 1000 / 1000; + c->px += c->vx * ndt / 1000 / 1000 / 1000; + c->py += c->vy * ndt / 1000 / 1000 / 1000; + + // bind within the window + if (c->px - c->r < 0) { + c->px = c->r; + c->vx *= -1; + } + if (win_width < c->px + c->r) { + c->px = win_width - c->r; + c->vx *= -1; + } + if (c->py - c->r < 0) { + c->py = c->r; + c->vy *= -1; + } + if (win_height < c->py + c->r) { + c->py = win_height - c->r; + c->vy *= -1; + } +} + +int +rect_test_collision(struct rect *s1, struct rect *s2) +{ + return s1->px < s2->px + s2->w && s2->px < s1->px + s1->w && + s2->py < s1->py + s1->h && s1->py < s2->py + s2->h; +} + +int +circle_test_collision(struct circle *c1, struct circle *c2) +{ + return (c1->px - c2->px) * (c1->px - c2->px) + + (c1->py - c2->py) * (c1->py - c2->py) < + (c1->r + c2->r) * (c1->r + c2->r); +} + +/* + * Handle collision of a moving rect against fixed rect + */ +void +rect_handle_collision_mf(struct rect *sm, struct rect *sf) +{ + if (!rect_test_collision(sm, sf)) + return; + if (sm->ppx + sm->w <= sf->ppx && sf->px < sm->px + sm->w) { + // collisioin from left to right + sm->px = sf->px - sm->w; + sm->vx = 0; + } + if (sf->ppx + sf->w <= sm->ppx && sm->px < sf->px + sf->w) { + // collision from right to left + sm->px = sf->px + sf->w; + sm->vx = 0; + } + + if (sm->ppy + sm->h <= sf->ppy && sf->py < sm->py + sm->h) { + // collision from up to down + sm->py = sf->py - sm->h; + sm->vy = 0; + } + if (sf->ppy + sf->h <= sm->ppy && sm->py < sf->py + sf->h) { + // collision from dohn to up + sm->py = sf->py + sf->h; + sm->vy = 0; + } + +} + +/* + * Handle collision of a moving rect against another moving rect + */ +void +rect_handle_collision_mm(struct rect *s1, struct rect *s2) +{ + if (!rect_test_collision(s1, s2)) + return; + + float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px); + float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py); + + if (lapx < lapy) { + if (s1->px + s1->w < s2->px + s2->w / 2) { + s1->px -= lapx / 2; + s2->px += lapx / 2; + } else { + s1->px += lapx / 2; + s2->px -= lapx / 2; + } + } else { + if (s1->py + s1->h < s2->py + s2->h / 2) { + s1->py -= lapy / 2; + s2->py += lapy / 2; + } else { + s1->py += lapy / 2; + s2->py -= lapy / 2; + } + } +} + +void +circle_handle_collision_mm(struct circle *c1, struct circle *c2) +{ + if (!circle_test_collision(c1, c2)) + return; + + float col_px = c2->px - c1->px; + float col_py = c2->py - c1->py; + float col_pr = sqrtf(col_px * col_px + col_py * col_py); + col_px /= col_pr; + col_py /= col_pr; + + c1->px = c1->px - col_px / 2; + c1->py = c1->py - col_py / 2; + c2->px = c2->px + col_px / 2; + c2->py = c2->py + col_py / 2; +} + +void +rect_handle_collision_elastic(struct rect *s1, struct rect *s2) +{ + if(!rect_test_collision(s1, s2)) + return; + + rect_handle_collision_mm(s1, s2); + + float v1, v2; + float m1 = s1->m; + float m2 = s2->m; + + float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px); + float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py); + + if (lapx < lapy) { + v1 = s1->vx; + v2 = s2->vx; + s1->vx = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1; + s2->vx = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2; + } else { + v1 = s1->vy; + v2 = s2->vy; + s1->vy = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1; + s2->vy = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2; + } +} + +void +circle_handle_collision_elastic(struct circle *c1, struct circle *c2) +{ + if(!circle_test_collision(c1, c2)) + return; + + circle_handle_collision_mm(c1, c2); + + float col_px = c2->px - c1->px; + float col_py = c2->py - c1->py; + float col_pr = sqrtf(col_px * col_px + col_py * col_py); + col_px /= col_pr; + col_py /= col_pr; + float nor_px = col_py; + float nor_py = -col_px; + + float m1 = c1->m; + float m2 = c2->m; + + float col_1v = c1->vx * col_px + c1->vy * col_py; + float col_2v = c2->vx * col_px + c2->vy * col_py; + + float col_1vxn = (2*m2/(m1+m2)*col_2v + (m1-m2)/(m1+m2)*col_1v) * col_px; + float col_1vyn = (2*m2/(m1+m2)*col_2v + (m1-m2)/(m1+m2)*col_1v) * col_py; + float col_2vxn = (2*m1/(m1+m2)*col_1v + (m2-m1)/(m1+m2)*col_2v) * col_px; + float col_2vyn = (2*m1/(m1+m2)*col_1v + (m2-m1)/(m1+m2)*col_2v) * col_py; + + float nor_1vx = nor_px * (c1->vx * nor_px + c1->vy * nor_py); + float nor_1vy = nor_py * (c1->vx * nor_px + c1->vy * nor_py); + float nor_2vx = nor_px * (c2->vx * nor_px + c2->vy * nor_py); + float nor_2vy = nor_py * (c2->vx * nor_px + c2->vy * nor_py); + + c1->vx = col_1vxn + nor_1vx; + c1->vy = col_1vyn + nor_1vy; + c2->vx = col_2vxn + nor_2vx; + c2->vy = col_2vyn + nor_2vy; +} + +void +update_falling_status(struct rect *player, long ndt) +{ + int collision = 0; + struct rect r = {0}; + r.ppx = player->ppx; + r.ppy = player->ppy; + r.px = player->px; + r.py = player->py; + r.vx = 0; + r.vy = 0; + r.ax = 0; + r.ay = GRAVITY; + r.w = player->w; + r.h = player->h; + r.m = player->m; + + rect_next_tick(&r, ndt); + for (int i = 0; i < NUM_RECT; i++) + if (rect_test_collision(&r, &block[i])) + collision = 1; + + if (collision == 1) + player_is_falling = 0; + else + player_is_falling = 1; +} + +void +game_play(void) +{ + int key_state[NUM_KEY]; + long t0, t1, dt; +#ifdef COUNT_FPS + int fps_count = 0; +#endif + struct timespec ts; + + int bi = 0; + for (int i = 0; i < WORLD_WIDTH * WORLD_HEIGHT; i++) { + if (world_map[i] == 'b') { + block[bi].ppx = block[bi].px = i % WORLD_WIDTH * BLOCK_SIZE; + block[bi].ppy = block[bi].py = i / WORLD_WIDTH * BLOCK_SIZE; + block[bi].ax = 0; + block[bi].ay = 0; + block[bi].vx = 0; + block[bi].vy = 0; + block[bi].w = block[bi].h = BLOCK_SIZE; + block[bi].m = block[bi].w * block[bi].h; + bi++; + } else if (world_map[i] == 'p') { + player.ppx = player.px = i % WORLD_WIDTH * BLOCK_SIZE; + player.ppy = player.py = i / WORLD_WIDTH * BLOCK_SIZE; + player.vx = 0; + player.vy = 0; + player.ax = 0; + player.ay = GRAVITY; + player.w = player.h = BLOCK_SIZE; + player.m = player.w * player.h; + } + } + + while (next_menu == GAME_PLAY){ + clock_gettime(CLOCK_MONOTONIC, &ts); + t0 = ts.tv_nsec; + receive_events(key_state); + handle_inputs(key_state); + + + clock_gettime(CLOCK_MONOTONIC, &ts); + t0 = ts.tv_nsec; + + int collision[NUM_RECT] = {0}; + for (int k = 0; k < SUB_TICK; k++) { + update_falling_status(&player, 1000 * 1000 * 1000 / FPS / SUB_TICK); + if (player_is_falling) + player.ay = GRAVITY; + else + player.ay = 0; + rect_next_tick(&player, 1000 * 1000 * 1000 / FPS / SUB_TICK); + for (int i = 0; i < NUM_RECT; i++) + rect_next_tick(&block[i], 1000 * 1000 * 1000 / FPS / SUB_TICK); + + for (int i = 0; i < NUM_RECT; i++){ + rect_handle_collision_mf(&player, &block[i]); + if (rect_test_collision(&player, &block[i])) + collision[i] = 1; + } + } + for (int i = 0; i < NUM_RECT; i++) + if (collision[i] == 1) + XSetForeground(display, sgc[i], 0xFFFF00); + else + XSetForeground(display, sgc[i], 0x90FF90); + + // fix fps + // TODO: This method create some strange stripe when + // rendered in 60fps on a 60fps monitor. + dt = 0; + while (dt < 1.0 * 1000 * 1000 * 1000 / FPS){ + clock_gettime(CLOCK_MONOTONIC, &ts); + t1 = ts.tv_nsec; + dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1000 * 1000 * 1000; + } +#ifdef COUNT_FPS + // count fps. + fps_count++; + if (t1 < t0){ + printf("fps: %u\n", fps_count); + fps_count = 0; + } +#endif + + XClearArea(display, window, + 0, 0, // position + win_width, win_height, // width and height + False); + for (int i = 0; i < NUM_RECT; i++) { + XDrawRectangle(display, window, sgc[i], + block[i].px, + block[i].py, // position + block[i].w, block[i].h); + } + XDrawRectangle(display, window, gc, + player.px, + player.py, // position + player.w, player.h); + char status_string[128]; + snprintf(status_string, 128, "falling: %d", player_is_falling); + XDrawString(display, window, gc, + 0, 20, + status_string, + strlen(status_string)); + } +} + +void +game_over(void) +{ + char *menu_char = "GAME OVER"; + + XClearArea(display, window, + 0, 0, // position + win_width, win_height, // width and height + False); + XDrawString(display, window, gc, + win_width/2 - strlen(menu_char)/2, win_height/2, + menu_char, strlen(menu_char)); + XFlush(display); + + sleep(1); + next_menu = START_MENU; +} + +void +cleanup(void) +{ + XCloseDisplay(display); +} + +int +main(void) +{ + setup(); + while (next_menu != QUIT){ + switch (next_menu){ + case START_MENU: + start_menu(); + break; + case GAME_PLAY: + game_play(); + break; + case GAME_OVER: + game_over(); + break; + default: + break; + } + } + + cleanup(); + return 0; +}