/**************************************************************************** * * * Chetyre - A falling tetromino game * * * * The game runs in a UNIX terminal and uses no libraries other than the * * C standard library. Therefore it can be compiled with: * * * * cc -o chetyre chetyre-3.c * * * * It can then be run with: * * * * ./chetyre * * * * This source code has been tested on Linux and should probably work on * * other UNIX systems as well. * * * * This source code, as well as binaries for some systems, are available at * * http://haddonthethird.net/chetyre * * * * Copyright 2011, 2018 William Haddon * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted, provided that the above * * copyright notice and this permission notice appear in all copies. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * * ****************************************************************************/ #include #include #include #include #include #include #include #define WIDTH 11 #define HEIGHT 23 struct tetromino { char width; char height; char data; char color; }; char *splash = " ,~~~, ,~~~, \033[1;34m+~~+\033[0m\n" " `~,\033[1;30m##\033[0m\\ `~,\033[1;30m##\033[0m\\ " "\033[1;34m|\033[0;34m[]\033[1;34m|\033[0m\n" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ " "\033[1;34m+~~+~~+\033[0m\n" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ " "\033[1;34m|\033[0;34m[]\033[1;34m|\033[0;34m[]\033[1;34m|\033[0m\n" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ " "\033[1;34m+~~+~~+\033[0m\n" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ " "\033[1;34m|\033[0;34m[]\033[1;34m|\033[0m\n" "/\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ " "\033[1;34m+~~+\033[0m\n" "|\033[1;30m##\033[0m| ,\"\033[1;30m##\033[0m/ ,~~~, ,~~~,,~~~~,,~~~, " ",~~~, ,~~~~,\033[1;33m+~\033[0m,~~~,,~~~~, ,~~~,\n" "\\\033[1;30m##\033[0m\\__,\"\033[1;30m###\033[0m/ ,\"\033[1;30m##\033[0m_" "\033[1;30m##\033[0m\\ `~,\033[1;30m####\033[0m_\033[1;30m#####\033[0m_" "\033[1;30m##\033[0m\\ `~,\033[1;30m##\033[0m\\ `~~,\033[1;30m##\033[0m\\" "\033[0;33m[\033[0m`~,\033[1;30m####\033[0m_\033[1;30m###\033[0m\\ ,\"" "\033[1;30m##\033[0m_\033[1;30m##\033[0m\\\n" " \\\033[1;30m########\033[0m/ /\033[1;30m##\033[0m,\" \",\033[1;30m#\033[0m\\" " /\033[1;30m##\033[0m,\" )\033[1;30m##\033[0m,\" )\033[1;30m#\033[0m| " "/\033[1;30m##\033[0m/ |\033[1;30m##\033[0m|\033[1;33m~~+\033[0m/" "\033[1;30m##\033[0m,\" )\033[1;30m##\033[0m| /\033[1;30m##\033[0m,\" \"," "\033[1;30m#\033[0m\\\n" " `~~\"/\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ |\033[1;30m#\033[0m| /" "\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /" "\033[1;30m##\033[0m/ \033[1;36m+~~+\033[0m/\033[1;30m##\033[0m/" "\033[0;33m[]\033[0m/\033[1;30m##\033[0m/ /\033[1;30m##\033[0m//" "\033[1;30m##\033[0m/ |\033[1;30m#\033[0m|\n" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/____/\033[1;30m##\033[0m//" "\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /" "\033[1;30m##\033[0m/\033[1;34m~~+\033[1;36m|\033[0;36m[]\033[0m/" "\033[1;30m##\033[0m/\033[1;33m+~\033[0m/\033[1;30m##\033[0m/\033[1;33m+" "\033[0m /\033[1;30m##\033[0m//\033[1;30m##\033[0m/____/\033[1;30m##" "\033[0m/\n" " /\033[1;30m##\033[0m/ /\033[1;30m#########\033[0m,\"/\033[1;30m##\033[0m/" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /\033[1;30m###\033[0m\"," "\033[0;34m]\033[1;34m|\033[1;36m+~\033[0m/\033[1;30m##\033[0m/\033[1;36m" "+~\033[0m/\033[1;30m##\033[0m/\033[0;33m]\033[1;33m|\033[0m /\033[1;30m#" "#\033[0m//\033[1;30m#########\033[0m,\"\n" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m,~~~~~~\" /\033[1;30m##\033[0m/" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m,~," "\033[1;30m#\033[0m\\\033[1;34m+~\033[0m/\033[1;30m##\033[0m/\033[0;36m]" "\033[1;36m|\033[0m/\033[1;30m##\033[0m/\033[1;33m~~+\033[0m/\033[1;30m##" "\033[0m//\033[1;30m##\033[0m,~~~~~~\"\n" " /\033[1;30m##\033[0m/ |\033[1;30m##\033[0m| /\033[1;30m##\033[0m/" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /" "\033[1;30m#\033[0m|\033[1;34m|\033[0m/\033[1;30m##\033[0m/\033[1;36m~~" "\033[0m/\033[1;30m##\033[0m/ ,\"\033[1;30m#\033[0m,\"\033[1;31m|" "\033[0m|\033[1;30m##\033[0m|\n" " /\033[1;30m##\033[0m(_/\\\\\033[1;30m##\033[0m\\___/\\ /\033[1;30m##\033[0m/" " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m(_," "\"\033[1;30m##\033[0m//\033[1;30m##\033[0m/\033[1;32m+~\033[0m/" "\033[1;30m##\033[0m/\033[1;32m+\033[0m,\"\033[1;30m#\033[0m,\"" "\033[1;31m~~+\033[0m\\\033[1;30m##\033[0m\\___/\\\n" " \\\033[1;30m#####\033[0m/\033[0;31m]\033[0m\\\033[1;30m######\033[0m//" "\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ \\\033[1;30m##\033[0m`~,\\" "\033[1;30m######\033[0m,\"\033[0;34m]\033[0m\\\033[1;30m##\033[0m`,/" "\033[1;30m##\033[0m/\033[0;32m]\033[0m(\033[1;30m###\033[0m`~," "\033[0;31m]\033[1;31m|\033[0m \\\033[1;30m######\033[0m/\n" " `~~~\"\033[1;31m~~+\033[0m`~~~~\" \\,\" \\,\" \"~~\" `~~~~\"" "\033[1;34m+~~+\033[0m\"~\"/\033[1;30m##\033[0m/\033[1;32m~~+\033[0m\"~~~" "\"\033[1;31m~~+\033[0m `~~~~\"\n" " \033[1;31m|\033[0;31m[]\033[1;31m|\033[0m+~~~~~~~~~~~~~~~~~~~~~~~~~~~~" "~~~+\033[1;31m+~~+\033[0m /\033[1;30m##\033[0m/\033[1;32m|\033[1;36m+" "~~+~~+\033[1;31m|\033[0;31m[]\033[1;31m|\033[1;33m+~~+~~+\033[0m\n" " \033[1;31m+~~+\033[0m| |\033[1;31m|" "\033[0;31m[]\033[1;31m|\033[0m /\033[1;30m##\033[0m/\033[1;32m~+" "\033[1;36m|\033[0;36m[]\033[1;36m|\033[0;36m[]\033[1;36m|\033[1;31m+~~+" "\033[1;33m|\033[0;33m[]\033[1;33m|\033[0;33m[]\033[1;33m|\033[0m\n" " \033[1;31m|\033[0;31m[]\033[1;31m|\033[0m| Press to play" " |\033[1;31m+~~+\033[0m/\033[1;30m##\033[0m/\033[1;31m~+ " "\033[1;36m+~~+~~+ \033[1;33m+~~+~~+~~+\033[0m\n" " \033[1;31m+~~+\033[0m| Press to quit |\033[1;31m|" "\033[0;31m[]\033[0m/\033[1;30m##\033[0m/\033[0;31m[]\033[1;31m| " "\033[1;36m|\033[0;36m[]\033[1;36m|\033[0;36m[]\033[1;36m| \033[1;33m|" "\033[0;33m[]\033[1;33m|\033[0;33m[]\033[1;33m|\033[0m\n" " +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+\033[1;31m+~~\033[0m\\,\"" "\033[1;31m+~~+ \033[1;36m+~~+~~+ \033[1;33m+~~+~~+\033[0m\n"; struct tetromino tetrominoes[7] = { {1, 4, 0xf, 'I'}, /* Line */ {2, 3, 0x3a, 'J'}, /* Reverse L */ {2, 3, 0x35, 'L'}, /* L */ {2, 2, 0xf, 'O'}, /* Square */ {2, 3, 0x2d, 'S'}, /* S */ {2, 3, 0x2e, 'T'}, /* T */ {2, 3, 0x1e, 'Z'}}; /* Reverse S */ char board[HEIGHT][WIDTH]; char input_array[3]; uint32_t dirty_lines; int score; int speed; size_t get_input(); void wait_key(); int display_splash(); int display_board(int); struct tetromino *copy_tetromino(struct tetromino *); struct tetromino *rotate_tetromino(struct tetromino *); void fill_tetromino(struct tetromino *, int, int, char); void place_tetromino(struct tetromino *, int, int); void clear_tetromino(struct tetromino *, int, int); int obstacle_tetromino(struct tetromino *, int, int); void move_tetromino(struct tetromino *, int *, int *, int, int); void rotate_in_place(struct tetromino **, int, int); int input_loop(struct tetromino **, int *, int *); int drop_tetromino(); void clear_display(); void clear_board(); void check_lines(); void play(); void setup(); void unsetup(); size_t get_input() { int i; for (i = 0; i < 3; i++) input_array[i] = 0; return read(0, &input_array, 3); } void wait_key() { while (1) { if (get_input()) break; usleep(1000); } } int display_splash() { int i; clear_display(); printf("%s", splash); while (1) { get_input(); switch (input_array[0]) { case 'q': case 'Q': return 0; case '\n': return 1; } usleep(1000); } } char *tiles[] = { " ", /* A */ " ", /* B */ " ", /* C */ " ", /* D */ " ", /* E */ " ", /* F */ " ", /* G */ " ", /* H */ " ", /* I - Line (Purple) */ "\033[0;35m[]", /* J - Reverse L (Red) */ "\033[0;31m[]", /* K */ " ", /* L (Green) */ "\033[0;32m[]", /* M */ " ", /* N */ " ", /* O - Square (Cyan) */ "\033[0;36m[]", /* P */ " ", /* Q */ " ", /* R */ " ", /* S (Yellow) */ "\033[0;33m[]", /* T (Blue) */ "\033[0;34m[]", /* U */ " ", /* V */ " ", /* W */ " ", /* X */ " ", /* Y */ " ", /* Z - Reverse S (Default Color) */ "\033[0m[]", " ", " ", " ", " ", " "}; int display_board(int gameover) { int i, j; printf("\033[1;1H"); for (i = 0; i < HEIGHT; i++) { if (dirty_lines & (1 << i)) { printf(" ||"); for (j = 0; j < WIDTH; j++) { if (board[i][j] >= 64 && board[i][j] < 96) printf("%s", tiles[board[i][j] - 64]); else printf(" "); } printf("\033[0m||"); switch (i) { case 7: printf(" Score: %d points", score); break; case 8: printf(" Speed: %.2f lines per second", (1000.0 / speed)); break; case 10: if (gameover) printf(" GAME OVER "); else printf(" Press to end game"); break; case 11: if (gameover) printf(" Press any key to continue"); break; } } printf("\n"); } dirty_lines = 0; } struct tetromino *copy_tetromino(struct tetromino *t) { struct tetromino *new; new = malloc(sizeof(struct tetromino)); memcpy(new, t, sizeof(struct tetromino)); return new; } struct tetromino *rotate_tetromino(struct tetromino *t) { struct tetromino *new; char i, j, n, width, height, data; width = t->height; height = t->width; n = 1; data = 0; for (i = 0; i < width; i++) { for (j = 0; j < height; j++) { if (t->data & n) data |= (1 << ((height - j - 1) * width) << i); n <<= 1; } } new = malloc(sizeof(struct tetromino)); new->width = width; new->height = height; new->data = data; new->color = t->color; return new; } void fill_tetromino(struct tetromino *t, int line, int pos, char c) { int i, j, n; n = 1; for (j = 0; j < t->height; j++) { for (i = 0; i < t->width; i++) { if (t->data & n) { board[line+j][pos+i] = c; dirty_lines |= 1 << (line+j); } n = n << 1; } } } void place_tetromino(struct tetromino *t, int line, int pos) { if (line + t->height <= HEIGHT && pos + t->width <= WIDTH) fill_tetromino(t, line, pos, t->color); } void clear_tetromino(struct tetromino *t, int line, int pos) { fill_tetromino(t, line, pos, ' '); } int obstacle_tetromino(struct tetromino *t, int line, int pos) { int i, j, n; if (line < 0 || pos < 0) return 1; if (line + t->height > HEIGHT || pos + t->width > WIDTH) return 1; n = 1; for (j = 0; j < t->height; j++) { for (i = 0; i < t->width; i++) { if ((t->data) & n && board[line+j][pos+i] != ' ') return 1; n <<= 1; } } return 0; } void move_tetromino(struct tetromino *t, int *x, int *y, int dx, int dy) { int i; clear_tetromino(t, *x, *y); for (i = 0; i < abs(dx) || i < abs(dy); i++) { if (i < abs(dx) && !obstacle_tetromino(t, *x + dx / abs(dx), *y)) *x += dx / abs(dx); if (i < abs(dy) && !obstacle_tetromino(t, *x, *y + dy / abs(dy))) *y += dy / abs(dy); } place_tetromino(t, *x, *y); display_board(0); } void rotate_in_place(struct tetromino **t, int x, int y) { struct tetromino *new; new = rotate_tetromino(*t); clear_tetromino(*t, x, y); if (!obstacle_tetromino(new, x, y)) { free(*t); *t = new; } place_tetromino(*t, x, y); display_board(0); } int input_loop(struct tetromino **t, int *x, int *y) { int i; for (i = 0; i < speed; i++) { get_input(); switch (input_array[0]) { case 033: if (input_array[1] == '[') { switch (input_array[2]) { case 'A': /* Up arrow */ rotate_in_place(t, *x, *y); break; case 'B': /* Down arrow */ move_tetromino(*t, x, y, 5, 0); break; case 'C': /* Right arrow */ move_tetromino(*t, x, y, 0, 1); break; case 'D': /* Left arrow */ move_tetromino(*t, x, y, 0, -1); break; } } break; case 'q': case 'Q': return 1; } usleep(1000); } return 0; } int drop_tetromino() { struct tetromino *curr_t, *old_t; int i, j; curr_t = copy_tetromino(&tetrominoes[rand() % 7]); for (i = 0; i < rand() % 4; i++) { old_t = curr_t; curr_t = rotate_tetromino(old_t); free(old_t); } j = WIDTH / 2; if (obstacle_tetromino(curr_t, i, j)) return 0; for (i = 0; i < HEIGHT; i++) { place_tetromino(curr_t, i, j); display_board(0); if (input_loop (&curr_t, &i, &j)) return 0; clear_tetromino(curr_t, i, j); if (obstacle_tetromino(curr_t, i + 1, j)) { place_tetromino(curr_t, i, j); return 1; } } } void clear_display() { printf("\033[2J\033[1;1H"); dirty_lines = 0xffffffffU; } void clear_board() { int i, j; for (i = 0; i < HEIGHT; i++) for (j = 0; j < WIDTH; j++) board[i][j] = ' '; } void check_lines() { int i, j, k, c, test; c = 0; for (i = 0; i < HEIGHT; i++) { test = 1; for (j = 0; j < WIDTH; j++) if (board[i][j] == ' ') test = 0; if (test) { for (k = i; k > 0; k--) for (j = 0; j < WIDTH; j++) board[k][j] = board[k-1][j]; for (j = 0; j < WIDTH; j++) board[0][j] = ' '; c++; } } speed -= c * (speed / 80 + 1); score += c * c * ((c - 1) + !(c - 1)); if (c) dirty_lines = 0xffffffffU; } void play() { int i; score = 0; speed = 500; clear_board(); clear_display(); while (drop_tetromino()) { check_lines(); display_board(0); } dirty_lines = 0xffffffffU; display_board(1); wait_key(); } struct termios stored_keyboard_settings; struct termios new_keyboard_settings; void setup() { srand(time(NULL)); printf("\033[?25l\033[?47h"); tcgetattr(0, &stored_keyboard_settings); new_keyboard_settings = stored_keyboard_settings; new_keyboard_settings.c_lflag &= (~(ECHO | ICANON)); new_keyboard_settings.c_cc[VTIME] = 0; new_keyboard_settings.c_cc[VMIN] = 0; tcsetattr(0, TCSANOW, &new_keyboard_settings); } void unsetup() { tcsetattr(0, TCSANOW, &stored_keyboard_settings); printf("\033[?25h\033[?47l\033[0m"); } int main() { setup(); while (display_splash()) play(); unsetup(); }