├── blue.png ├── green.png ├── red.png ├── indigo.png ├── orange.png ├── violet.png ├── yellow.png ├── background.png ├── makefile ├── LICENSE ├── README.md └── main.c /blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/blue.png -------------------------------------------------------------------------------- /green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/green.png -------------------------------------------------------------------------------- /red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/red.png -------------------------------------------------------------------------------- /indigo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/indigo.png -------------------------------------------------------------------------------- /orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/orange.png -------------------------------------------------------------------------------- /violet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/violet.png -------------------------------------------------------------------------------- /yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/yellow.png -------------------------------------------------------------------------------- /background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuqdog/tetris/HEAD/background.png -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CXX=gcc 2 | CFLAGS=-Wall -g 3 | LDLFLAGS=-L/usr/local/lib -lallegro -lallegro_main -lallegro_image -lallegro_font 4 | INCLUDE=-I. -I/usr/local/lib 5 | 6 | 7 | OBJS=main.c 8 | OUT=main 9 | 10 | all: hello_rule 11 | 12 | clean: 13 | rm -f 14 | 15 | hello_rule: $(OBJS) 16 | $(CXX) $(OBJS) -o $(OUT) $(INCLUDE) $(CFLAGS) $(LDLFLAGS) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ethan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tetris 2 | 3 | This is a working implementation of Tetris, with all central rules in play, written entirely in C. 4 | It uses the [Allego game programming library](http://www.liballeg.org) for managing graphics and player 5 | input. 6 | 7 | The core rules of the game are there, but it is lacking many of the modern updates (a hold option, 8 | e.g.) to the game. My goal here was not to reinvent the wheel, but rather to create a project that 9 | would force me to research and become comfortable with a new C library, while reinforcing fundamental 10 | C concepts (structs, pointers, memory management, etc.). 11 | 12 | --- 13 | 14 | ##### How do I play? 15 | 16 | Great question! Creating a shareable binary file that can run the game is still a work in progress. 17 | The readme and repo will be updated when it is available. For now, you will have to compile yourself. 18 | 19 | To do so, first download and install Allegro v5. Then, you will need to use a custom make file to 20 | ensure the proper header files are accessible. I have included my makefile, which should work for 21 | Mac systems. Additional information will be available on the Allegro forums if you are unable to get 22 | the game running, and feel free to leave a comment if you have any questions! 23 | 24 | ##### I have thoughts on how to improve this. 25 | 26 | Wonderful! I am delighted to receive any and all feedback, so feel free to make a pull request or leave 27 | a comment with any thoughts you might have. 28 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | // Variables that we'll want to remain static or mostly static. 10 | static int FPS = 30; 11 | int height = 640; //We can use argv to modify these, and otherwise set a default 12 | int width = 480; 13 | int y_speed; // how fast is current tetromino moving? This value should be updating. 14 | static int DX = 54; // pixels necessary to move from column to column/row to row 15 | static int DY = 54; 16 | static int starting_x = 218; // Aligns with column five. 17 | static int starting_y = 43; // aligns with row 0. 18 | int score = 0; 19 | int total_cleared = 0; 20 | int level = 0; 21 | 22 | typedef struct Block { // generic struct for the blocks that make up a tetromino 23 | int x; 24 | int y; 25 | int type; 26 | } Block; 27 | 28 | typedef struct Tetromino { 29 | struct Block *blocks[4]; 30 | int arrangement; // Range from 0 to 3, to cover all rotations. 31 | int type; // Range 0 to 6, so we know shape when rotating. 32 | int x1; // low end of x range 33 | int x2; // high end of x range 34 | int y; // lowest point of the tet 35 | } Tetromino; 36 | 37 | 38 | // Declaration of various functions. 39 | void move_left(struct Block *board[21][10], struct Tetromino *current); 40 | void move_right(struct Block *board[21][10], struct Tetromino *current); 41 | void drop(struct Tetromino *current, struct Block *board[21][10]); 42 | void rotate(struct Tetromino *current, int direction, struct Block *board[21][10]); 43 | bool rebalance(struct Tetromino *current, struct Block *board[21][10]); 44 | bool is_game_over(struct Tetromino *current, struct Block *board[21][10]); 45 | bool rotation_is_legal(struct Tetromino *new, struct Block *board[21][10]); 46 | 47 | /* default_movement calls create_tet if current has settled, and returns pointer 48 | to whatever our current tet is. */ 49 | struct Tetromino* default_movement(struct Block *board[21][10], 50 | struct Tetromino *current); 51 | int clear_lines(struct Block *board[21][10]); 52 | 53 | void draw_screen(struct Block *board[21][10], struct Tetromino *current, 54 | ALLEGRO_BITMAP *background, ALLEGRO_BITMAP *shapes[7]); 55 | struct Tetromino* create_tetromino(); 56 | 57 | int main(int argc, char *argv[]) { 58 | //create miscellaneous variables! 59 | srand(time(NULL)); 60 | bool running = true; 61 | bool draw = true; 62 | struct Tetromino *current; 63 | 64 | // Various initializations, confirmations that everything is working. // 65 | ALLEGRO_DISPLAY *display = NULL; 66 | ALLEGRO_EVENT_QUEUE *queue = NULL; 67 | ALLEGRO_TIMER *timer = NULL; 68 | ALLEGRO_KEYBOARD_STATE state; 69 | 70 | if (!al_init()) { 71 | printf("Error: failed to initialize Allegro.\n"); 72 | return 1; 73 | } 74 | timer = al_create_timer(1.0 / FPS); 75 | if (!timer) { 76 | printf("Error: failed to initialize timer.\n"); 77 | return 1; 78 | } 79 | 80 | display = al_create_display(width, height); 81 | if (!display) { 82 | printf("Error: failed to initialize display.\n"); 83 | return 1; 84 | } 85 | 86 | al_install_keyboard(); 87 | al_init_image_addon(); 88 | 89 | // Loading images of the various shapes. // 90 | ALLEGRO_BITMAP *shapes[7]; 91 | ALLEGRO_BITMAP *red = al_load_bitmap("red.png"); 92 | ALLEGRO_BITMAP *orange = al_load_bitmap("orange.png"); 93 | ALLEGRO_BITMAP *yellow = al_load_bitmap("yellow.png"); 94 | ALLEGRO_BITMAP *green = al_load_bitmap("green.png"); 95 | ALLEGRO_BITMAP *blue = al_load_bitmap("blue.png"); 96 | ALLEGRO_BITMAP *indigo = al_load_bitmap("indigo.png"); 97 | ALLEGRO_BITMAP *violet = al_load_bitmap("violet.png"); 98 | ALLEGRO_BITMAP *background = al_load_bitmap("background.png"); 99 | shapes[0] = red; 100 | shapes[1] = orange; 101 | shapes[2] = yellow; 102 | shapes[3] = green; 103 | shapes[4] = blue; 104 | shapes[5] = indigo; 105 | shapes[6] = violet; 106 | 107 | // Create event queue, register event sources, start timer. 108 | queue = al_create_event_queue(); 109 | al_register_event_source(queue, al_get_timer_event_source(timer)); 110 | al_register_event_source(queue, al_get_display_event_source(display)); 111 | al_register_event_source(queue, al_get_keyboard_event_source()); 112 | al_start_timer(timer); 113 | 114 | // Create an initial tetromino and a board. 115 | current = create_tetromino(); 116 | 117 | struct Block *board[21][10]; // 21 rows height, 10 columns wide. Top row is 118 | // just a buffer, but causes game over if used. 119 | for (int y = 0; y < 21; ++y) { 120 | for (int x = 0; x < 10; ++x) { 121 | board[y][x] = NULL; 122 | } 123 | } 124 | 125 | while (running) { 126 | ALLEGRO_EVENT event; 127 | ALLEGRO_TIMEOUT timeout; 128 | al_init_timeout(&timeout, 0.06); 129 | bool get_event = al_wait_for_event_until(queue, &event, &timeout); 130 | al_get_keyboard_state(&state); 131 | 132 | if (get_event) { 133 | switch (event.type) { 134 | case ALLEGRO_EVENT_TIMER: 135 | draw = true; 136 | break; 137 | case ALLEGRO_EVENT_DISPLAY_CLOSE: 138 | running = false; 139 | break; 140 | case ALLEGRO_EVENT_KEY_DOWN: 141 | switch (event.keyboard.keycode) { 142 | case ALLEGRO_KEY_LEFT: 143 | move_left(board, current); 144 | break; 145 | case ALLEGRO_KEY_RIGHT: 146 | move_right(board, current); 147 | break; 148 | case ALLEGRO_KEY_UP: 149 | drop(current, board); 150 | /* normally default_movement calls create_tet, but drop 151 | circumvents default movement so we call create_tet manually. */ 152 | current = create_tetromino(); 153 | break; 154 | case ALLEGRO_KEY_Q: 155 | running = false; 156 | break; 157 | case ALLEGRO_KEY_A: 158 | rotate(current, -1, board); 159 | break; 160 | case ALLEGRO_KEY_S: 161 | rotate(current, 1, board); 162 | default: 163 | break; 164 | } 165 | default: 166 | break; 167 | } 168 | if (al_key_down(&state, ALLEGRO_KEY_DOWN)) { // if down key is held, boost speed 169 | y_speed += 10; 170 | current = default_movement(board, current); 171 | y_speed -= 10; 172 | } 173 | } 174 | if (draw && al_is_event_queue_empty(queue)) { 175 | draw = false; 176 | draw_screen(board, current, background, shapes); 177 | current = default_movement(board, current); 178 | } 179 | 180 | //update game variables - score, level, speed, etc. 181 | int lines_cleared = clear_lines(board); 182 | total_cleared += lines_cleared; 183 | score += pow(2, lines_cleared) * 100; 184 | level = 1 + total_cleared / 10; 185 | y_speed = 5 + level; 186 | 187 | if (is_game_over(current, board)) { 188 | running = false; 189 | } 190 | } 191 | 192 | 193 | al_destroy_bitmap(red); 194 | al_destroy_bitmap(orange); 195 | al_destroy_bitmap(yellow); 196 | al_destroy_bitmap(green); 197 | al_destroy_bitmap(blue); 198 | al_destroy_bitmap(indigo); 199 | al_destroy_bitmap(violet); 200 | al_destroy_bitmap(background); 201 | al_destroy_display(display); 202 | al_destroy_event_queue(queue); 203 | return 0; 204 | } 205 | 206 | bool is_game_over(struct Tetromino *current, struct Block *board[21][10]) { 207 | if (current->blocks[0]->y == starting_y) { // if we're at the start and in a block's 208 | for (int i = 0; i < 4; i++) { // space, we have lost and the game ends. 209 | if (board[0][current->blocks[i]->x / DX]) { 210 | return true; 211 | } 212 | } 213 | } 214 | /* The below solution is cleaner, but currently doesn't work. Problem is 215 | board[20] never gets accessed. We need to adjust such that the top line of 216 | the grid is actually board[1], not board[0], and board [0] functionally 217 | becomes a "negative" row. Then change the if loop below to if (board[0][i]) 218 | and voila. */ 219 | // for (int i = 0; i < 10; ++i) { 220 | // if(board[20][i]) { 221 | // return false; 222 | // } 223 | // } 224 | return false; 225 | } 226 | 227 | struct Tetromino* default_movement(struct Block *board[21][10], 228 | struct Tetromino *current) { 229 | current->y += y_speed; 230 | bool should_create_new_tet = rebalance(current, board); 231 | if (should_create_new_tet) { 232 | free(current); 233 | current = create_tetromino(); 234 | } 235 | return current; 236 | } 237 | 238 | /* Checks if a block has landed on another block, in which case we rebalance the 239 | current tet to neatly fit the grid */ 240 | bool rebalance(struct Tetromino *current, struct Block *board[21][10]) { 241 | bool should_rebalance = false; 242 | struct Block *cur_block; 243 | int delta; 244 | for (int i = 0; i < 4; ++i) { 245 | cur_block = current->blocks[i]; 246 | cur_block->y += y_speed; 247 | if (current->y > 0) { 248 | if (board[cur_block->y / DY][cur_block->x / DX]) { 249 | delta = cur_block->y - board[cur_block->y / DY][cur_block->x / DX]->y; 250 | should_rebalance = true; 251 | } else if (cur_block->y > 20 * DY) { 252 | delta = cur_block-> y - (starting_y + 20 * DY); 253 | should_rebalance = true; 254 | } 255 | } 256 | } 257 | if (should_rebalance) { 258 | for (int i = 0; i < 4; ++i) { 259 | cur_block = current->blocks[i]; 260 | cur_block->y -= delta + DY; 261 | int x = cur_block->x / DX; 262 | int y = cur_block->y / DY; 263 | struct Block *new; 264 | new = (struct Block*) malloc(sizeof(struct Block)); 265 | new->x = cur_block->x; 266 | new->y = cur_block->y; 267 | new->type = cur_block->type; 268 | if (y >= 0 && y < 20) { 269 | board[y][x] = new; 270 | } 271 | free(current->blocks[i]); 272 | } 273 | } 274 | return should_rebalance; 275 | } 276 | 277 | 278 | void draw_screen(struct Block *board[21][10], struct Tetromino *current, 279 | ALLEGRO_BITMAP *background, ALLEGRO_BITMAP *shapes[7]) { 280 | 281 | struct Block *cur_block; 282 | 283 | for (int i = 0; i < 4; ++i) { 284 | cur_block = current->blocks[i]; 285 | al_draw_bitmap(shapes[cur_block->type], cur_block->x, cur_block->y, 0); 286 | } 287 | 288 | al_draw_bitmap(background, 10, 50, 0); 289 | for (int y = 0; y < 20; ++y) { 290 | for (int x = 0; x < 10; ++x) { 291 | if (board[y][x]) { 292 | cur_block = board[y][x]; 293 | al_draw_bitmap(shapes[cur_block->type], cur_block->x, 294 | cur_block->y, 0); 295 | } 296 | } 297 | } 298 | al_flip_display(); 299 | al_clear_to_color(al_map_rgb(180, 180, 180)); 300 | } 301 | 302 | void move_left(struct Block *board[21][10], struct Tetromino *current) { 303 | if ((current->x1 / DX) <= 0) { 304 | return; 305 | } 306 | for (int i = 0; i < 4; ++i) { 307 | struct Block *cur_block = current->blocks[i]; 308 | if (board[cur_block->y / DY][cur_block->x / DX - 1]) { 309 | return; 310 | } 311 | } 312 | for (int i = 0; i < 4; ++i) { 313 | current->blocks[i]->x -= DX; 314 | } 315 | current->x1 -= DX; 316 | current->x2 -= DX; 317 | } 318 | 319 | void move_right(struct Block *board[21][10], struct Tetromino *current) { 320 | if (current->x2 / DX >= 9) { 321 | return; 322 | } 323 | for (int i = 0; i < 4; ++i) { 324 | struct Block *cur_block = current->blocks[i]; 325 | if (board[cur_block->y / DY][cur_block->x / DX + 1]) { 326 | return; 327 | } 328 | } 329 | for (int i = 0; i < 4; ++i) { 330 | current->blocks[i]->x += DX; 331 | } 332 | current->x1 += DX; 333 | current->x2 += DX; 334 | } 335 | 336 | 337 | void drop(struct Tetromino *current, struct Block *board[21][10]) { 338 | bool should_drop = true; 339 | struct Block *cur_block; 340 | while (should_drop) { 341 | for (int i = 0; i < 4; i++) { 342 | cur_block = current->blocks[i]; 343 | cur_block->y += y_speed; 344 | if (board[cur_block->y / DY][cur_block->x / DX]) { 345 | should_drop = false; 346 | } else if (cur_block->y > 20 * DY) { 347 | should_drop = false; 348 | } 349 | } 350 | current->y += DY; 351 | } 352 | rebalance(current, board); 353 | } 354 | 355 | int clear_lines(struct Block *board[21][10]) { 356 | int cleared = 0; 357 | bool clear_line; 358 | for (int y = 1; y < 20; y++) { 359 | clear_line = true; 360 | for (int x = 0; x < 10; x++) { 361 | if (!board[y][x]) { 362 | clear_line = false; 363 | break; 364 | } 365 | } 366 | if (clear_line) { 367 | cleared++; 368 | for (int i = y; i > 0; i--) { 369 | for (int j = 0; j < 10; j++) { 370 | if (board[i-1][j]) { 371 | board[i][j] = board[i-1][j]; 372 | board[i][j]->y += DY; 373 | } else { 374 | board[i][j] = NULL; 375 | } 376 | } 377 | } 378 | for (int i = 0; i < 10; i++) { 379 | board[0][i] = NULL; 380 | } 381 | } 382 | } 383 | return cleared; 384 | } 385 | 386 | 387 | struct Tetromino* create_tetromino() { 388 | struct Tetromino *new_tet; 389 | new_tet = (struct Tetromino*) malloc(sizeof(struct Tetromino)); 390 | int type = rand() % 7; // Determine which of the seven types of tet we get. 391 | new_tet->type = type; 392 | new_tet->arrangement = 0; 393 | 394 | /* create blocks, put them into the new_tet.blocks arr. set their x and y 395 | coordinates. Track total tet x and y ranges, so we know to stop if any 396 | of them fall out of that range. */ 397 | 398 | switch (type) { 399 | case 0: // stick block 400 | for (int i = 0; i < 4; ++i) { 401 | struct Block *new_block; 402 | new_block = (struct Block*) malloc(sizeof(struct Block)); 403 | new_block->x = starting_x; 404 | new_block->y = starting_y - (i * DY); 405 | new_tet->blocks[i] = new_block; 406 | } 407 | break; 408 | case 1: // square. 409 | for (int i = 0; i < 4; ++i) { 410 | struct Block *new_block; 411 | new_block = (struct Block*) malloc(sizeof(struct Block)); 412 | new_tet->blocks[i] = new_block; 413 | } 414 | new_tet->blocks[0]->x = starting_x; 415 | new_tet->blocks[0]->y = starting_y; 416 | new_tet->blocks[1]->x = starting_x + DX; 417 | new_tet->blocks[1]->y = starting_y; 418 | new_tet->blocks[2]->x = starting_x; 419 | new_tet->blocks[2]->y = starting_y - DY; 420 | new_tet->blocks[3]->x = starting_x + DX; 421 | new_tet->blocks[3]->y = starting_y - DY; 422 | 423 | break; 424 | case 2: // T block; 425 | for (int i = 0; i < 4; ++i) { 426 | struct Block *new_block; 427 | new_block = (struct Block*) malloc(sizeof(struct Block)); 428 | new_tet->blocks[i] = new_block; 429 | } 430 | new_tet->blocks[0]->x = starting_x; 431 | new_tet->blocks[0]->y = starting_y; 432 | new_tet->blocks[1]->x = starting_x; 433 | new_tet->blocks[1]->y = starting_y - DY; 434 | new_tet->blocks[2]->x = starting_x - DX; 435 | new_tet->blocks[2]->y = starting_y - DY; 436 | new_tet->blocks[3]->x = starting_x + DX; 437 | new_tet->blocks[3]->y = starting_y - DY; 438 | break; 439 | case 3: // El block; 440 | for (int i = 0; i < 4; ++i) { 441 | struct Block *new_block; 442 | new_block = (struct Block*) malloc(sizeof(struct Block)); 443 | new_tet->blocks[i] = new_block; 444 | } 445 | new_tet->blocks[0]->x = starting_x; 446 | new_tet->blocks[0]->y = starting_y; 447 | new_tet->blocks[1]->x = starting_x + DX; 448 | new_tet->blocks[1]->y = starting_y; 449 | new_tet->blocks[2]->x = starting_x; 450 | new_tet->blocks[2]->y = starting_y - DY; 451 | new_tet->blocks[3]->x = starting_x; 452 | new_tet->blocks[3]->y = starting_y - 2 * DY; 453 | break; 454 | case 4: // reverse el block; 455 | for (int i = 0; i < 4; ++i) { 456 | struct Block *new_block; 457 | new_block = (struct Block*) malloc(sizeof(struct Block)); 458 | new_tet->blocks[i] = new_block; 459 | } 460 | new_tet->blocks[0]->x = starting_x; 461 | new_tet->blocks[0]->y = starting_y; 462 | new_tet->blocks[1]->x = starting_x - DX; 463 | new_tet->blocks[1]->y = starting_y; 464 | new_tet->blocks[2]->x = starting_x; 465 | new_tet->blocks[2]->y = starting_y - DY; 466 | new_tet->blocks[3]->x = starting_x; 467 | new_tet->blocks[3]->y = starting_y - 2 * DY; 468 | break; 469 | case 5: // dog block 470 | for (int i = 0; i < 4; ++i) { 471 | struct Block *new_block; 472 | new_block = (struct Block*) malloc(sizeof(struct Block)); 473 | new_tet->blocks[i] = new_block; 474 | } 475 | new_tet->blocks[0]->x = starting_x; 476 | new_tet->blocks[0]->y = starting_y; 477 | new_tet->blocks[1]->x = starting_x - DX; 478 | new_tet->blocks[1]->y = starting_y; 479 | new_tet->blocks[2]->x = starting_x; 480 | new_tet->blocks[2]->y = starting_y - DY; 481 | new_tet->blocks[3]->x = starting_x + DX; 482 | new_tet->blocks[3]->y = starting_y - DY; 483 | break; 484 | case 6: // reverse dog block 485 | for (int i = 0; i < 4; ++i) { 486 | struct Block *new_block; 487 | new_block = (struct Block*) malloc(sizeof(struct Block)); 488 | new_tet->blocks[i] = new_block; 489 | } 490 | new_tet->blocks[0]->x = starting_x; 491 | new_tet->blocks[0]->y = starting_y; 492 | new_tet->blocks[1]->x = starting_x + DX; 493 | new_tet->blocks[1]->y = starting_y; 494 | new_tet->blocks[2]->x = starting_x; 495 | new_tet->blocks[2]->y = starting_y - DY; 496 | new_tet->blocks[3]->x = starting_x - DX; 497 | new_tet->blocks[3]->y = starting_y - DY; 498 | break; 499 | default: 500 | printf("Error: illegal tetromino type selection.\n"); 501 | exit (1); 502 | break; 503 | } 504 | for (int i = 0; i < 4; ++i) { 505 | new_tet->blocks[i]->type = type; 506 | } 507 | 508 | new_tet->x1 = new_tet->x2 = new_tet->blocks[0]->x; // This guarantees x1, etc. 509 | new_tet->y = new_tet->blocks[0]->y; // will actually update. 510 | for (int i = 0; i < 4; ++i) { 511 | new_tet->x1 = (new_tet->x1 > new_tet->blocks[i]->x) ? 512 | new_tet->blocks[i]->x : new_tet->x1; 513 | new_tet->x2 = (new_tet->x2 < new_tet->blocks[i]->x) ? 514 | new_tet->blocks[i]->x : new_tet->x2; 515 | new_tet->y = (new_tet->y > new_tet->blocks[i]->y) ? 516 | new_tet->blocks[i]->y : new_tet->y; 517 | } 518 | return new_tet; 519 | } 520 | 521 | void rotate(struct Tetromino *current, int direction, struct Block *board[21][10]) { 522 | 523 | /* First we create a copy of current to apply rotations to. This way we can 524 | make changes, see if they are legal, and only then apply them to the 525 | current tetromino. */ 526 | struct Tetromino *new; 527 | new = (struct Tetromino*) malloc(sizeof(struct Tetromino)); 528 | new->type = current->type; 529 | new->x1 = current->x1; 530 | new->x2 = current->x2; 531 | new->y = current->y; 532 | new->arrangement = (current->arrangement + direction) % 4; 533 | if (new->arrangement < 0) { // % is remainder in C, not modulo, so we can 534 | new->arrangement += 4; // end up with negative values. 535 | } 536 | 537 | for (int i = 0; i < 4; ++i) { 538 | struct Block *new_block; 539 | new_block = (struct Block*) malloc(sizeof(struct Block)); 540 | new_block->x = current->blocks[i]->x; 541 | new_block->y = current->blocks[i]->y; 542 | new_block->type = current->blocks[i]->type; 543 | new->blocks[i] = new_block; 544 | } 545 | 546 | struct Block *cur_block; // Lets us look at individual blocks. 547 | int cur_y = new->blocks[0]->y; // Determine X and Y of center block, 548 | int cur_x = new->blocks[0]->x; // to modulate other blocks around. 549 | switch (new->type) { 550 | case 0: // line. Unfortunately lines work kind of wonky, because the central 551 | // block is the second one. This makes it rotate slightly nicer IMO, 552 | // but does lead to some slightly wonky arithmetic. 553 | switch (new->arrangement) { 554 | case 0: case 2: 555 | for (int i = 0; i < 4; i++) { 556 | cur_block = new->blocks[i]; 557 | cur_block->x = cur_x + DX; 558 | cur_block->y = cur_y - (i * DY); 559 | } 560 | break; 561 | case 1: case 3: 562 | for (int i = 0; i < 4; i++) { 563 | cur_block = new->blocks[i]; 564 | cur_block->y = cur_y; 565 | cur_block->x = cur_x + (i - 1) * DX; 566 | } 567 | break; 568 | default: 569 | printf("Illegal arrangement of line block\n"); 570 | break; 571 | } 572 | break; 573 | 574 | case 1: // square 575 | break; 576 | case 2: // T block 577 | cur_block = new->blocks[0]; 578 | int possible_x[4] = {cur_block->x - DX, cur_block->x, 579 | cur_block->x + DX, cur_block->x}; 580 | int possible_y[4] = {cur_block->y, cur_block->y + DY, 581 | cur_block->y, cur_block->y - DY}; 582 | for (int i = 0; i < 3; i++) { 583 | new->blocks[i+1]->x = possible_x[(i+new->arrangement) % 4]; 584 | new->blocks[i+1]->y = possible_y[(i+new->arrangement) % 4]; 585 | } 586 | break; 587 | case 3: case 4: // Cover both el blocks with ternary. 588 | switch (new->arrangement) { 589 | case 0: 590 | new->blocks[1]->y = cur_y; 591 | new->blocks[1]->x = (new->type == 3) ? 592 | cur_x + DX : cur_x - DX; 593 | new->blocks[2]->y = cur_y - DY; 594 | new->blocks[2]->x = cur_x; 595 | new->blocks[3]->y = cur_y - 2 * DY; 596 | new->blocks[3]->x = cur_x; 597 | break; 598 | case 1: 599 | new->blocks[1]->y = (new->type == 3) ? 600 | cur_y + DY : cur_y - DY; 601 | new->blocks[1]->x = cur_x; 602 | new->blocks[2]->y = cur_y; 603 | new->blocks[2]->x = cur_x + DX; 604 | new->blocks[3]->y = cur_y; 605 | new->blocks[3]->x = cur_x + 2 * DX; 606 | break; 607 | case 2: 608 | new->blocks[1]->y = cur_y; 609 | new->blocks[1]->x = (new->type == 3) ? 610 | cur_x - DX : cur_x + DX; 611 | new->blocks[2]->y = cur_y + DY; 612 | new->blocks[2]->x = cur_x; 613 | new->blocks[3]->y = cur_y + 2 * DY; 614 | new->blocks[3]->x = cur_x; 615 | break; 616 | case 3: 617 | new->blocks[1]->y = (new->type == 3) ? 618 | cur_y - DY : cur_y + DY; 619 | new->blocks[1]->x = cur_x; 620 | new->blocks[2]->y = cur_y; 621 | new->blocks[2]->x = cur_x - DX; 622 | new->blocks[3]->y = cur_y; 623 | new->blocks[3]->x = cur_x - 2 * DX; 624 | break; 625 | default: 626 | printf("Error: Illegal arrangement of el block\n"); 627 | break; 628 | } 629 | break; 630 | case 5: case 6: // dog blocks, both. Swap X, then Y, of 1/3. 631 | switch (new->arrangement) { 632 | case 0: case 2: 633 | new->blocks[1]->x = (new->type == 5) ? 634 | cur_x - DX : cur_x + DX; 635 | new->blocks[1]->y = cur_y; 636 | new->blocks[2]->x = cur_x; 637 | new->blocks[2]->y = cur_y - DY; 638 | new->blocks[3]->x = (new->type == 5) ? 639 | cur_x + DX : cur_x - DX; 640 | new->blocks[3]->y = cur_y - DY; 641 | break; 642 | case 1: case 3: 643 | new->blocks[1]->x = cur_x; 644 | new->blocks[1]->y = (new->type == 5) ? 645 | cur_y - DY : cur_y + DY; 646 | new->blocks[2]->x = cur_x + DX; 647 | new->blocks[2]->y = cur_y; 648 | new->blocks[3]->x = cur_x + DX; 649 | new->blocks[3]->y = (current->type == 5) ? 650 | cur_y + DY : cur_y - DY; 651 | break; 652 | default: 653 | printf("Error: illegal dog block arrangement\n"); 654 | break; 655 | } 656 | break; 657 | default: 658 | printf("Additional rotation commands TK\n"); 659 | break; 660 | } 661 | 662 | if (rotation_is_legal(new, board)) { 663 | // Update x and y ranges for current tet. 664 | current->arrangement = new->arrangement; 665 | current->x1 = current->y = 10000; // arbitrarily high/low values to 666 | current->x2 = 0; // guarantee correct updates. 667 | 668 | for (int i = 0; i < 4; i++) { 669 | cur_block = current->blocks[i]; 670 | cur_block->x = new->blocks[i]->x; 671 | cur_block->y = new->blocks[i]->y; 672 | } 673 | for (int i = 0; i < 4; i++) { 674 | cur_block = current->blocks[i]; 675 | current->x1 = (current->x1 < cur_block->x) ? 676 | current->x1 : cur_block-> x; 677 | current->x2 = (current->x2 > cur_block->x) ? 678 | current->x2 : cur_block-> x; 679 | current->y = (current->y < cur_block->y) ? 680 | current->y : cur_block-> y; 681 | } 682 | while (current->x1 < 0) { 683 | for (int i = 0; i < 4; i++) { 684 | current->blocks[i]->x += DX; 685 | } 686 | current->x1 += DX; 687 | } 688 | while (current->x2 > 10 * DX) { 689 | for (int i = 0; i < 4; i++) { 690 | current->blocks[i]->x -= DX; 691 | } 692 | current->x2 -= DX; 693 | } 694 | free(new); 695 | } 696 | } 697 | 698 | 699 | bool rotation_is_legal(struct Tetromino *new, struct Block *board[21][10]) { 700 | struct Block *cur_block; 701 | for (int i = 0; i < 4; ++i) { 702 | cur_block = new->blocks[i]; 703 | int x = cur_block->x / DX; 704 | int y = cur_block->y / DY; 705 | if (board[y][x]) { 706 | return false; 707 | } 708 | } 709 | return true; 710 | } 711 | --------------------------------------------------------------------------------