/**************************************************************************** * * * 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.c * * * * It can then be run with: * * * * ./chetyre * * * * This source code should probably compile and run successfully on typical * * current UNIX systems such as Linux, BSD, and Mac OS X. Windows will * * probably be more difficult. * * * * This source code, as well as binaries for some systems, are available at * * http://haddonthethird.net/chetyre * * * * Copyright 2011 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 #define WIDTH 11 #define HEIGHT 23 struct tetromino { char width; char height; char data; char color; }; char *splash[] = { " ,~~~, ,~~~, \033[1;34m+~~+\033[0m", " `~,\033[1;30m##\033[0m\\ `~,\033[1;30m##\033[0m\\ \033[1;34m|\033[0;34m[]\033[1;34m|\033[0m", " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ \033[1;34m+~~+~~+\033[0m", " /\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", " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ \033[1;34m+~~+~~+\033[0m", " /\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ \033[1;34m|\033[0;34m[]\033[1;34m|\033[0m", "/\033[1;30m##\033[0m/ /\033[1;30m##\033[0m/ \033[1;34m+~~+\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\\ `~,\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\\", " \\\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\\", " `~~\"/\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|", " /\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/", " /\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,\"", " /\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,~~~~~~\"", " /\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|", " /\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\\___/\\", " \\\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/", " `~~~\"\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 `~~~~\"", " \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", " \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", " \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", " \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", " +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+\033[1;31m+~~\033[0m\\,\"\033[1;31m+~~+ \033[1;36m+~~+~~+ \033[1;33m+~~+~~+\033[0m", NULL}; 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]; int score; int speed; void wait_key(); int display_splash(); int display_board(int); struct tetromino *copy_tetromino(struct tetromino *); struct tetromino *rotate_tetromino(struct tetromino *); void place_tetromino(struct tetromino *, int, int); void clear_tetromino(struct tetromino *, int, int); int obstacle_tetromino(struct tetromino *, int, int); void move_right(struct tetromino *, int *, int *); void move_left(struct tetromino *, int *, int *); void push_down(struct tetromino *, int *, int *); void rotate_in_place(struct tetromino **, int, int); int input_loop(struct tetromino **, int *, int *); int drop_tetromino(struct tetromino **); void clear_board(); void check_lines(); void play(); void setup_keyboard(); void unsetup_keyboard(); void wait_key() { int i; size_t s; while (1) { for (i = 0; i < 3; i++) input_array[i] = 0; s = read(0, &input_array, 3); if (s) break; usleep(1000); } } int display_splash() { int i; size_t s; printf("\033[2J\033[1;1H\n"); for (i = 0; splash[i] != NULL; i++) puts(splash[i]); while (1) { for (i = 0; i < 3; i++) input_array[i] = 0; s = read(0, &input_array, 3); if (s) { switch (input_array[0]) { case 'q': case 'Q': return 0; case '\n': return 1; } } usleep(1000); } } /* * ' ' No piece * 'I' Line (Purple) * 'J' Reverse L (Red) * 'L' L (Green) * 'O' Square (Cyan) * 'S' S (Yellow) * 'T' T (Blue) * 'Z' Reverse S (Gray) */ int display_board(int gameover) { int i, j; printf("\033[2J\033[1;1H\n"); for (i = 0; i < HEIGHT; i++) { printf(" ||"); for (j = 0; j < WIDTH; j++) { if (board[i][j] == ' ') { printf(" "); } else { switch (board[i][j]) { case 'I': printf("\033[0;35m"); break; case 'J': printf("\033[0;31m"); break; case 'L': printf("\033[0;32m"); break; case 'O': printf("\033[0;36m"); break; case 'S': printf("\033[0;33m"); break; case 'T': printf("\033[0;34m"); break; case 'Z': printf("\033[1;30m"); break; } 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"); } printf("\n"); } } struct tetromino *copy_tetromino(struct tetromino *t) { struct tetromino *new; new = malloc(sizeof(struct tetromino)); new->width = t->width; new->height = t->height; new->data = t->data; new->color = t->color; } 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 place_tetromino(struct tetromino *t, int line, int pos) { int i, j, n; if (line + t->height > HEIGHT) return; if (pos + t->width > WIDTH) return; 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] = t->color; n = n << 1; } } } void clear_tetromino(struct tetromino *t, int line, int pos) { 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] = ' '; n = n << 1; } } } int obstacle_tetromino(struct tetromino *t, int line, int pos) { int i, j, n; if (line < 0) return 1; if (pos < 0) return 1; if (line + t->height > HEIGHT) return 1; if (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_right(struct tetromino *t, int *x, int *y) { clear_tetromino(t, *x, *y); if (!obstacle_tetromino(t, *x, *y + 1)) *y = *y + 1; place_tetromino(t, *x, *y); display_board(0); } void move_left(struct tetromino *t, int *x, int *y) { clear_tetromino(t, *x, *y); if (!obstacle_tetromino(t, *x, *y - 1)) *y = *y - 1; place_tetromino(t, *x, *y); display_board(0); } void push_down(struct tetromino *t, int *x, int *y) { int i; clear_tetromino(t, *x, *y); for (i = 0; i < 5; i++) if (!obstacle_tetromino(t, *x + 1, *y)) *x = *x + 1; 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, j; size_t s; for (i = 0; i < speed; i++) { for (j = 0; j < 3; j++) input_array[j] = 0; s = read(0, &input_array, 3); 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 */ push_down(*t, x, y); break; case 'C': /* Right arrow */ move_right(*t, x, y); break; case 'D': /* Left arrow */ move_left(*t, x, y); break; } } break; case 'q': case 'Q': return 1; } usleep(1000); } return 0; } int drop_tetromino(struct tetromino **t) { int i, j, result; i = 0; j = WIDTH / 2; if (obstacle_tetromino(*t, i, j)) return 0; while(1) { place_tetromino(*t, i, j); display_board(0); result = input_loop (t, &i, &j); if (result) return 0; clear_tetromino(*t, i, j); if (obstacle_tetromino(*t, i + 1, j)) { place_tetromino(*t, i, j); return 1; } i++; } } 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 * 2; if (c == 1) score++; else if (c == 2) score += 3; else if (c == 3) score += 10; else if (c >= 4) score += 50; } void play() { struct tetromino *curr_t, *old_t; int i; score = 0; speed = 500; clear_board(); 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); } while (drop_tetromino(&curr_t)) { check_lines(); display_board(0); 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); } } display_board(1); wait_key(); } struct termios stored_keyboard_settings; struct termios new_keyboard_settings; void setup_keyboard() { 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_keyboard() { tcsetattr(0, TCSANOW, &stored_keyboard_settings); printf("\033[0m"); } int main() { int i; setup_keyboard(); srand(time(NULL)); while (display_splash()) play(); unsetup_keyboard(); }