├── README.md └── play_tetris.py /README.md: -------------------------------------------------------------------------------- 1 | # Terminal-Tetris 2 | 3 | Instructions to run: 4 | -------------------- 5 | 6 | - Fire up the terminal 7 | - cd to the directory where play_tetris.py is located 8 | - run `python3 play_tetris.py` (Yes, please make sure you're running with Python 3) 9 | - Follow on screen instructions to play the game 10 | 11 | -------------------------------------------------------------------------------- /play_tetris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | Python implementation of text-mode version of the Tetris game 5 | 6 | Quick play instructions: 7 | 8 | - a (return): move piece left 9 | - d (return): move piece right 10 | - w (return): rotate piece counter clockwise 11 | - s (return): rotate piece clockwise 12 | - e (return): just move the piece downwards as is 13 | 14 | """ 15 | 16 | import os 17 | import random 18 | import sys 19 | 20 | from copy import deepcopy 21 | 22 | # DECLARE ALL THE CONSTANTS 23 | BOARD_SIZE = 20 24 | # Extra two are for the walls, playing area will have size as BOARD_SIZE 25 | EFF_BOARD_SIZE = BOARD_SIZE + 2 26 | 27 | PIECES = [ 28 | 29 | [[1], [1], [1], [1]], 30 | 31 | [[1, 0], 32 | [1, 0], 33 | [1, 1]], 34 | 35 | [[0, 1], 36 | [0, 1], 37 | [1, 1]], 38 | 39 | [[0, 1], 40 | [1, 1], 41 | [1, 0]], 42 | 43 | [[1, 1], 44 | [1, 1]] 45 | 46 | ] 47 | 48 | # Constants for user input 49 | MOVE_LEFT = 'a' 50 | MOVE_RIGHT = 'd' 51 | ROTATE_ANTICLOCKWISE = 'w' 52 | ROTATE_CLOCKWISE = 's' 53 | NO_MOVE = 'e' 54 | QUIT_GAME = 'q' 55 | 56 | def print_board(board, curr_piece, piece_pos, error_message=''): 57 | """ 58 | Parameters: 59 | ----------- 60 | board - matrix of the size of the board 61 | curr_piece - matrix for the piece active in the game 62 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 63 | w.r.t. the board 64 | 65 | Details: 66 | -------- 67 | Prints out the board, piece and playing instructions to STDOUT 68 | If there are any error messages then prints them to STDOUT as well 69 | """ 70 | os.system('cls' if os.name=='nt' else 'clear') 71 | print("Text mode version of the TETRIS game\n\n") 72 | 73 | board_copy = deepcopy(board) 74 | curr_piece_size_x = len(curr_piece) 75 | curr_piece_size_y = len(curr_piece[0]) 76 | for i in range(curr_piece_size_x): 77 | for j in range(curr_piece_size_y): 78 | board_copy[piece_pos[0]+i][piece_pos[1]+j] = curr_piece[i][j] | board[piece_pos[0]+i][piece_pos[1]+j] 79 | 80 | # Print the board to STDOUT 81 | for i in range(EFF_BOARD_SIZE): 82 | for j in range(EFF_BOARD_SIZE): 83 | if board_copy[i][j] == 1: 84 | print("*", end='') 85 | else: 86 | print(" ", end='') 87 | print("") 88 | 89 | print("Quick play instructions:\n") 90 | print(" - a (return): move piece left") 91 | print(" - d (return): move piece right") 92 | print(" - w (return): rotate piece counter clockwise") 93 | print(" - s (return): rotate piece clockwise") 94 | 95 | # In case user doesn't want to alter the position of the piece 96 | # and he doesn't want to rotate the piece either and just wants to move 97 | # in the downward direction, he can choose 'f' 98 | print(" - e (return): just move the piece downwards as is") 99 | print(" - q (return): to quit the game anytime") 100 | 101 | if error_message: 102 | print(error_message) 103 | print("Your move:",) 104 | 105 | 106 | def init_board(): 107 | """ 108 | Parameters: 109 | ----------- 110 | None 111 | 112 | Returns: 113 | -------- 114 | board - the matrix with the walls of the gameplay 115 | """ 116 | board = [[0 for x in range(EFF_BOARD_SIZE)] for y in range(EFF_BOARD_SIZE)] 117 | for i in range(EFF_BOARD_SIZE): 118 | board[i][0] = 1 119 | for i in range(EFF_BOARD_SIZE): 120 | board[EFF_BOARD_SIZE-1][i] = 1 121 | for i in range(EFF_BOARD_SIZE): 122 | board[i][EFF_BOARD_SIZE-1] = 1 123 | return board 124 | 125 | 126 | def get_random_piece(): 127 | """ 128 | Parameters: 129 | ----------- 130 | None 131 | 132 | Returns: 133 | -------- 134 | piece - a random piece from the PIECES constant declared above 135 | """ 136 | idx = random.randrange(len(PIECES)) 137 | return PIECES[idx] 138 | 139 | 140 | def get_random_position(curr_piece): 141 | """ 142 | Parameters: 143 | ----------- 144 | curr_piece - piece which is alive in the game at the moment 145 | 146 | Returns: 147 | -------- 148 | piece_pos - a randomly (along x-axis) chosen position for this piece 149 | """ 150 | curr_piece_size = len(curr_piece) 151 | 152 | # This x refers to rows, rows go along y-axis 153 | x = 0 154 | # This y refers to columns, columns go along x-axis 155 | y = random.randrange(1, EFF_BOARD_SIZE-curr_piece_size) 156 | return [x, y] 157 | 158 | 159 | def is_game_over(board, curr_piece, piece_pos): 160 | """ 161 | Parameters: 162 | ----------- 163 | board - matrix of the size of the board 164 | curr_piece - matrix for the piece active in the game 165 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 166 | w.r.t. the board 167 | Returns: 168 | -------- 169 | True - if game is over 170 | False - if game is live and player can still move 171 | """ 172 | # If the piece cannot move down and the position is still the first row 173 | # of the board then the game has ended 174 | if not can_move_down(board, curr_piece, piece_pos) and piece_pos[0] == 0: 175 | return True 176 | return False 177 | 178 | 179 | def get_left_move(piece_pos): 180 | """ 181 | Parameters: 182 | ----------- 183 | piece_pos - position of piece on the board 184 | 185 | Returns: 186 | -------- 187 | piece_pos - new position of the piece shifted to the left 188 | """ 189 | # Shift the piece left by 1 unit 190 | new_piece_pos = [piece_pos[0], piece_pos[1] - 1] 191 | return new_piece_pos 192 | 193 | 194 | def get_right_move(piece_pos): 195 | """ 196 | Parameters: 197 | ----------- 198 | piece_pos - position of piece on the board 199 | 200 | Returns: 201 | -------- 202 | piece_pos - new position of the piece shifted to the right 203 | """ 204 | # Shift the piece right by 1 unit 205 | new_piece_pos = [piece_pos[0], piece_pos[1] + 1] 206 | return new_piece_pos 207 | 208 | 209 | def get_down_move(piece_pos): 210 | """ 211 | Parameters: 212 | ----------- 213 | piece_pos - position of piece on the board 214 | 215 | Returns: 216 | -------- 217 | piece_pos - new position of the piece shifted downward 218 | """ 219 | # Shift the piece down by 1 unit 220 | new_piece_pos = [piece_pos[0] + 1, piece_pos[1]] 221 | return new_piece_pos 222 | 223 | 224 | def rotate_clockwise(piece): 225 | """ 226 | Paramertes: 227 | ----------- 228 | piece - matrix of the piece to rotate 229 | 230 | Returns: 231 | -------- 232 | piece - Clockwise rotated piece 233 | 234 | Details: 235 | -------- 236 | We first reverse all the sub lists and then zip all the sublists 237 | This will give us a clockwise rotated matrix 238 | """ 239 | piece_copy = deepcopy(piece) 240 | reverse_piece = piece_copy[::-1] 241 | return list(list(elem) for elem in zip(*reverse_piece)) 242 | 243 | 244 | def rotate_anticlockwise(piece): 245 | """ 246 | Paramertes: 247 | ----------- 248 | piece - matrix of the piece to rotate 249 | 250 | Returns: 251 | -------- 252 | Anti-clockwise rotated piece 253 | 254 | Details: 255 | -------- 256 | If we rotate any piece in clockwise direction for 3 times, we would eventually 257 | get the piece rotated in anti clockwise direction 258 | """ 259 | piece_copy = deepcopy(piece) 260 | # Rotating clockwise thrice will be same as rotating anticlockwise :) 261 | piece_1 = rotate_clockwise(piece_copy) 262 | piece_2 = rotate_clockwise(piece_1) 263 | return rotate_clockwise(piece_2) 264 | 265 | 266 | def merge_board_and_piece(board, curr_piece, piece_pos): 267 | """ 268 | Parameters: 269 | ----------- 270 | board - matrix of the size of the board 271 | curr_piece - matrix for the piece active in the game 272 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 273 | w.r.t. the board 274 | 275 | Returns: 276 | -------- 277 | None 278 | 279 | Details: 280 | -------- 281 | Fixes the position of the passed piece at piece_pos in the board 282 | This means that the new piece will now come into the play 283 | 284 | We also remove any filled up rows from the board to continue the gameplay 285 | as it happends in a tetris game 286 | """ 287 | curr_piece_size_x = len(curr_piece) 288 | curr_piece_size_y = len(curr_piece[0]) 289 | for i in range(curr_piece_size_x): 290 | for j in range(curr_piece_size_y): 291 | board[piece_pos[0]+i][piece_pos[1]+j] = curr_piece[i][j] | board[piece_pos[0]+i][piece_pos[1]+j] 292 | 293 | # After merging the board and piece 294 | # If there are rows which are completely filled then remove those rows 295 | 296 | # Declare empty row to add later 297 | empty_row = [0]*EFF_BOARD_SIZE 298 | empty_row[0] = 1 299 | empty_row[EFF_BOARD_SIZE-1] = 1 300 | 301 | # Declare a constant row that is completely filled 302 | filled_row = [1]*EFF_BOARD_SIZE 303 | 304 | # Count the total filled rows in the board 305 | filled_rows = 0 306 | for row in board: 307 | if row == filled_row: 308 | filled_rows += 1 309 | 310 | # The last row is always a filled row because it is the boundary 311 | # So decrease the count for that one 312 | filled_rows -= 1 313 | 314 | for i in range(filled_rows): 315 | board.remove(filled_row) 316 | 317 | # Add extra empty rows on the top of the board to compensate for deleted rows 318 | for i in range(filled_rows): 319 | board.insert(0, empty_row) 320 | 321 | 322 | def overlap_check(board, curr_piece, piece_pos): 323 | """ 324 | Parameters: 325 | ----------- 326 | board - matrix of the size of the board 327 | curr_piece - matrix for the piece active in the game 328 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 329 | w.r.t. the board 330 | 331 | Returns: 332 | -------- 333 | True - if piece do not overlap with any other piece or walls 334 | False - if piece overlaps with any other piece or board walls 335 | """ 336 | curr_piece_size_x = len(curr_piece) 337 | curr_piece_size_y = len(curr_piece[0]) 338 | for i in range(curr_piece_size_x): 339 | for j in range(curr_piece_size_y): 340 | if board[piece_pos[0]+i][piece_pos[1]+j] == 1 and curr_piece[i][j] == 1: 341 | return False 342 | return True 343 | 344 | 345 | def can_move_left(board, curr_piece, piece_pos): 346 | """ 347 | Parameters: 348 | ----------- 349 | board - matrix of the size of the board 350 | curr_piece - matrix for the piece active in the game 351 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 352 | w.r.t. the board 353 | 354 | Returns: 355 | -------- 356 | True - if we can move the piece left 357 | False - if we cannot move the piece to the left, 358 | means it will overlap if we move it to the left 359 | """ 360 | piece_pos = get_left_move(piece_pos) 361 | return overlap_check(board, curr_piece, piece_pos) 362 | 363 | 364 | def can_move_right(board, curr_piece, piece_pos): 365 | """ 366 | Parameters: 367 | ----------- 368 | board - matrix of the size of the board 369 | curr_piece - matrix for the piece active in the game 370 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 371 | w.r.t. the board 372 | 373 | Returns: 374 | -------- 375 | True - if we can move the piece left 376 | False - if we cannot move the piece to the right, 377 | means it will overlap if we move it to the right 378 | """ 379 | piece_pos = get_right_move(piece_pos) 380 | return overlap_check(board, curr_piece, piece_pos) 381 | 382 | 383 | def can_move_down(board, curr_piece, piece_pos): 384 | """ 385 | Parameters: 386 | ----------- 387 | board - matrix of the size of the board 388 | curr_piece - matrix for the piece active in the game 389 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 390 | w.r.t. the board 391 | 392 | Returns: 393 | -------- 394 | True - if we can move the piece downwards 395 | False - if we cannot move the piece to the downward direction 396 | """ 397 | piece_pos = get_down_move(piece_pos) 398 | return overlap_check(board, curr_piece, piece_pos) 399 | 400 | 401 | def can_rotate_anticlockwise(board, curr_piece, piece_pos): 402 | """ 403 | Parameters: 404 | ----------- 405 | board - matrix of the size of the board 406 | curr_piece - matrix for the piece active in the game 407 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 408 | w.r.t. the board 409 | 410 | Returns: 411 | -------- 412 | True - if we can move the piece anti-clockwise 413 | False - if we cannot move the piece to anti-clockwise 414 | might happen in case rotating would overlap with any existing piece 415 | """ 416 | curr_piece = rotate_anticlockwise(curr_piece) 417 | return overlap_check(board, curr_piece, piece_pos) 418 | 419 | 420 | def can_rotate_clockwise(board, curr_piece, piece_pos): 421 | """ 422 | Parameters: 423 | ----------- 424 | board - matrix of the size of the board 425 | curr_piece - matrix for the piece active in the game 426 | piece_pos - [x,y] co-ordinates of the top-left cell in the piece matrix 427 | w.r.t. the board 428 | 429 | Returns: 430 | -------- 431 | True - if we can move the piece clockwise 432 | False - if we cannot move the piece to clockwise 433 | might happen in case rotating would overlap with any existing piece 434 | """ 435 | curr_piece = rotate_clockwise(curr_piece) 436 | return overlap_check(board, curr_piece, piece_pos) 437 | 438 | 439 | def play_game(): 440 | 441 | """ 442 | Parameters: 443 | ----------- 444 | None 445 | 446 | Returns: 447 | -------- 448 | None 449 | 450 | Details: 451 | -------- 452 | - Initializes the game 453 | - Reads player move from the STDIN 454 | - Checks for the move validity 455 | - Continues the gameplay if valid move, else prints out error msg 456 | without changing the board 457 | - Fixes the piece position on board if it cannot be moved 458 | - Pops in new piece on top of the board 459 | - Quits if no valid moves and possible for a new piece 460 | - Quits in case user wants to quit 461 | 462 | """ 463 | 464 | # Initialize the game board, piece and piece position 465 | board = init_board() 466 | curr_piece = get_random_piece() 467 | piece_pos = get_random_position(curr_piece) 468 | print_board(board, curr_piece, piece_pos) 469 | 470 | # Get player move from STDIN 471 | player_move = input() 472 | while (not is_game_over(board, curr_piece, piece_pos)): 473 | ERR_MSG = "" 474 | do_move_down = False 475 | if player_move == MOVE_LEFT: 476 | if can_move_left(board, curr_piece, piece_pos): 477 | piece_pos = get_left_move(piece_pos) 478 | do_move_down = True 479 | else: 480 | ERR_MSG = "Cannot move left!" 481 | elif player_move == MOVE_RIGHT: 482 | if can_move_right(board, curr_piece, piece_pos): 483 | piece_pos = get_right_move(piece_pos) 484 | do_move_down = True 485 | else: 486 | ERR_MSG = "Cannot move right!" 487 | elif player_move == ROTATE_ANTICLOCKWISE: 488 | if can_rotate_anticlockwise(board, curr_piece, piece_pos): 489 | curr_piece = rotate_anticlockwise(curr_piece) 490 | do_move_down = True 491 | else: 492 | ERR_MSG = "Cannot rotate anti-clockwise !" 493 | elif player_move == ROTATE_CLOCKWISE: 494 | if can_rotate_clockwise(board, curr_piece, piece_pos): 495 | curr_piece = rotate_clockwise(curr_piece) 496 | do_move_down = True 497 | else: 498 | ERR_MSG = "Cannot rotate clockwise!" 499 | elif player_move == NO_MOVE: 500 | do_move_down = True 501 | elif player_move == QUIT_GAME: 502 | print("Bye. Thank you for playing!") 503 | sys.exit(0) 504 | else: 505 | ERR_MSG = "That is not a valid move!" 506 | 507 | if do_move_down and can_move_down(board, curr_piece, piece_pos): 508 | piece_pos = get_down_move(piece_pos) 509 | 510 | # This means the current piece in the game cannot be moved 511 | # We have to fix this piece in the board and generate a new piece 512 | if not can_move_down(board, curr_piece, piece_pos): 513 | merge_board_and_piece(board, curr_piece, piece_pos) 514 | curr_piece = get_random_piece() 515 | piece_pos = get_random_position(curr_piece) 516 | 517 | # Redraw board 518 | print_board(board, curr_piece, piece_pos, error_message=ERR_MSG) 519 | 520 | # Get player move from STDIN 521 | player_move = input() 522 | 523 | print("GAME OVER!") 524 | 525 | if __name__ == "__main__": 526 | play_game() 527 | --------------------------------------------------------------------------------