├── .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 | 
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
--------------------------------------------------------------------------------