xlib_playground

Xlib playground for experiments.
Log | Files | Refs

ex4.c (10784B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <time.h>
      4 #include <unistd.h>
      5 #include <string.h>
      6 #include <math.h>
      7 
      8 #include <X11/Xlib.h>
      9 
     10 /* macros */
     11 #define INIT_WIDTH (250)
     12 #define INIT_HEIGHT (200)
     13 #define FPS (60)
     14 #define SUB_TICK (4)
     15 #define NUM_SQUARE (100)
     16 #define max(a, b) ((a) > (b) ? (a) : (b))
     17 #define min(a, b) ((a) < (b) ? (a) : (b))
     18 
     19 #define COUNT_FPS
     20 
     21 enum keys {
     22 	KEY_D,
     23 	KEY_S,
     24 	KEY_A,
     25 	KEY_W,
     26 	KEY_Q,
     27 	KEY_SPACE,
     28 	NUM_KEY, //number of keys in this enum
     29 };
     30 enum key_state {
     31 	KEY_UP,
     32 	KEY_DOWN,
     33 };
     34 enum next_menu {
     35 	START_MENU,
     36 	GAME_PLAY,
     37 	GAME_OVER,
     38 	QUIT,
     39 };
     40 
     41 struct square {
     42 	float ppx, ppy; // previous position
     43 	float px, py;
     44 	float vx, vy;
     45 	int w, h;
     46 };
     47 
     48 /* variables */
     49 Display      *display;
     50 Window        window;
     51 unsigned int  win_width = INIT_WIDTH, win_height = INIT_HEIGHT;
     52 GC            gc, sgc[NUM_SQUARE];
     53 Atom          wm_delete_window;
     54 struct square square[NUM_SQUARE];
     55 int           next_menu = START_MENU;
     56 
     57 
     58 /* function prototypes */
     59 void setup(void);
     60 void cleanup(void);
     61 void start_menu(void);
     62 void game_play(void);
     63 void receive_events(int[]);
     64 void handle_inputs(int[]);
     65 void next_tick(struct square *, long);
     66 int  test_collision(struct square *, struct square *);
     67 void handle_collision_mf(struct square *, struct square *);
     68 void handle_collision(struct square *, struct square *);
     69 void handle_collision_elastic(struct square *, struct square *);
     70 void game_over(void);
     71 
     72 
     73 void
     74 setup(void)
     75 {
     76 	if ((display = XOpenDisplay(NULL)) == NULL){
     77 	    fprintf(stderr, "ERROR: could not open display\n");
     78 	    exit(1);
     79 	}
     80 	window = XCreateSimpleWindow(
     81 	                    display,
     82 	                    XDefaultRootWindow(display),
     83 	                    0, 0,
     84 	                    win_width, win_height,
     85 	                    0, 0,
     86 						0);
     87 	XStoreName(display, window, "UNKO");
     88 	gc = XCreateGC(display, window, 0, NULL);
     89 	XSetForeground(display, gc, 0x00FFFF);
     90 	for (int i = 0; i < NUM_SQUARE; i++){
     91 		sgc[i] = XCreateGC(display, window, 0, NULL);
     92 		XSetForeground(display, sgc[i], 0x00FF00);
     93 	}
     94 
     95 	wm_delete_window = XInternAtom(display,
     96 	                               "WM_DELETE_WINDOW", False);
     97 	XSetWMProtocols(display, window, &wm_delete_window, 1);
     98 
     99 	XSelectInput(display, window,
    100 	             ExposureMask | KeyPressMask | KeyReleaseMask);
    101 
    102 	XMapWindow(display, window);
    103 }
    104 
    105 void
    106 start_menu(void)
    107 {
    108 	XEvent event;
    109 	char *menu_char_q = "press q to quit.";
    110 	char *menu_char_s = "press <space> to start.";
    111 
    112 	XClearArea(display, window,
    113 			   0, 0,                  // position
    114 			   win_width, win_height, // width and height
    115 			   False);
    116 	XDrawString(display, window, gc,
    117 				win_width/2 - strlen(menu_char_q)/2, win_height/2,
    118 				menu_char_q, strlen(menu_char_q));
    119 	XDrawString(display, window, gc,
    120 				win_width/2 - strlen(menu_char_s)/2, win_height/2 + 20,
    121 				menu_char_s, strlen(menu_char_s));
    122 
    123 	while (next_menu == START_MENU) {
    124 		XNextEvent(display, &event);
    125 		switch (event.type) {
    126 		case Expose: {
    127 			XDrawString(display, window, gc,
    128 						win_width/2 - strlen(menu_char_q)/2,
    129 						win_height/2,
    130 						menu_char_q, strlen(menu_char_q));
    131 			XDrawString(display, window, gc,
    132 						win_width/2 - strlen(menu_char_s)/2,
    133 						win_height/2 + 20,
    134 						menu_char_s, strlen(menu_char_s));
    135 
    136 		} break;
    137 		case KeyPress: {
    138 			switch (XLookupKeysym(&event.xkey, 0)) {
    139 			case 'q':
    140 				next_menu = QUIT;
    141 				break;
    142 			case ' ':
    143 				next_menu = GAME_PLAY;
    144 				break;
    145 			default:
    146 				break;
    147 			}
    148 		} break;
    149 		case ClientMessage: {
    150 			if ((Atom) event.xclient.data.l[0] == wm_delete_window) {
    151 				next_menu = QUIT;
    152 			}
    153 		} break;
    154 		default:
    155 			break;
    156 		}
    157 	}
    158 }
    159 
    160 void
    161 receive_events(int key_state[])
    162 {
    163 	XEvent event;
    164 	XWindowAttributes wattr;
    165 
    166 	while (XPending(display) > 0) {
    167 		XNextEvent(display, &event);
    168 		switch (event.type) {
    169 		case Expose: {
    170 			XGetWindowAttributes(display, window, &wattr);
    171 			win_width = wattr.width;
    172 			win_height = wattr.height;
    173 		} break;
    174 		case KeyPress: {
    175 			switch (XLookupKeysym(&event.xkey, 0)) {
    176 			case 'q':
    177 				//next_menu = GAME_OVER;
    178 				key_state[KEY_Q] = KEY_DOWN;
    179 				break;
    180 			case 'd':
    181 				key_state[KEY_D] = KEY_DOWN;
    182 				break;
    183 			case 'a':
    184 				key_state[KEY_A] = KEY_DOWN;
    185 				break;
    186 			case 'w':
    187 				key_state[KEY_W] = KEY_DOWN;
    188 				break;
    189 			case 's':
    190 				key_state[KEY_S] = KEY_DOWN;
    191 				break;
    192 			default:
    193 				break;
    194 			}
    195 		} break;
    196 		case KeyRelease: {
    197 			switch (XLookupKeysym(&event.xkey, 0)) {
    198 			case 'q':
    199 				key_state[KEY_Q] = KEY_UP;
    200 				break;
    201 			case 'd':
    202 				key_state[KEY_D] = KEY_UP;
    203 				break;
    204 			case 'a':
    205 				key_state[KEY_A] = KEY_UP;
    206 				break;
    207 			case 'w':
    208 				key_state[KEY_W] = KEY_UP;
    209 				break;
    210 			case 's':
    211 				key_state[KEY_S] = KEY_UP;
    212 				break;
    213 			default:
    214 				break;
    215 			}
    216 		} break;
    217 		case ClientMessage: {
    218 			if ((Atom) event.xclient.data.l[0] == wm_delete_window) {
    219 				next_menu = QUIT;
    220 			}
    221 		} break;
    222 		default:
    223 			break;
    224 		}
    225 	}
    226 }
    227 
    228 void
    229 handle_inputs(int key_state[])
    230 {
    231 	if (key_state[KEY_Q] == KEY_DOWN){
    232 		next_menu = GAME_OVER;
    233 		return;
    234 	}
    235 	/*
    236 	square[0].vx = square[0].vy = 0;
    237 	if (key_state[KEY_D] == KEY_DOWN)
    238 		square[0].vx += 300;
    239 	if (key_state[KEY_A] == KEY_DOWN)
    240 		square[0].vx += -300;
    241 	if (key_state[KEY_S] == KEY_DOWN)
    242 		square[0].vy += 300;
    243 	if (key_state[KEY_W] == KEY_DOWN)
    244 		square[0].vy += -300;
    245 	*/
    246 }
    247 
    248 void
    249 next_tick(struct square *s, long ndt) // nano second
    250 {
    251 	s->ppx = s->px;
    252 	s->ppy = s->py;
    253 	s->px = s->px + s->vx * ndt / 1000 / 1000 / 1000;
    254 	s->py = s->py + s->vy * ndt / 1000 / 1000 / 1000;
    255 
    256 	// bind within the window
    257 	if (s->px < 0) {
    258 		s->px = 0;
    259 		s->vx *= -1;
    260 	}
    261 	if (win_width < s->px + s->w) {
    262 		s->px = win_width - s->w;
    263 		s->vx *= -1;
    264 	}
    265 	if (s->py < 0) {
    266 		s->py = 0;
    267 		s->vy *= -1;
    268 	}
    269 	if (win_height < s->py + s->h) {
    270 		s->py = win_height - s->h;
    271 		s->vy *= -1;
    272 	}
    273 }
    274 
    275 int
    276 test_collision(struct square *s1, struct square* s2)
    277 {
    278 	return s1->px < s2->px + s2->w && s2->px < s1->px + s1->w &&
    279 	       s2->py < s1->py + s1->h && s1->py < s2->py + s2->h;
    280 }
    281 
    282 /*
    283  * Handle collision of a moving square against fixed square
    284  */
    285 void
    286 handle_collision_mf(struct square *sm, struct square *sf)
    287 {
    288 	if (!test_collision(sm, sf))
    289 		return;
    290 	if (sm->ppx + sm->w <= sf->ppx && sf->px < sm->px + sm->w)
    291 		// collisioin from left to right
    292 		sm->px = sf->px - sm->w;
    293 	if (sf->ppx + sf->w <= sm->ppx && sm->px < sf->px + sf->w)
    294 		// collision from right to left
    295 		sm->px = sf->px + sf->w;
    296 
    297 	if (sm->ppy + sm->h <= sf->ppy && sf->py < sm->py + sm->h)
    298 		// collision from up to down
    299 		sm->py = sf->py - sm->h;
    300 	if (sf->ppy + sf->h <= sm->ppy && sm->py < sf->py + sf->h)
    301 		// collision from dohn to up
    302 		sm->py = sf->py + sf->h;
    303 }
    304 
    305 /*
    306  * Handle collision of a moving square against another moving square
    307  */
    308 void
    309 handle_collision_mm(struct square *s1, struct square *s2)
    310 {
    311 	if (!test_collision(s1, s2))
    312 		return;
    313 
    314 	float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px);
    315 	float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py);
    316 	float rel_vx = max(s1->vx - s2->vx, s2->vx - s1->vx);
    317 	float rel_vy = max(s1->vy - s2->vy, s2->vy - s1->vy);
    318 
    319 	if (lapx / rel_vx < lapy / rel_vy) {
    320 		if (s1->px + s1->w < s2->px + s2->w / 2) {
    321 			s1->px -= lapx / 2;
    322 			s2->px += lapx / 2;
    323 		} else {
    324 			s1->px += lapx / 2;
    325 			s2->px -= lapx / 2;
    326 		}
    327 	} else {
    328 		if (s1->py + s1->h < s2->py + s2->h / 2) {
    329 			s1->py -= lapy / 2;
    330 			s2->py += lapy / 2;
    331 		} else {
    332 			s1->py += lapy / 2;
    333 			s2->py -= lapy / 2;
    334 		}
    335 	}
    336 }
    337 
    338 void
    339 handle_collision_elastic(struct square *s1, struct square *s2)
    340 {
    341 	if(!test_collision(s1, s2))
    342 		return;
    343 
    344 	float v1, v2;
    345 	float m1 = s1->w * s1->h;
    346 	float m2 = s2->w * s2->h;
    347 
    348 	float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px);
    349 	float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py);
    350 
    351 	if (lapx < lapy) {
    352 		v1 = s1->vx;
    353 		v2 = s2->vx;
    354 		s1->vx = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1;
    355 		s2->vx = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2;
    356 	} else {
    357 		v1 = s1->vy;
    358 		v2 = s2->vy;
    359 		s1->vy = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1;
    360 		s2->vy = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2;
    361 	}
    362 
    363 	handle_collision_mm(s1, s2);
    364 }
    365 
    366 void
    367 game_play(void)
    368 {
    369 	int  key_state[NUM_KEY];
    370 	long t0, t1, dt;
    371 #ifdef COUNT_FPS
    372 	int fps_count = 0;
    373 #endif
    374 	struct timespec ts;
    375 
    376 	for(int i = 0; i < NUM_SQUARE; i++){
    377 		square[i].ppx = square[i].px = (float)rand() * win_width / RAND_MAX;
    378 		square[i].ppy = square[i].py = (float)rand() * win_height / RAND_MAX;
    379 		square[i].vx = (float)rand() * 100 / RAND_MAX - 50;
    380 		square[i].vy = (float)rand() * 100 / RAND_MAX - 50;
    381 		square[i].w = square[i].h = (float)rand() * 10 / RAND_MAX;
    382 	}
    383 
    384 
    385 	while (next_menu == GAME_PLAY){
    386 		clock_gettime(CLOCK_MONOTONIC, &ts);
    387 		t0 = ts.tv_nsec;
    388 		receive_events(key_state);
    389 		handle_inputs(key_state);
    390 
    391 
    392 		clock_gettime(CLOCK_MONOTONIC, &ts);
    393 		t0 = ts.tv_nsec;
    394 
    395 		int collision[NUM_SQUARE] = {0};
    396 		for (int j = 0; j < SUB_TICK; j++) {
    397 			for (int i = 0; i < NUM_SQUARE; i++)
    398 				next_tick(&square[i], 1000 * 1000 * 1000 / FPS / SUB_TICK);
    399 
    400 			for (int i = 0; i < NUM_SQUARE; i++)
    401 				for (int j = i + 1; j < NUM_SQUARE; j++) {
    402 					handle_collision_elastic(&square[i], &square[j]);
    403 					if (test_collision(&square[i], &square[j]))
    404 						collision[i] = collision[j] = 1;
    405 				}
    406 		}
    407 		for (int i = 0; i < NUM_SQUARE; i++)
    408 			if (collision[i] == 1)
    409 				XSetForeground(display, sgc[i], 0xFFFF00);
    410 			else
    411 				XSetForeground(display, sgc[i], 0xFF << i % 3 * 8);
    412 
    413 		// fix fps
    414 		// TODO: This method create some strange stripe when
    415 		// rendered in 60fps on a 60fps monitor.
    416 		dt = 0;
    417 		while (dt < 1.0 * 1000 * 1000 * 1000 / FPS){
    418 			clock_gettime(CLOCK_MONOTONIC, &ts);
    419 			t1 = ts.tv_nsec;
    420 			dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1000 * 1000 * 1000;
    421 		}
    422 #ifdef COUNT_FPS
    423 		// count fps.
    424 		fps_count++;
    425 		if (t1 < t0){
    426 			printf("fps: %u\n", fps_count);
    427 			fps_count = 0;
    428 		}
    429 #endif
    430 
    431 		XClearArea(display, window,
    432 				   0, 0,                  // position
    433 				   win_width, win_height, // width and height
    434 				   False);
    435 		for (int i = 0; i < NUM_SQUARE; i++) {
    436 			XDrawRectangle(display, window, sgc[i],
    437 						   square[i].px, square[i].py,    // position
    438 						   square[i].w, square[i].h);   // width and height
    439 		}
    440 	}
    441 	XSetForeground(display, gc, 0x00FFFF);
    442 }
    443 
    444 void
    445 game_over(void)
    446 {
    447 	char *menu_char = "GAME OVER";
    448 
    449 	XClearArea(display, window,
    450 			   0, 0,                  // position
    451 			   win_width, win_height, // width and height
    452 			   False);
    453 	XDrawString(display, window, gc,
    454 				win_width/2 - strlen(menu_char)/2, win_height/2,
    455 				menu_char, strlen(menu_char));
    456 	XFlush(display);
    457 
    458 	sleep(1);
    459 	next_menu = START_MENU;
    460 }
    461 
    462 void
    463 cleanup(void)
    464 {
    465 	XCloseDisplay(display);
    466 }
    467 
    468 int
    469 main(void)
    470 {
    471 	setup();
    472 	while (next_menu != QUIT){
    473 		switch (next_menu){
    474 		case START_MENU:
    475 			start_menu();
    476 			break;
    477 		case GAME_PLAY:
    478 			game_play();
    479 			break;
    480 		case GAME_OVER:
    481 			game_over();
    482 			break;
    483 		default:
    484 			break;
    485 		}
    486 	}
    487 
    488 	cleanup();
    489 	return 0;
    490 }