commit ccc2ec76319127f7532ffa43994c9878a485623e
parent 1fa061078ffd1d1b625d96c08c92321d553f2a2a
Author: Matsuda Kenji <info@mtkn.jp>
Date: Sun, 25 Dec 2022 17:07:08 +0900
copy to ex6
Diffstat:
A | ex6/Makefile | | | 14 | ++++++++++++++ |
A | ex6/ex6.c | | | 606 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 620 insertions(+), 0 deletions(-)
diff --git a/ex6/Makefile b/ex6/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=ex6
+
+all: $(IN)
+ $(CC) $(INCS) $(CFLAGS) -o $(OUT) $(IN) $(LIBS)
+
+run: all
+ ./$(OUT)
+
+clean:
+ rm -f $(OUT)
diff --git a/ex6/ex6.c b/ex6/ex6.c
@@ -0,0 +1,606 @@
+#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 (100)
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+// #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;
+ int w, h;
+ int m;
+};
+
+struct circle {
+ float ppx, ppy;
+ float px, py;
+ float vx, vy;
+ int r;
+ int m;
+};
+
+/* 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 circle circle[NUM_RECT];
+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 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;
+ 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;
+ }
+ /*
+ circle[0].vx = circle[0].vy = 0;
+ if (key_state[KEY_D] == KEY_DOWN)
+ circle[0].vx += 300;
+ if (key_state[KEY_A] == KEY_DOWN)
+ circle[0].vx += -300;
+ if (key_state[KEY_S] == KEY_DOWN)
+ circle[0].vy += 300;
+ if (key_state[KEY_W] == KEY_DOWN)
+ circle[0].vy += -300;
+ */
+}
+
+void
+rect_next_tick(struct rect *s, long ndt) // nano second
+{
+ s->ppx = s->px;
+ s->ppy = s->py;
+ s->px = s->px + s->vx * ndt / 1000 / 1000 / 1000;
+ s->py = 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;
+ }
+}
+
+void
+circle_next_tick(struct circle *c, long ndt)
+{
+ c->ppx = c->px;
+ c->ppy = c->py;
+ c->px = c->px + c->vx * ndt / 1000 / 1000 / 1000;
+ c->py = 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;
+ if (sf->ppx + sf->w <= sm->ppx && sm->px < sf->px + sf->w)
+ // collision from right to left
+ sm->px = sf->px + sf->w;
+
+ if (sm->ppy + sm->h <= sf->ppy && sf->py < sm->py + sm->h)
+ // collision from up to down
+ sm->py = sf->py - sm->h;
+ if (sf->ppy + sf->h <= sm->ppy && sm->py < sf->py + sf->h)
+ // collision from dohn to up
+ sm->py = sf->py + sf->h;
+}
+
+/*
+ * 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
+game_play(void)
+{
+ int key_state[NUM_KEY];
+ long t0, t1, dt;
+#ifdef COUNT_FPS
+ int fps_count = 0;
+#endif
+ struct timespec ts;
+
+ for(int i = 0; i < NUM_RECT; i++){
+ circle[i].ppx = circle[i].px = rand() * (float)win_width / (float)RAND_MAX;
+ circle[i].ppy = circle[i].py = rand() * (float)win_height / (float)RAND_MAX;
+ circle[i].vx = rand() * 300.0 / (float)RAND_MAX - 150;
+ circle[i].vy = rand() * 300.0 / (float)RAND_MAX - 150;
+ circle[i].r = rand() * 30.0 / (float)RAND_MAX + 5;
+ circle[i].m = circle[i].r * circle[i].r;
+ }
+/*
+ circle[0].ppx = circle[0].px = 100;
+ circle[0].ppy = circle[0].py = 400;
+ circle[0].vx = 100;
+ circle[0].vy = -100;
+ circle[0].r = 100;
+ circle[0].m = circle[0].r * circle[0].r;
+ circle[1].ppx = circle[1].px = 400;
+ circle[1].ppy = circle[1].py = 100;
+ circle[1].vx = -100;
+ circle[1].vy = 200;
+ circle[1].r = 100;
+ circle[1].m = circle[1].r * circle[1].r;
+
+*/
+
+ 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 j = 0; j < SUB_TICK; j++) {
+ for (int i = 0; i < NUM_RECT; i++)
+ circle_next_tick(&circle[i], 1000 * 1000 * 1000 / FPS / SUB_TICK);
+
+ for (int i = 0; i < NUM_RECT; i++)
+ for (int j = i + 1; j < NUM_RECT; j++) {
+ circle_handle_collision_elastic(&circle[i], &circle[j]);
+ if (circle_test_collision(&circle[i], &circle[j]))
+ collision[i] = collision[j] = 1;
+ }
+ }
+ for (int i = 0; i < NUM_RECT; i++)
+ if (collision[i] == 1)
+ XSetForeground(display, sgc[i], 0xFFFF00);
+ else
+ XSetForeground(display, sgc[i], 0xFF << i % 3 * 8);
+
+ // 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++) {
+ XDrawArc(display, window, sgc[i],
+ circle[i].px - circle[i].r,
+ circle[i].py - circle[i].r, // position
+ circle[i].r * 2, circle[i].r * 2,
+ 0, 360 << 6); // width and height
+ }
+ }
+ XSetForeground(display, gc, 0x00FFFF);
+}
+
+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;
+}