How to create a Tetris clone with python and pygame (2020 tutorial)
This is a simple python game, use left and right arrows to move left and right.
# tetris import pygame, sys, random, time from pygame.locals import * SCREEN_WIDTH = 640 SCREEN_HEIGHT = 480 BOARD_WIDTH = 10 BOARD_HEIGHT = 20 SQUARE_SIZE = 20 # coordinates of the board's top-left corner START_POS = (218, 78) # fps FPS = 30 # color set up WHITE = (255,255,255) BLACK = (0,0,0) RED = (255,0,0) GREEN = (0,255,0) BLUE = (0,0,255) LIGHT_GREEN = (144,238,144) # define shapes # T shape data structure t_shape = [[ ' ', ' * ', ' *** ', ' ', ' ', ], [ ' ', ' *** ', ' * ', ' ', ' ', ], [ ' ', ' * ', ' ** ', ' * ', ' ', ], [ ' ', ' * ', ' ** ', ' * ', ' ', ], ] s_shape = [[ ' ', ' ', ' ** ', ' ** ', ' ', ], [ ' ', ' * ', ' ** ', ' * ', ' ', ], ] z_shape = [[ ' ', ' ', ' ** ', ' ** ', ' ', ], [ ' ', ' * ', ' ** ', ' * ', ' ', ], ] i_shape = [[ ' * ', ' * ', ' * ', ' * ', ' ', ], [ ' ', ' ', '**** ', ' ', ' ', ], ] o_shape = [[ ' ', ' ', ' ** ', ' ** ', ' ', ], ] l_shape = [[ ' ', ' * ', ' *** ', ' ', ' ', ], [ ' ', ' * ', ' * ', ' ** ', ' ', ], [ ' ', ' ', ' *** ', ' * ', ' ', ], [ ' ', ' ** ', ' * ', ' * ', ' ', ], ] j_shape = [[ ' ', ' * ', ' *** ', ' ', ' ', ], [ ' ', ' * ', ' * ', ' ** ', ' ', ], [ ' ', ' * ', ' *** ', ' ', ' ', ], [ ' ', ' ** ', ' * ', ' * ', ' ', ], ] shapes = { 'T': t_shape, 'S': s_shape, 'Z': z_shape, 'J': j_shape, 'L': l_shape, 'I': i_shape, 'O': o_shape, } # generate the data structure of the pattern # it is a dictionary, contains all the information about the pattern # shape: the shape of the pattern, 'T', 'S', 'Z','J','L','I', 'O' # state: the state of the shape, 'T' has four states... # x: the starting x of the pattern, default 3 # y: the starting y of the pattern, default 0 def get_random_pattern(): # select a shape randomly shape_name = random.choice('TSZJLIO') shape = shapes[shape_name] pattern = { 'shape': shape_name, 'state': random.randint(0, len(shape)-1), 'x': 3, 'y': 0, } return pattern def draw_shape(pattern, next_pattern): global screen shape_pattern = shapes[pattern['shape']][pattern['state']] next_shape_pattern = shapes[next_pattern['shape']][next_pattern['state']] start_x, start_y = pattern['x'], pattern['y'] next_start_x, next_start_y = next_pattern['x'], next_pattern['y'] for i in range(5): for j in range(5): if shape_pattern[j][i] == '*': rect = pygame.Rect((START_POS[0]+1)+(start_x+i)*SQUARE_SIZE, (START_POS[1]+1)+(start_y+j)*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE) light_rect = pygame.Rect((START_POS[0]+4)+(start_x+i)*SQUARE_SIZE, (START_POS[1]+4)+(start_y+j)*SQUARE_SIZE, SQUARE_SIZE-3, SQUARE_SIZE-3) pygame.draw.rect(screen, GREEN, rect) pygame.draw.rect(screen, LIGHT_GREEN, light_rect) if next_shape_pattern[j][i] == '*': next_rect = pygame.Rect((420)+(next_start_x+i)*SQUARE_SIZE, (100)+(next_start_y+j)*SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE) light_next_rect = pygame.Rect((420+3)+(next_start_x+i)*SQUARE_SIZE, (100+3)+(next_start_y+j)*SQUARE_SIZE, SQUARE_SIZE-3, SQUARE_SIZE-3) pygame.draw.rect(screen, GREEN, next_rect) pygame.draw.rect(screen, LIGHT_GREEN, light_next_rect) # generate the board data structure, namely a list of list or 2D list def get_board(): board = [] for i in range(BOARD_WIDTH): board.append([]) for j in range(BOARD_HEIGHT): board[i].append('') return board def draw_board(board): global screen screen.fill(BLACK) # draw the board boarder boarder_rect = pygame.Rect(START_POS[0]-2,START_POS[1]-2,SQUARE_SIZE*BOARD_WIDTH+2, SQUARE_SIZE*BOARD_HEIGHT+2) pygame.draw.rect(screen, BLUE, boarder_rect, 2) # draw the board on the screen for i in range(BOARD_HEIGHT): for j in range(BOARD_WIDTH): if board[j][i]: # if board cell is not empty string '' rect = pygame.Rect(START_POS[0]+j*SQUARE_SIZE,START_POS[1]+i*SQUARE_SIZE,SQUARE_SIZE, SQUARE_SIZE) light_rect = pygame.Rect(START_POS[0]+3+j*SQUARE_SIZE, START_POS[1]+3+i*SQUARE_SIZE, SQUARE_SIZE-3, SQUARE_SIZE-3) pygame.draw.rect(screen, GREEN, rect) pygame.draw.rect(screen, LIGHT_GREEN, light_rect) def touch_bottom(pattern): shape_pattern = shapes[pattern['shape']][pattern['state']] y = pattern['y'] for i in range(5): for j in range(5): if shape_pattern[j][i] == '*': if j + y == (BOARD_HEIGHT-1): return True return False def touch_others(pattern): shape_pattern = shapes[pattern['shape']][pattern['state']] x, y = pattern['x'], pattern['y'] touch_pos = [] for i in range(BOARD_WIDTH): for j in range(BOARD_HEIGHT): if board[i][j] == '*' and board[i][j-1] == '': touch_pos.append((i,j-1)) if touch_pos: for i in range(5): for j in range(5): if shape_pattern[j][i] == '*': if (x+i, y+j) in touch_pos: return True return False def add_pattern_to_board(pattern): shape_pattern = shapes[pattern['shape']][pattern['state']] x, y = pattern['x'], pattern['y'] for i in range(5): for j in range(5): if shape_pattern[j][i] == '*': board[x+i][y+j] = '*' def is_valid_pos(pattern): shape_pattern = shapes[pattern['shape']][pattern['state']] x, y = pattern['x'], pattern['y'] for i in range(5): for j in range(5): if shape_pattern[j][i] == '*': if not (0 <= x+i <= 9): return False elif board[x+i][y+j] == '*': return False return True # covert between row-based data structure and column-based data structure def convert_board(board, colomn_based=True): b = [] if colomn_based: for i in range(len(board[0])): b.append([]) for j in range(len(board)): b[i].append(board[j][i]) return b else: for i in range(len(board[0])): b.append([]) for j in range(len(board)): b[i].append(board[j][i]) return b def check_board_update(): ## print('check_board_update') ## print(board) # convert column-based board to row-based board global board, score b = convert_board(board.copy()) for row in range(len(b)): if b[row] == ['*'] * BOARD_WIDTH: score += 1 while row > 0: b[row] = b[row-1] row -= 1 # convert row-based board to column-based board b = convert_board(b, False) # update the board with b board = b ## print(board) def display_game_info(): global info_font, score score_surf = info_font.render(f'Score: {score}', True, WHITE) next_surf = info_font.render('Next: ', True, WHITE) screen.blit(score_surf,(450, 20)) screen.blit(next_surf,(450, 60)) def game_over(pattern): global board shape_pattern = shapes[pattern['shape']][pattern['state']] x, y = pattern['x'], pattern['y'] for i in range(5): for j in range(5): if shape_pattern[j][i] == '*': if board[x+i][y+j] == '*': return True return False def show_game_over(): global info_font, screen game_over_surf = info_font.render('GAME OVER', True, WHITE) screen.blit(game_over_surf, (240, 200)) pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption('Tetris') game_clock = pygame.time.Clock() board = get_board() pattern = get_random_pattern() next_pattern = get_random_pattern() # time before and after drop # we use the difference between begin_drop_time and end_drop_time to control the drop frequency begin_drop_time = time.time() end_drop_time = time.time() drop_interval = 1 # each drop will take roughly 1 second # score score = 0 info_font = pygame.font.SysFont('arial', 38) key_down_pressed = False while True: for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYDOWN: # handle left and right movement if event.key == K_LEFT: pattern['x'] -= 1 if not is_valid_pos(pattern): pattern['x'] += 1 elif event.key == K_RIGHT: pattern['x'] += 1 if not is_valid_pos(pattern): pattern['x'] -= 1 elif event.key == K_DOWN: # move the pattern to the bottom quickly key_down_pressed = True # handle state change elif event.key == K_UP: # print(pattern['shape']) original_state = pattern['state'] # print(original_state) pattern['state'] = (pattern['state'] + 1) % len(shapes[pattern['shape']]) if not is_valid_pos(pattern): # if there is no enough space to rotate pattern['state'] = original_state # go back to the original state #print(end_drop_time - begin_drop_time) if end_drop_time - begin_drop_time > drop_interval or key_down_pressed: pattern['y'] += 1 begin_drop_time = time.time() if touch_bottom(pattern) or touch_others(pattern): if key_down_pressed: key_down_pressed = False # if pattern touches bottom, add the pattern to the board add_pattern_to_board(pattern) # check_board for eliminate the full rows check_board_update() # get a new random pattern for next drop pattern = next_pattern next_pattern = get_random_pattern() if game_over(pattern): draw_board(board) draw_shape(pattern, next_pattern) show_game_over() pygame.display.update() break end_drop_time = time.time() # draw everything on the screen draw_board(board) draw_shape(pattern, next_pattern) display_game_info() pygame.display.update() game_clock.tick(FPS)
Comments
Post a Comment