├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── src ├── main.c ├── tetris.c ├── tetris.h ├── util.c └── util.h └── tetris.gif /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | deps 4 | tetris.mp3 5 | *~ 6 | GTAGS 7 | GRTAGS 8 | GPATH 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Stephen Brennan 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # 3 | # File: Makefile 4 | # 5 | # Author: Stephen Brennan 6 | # 7 | # Date Created: Tuesday, 10 June 2015 8 | # 9 | # Description: Makefile for CKY parser program. 10 | # 11 | # Copyright (c) 2015, Stephen Brennan. Released under the Revised BSD License. 12 | # See the LICENSE.txt file for details. 13 | # 14 | #------------------------------------------------------------------------------- 15 | 16 | # Compiler Variable Declarations 17 | CC=gcc 18 | FLAGS=-Wall -pedantic 19 | INC=-Isrc/ 20 | CFLAGS=$(FLAGS) -c -g --std=c99 $(INC) 21 | LFLAGS=$(FLAGS) -lncurses 22 | DIR_GUARD=@mkdir -p $(@D) 23 | 24 | # Build configurations. 25 | CFG=release 26 | ifeq ($(CFG),debug) 27 | FLAGS += -g -DDEBUG -DSMB_DEBUG 28 | endif 29 | ifneq ($(CFG),debug) 30 | ifneq ($(CFG),release) 31 | @echo "Invalid configuration "$(CFG)" specified." 32 | @echo "You must specify a configuration when running make, e.g." 33 | @echo " make CFG=debug" 34 | @echo "Choices are 'release', 'debug'." 35 | @exit 1 36 | endif 37 | endif 38 | 39 | SDL=yes 40 | ifeq ($(SDL),yes) 41 | CFLAGS += `sdl-config --cflags` -DWITH_SDL=1 42 | LFLAGS += `sdl-config --libs` -lSDL_mixer 43 | endif 44 | ifneq ($(SDL),yes) 45 | ifneq ($(SDL),no) 46 | @echo "Invalid SDL configuration "$(SDL)" specified." 47 | @echo "You must specify the SDL configuration when running make, e.g." 48 | @echo " make SDL=yes" 49 | @echo "Choices are 'yes', 'no'." 50 | @exit 1 51 | endif 52 | endif 53 | 54 | # Sources and Objects 55 | SOURCES=$(shell find src/ -type f -name "*.c") 56 | OBJECTS=$(patsubst src/%.c,obj/$(CFG)/%.o,$(SOURCES)) 57 | DEPS=$(patsubst src/%.c,deps/%.d,$(SOURCES)) 58 | 59 | # Main targets 60 | .PHONY: all clean clean_all 61 | 62 | all: bin/$(CFG)/main 63 | 64 | GTAGS: $(SOURCES) 65 | gtags 66 | 67 | clean: 68 | rm -rf obj/$(CFG)/* bin/$(CFG)/* src/*.gch GTAGS GPATH GRTAGS 69 | 70 | clean_all: 71 | rm -rf bin/* obj/* deps/* 72 | 73 | # --- Compile Rule 74 | obj/$(CFG)/%.o: src/%.c 75 | $(DIR_GUARD) 76 | $(CC) $(CFLAGS) $< -o $@ 77 | 78 | # --- Link Rule 79 | bin/$(CFG)/main: $(OBJECTS) 80 | $(DIR_GUARD) 81 | $(CC) $(OBJECTS) $(LFLAGS) -o bin/$(CFG)/main 82 | 83 | # --- Dependency Rule 84 | deps/%.d: src/%.c 85 | $(DIR_GUARD) 86 | $(CC) $(CFLAGS) -MM $< | sed -e 's/~\(.*\)\.o:/\1.d \1.o:/' > $@ 87 | 88 | ifneq "$(MAKECMDGOALS)" "clean_all" 89 | -include $(DEPS) 90 | endif 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tetris 2 | ====== 3 | 4 | ![tetris.gif](tetris.gif) 5 | 6 | A tetris game in C using NCURSES. It's pretty feature complete, except for 7 | stretch goals I may pick up in my free time. 8 | 9 | 10 | Building 11 | -------- 12 | 13 | My dependencies are: 14 | 15 | * `libsdl` and `libsdl_mixer` 1.2 for sound. 16 | * `ncurses` for terminal manipulation. 17 | 18 | To install them on Arch Linux: 19 | 20 | sudo pacman -S sdl_mixer ncurses 21 | 22 | To install them on Ubuntu: 23 | 24 | sudo apt-get install libsdl-mixer1.2-dev libncurses5-dev 25 | 26 | To compile: 27 | 28 | make 29 | 30 | To run: 31 | 32 | bin/release/main 33 | 34 | You will need to provide a file named `tetris.mp3` in the same directory that 35 | you're running the game from. As I understand it, the official Tetris theme 36 | song is legally protected in the use of games like this, so I will not be 37 | providing or linking to that. But I'm sure you could find something! (**You do 38 | not need to provide `tetris.mp3` in order to play the game, only if you want 39 | sound!**). 40 | 41 | 42 | Instructions 43 | ------------ 44 | 45 | The controls are typical of Tetris: 46 | * and : Move the tetromino, 47 | * : Rotate (clockwise?) the tetromino, 48 | * : Immediately drop the tetromino (not a fast drop, an immediate drop), 49 | * Q: Exit the game prematurely, 50 | * P: Pause the game (any key to resume), 51 | * B: "Boss mode" - show a mock terminal screen to fool nosy onlookers. Hit 52 | F1 to resume the game afterwards. 53 | * S: Save game and exit (just assumes filename `tetris.save`). To resume the 54 | game, run `bin/release/main tetris.save` (or whatever you may have renamed the 55 | game save to). 56 | 57 | 58 | Future/Stretch Goals 59 | -------------------- 60 | 61 | * Sound effects (in addition to the theme music). 62 | * Networked multiplayer! 63 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************//** 2 | 3 | @file main.c 4 | 5 | @author Stephen Brennan 6 | 7 | @date Created Wednesday, 10 June 2015 8 | 9 | @brief Main program for tetris. 10 | 11 | @copyright Copyright (c) 2015, Stephen Brennan. Released under the Revised 12 | BSD License. See LICENSE.txt for details. 13 | 14 | *******************************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #if WITH_SDL 24 | # include 25 | # include 26 | #endif 27 | 28 | #include "tetris.h" 29 | #include "util.h" 30 | 31 | /* 32 | 2 columns per cell makes the game much nicer. 33 | */ 34 | #define COLS_PER_CELL 2 35 | /* 36 | Macro to print a cell of a specific type to a window. 37 | */ 38 | #define ADD_BLOCK(w,x) waddch((w),' '|A_REVERSE|COLOR_PAIR(x)); \ 39 | waddch((w),' '|A_REVERSE|COLOR_PAIR(x)) 40 | #define ADD_EMPTY(w) waddch((w), ' '); waddch((w), ' ') 41 | 42 | /* 43 | Print the tetris board onto the ncurses window. 44 | */ 45 | void display_board(WINDOW *w, tetris_game *obj) 46 | { 47 | int i, j; 48 | box(w, 0, 0); 49 | for (i = 0; i < obj->rows; i++) { 50 | wmove(w, 1 + i, 1); 51 | for (j = 0; j < obj->cols; j++) { 52 | if (TC_IS_FILLED(tg_get(obj, i, j))) { 53 | ADD_BLOCK(w,tg_get(obj, i, j)); 54 | } else { 55 | ADD_EMPTY(w); 56 | } 57 | } 58 | } 59 | wnoutrefresh(w); 60 | } 61 | 62 | /* 63 | Display a tetris piece in a dedicated window. 64 | */ 65 | void display_piece(WINDOW *w, tetris_block block) 66 | { 67 | int b; 68 | tetris_location c; 69 | wclear(w); 70 | box(w, 0, 0); 71 | if (block.typ == -1) { 72 | wnoutrefresh(w); 73 | return; 74 | } 75 | for (b = 0; b < TETRIS; b++) { 76 | c = TETROMINOS[block.typ][block.ori][b]; 77 | wmove(w, c.row + 1, c.col * COLS_PER_CELL + 1); 78 | ADD_BLOCK(w, TYPE_TO_CELL(block.typ)); 79 | } 80 | wnoutrefresh(w); 81 | } 82 | 83 | /* 84 | Display score information in a dedicated window. 85 | */ 86 | void display_score(WINDOW *w, tetris_game *tg) 87 | { 88 | wclear(w); 89 | box(w, 0, 0); 90 | wprintw(w, "Score\n%d\n", tg->points); 91 | wprintw(w, "Level\n%d\n", tg->level); 92 | wprintw(w, "Lines\n%d\n", tg->lines_remaining); 93 | wnoutrefresh(w); 94 | } 95 | 96 | /* 97 | Boss mode! Make it look like you're doing work. 98 | */ 99 | void boss_mode(void) 100 | { 101 | clear(); 102 | #if WITH_SDL 103 | Mix_PauseMusic(); 104 | #endif 105 | printw("user@workstation-312:~/Documents/presentation $ ls -l\n" 106 | "total 528\n" 107 | "drwxr-xr-x 2 user users 4096 Jun 9 17:05 .\n" 108 | "drwxr-xr-x 4 user users 4096 Jun 10 09:52 ..\n" 109 | "-rw-r--r-- 1 user users 88583 Jun 9 14:13 figure1.png\n" 110 | "-rw-r--r-- 1 user users 65357 Jun 9 15:40 figure2.png\n" 111 | "-rw-r--r-- 1 user users 4469 Jun 9 16:17 presentation.aux\n" 112 | "-rw-r--r-- 1 user users 42858 Jun 9 16:17 presentation.log\n" 113 | "-rw-r--r-- 1 user users 2516 Jun 9 16:17 presentation.nav\n" 114 | "-rw-r--r-- 1 user users 183 Jun 9 16:17 presentation.out\n" 115 | "-rw-r--r-- 1 user users 349607 Jun 9 16:17 presentation.pdf\n" 116 | "-rw-r--r-- 1 user users 0 Jun 9 16:17 presentation.snm\n" 117 | "-rw-r--r-- 1 user users 9284 Jun 9 17:05 presentation.tex\n" 118 | "-rw-r--r-- 1 user users 229 Jun 9 16:17 presentation.toc\n" 119 | "\n" 120 | "user@workstation-312:~/Documents/presentation $ "); 121 | echo(); 122 | timeout(-1); 123 | while (getch() != KEY_F(1)); 124 | timeout(0); 125 | noecho(); 126 | clear(); 127 | #if WITH_SDL 128 | Mix_ResumeMusic(); 129 | #endif 130 | } 131 | 132 | /* 133 | Save and exit the game. 134 | */ 135 | void save(tetris_game *game, WINDOW *w) 136 | { 137 | FILE *f; 138 | 139 | wclear(w); 140 | box(w, 0, 0); // return the border 141 | wmove(w, 1, 1); 142 | wprintw(w, "Save and exit? [Y/n] "); 143 | wrefresh(w); 144 | timeout(-1); 145 | if (getch() == 'n') { 146 | timeout(0); 147 | return; 148 | } 149 | f = fopen("tetris.save", "w"); 150 | tg_save(game, f); 151 | fclose(f); 152 | tg_delete(game); 153 | endwin(); 154 | printf("Game saved to \"tetris.save\".\n"); 155 | printf("Resume by passing the filename as an argument to this program.\n"); 156 | exit(EXIT_SUCCESS); 157 | } 158 | 159 | /* 160 | Do the NCURSES initialization steps for color blocks. 161 | */ 162 | void init_colors(void) 163 | { 164 | start_color(); 165 | //init_color(COLOR_ORANGE, 1000, 647, 0); 166 | init_pair(TC_CELLI, COLOR_CYAN, COLOR_BLACK); 167 | init_pair(TC_CELLJ, COLOR_BLUE, COLOR_BLACK); 168 | init_pair(TC_CELLL, COLOR_WHITE, COLOR_BLACK); 169 | init_pair(TC_CELLO, COLOR_YELLOW, COLOR_BLACK); 170 | init_pair(TC_CELLS, COLOR_GREEN, COLOR_BLACK); 171 | init_pair(TC_CELLT, COLOR_MAGENTA, COLOR_BLACK); 172 | init_pair(TC_CELLZ, COLOR_RED, COLOR_BLACK); 173 | } 174 | 175 | /* 176 | Main tetris game! 177 | */ 178 | int main(int argc, char **argv) 179 | { 180 | tetris_game *tg; 181 | tetris_move move = TM_NONE; 182 | bool running = true; 183 | WINDOW *board, *next, *hold, *score; 184 | #if WITH_SDL 185 | Mix_Music *music; 186 | #endif 187 | 188 | // Load file if given a filename. 189 | if (argc >= 2) { 190 | FILE *f = fopen(argv[1], "r"); 191 | if (f == NULL) { 192 | perror("tetris"); 193 | exit(EXIT_FAILURE); 194 | } 195 | tg = tg_load(f); 196 | fclose(f); 197 | } else { 198 | // Otherwise create new game. 199 | tg = tg_create(22, 10); 200 | } 201 | 202 | #if WITH_SDL 203 | 204 | // Initialize music. 205 | if (SDL_Init(SDL_INIT_AUDIO) < 0) { 206 | fprintf(stderr, "unable to initialize SDL\n"); 207 | exit(EXIT_FAILURE); 208 | } 209 | if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3) { 210 | fprintf(stderr, "unable to initialize SDL_mixer\n"); 211 | exit(EXIT_FAILURE); 212 | } 213 | if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) != 0) { 214 | fprintf(stderr, "unable to initialize audio\n"); 215 | exit(EXIT_FAILURE); 216 | } 217 | Mix_AllocateChannels(1); // only need background music 218 | music = Mix_LoadMUS("tetris.mp3"); 219 | if (music) { 220 | Mix_PlayMusic(music, -1); 221 | } 222 | 223 | #endif 224 | 225 | // NCURSES initialization: 226 | initscr(); // initialize curses 227 | cbreak(); // pass key presses to program, but not signals 228 | noecho(); // don't echo key presses to screen 229 | keypad(stdscr, TRUE); // allow arrow keys 230 | timeout(0); // no blocking on getch() 231 | curs_set(0); // set the cursor to invisible 232 | init_colors(); // setup tetris colors 233 | 234 | // Create windows for each section of the interface. 235 | board = newwin(tg->rows + 2, 2 * tg->cols + 2, 0, 0); 236 | next = newwin(6, 10, 0, 2 * (tg->cols + 1) + 1); 237 | hold = newwin(6, 10, 7, 2 * (tg->cols + 1) + 1); 238 | score = newwin(6, 10, 14, 2 * (tg->cols + 1 ) + 1); 239 | 240 | // Game loop 241 | while (running) { 242 | running = tg_tick(tg, move); 243 | display_board(board, tg); 244 | display_piece(next, tg->next); 245 | display_piece(hold, tg->stored); 246 | display_score(score, tg); 247 | doupdate(); 248 | sleep_milli(10); 249 | 250 | switch (getch()) { 251 | case KEY_LEFT: 252 | move = TM_LEFT; 253 | break; 254 | case KEY_RIGHT: 255 | move = TM_RIGHT; 256 | break; 257 | case KEY_UP: 258 | move = TM_CLOCK; 259 | break; 260 | case KEY_DOWN: 261 | move = TM_DROP; 262 | break; 263 | case 'q': 264 | running = false; 265 | move = TM_NONE; 266 | break; 267 | case 'p': 268 | wclear(board); 269 | box(board, 0, 0); 270 | wmove(board, tg->rows/2, (tg->cols*COLS_PER_CELL-6)/2); 271 | wprintw(board, "PAUSED"); 272 | wrefresh(board); 273 | timeout(-1); 274 | getch(); 275 | timeout(0); 276 | move = TM_NONE; 277 | break; 278 | case 'b': 279 | boss_mode(); 280 | move = TM_NONE; 281 | break; 282 | case 's': 283 | save(tg, board); 284 | move = TM_NONE; 285 | break; 286 | case ' ': 287 | move = TM_HOLD; 288 | break; 289 | default: 290 | move = TM_NONE; 291 | } 292 | } 293 | 294 | // Deinitialize NCurses 295 | wclear(stdscr); 296 | endwin(); 297 | 298 | #if WITH_SDL 299 | 300 | // Deinitialize Sound 301 | Mix_HaltMusic(); 302 | Mix_FreeMusic(music); 303 | Mix_CloseAudio(); 304 | Mix_Quit(); 305 | 306 | #endif 307 | 308 | // Output ending message. 309 | printf("Game over!\n"); 310 | printf("You finished with %d points on level %d.\n", tg->points, tg->level); 311 | 312 | // Deinitialize Tetris 313 | tg_delete(tg); 314 | return 0; 315 | } 316 | -------------------------------------------------------------------------------- /src/tetris.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************//** 2 | 3 | @file tetris.c 4 | 5 | @author Stephen Brennan 6 | 7 | @date Created Wednesday, 10 June 2015 8 | 9 | @brief Tetris game logic. 10 | 11 | @copyright Copyright (c) 2015, Stephen Brennan. Released under the Revised 12 | BSD License. See LICENSE.txt for details. 13 | 14 | *******************************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "tetris.h" 23 | 24 | #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) 25 | #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) 26 | 27 | /******************************************************************************* 28 | 29 | Array Definitions 30 | 31 | *******************************************************************************/ 32 | 33 | tetris_location TETROMINOS[NUM_TETROMINOS][NUM_ORIENTATIONS][TETRIS] = { 34 | // I 35 | {{{1, 0}, {1, 1}, {1, 2}, {1, 3}}, 36 | {{0, 2}, {1, 2}, {2, 2}, {3, 2}}, 37 | {{3, 0}, {3, 1}, {3, 2}, {3, 3}}, 38 | {{0, 1}, {1, 1}, {2, 1}, {3, 1}}}, 39 | // J 40 | {{{0, 0}, {1, 0}, {1, 1}, {1, 2}}, 41 | {{0, 1}, {0, 2}, {1, 1}, {2, 1}}, 42 | {{1, 0}, {1, 1}, {1, 2}, {2, 2}}, 43 | {{0, 1}, {1, 1}, {2, 0}, {2, 1}}}, 44 | // L 45 | {{{0, 2}, {1, 0}, {1, 1}, {1, 2}}, 46 | {{0, 1}, {1, 1}, {2, 1}, {2, 2}}, 47 | {{1, 0}, {1, 1}, {1, 2}, {2, 0}}, 48 | {{0, 0}, {0, 1}, {1, 1}, {2, 1}}}, 49 | // O 50 | {{{0, 1}, {0, 2}, {1, 1}, {1, 2}}, 51 | {{0, 1}, {0, 2}, {1, 1}, {1, 2}}, 52 | {{0, 1}, {0, 2}, {1, 1}, {1, 2}}, 53 | {{0, 1}, {0, 2}, {1, 1}, {1, 2}}}, 54 | // S 55 | {{{0, 1}, {0, 2}, {1, 0}, {1, 1}}, 56 | {{0, 1}, {1, 1}, {1, 2}, {2, 2}}, 57 | {{1, 1}, {1, 2}, {2, 0}, {2, 1}}, 58 | {{0, 0}, {1, 0}, {1, 1}, {2, 1}}}, 59 | // T 60 | {{{0, 1}, {1, 0}, {1, 1}, {1, 2}}, 61 | {{0, 1}, {1, 1}, {1, 2}, {2, 1}}, 62 | {{1, 0}, {1, 1}, {1, 2}, {2, 1}}, 63 | {{0, 1}, {1, 0}, {1, 1}, {2, 1}}}, 64 | // Z 65 | {{{0, 0}, {0, 1}, {1, 1}, {1, 2}}, 66 | {{0, 2}, {1, 1}, {1, 2}, {2, 1}}, 67 | {{1, 0}, {1, 1}, {2, 1}, {2, 2}}, 68 | {{0, 1}, {1, 0}, {1, 1}, {2, 0}}}, 69 | }; 70 | 71 | int GRAVITY_LEVEL[MAX_LEVEL+1] = { 72 | // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 73 | 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 74 | //10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 75 | 30, 28, 26, 24, 22, 20, 16, 12, 8, 4 76 | }; 77 | 78 | /******************************************************************************* 79 | 80 | Helper Functions for Blocks 81 | 82 | *******************************************************************************/ 83 | 84 | /* 85 | Return the block at the given row and column. 86 | */ 87 | char tg_get(tetris_game *obj, int row, int column) 88 | { 89 | return obj->board[obj->cols * row + column]; 90 | } 91 | 92 | /* 93 | Set the block at the given row and column. 94 | */ 95 | static void tg_set(tetris_game *obj, int row, int column, char value) 96 | { 97 | obj->board[obj->cols * row + column] = value; 98 | } 99 | 100 | /* 101 | Check whether a row and column are in bounds. 102 | */ 103 | bool tg_check(tetris_game *obj, int row, int col) 104 | { 105 | return 0 <= row && row < obj->rows && 0 <= col && col < obj->cols; 106 | } 107 | 108 | /* 109 | Place a block onto the board. 110 | */ 111 | static void tg_put(tetris_game *obj, tetris_block block) 112 | { 113 | int i; 114 | for (i = 0; i < TETRIS; i++) { 115 | tetris_location cell = TETROMINOS[block.typ][block.ori][i]; 116 | tg_set(obj, block.loc.row + cell.row, block.loc.col + cell.col, 117 | TYPE_TO_CELL(block.typ)); 118 | } 119 | } 120 | 121 | /* 122 | Clear a block out of the board. 123 | */ 124 | static void tg_remove(tetris_game *obj, tetris_block block) 125 | { 126 | int i; 127 | for (i = 0; i < TETRIS; i++) { 128 | tetris_location cell = TETROMINOS[block.typ][block.ori][i]; 129 | tg_set(obj, block.loc.row + cell.row, block.loc.col + cell.col, TC_EMPTY); 130 | } 131 | } 132 | 133 | /* 134 | Check if a block can be placed on the board. 135 | */ 136 | static bool tg_fits(tetris_game *obj, tetris_block block) 137 | { 138 | int i, r, c; 139 | for (i = 0; i < TETRIS; i++) { 140 | tetris_location cell = TETROMINOS[block.typ][block.ori][i]; 141 | r = block.loc.row + cell.row; 142 | c = block.loc.col + cell.col; 143 | if (!tg_check(obj, r, c) || TC_IS_FILLED(tg_get(obj, r, c))) { 144 | return false; 145 | } 146 | } 147 | return true; 148 | } 149 | 150 | /* 151 | Return a random tetromino type. 152 | */ 153 | static int random_tetromino(void) { 154 | return rand() % NUM_TETROMINOS; 155 | } 156 | 157 | /* 158 | Create a new falling block and populate the next falling block with a random 159 | one. 160 | */ 161 | static void tg_new_falling(tetris_game *obj) 162 | { 163 | // Put in a new falling tetromino. 164 | obj->falling = obj->next; 165 | obj->next.typ = random_tetromino(); 166 | obj->next.ori = 0; 167 | obj->next.loc.row = 0; 168 | obj->next.loc.col = obj->cols/2 - 2; 169 | } 170 | 171 | /******************************************************************************* 172 | 173 | Game Turn Helpers 174 | 175 | *******************************************************************************/ 176 | 177 | /* 178 | Tick gravity, and move the block down if gravity should act. 179 | */ 180 | static void tg_do_gravity_tick(tetris_game *obj) 181 | { 182 | obj->ticks_till_gravity--; 183 | if (obj->ticks_till_gravity <= 0) { 184 | tg_remove(obj, obj->falling); 185 | obj->falling.loc.row++; 186 | if (tg_fits(obj, obj->falling)) { 187 | obj->ticks_till_gravity = GRAVITY_LEVEL[obj->level]; 188 | } else { 189 | obj->falling.loc.row--; 190 | tg_put(obj, obj->falling); 191 | 192 | tg_new_falling(obj); 193 | } 194 | tg_put(obj, obj->falling); 195 | } 196 | } 197 | 198 | /* 199 | Move the falling tetris block left (-1) or right (+1). 200 | */ 201 | static void tg_move(tetris_game *obj, int direction) 202 | { 203 | tg_remove(obj, obj->falling); 204 | obj->falling.loc.col += direction; 205 | if (!tg_fits(obj, obj->falling)) { 206 | obj->falling.loc.col -= direction; 207 | } 208 | tg_put(obj, obj->falling); 209 | } 210 | 211 | /* 212 | Send the falling tetris block to the bottom. 213 | */ 214 | static void tg_down(tetris_game *obj) 215 | { 216 | tg_remove(obj, obj->falling); 217 | while (tg_fits(obj, obj->falling)) { 218 | obj->falling.loc.row++; 219 | } 220 | obj->falling.loc.row--; 221 | tg_put(obj, obj->falling); 222 | tg_new_falling(obj); 223 | } 224 | 225 | /* 226 | Rotate the falling block in either direction (+/-1). 227 | */ 228 | static void tg_rotate(tetris_game *obj, int direction) 229 | { 230 | tg_remove(obj, obj->falling); 231 | 232 | while (true) { 233 | obj->falling.ori = (obj->falling.ori + direction) % NUM_ORIENTATIONS; 234 | 235 | // If the new orientation fits, we're done. 236 | if (tg_fits(obj, obj->falling)) 237 | break; 238 | 239 | // Otherwise, try moving left to make it fit. 240 | obj->falling.loc.col--; 241 | if (tg_fits(obj, obj->falling)) 242 | break; 243 | 244 | // Finally, try moving right to make it fit. 245 | obj->falling.loc.col += 2; 246 | if (tg_fits(obj, obj->falling)) 247 | break; 248 | 249 | // Put it back in its original location and try the next orientation. 250 | obj->falling.loc.col--; 251 | // Worst case, we come back to the original orientation and it fits, so this 252 | // loop will terminate. 253 | } 254 | 255 | tg_put(obj, obj->falling); 256 | } 257 | 258 | /* 259 | Swap the falling block with the block in the hold buffer. 260 | */ 261 | static void tg_hold(tetris_game *obj) 262 | { 263 | tg_remove(obj, obj->falling); 264 | if (obj->stored.typ == -1) { 265 | obj->stored = obj->falling; 266 | tg_new_falling(obj); 267 | } else { 268 | int typ = obj->falling.typ, ori = obj->falling.ori; 269 | obj->falling.typ = obj->stored.typ; 270 | obj->falling.ori = obj->stored.ori; 271 | obj->stored.typ = typ; 272 | obj->stored.ori = ori; 273 | while (!tg_fits(obj, obj->falling)) { 274 | obj->falling.loc.row--; 275 | } 276 | } 277 | tg_put(obj, obj->falling); 278 | } 279 | 280 | /* 281 | Perform the action specified by the move. 282 | */ 283 | static void tg_handle_move(tetris_game *obj, tetris_move move) 284 | { 285 | switch (move) { 286 | case TM_LEFT: 287 | tg_move(obj, -1); 288 | break; 289 | case TM_RIGHT: 290 | tg_move(obj, 1); 291 | break; 292 | case TM_DROP: 293 | tg_down(obj); 294 | break; 295 | case TM_CLOCK: 296 | tg_rotate(obj, 1); 297 | break; 298 | case TM_COUNTER: 299 | tg_rotate(obj, -1); 300 | break; 301 | case TM_HOLD: 302 | tg_hold(obj); 303 | break; 304 | default: 305 | // pass 306 | break; 307 | } 308 | } 309 | 310 | /* 311 | Return true if line i is full. 312 | */ 313 | static bool tg_line_full(tetris_game *obj, int i) 314 | { 315 | int j; 316 | for (j = 0; j < obj->cols; j++) { 317 | if (TC_IS_EMPTY(tg_get(obj, i, j))) 318 | return false; 319 | } 320 | return true; 321 | } 322 | 323 | /* 324 | Shift every row above r down one. 325 | */ 326 | static void tg_shift_lines(tetris_game *obj, int r) 327 | { 328 | int i, j; 329 | for (i = r-1; i >= 0; i--) { 330 | for (j = 0; j < obj->cols; j++) { 331 | tg_set(obj, i+1, j, tg_get(obj, i, j)); 332 | tg_set(obj, i, j, TC_EMPTY); 333 | } 334 | } 335 | } 336 | 337 | /* 338 | Find rows that are filled, remove them, shift, and return the number of 339 | cleared rows. 340 | */ 341 | static int tg_check_lines(tetris_game *obj) 342 | { 343 | int i, nlines = 0; 344 | tg_remove(obj, obj->falling); // don't want to mess up falling block 345 | 346 | for (i = obj->rows-1; i >= 0; i--) { 347 | if (tg_line_full(obj, i)) { 348 | tg_shift_lines(obj, i); 349 | i++; // do this line over again since they're shifted 350 | nlines++; 351 | } 352 | } 353 | 354 | tg_put(obj, obj->falling); // replace 355 | return nlines; 356 | } 357 | 358 | /* 359 | Adjust the score for the game, given how many lines were just cleared. 360 | */ 361 | static void tg_adjust_score(tetris_game *obj, int lines_cleared) 362 | { 363 | static int line_multiplier[] = {0, 40, 100, 300, 1200}; 364 | obj->points += line_multiplier[lines_cleared] * (obj->level + 1); 365 | if (lines_cleared >= obj->lines_remaining) { 366 | obj->level = MIN(MAX_LEVEL, obj->level + 1); 367 | lines_cleared -= obj->lines_remaining; 368 | obj->lines_remaining = LINES_PER_LEVEL - lines_cleared; 369 | } else { 370 | obj->lines_remaining -= lines_cleared; 371 | } 372 | } 373 | 374 | /* 375 | Return true if the game is over. 376 | */ 377 | static bool tg_game_over(tetris_game *obj) 378 | { 379 | int i, j; 380 | bool over = false; 381 | tg_remove(obj, obj->falling); 382 | for (i = 0; i < 2; i++) { 383 | for (j = 0; j < obj->cols; j++) { 384 | if (TC_IS_FILLED(tg_get(obj, i, j))) { 385 | over = true; 386 | } 387 | } 388 | } 389 | tg_put(obj, obj->falling); 390 | return over; 391 | } 392 | 393 | /******************************************************************************* 394 | 395 | Main Public Functions 396 | 397 | *******************************************************************************/ 398 | 399 | /* 400 | Do a single game tick: process gravity, user input, and score. Return true if 401 | the game is still running, false if it is over. 402 | */ 403 | bool tg_tick(tetris_game *obj, tetris_move move) 404 | { 405 | int lines_cleared; 406 | // Handle gravity. 407 | tg_do_gravity_tick(obj); 408 | 409 | // Handle input. 410 | tg_handle_move(obj, move); 411 | 412 | // Check for cleared lines 413 | lines_cleared = tg_check_lines(obj); 414 | 415 | tg_adjust_score(obj, lines_cleared); 416 | 417 | // Return whether the game will continue (NOT whether it's over) 418 | return !tg_game_over(obj); 419 | } 420 | 421 | void tg_init(tetris_game *obj, int rows, int cols) 422 | { 423 | // Initialization logic 424 | obj->rows = rows; 425 | obj->cols = cols; 426 | obj->board = malloc(rows * cols); 427 | memset(obj->board, TC_EMPTY, rows * cols); 428 | obj->points = 0; 429 | obj->level = 0; 430 | obj->ticks_till_gravity = GRAVITY_LEVEL[obj->level]; 431 | obj->lines_remaining = LINES_PER_LEVEL; 432 | srand(time(NULL)); 433 | tg_new_falling(obj); 434 | tg_new_falling(obj); 435 | obj->stored.typ = -1; 436 | obj->stored.ori = 0; 437 | obj->stored.loc.row = 0; 438 | obj->next.loc.col = obj->cols/2 - 2; 439 | printf("%d", obj->falling.loc.col); 440 | } 441 | 442 | tetris_game *tg_create(int rows, int cols) 443 | { 444 | tetris_game *obj = malloc(sizeof(tetris_game)); 445 | tg_init(obj, rows, cols); 446 | return obj; 447 | } 448 | 449 | void tg_destroy(tetris_game *obj) 450 | { 451 | // Cleanup logic 452 | free(obj->board); 453 | } 454 | 455 | void tg_delete(tetris_game *obj) { 456 | tg_destroy(obj); 457 | free(obj); 458 | } 459 | 460 | /* 461 | Load a game from a file. 462 | */ 463 | tetris_game *tg_load(FILE *f) 464 | { 465 | tetris_game *obj = malloc(sizeof(tetris_game)); 466 | fread(obj, sizeof(tetris_game), 1, f); 467 | obj->board = malloc(obj->rows * obj->cols); 468 | fread(obj->board, sizeof(char), obj->rows * obj->cols, f); 469 | return obj; 470 | } 471 | 472 | /* 473 | Save a game to a file. 474 | */ 475 | void tg_save(tetris_game *obj, FILE *f) 476 | { 477 | fwrite(obj, sizeof(tetris_game), 1, f); 478 | fwrite(obj->board, sizeof(char), obj->rows * obj->cols, f); 479 | } 480 | 481 | /* 482 | Print a game board to a file. Really just for early debugging. 483 | */ 484 | void tg_print(tetris_game *obj, FILE *f) { 485 | int i, j; 486 | for (i = 0; i < obj->rows; i++) { 487 | for (j = 0; j < obj->cols; j++) { 488 | if (TC_IS_EMPTY(tg_get(obj, i, j))) { 489 | fputs(TC_EMPTY_STR, f); 490 | } else { 491 | fputs(TC_BLOCK_STR, f); 492 | } 493 | } 494 | fputc('\n', f); 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /src/tetris.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************//** 2 | 3 | @file tetris.h 4 | 5 | @author Stephen Brennan 6 | 7 | @date Created Wednesday, 10 June 2015 8 | 9 | @brief Tetris game declarations. 10 | 11 | @copyright Copyright (c) 2015, Stephen Brennan. Released under the Revised 12 | BSD License. See LICENSE.txt for details. 13 | 14 | *******************************************************************************/ 15 | 16 | #ifndef TETRIS_H 17 | #define TETRIS_H 18 | 19 | #include // for FILE 20 | #include // for bool 21 | 22 | /* 23 | Convert a tetromino type to its corresponding cell. 24 | */ 25 | #define TYPE_TO_CELL(x) ((x)+1) 26 | 27 | /* 28 | Strings for how you would print a tetris board. 29 | */ 30 | #define TC_EMPTY_STR " " 31 | #define TC_BLOCK_STR "\u2588" 32 | 33 | /* 34 | Questions about a tetris cell. 35 | */ 36 | #define TC_IS_EMPTY(x) ((x) == TC_EMPTY) 37 | #define TC_IS_FILLED(x) (!TC_IS_EMPTY(x)) 38 | 39 | /* 40 | How many cells in a tetromino? 41 | */ 42 | #define TETRIS 4 43 | /* 44 | How many tetrominos? 45 | */ 46 | #define NUM_TETROMINOS 7 47 | /* 48 | How many orientations of a tetromino? 49 | */ 50 | #define NUM_ORIENTATIONS 4 51 | 52 | /* 53 | Level constants. 54 | */ 55 | #define MAX_LEVEL 19 56 | #define LINES_PER_LEVEL 10 57 | 58 | /* 59 | A "cell" is a 1x1 block within a tetris board. 60 | */ 61 | typedef enum { 62 | TC_EMPTY, TC_CELLI, TC_CELLJ, TC_CELLL, TC_CELLO, TC_CELLS, TC_CELLT, TC_CELLZ 63 | } tetris_cell; 64 | 65 | /* 66 | A "type" is a type/shape of a tetromino. Not including orientation. 67 | */ 68 | typedef enum { 69 | TET_I, TET_J, TET_L, TET_O, TET_S, TET_T, TET_Z 70 | } tetris_type; 71 | 72 | /* 73 | A row,column pair. Negative numbers allowed, because we need them for 74 | offsets. 75 | */ 76 | typedef struct { 77 | int row; 78 | int col; 79 | } tetris_location; 80 | 81 | /* 82 | A "block" is a struct that contains information about a tetromino. 83 | Specifically, what type it is, what orientation it has, and where it is. 84 | */ 85 | typedef struct { 86 | int typ; 87 | int ori; 88 | tetris_location loc; 89 | } tetris_block; 90 | 91 | /* 92 | All possible moves to give as input to the game. 93 | */ 94 | typedef enum { 95 | TM_LEFT, TM_RIGHT, TM_CLOCK, TM_COUNTER, TM_DROP, TM_HOLD, TM_NONE 96 | } tetris_move; 97 | 98 | /* 99 | A game object! 100 | */ 101 | typedef struct { 102 | /* 103 | Game board stuff: 104 | */ 105 | int rows; 106 | int cols; 107 | char *board; 108 | /* 109 | Scoring information: 110 | */ 111 | int points; 112 | int level; 113 | /* 114 | Falling block is the one currently going down. Next block is the one that 115 | will be falling after this one. Stored is the block that you can swap out. 116 | */ 117 | tetris_block falling; 118 | tetris_block next; 119 | tetris_block stored; 120 | /* 121 | Number of game ticks until the block will move down. 122 | */ 123 | int ticks_till_gravity; 124 | /* 125 | Number of lines until you advance to the next level. 126 | */ 127 | int lines_remaining; 128 | } tetris_game; 129 | 130 | /* 131 | This array stores all necessary information about the cells that are filled by 132 | each tetromino. The first index is the type of the tetromino (i.e. shape, 133 | e.g. I, J, Z, etc.). The next index is the orientation (0-3). The final 134 | array contains 4 tetris_location objects, each mapping to an offset from a 135 | point on the upper left that is the tetromino "origin". 136 | */ 137 | extern tetris_location TETROMINOS[NUM_TETROMINOS][NUM_ORIENTATIONS][TETRIS]; 138 | 139 | /* 140 | This array tells you how many ticks per gravity by level. Decreases as level 141 | increases, to add difficulty. 142 | */ 143 | extern int GRAVITY_LEVEL[MAX_LEVEL+1]; 144 | 145 | // Data structure manipulation. 146 | void tg_init(tetris_game *obj, int rows, int cols); 147 | tetris_game *tg_create(int rows, int cols); 148 | void tg_destroy(tetris_game *obj); 149 | void tg_delete(tetris_game *obj); 150 | tetris_game *tg_load(FILE *f); 151 | void tg_save(tetris_game *obj, FILE *f); 152 | 153 | // Public methods not related to memory: 154 | char tg_get(tetris_game *obj, int row, int col); 155 | bool tg_check(tetris_game *obj, int row, int col); 156 | bool tg_tick(tetris_game *obj, tetris_move move); 157 | void tg_print(tetris_game *obj, FILE *f); 158 | 159 | #endif // TETRIS_H 160 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************//** 2 | 3 | @file util.c 4 | 5 | @author Stephen Brennan 6 | 7 | @date Created Wednesday, 10 June 2015 8 | 9 | @brief Utilities for Tetris 10 | 11 | @copyright Copyright (c) 2015, Stephen Brennan. Released under the Revised 12 | BSD License. See LICENSE.txt for details. 13 | 14 | *******************************************************************************/ 15 | 16 | #define _POSIX_C_SOURCE 199309L 17 | 18 | #include // nanosleep 19 | 20 | 21 | void sleep_milli(int milliseconds) 22 | { 23 | struct timespec ts; 24 | ts.tv_sec = 0; 25 | ts.tv_nsec = milliseconds * 1000 * 1000; 26 | nanosleep(&ts, NULL); 27 | } 28 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************//** 2 | 3 | @file util.h 4 | 5 | @author Stephen Brennan 6 | 7 | @date Created Wednesday, 10 June 2015 8 | 9 | @brief Utility declarations for Tetris 10 | 11 | @copyright Copyright (c) 2015, Stephen Brennan. Released under the Revised 12 | BSD License. See LICENSE.txt for details. 13 | 14 | *******************************************************************************/ 15 | 16 | #ifndef UTIL_H 17 | #define UTIL_H 18 | 19 | void sleep_milli(int milliseconds); 20 | 21 | #endif // UTIL_H 22 | -------------------------------------------------------------------------------- /tetris.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brenns10/tetris/9133050f54596b5511697a59cee2dc62240ee45f/tetris.gif --------------------------------------------------------------------------------