#include #include #include #include // XXX POSIX #include // XXX POSIX #include "xshmu.h" static inline void fill(xshmu_pic pic, uint32_t color) { for (int i = 0; i < pic.h; i++) { uint32_t *p = xshmu_pix(pic, 0, i); for (int j = 0; j < pic.w; j++) p[j] = color; } } static inline xshmu_pic block(int x, int y, xshmu_pic pic) { return xshmu_subpic(pic, (x+1) * 20 + 1, y * 20 + 1, 18, 18); } void rotate_piece(char *piece, char piece_rotated[][4], int piece_r) { int origin, xs, ys; switch (piece_r) { case 0: origin = 0; xs = 1; ys = 4; break; case 1: origin = 3; xs = 4; ys = -1; break; case 2: origin = 15; xs = -1; ys = -4; break; case 3: default: origin = 12; xs = -4; ys = 1; break; } for (int y = 0; y != 4; y++) { for (int x = 0; x != 4; x++) { piece_rotated[x][y] = (' ' != piece[origin + x*xs + y*ys]); } } } int collision(int pit[10][20], char piece_rotated[4][4], int piece_x, int piece_y) { for (int x = 0; x < 4; x++) { for (int y = 0; y < 4; y++) { if (!piece_rotated[x][y]) continue; if (piece_y + y == 20 || piece_y + y < 0) return 1; if (piece_x + x < 0 || piece_x + x == 10) return 1; if (pit[x + piece_x][y + piece_y]) return 1; } } return 0; } void show(xshmu_pic where, char *text) { /* Half-assed fixed-width font format: each byte here contains one 6-bit row of pixels in its 6 least significant bits, ordered with the least significant on the left on the screen. There is an implicit horizontal pixel of spacing between glyphs. */ char *font = "NQQUUQQN" // 0 "DFDDDDDN" // 1 "NQPPHDB_" // 2 "OPHDHPQN" // 3 "@JJI_HHH" // 4 "_AAOPPQN" // 5 "HDBAOQQN" // 6 "_QHDNDDD" // 7 "NQQNQQQN" // 8 "NQQ^PPQN" // 9 ; // Designing that 10-digit font took from 2:59 to 3:10 with a broken // version of glyphed; the font could surely be improved. uint32_t fg = 0xff33aa, bg = 0x333333; for (char *p = text; *p; p++) { char *glyph = font + (*p & 0xf) % 10 * 8; for (int y = 0; y != 8; y++) { for (int x = 0; x != 6; x++) { *xshmu_pix(where, 1 + x + 7 * (p - text), 1 + y) = (glyph[y] & (1 << x)) ? fg : bg; } } } } int main() { xshmu w = xshmu_open("Tetris", 300, 480, ""); long drop_us = 900*1000; int lines_deleted = 0; int frames = 0; struct timeval last_drop; gettimeofday(&last_drop, NULL); srand(last_drop.tv_usec + last_drop.tv_sec); int pit[10][20] = {0}; uint32_t colors[] = {0, 0x993399, 0xcc3333, 0xffccff, 0xff3399, 0x3366ff, 0x33ff33, 0xccffcc}; char pieces[7][16] = { " " " ## " "## " " ", " " "## " " ## " " ", "# " "### " " " " ", " " "####" " " " ", " # " "### " " " " ", " # " "### " " " " ", " " " ## " " ## " " ", }; int piece = rand() % 7, piece_x = 3, piece_y = 0, piece_r = 0; char piece_rotated[4][4] = {{0}}; /* really bool */ for (;;) { rotate_piece(pieces[piece], piece_rotated, piece_r); int dropping = 0; for (xshmu_event *ev; (ev = xshmu_get_event(w));) { if (xshmu_as_die_event(ev)) { xshmu_close(w); return 0; } xshmu_key_event *key = xshmu_as_key_event(ev); if (key && key->down) { switch (key->keysym) { case xks_left: case 'a': case 'A': if (!collision(pit, piece_rotated, piece_x-1, piece_y)) piece_x--; break; case xks_right: case 'd': case 'D': if (!collision(pit, piece_rotated, piece_x+1, piece_y)) piece_x++; break; case xks_up: case 'w': case 'W': ; int new_rotation = (piece_r + 1) % 4; char piece_rotated_more[4][4]; rotate_piece(pieces[piece], piece_rotated_more, new_rotation); if (!collision(pit, piece_rotated_more, piece_x, piece_y)) { piece_r = new_rotation; rotate_piece(pieces[piece], piece_rotated, piece_r); } break; case xks_down: case 's': case 'S': dropping = 1; break; default: printf("keysym %d\n", key->keysym); break; } } } /* Has enough time passed to drop automatically? */ struct timeval now; gettimeofday(&now, NULL); struct timeval diff; timersub(&now, &last_drop, &diff); if (diff.tv_usec > drop_us) { dropping = 1; last_drop = now; } if (dropping) { if (collision(pit, piece_rotated, piece_x, piece_y+1)) { for (int x = 0; x < 4; x++) { for (int y = 0; y < 4; y++) { if (piece_rotated[x][y]) pit[x + piece_x][y + piece_y] = piece + 1; } } piece_y = 0; piece_x = 3; piece = rand() % 7; piece_r = 0; rotate_piece(pieces[piece], piece_rotated, piece_r); } else { piece_y++; } } for (int y = 20; y >= 0; y--) { for (int x = 0; x != 10; x++) { if (!pit[x][y]) goto dont_wipe; } /* wipe a line out */ lines_deleted++; for (int y2 = y; y2 > 0; y2--) { for (int x = 0; x != 10; x++) pit[x][y2] = pit[x][y2-1]; } for (int x = 0; x != 10; x++) pit[x][0] = 0; drop_us = drop_us / 16 * 15; y = 20; dont_wipe: ; } if (collision(pit, piece_rotated, piece_x, piece_y)) { /* game over */ for (int x = 0; x != 10; x++) { for (int y = 0; y != 20; y++) pit[x][y] = 0; } drop_us = 900*1000; printf("%d lines\n", lines_deleted); lines_deleted = 0; } xshmu_pic fb = xshmu_framebuffer(w); fill(fb, 0x333333); xshmu_pic board = xshmu_subpic(fb, (fb.w-12*20)/2, 20, 12*20, 21*20); for (int y = 0; y != 20; y++) { fill(block(-1, y, board), 0xcccccc); for (int x = 0; x != 10; x++) fill(block(x, y, board), colors[pit[x][y]]); fill(block(10, y, board), 0xcccccc); } for (int x = -1; x != 11; x++) fill(block(x, 20, board), 0xcccccc); for (int y = 0; y != 4; y++) { for (int x = 0; x != 4; x++) { if (piece_rotated[x][y]) { fill(block(x + piece_x, y + piece_y, board), colors[piece + 1]); } } } char lines_buf[5]; sprintf(lines_buf, "%d", lines_deleted); show(xshmu_subpic(fb, (fb.w-14)/2, 22*20+10, 100, 10), lines_buf); xshmu_flush(w); frames++; // this is currently about 255 frames per second on my laptop without the following: /* Before we rendered a frame, we handled all pending input events. Now let’s wait until there’s a new pending input event, or until a drop is due. This piece of shit is significant evidence that xshmu_wait should take a timeout argument. */ struct timeval desired_diff = { .tv_sec = 0, .tv_usec = drop_us }; struct timeval wait_end; timeradd(&last_drop, &desired_diff, &wait_end); struct timeval timeout; timersub(&wait_end, &now, &timeout); fd_set read_fds; FD_ZERO(&read_fds); FD_SET(xshmu_fd(w), &read_fds); select(xshmu_fd(w)+1, &read_fds, NULL, NULL, &timeout); } }