xlib_playground

Xlib playground for experiments.
Log | Files | Refs

commit 5bd341966a2d5705f2b58898183fb54f18e67399
parent 6fd8defac556bc81f1d638f190005b6952492a00
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Fri, 23 Dec 2022 16:44:05 +0900

copy

Diffstat:
Aex4/Makefile | 14++++++++++++++
Aex4/ex4.c | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 357 insertions(+), 0 deletions(-)

diff --git a/ex4/Makefile b/ex4/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=ex4 + +all: $(IN) + $(CC) $(INCS) $(CFLAGS) -o $(OUT) $(IN) $(LIBS) + +run: all + ./$(OUT) + +clean: + rm -f $(OUT) diff --git a/ex4/ex4.c b/ex4/ex4.c @@ -0,0 +1,343 @@ +#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 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, +}; + +/* variables */ +Display *display; +Window window; +unsigned int win_width = INIT_WIDTH, win_height = INIT_HEIGHT; +GC gc; +Atom wm_delete_window; +float px = 200, py = 200; +float vx = 0, vy = 0; +int width = 40, height = 40; +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 next_tick(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); + + wm_delete_window = XInternAtom(display, + "WM_DELETE_WINDOW", False); + XSetWMProtocols(display, window, &wm_delete_window, 1); + + XSelectInput(display, window, + ExposureMask | KeyPressMask | KeyReleaseMask); + + XSetForeground(display, gc, 0x00FFFF); + 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; + 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; + 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; + } + vx = vy = 0; + if (key_state[KEY_D] == KEY_DOWN) + vx += 300; + if (key_state[KEY_A] == KEY_DOWN) + vx += -300; + if (key_state[KEY_S] == KEY_DOWN) + vy += 300; + if (key_state[KEY_W] == KEY_DOWN) + vy += -300; +} + +void +next_tick(long ndt) // nano second +{ + px = px + vx * ndt / 1000 / 1000 / 1000; + py = py + vy * ndt / 1000 / 1000 / 1000; + // bind within the window + if (px < 0) + px = 0; + if (win_width < px + width) + px = win_width - width; + if (py < 0) + py = 0; + if (win_height < py + height) + py = win_height - height; +} + +void +game_play(void) +{ + int key_state[NUM_KEY]; + long t0, t1, dt; +#ifdef COUNT_FPS + int fps_count = 0; +#endif + struct timespec ts; + + while (next_menu == GAME_PLAY){ + clock_gettime(CLOCK_MONOTONIC, &ts); + t0 = ts.tv_nsec; + receive_events(key_state); + handle_inputs(key_state); + + // 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 + + clock_gettime(CLOCK_MONOTONIC, &ts); + t0 = ts.tv_nsec; + + next_tick(1000 * 1000 * 1000 / FPS); + + XClearArea(display, window, + 0, 0, // position + win_width, win_height, // width and height + False); + XFillRectangle(display, window, gc, + px, py, // position + width, height); // width and height + } +} + +void +game_over(void) +{ + XEvent event; + 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; +}