commit 8d3953956f7cca1dc5b3064155a8d32251658139
parent 4487faf9bbacc1c704f105db70f0060175d80339
Author: Matsuda Kenji <info@mtkn.jp>
Date: Sun, 25 Dec 2022 09:31:52 +0900
copy
Diffstat:
A | ex5/Makefile | | | 14 | ++++++++++++++ |
A | ex5/ex5.c | | | 488 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 502 insertions(+), 0 deletions(-)
diff --git a/ex5/Makefile b/ex5/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=ex5
+
+all: $(IN)
+ $(CC) $(INCS) $(CFLAGS) -o $(OUT) $(IN) $(LIBS)
+
+run: all
+ ./$(OUT)
+
+clean:
+ rm -f $(OUT)
diff --git a/ex5/ex5.c b/ex5/ex5.c
@@ -0,0 +1,488 @@
+#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_SQUARE (500)
+#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 square {
+ float ppx, ppy; // previous position
+ float px, py;
+ float vx, vy;
+ int w, h;
+};
+
+/* variables */
+Display *display;
+Window window;
+unsigned int win_width = INIT_WIDTH, win_height = INIT_HEIGHT;
+GC gc, sgc[NUM_SQUARE];
+Atom wm_delete_window;
+struct square square[NUM_SQUARE];
+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(struct square *, long);
+int test_collision(struct square *, struct square *);
+void handle_collision_mf(struct square *, struct square *);
+void handle_collision(struct square *, struct square *);
+void handle_collision_elastic(struct square *, struct square *);
+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_SQUARE; 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;
+ }
+ /*
+ square[0].vx = square[0].vy = 0;
+ if (key_state[KEY_D] == KEY_DOWN)
+ square[0].vx += 300;
+ if (key_state[KEY_A] == KEY_DOWN)
+ square[0].vx += -300;
+ if (key_state[KEY_S] == KEY_DOWN)
+ square[0].vy += 300;
+ if (key_state[KEY_W] == KEY_DOWN)
+ square[0].vy += -300;
+ */
+}
+
+void
+next_tick(struct square *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;
+ }
+}
+
+int
+test_collision(struct square *s1, struct square* 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;
+}
+
+/*
+ * Handle collision of a moving square against fixed square
+ */
+void
+handle_collision_mf(struct square *sm, struct square *sf)
+{
+ if (!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 square against another moving square
+ */
+void
+handle_collision_mm(struct square *s1, struct square *s2)
+{
+ if (!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
+handle_collision_elastic(struct square *s1, struct square *s2)
+{
+ if(!test_collision(s1, s2))
+ return;
+
+ handle_collision_mm(s1, s2);
+
+ float v1, v2;
+ float m1 = s1->w * s1->h;
+ float m2 = s2->w * s2->h;
+
+ 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
+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_SQUARE; i++){
+ square[i].ppx = square[i].px = (float)rand() * win_width / RAND_MAX;
+ square[i].ppy = square[i].py = (float)rand() * win_height / RAND_MAX;
+ square[i].vx = (float)rand() * 300 / RAND_MAX - 150;
+ square[i].vy = (float)rand() * 300 / RAND_MAX - 150;
+ square[i].w = square[i].h = (float)rand() * 30 / RAND_MAX;
+ }
+
+
+ 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_SQUARE] = {0};
+ for (int j = 0; j < SUB_TICK; j++) {
+ for (int i = 0; i < NUM_SQUARE; i++)
+ next_tick(&square[i], 1000 * 1000 * 1000 / FPS / SUB_TICK);
+
+ for (int i = 0; i < NUM_SQUARE; i++)
+ for (int j = i + 1; j < NUM_SQUARE; j++) {
+ handle_collision_elastic(&square[i], &square[j]);
+ if (test_collision(&square[i], &square[j]))
+ collision[i] = collision[j] = 1;
+ }
+ }
+ for (int i = 0; i < NUM_SQUARE; 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_SQUARE; i++) {
+ XDrawRectangle(display, window, sgc[i],
+ square[i].px, square[i].py, // position
+ square[i].w, square[i].h); // 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;
+}