13
\$\begingroup\$

I have an extremely simple self-playing Tetris game which I coded up and I am looking to see how it could be improved. It would also be a learning curve for me to see how those much better than I am would go about improving the code.

###############################################################################
## an implementation of a ***very*** basic Tetris game in Python using Pygame
###############################################################################
'''rotate --- r
   pause ---- p
   direction buttons for movement'''

import sys
import copy
import pygame
import random

size = width, height = 200, 400
sqrsize, pen_size = 20, 1
occupied_squares = []
top_of_screen = (0, 0)
color = {'white':(255, 255, 255)}
top_x, top_y = top_of_screen[0], top_of_screen[1]

pygame.init()
screen = pygame.display.set_mode(size)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((color['white']))
screen.blit(background, top_of_screen)
pygame.display.flip()

################################################################################
#constructors and selectors for a tetrominoe shape
################################################################################
def make_tetrominoe(block1, block2, block3, block4, name):
    """Inputs<- 4 constituent blocks that make up a tetrominoe shape and name
       of tetrominoe shape.
       This returns a tetrominoe shape."""
    return [block1, block2, block3, block4, name]

def get_tetname(tetrominoe):
    """returns the name of a tetrominoe shape"""
    return tetrominoe[4]

def get_blocks(tetrominoe):
    """returns a list of blocks that make up a tetrominoe piece"""
    return tetrominoe[:4]

def get_refblock(tetrominoe):
    """gets reference block.Reference block is one around which other
    blocks are drawn"""
    return tetrominoe[3]

def block_points(tetronimoe):
    """gets the coordinates of the individual blocks that make up a tetrominoe
    piece"""
    blocks = get_blocks(tetronimoe) 
    return [get_point(block) for block in blocks]


###############################################################################
#constructors and selectors for a tetrominoe shape block
###############################################################################
def make_block(point, breadth, length):
    """This returns a block. A block is one of the constituent parts of a
    tetrominoe shape and is made up of a start coordinate,the breadth of the
    block and the lenght"""
    return [point, breadth, length]

def get_point(a_block):
    """returns the coordinate start point of block"""
    return a_block[0]

def block_width(a_block):
    """Returns the width of a block"""
    return a_block[1]

def block_height(a_block):
    """Returns the height of a block"""
    return a_block[2]


#############################################################################    
#constructors and selectors for coordinate points
##############################################################################
def make_point(x_coord, y_coord, colour):
    """Input<-coordinate of a point, color
       returns a point object with the coordinates of the point and color"""
    return [x_coord, y_coord, colour]

def point_x(a_point):
    """Returns the xcoordinate of a point structure"""
    return a_point[0]

def point_y(a_point):
    """Returns the ycoordinate of a point structure"""
    return a_point[1]

def point_color(a_point):
    """Returns the color of a point structure"""
    return a_point[2]


###############################################################################
###############################################################################
def delta_point(a_block, delta_x, delta_y):
    """input<- a block(constituent of a tetrominoe shape), integer, integer
       output->a block
       function which takes a block and increments its POINT"""
    point = get_point(a_block)
    return (make_block(make_point(point_x(point)+delta_x,
                                  point_y(point)+delta_y, point_color(point)),
                       block_width(a_block), block_height(a_block)))


###############################################################################
## game controller
###############################################################################
def tetris():
    """Sets up the whole game play and handles event handling"""
    mov_delay = 150
    events = {276: 'left', 275: 'right', 112: 'pause'}

    while True:
        move_dir = 'down' #default move direction
        game = 'playing'  #default game state play:- is game paused or playing?

        tet_shape = random_shape()

        if legal(tet_shape):
            draw_shape(tet_shape)
        else:
            break  #game over

        dets = find_column(get_tetname(tet_shape))

        sel_col = dets[0]
        rotate_count = dets[-1]

        mov_cnt = (sel_col - 80) / sqrsize

        if mov_cnt < 0:
            move_dir = 'left'
        elif mov_cnt > 0:
            move_dir = 'right'
        elif mov_cnt == 0:
            move_dir = 'down'

        mov_cnt = abs(mov_cnt)

        while rotate_count > 0:
            new_tet_shape = rotate(tet_shape)
            if legal(new_tet_shape):
                prev_tet, tet_shape = tet_shape, new_tet_shape
                draw_and_clear(tet_shape, prev_tet, mov_delay)
            rotate_count = rotate_count - 1

        while mov_cnt > 0:
            new_tet_shape = move(tet_shape, move_dir)
            if legal(new_tet_shape):
                prev_tet, tet_shape = tet_shape, new_tet_shape
                draw_and_clear(tet_shape, prev_tet, mov_delay)
            mov_cnt = mov_cnt - 1


        while True:
            if game == 'paused':
                for event in pygame.event.get((pygame.KEYDOWN, pygame.KEYUP)):
                    if event.key == pygame.K_p:
                        game, move_dir = 'playing', 'down'  
            else:
                for event in pygame.event.get((pygame.KEYDOWN, pygame.KEYUP)):
                    if event.type == pygame.KEYDOWN:
                        if event.key == pygame.K_p:
                            game, move_dir = 'paused', 'pause'
                            break

                    elif event.type == pygame.KEYUP:
                        mov_delay, move_dir = mov_delay, 'down'


                move_dir = 'down'
                new_tet_shape = move(tet_shape, move_dir)
                if legal(new_tet_shape):
                    prev_tet, tet_shape = tet_shape, new_tet_shape
                    draw_and_clear(tet_shape, prev_tet, mov_delay) 
                else:
                    #If shape didn't move and direction of movement is down
                    #then shape has come to rest so we can check for a full row
                    #which we delete before exiting loop and generating a new
                    #tetrominoe. if direction for movement is sideways
                    #and   block did not move it should be moved down rather
                    if move_dir == 'down':
                        occupied_squares.extend(block_points(tet_shape))
                        for row_no in range(height, -sqrsize, -sqrsize):
                            while row_filled(row_no):
                                delete_row(row_no)
                                background.fill(color['white'])
                                for point in occupied_squares:
                                    draw_block(point)
                        break
                    else:
                        draw_shape(tet_shape)
                        pygame.time.delay(mov_delay)


###########################################################################
###########################################################################                      
def draw_and_clear(tetrominoe, prev_tet, delay):
    """input<-two tetrominoe shapes
       clear the previously drawn tetrominoe first and then draw a new
       tetrominoe"""
    for point in block_points(prev_tet):
        background.fill((color['white']), (point_x(point), point_y(point),
                                           sqrsize, sqrsize))
        screen.blit(background, top_of_screen)
        pygame.display.update()
    draw_shape(tetrominoe)
    pygame.time.delay(delay)


############################################################################
############################################################################
def draw_shape(tetrominoe):
    """input<-tetriminoe shape
       This draws a tetrominoe shape to game board"""
    for point in block_points(tetrominoe):
        draw_block(point)
    screen.blit(background, top_of_screen)
    pygame.display.update()


#############################################################################
#############################################################################
def draw_block(a_point):
    """draws a basic shape to screen"""
    pygame.draw.rect(background, point_color(a_point), (point_x(a_point),
                     point_y(a_point), sqrsize, sqrsize), 1)


############################################################################
############################################################################
def row_filled(row_no, board=None):
    """input<-tetriminoe shape
       checks if a row on game board is fully occupied by a shape block"""
    if board:
        filled_coords = [(point_x(point), point_y(point)) for point in board]
        for col in range(0, width, sqrsize):
            if (col, row_no) in filled_coords:
                continue
            else:
                return False
        return True 

    else:
        filled_coords = [[point_x(pt), point_y(pt)] for pt in occupied_squares]
        for col in range(0, width, sqrsize):
            if [col, row_no] in filled_coords:
                continue
            else:
                return False
        return True 


##############################################################################
##############################################################################
def delete_row(row_no):
    """input<-integer(a row number)
       output->list of points
       removes all squares on a row from the occupied_squares list and then
       moves all square positions which have y-axis coord less than row_no down
       board"""
    global occupied_squares
    occupied_squares = [point for point in occupied_squares
                        if point_y(point) != row_no]
    for index in range(len(occupied_squares)):
        if point_y(occupied_squares[index]) < row_no:
            occupied_squares[index] = make_point(point_x(occupied_squares[index]),
                                    point_y(occupied_squares[index]) + sqrsize,
                                    point_color(occupied_squares[index]))


##############################################################################
##############################################################################
def legal(tet_shape):
    """input<-tetrominoe piece
       output->bool
       checks that a tetromone is in a legal portion of the board"""
    tet_block_points = block_points(tet_shape)
    filled_coords = [(point_x(pt), point_y(pt)) for pt in occupied_squares]
    for point in tet_block_points:
        new_x, new_y = point_x(point), point_y(point)
        if ((new_x, new_y) in filled_coords or (new_y >= height or
                                        (new_x >= width or new_x < top_x))):
            return False
    return True


##############################################################################
##############################################################################
def move(shape, direction, undo=False):
    """input<- a tetrominoe shape
       output<- a terominoe shape
       function moves a tetrominoe shape by moving all constituent blocks
       by a fixed amount in a direction given by 'direction' argument"""
    no_move = 0
    directions = {'down':(no_move, sqrsize), 'left':(-sqrsize, no_move),
        'right':(sqrsize, no_move), 'pause': (no_move, no_move)}
    delta_x, delta_y = directions[direction]
    if undo:
        delta_x, delta_y = -delta_x, -delta_y
    new_blocks = [delta_point (block, delta_x, delta_y)
                  for block in get_blocks(shape)]
    return (make_tetrominoe(new_blocks[0], new_blocks[1],
                            new_blocks[2], new_blocks[3], get_tetname(shape)))


##############################################################################
##############################################################################
def tetrominoe_shape(shape, start_x=80, start_y=0):
    """function returns a random tetrominoe piece"""
    shapes = {'S': make_tetrominoe(make_block(make_point(start_x + 1*sqrsize,
                                                         start_y + 2*sqrsize,
                                                         (0, 0, 0)),
                                             sqrsize, sqrsize),
                                  make_block(make_point(start_x, start_y,
                                                        (0, 0, 0)),
                                             sqrsize, sqrsize),
                                  make_block(make_point(start_x,
                                                        start_y + 1*sqrsize,
                                                        (0, 0, 0)),
                                             sqrsize, sqrsize),
                                  make_block(make_point(start_x + 1*sqrsize,
                                                    start_y + 1*sqrsize,
                                                    (0, 0, 0)),
                                             sqrsize, sqrsize)
                                  , 'S'),

        'O': make_tetrominoe(make_block(make_point(start_x + 1*sqrsize,
                                                  start_y + 1*sqrsize,
                                                (200, 200, 200)),
                                        sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y,
                                                  (200, 200, 200)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y + 1*sqrsize,
                                                  (200, 200, 200)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x + 1*sqrsize, start_y,
                                                  (200, 200, 200)),
                                       sqrsize, sqrsize),
                            'O'),

        'I': make_tetrominoe(make_block(make_point(start_x, start_y + 3*sqrsize,
                                                   (0, 255, 0)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y,
                                                  (0, 255, 0)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y + 2*sqrsize,
                                                  (0, 255, 0)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y + 1*sqrsize,
                                                  (0, 255, 0)),
                                       sqrsize, sqrsize),
                            'I'),

        'L':make_tetrominoe(make_block(make_point(start_x + 1*sqrsize,
                                                  start_y + 2*sqrsize,
                                                  (0, 0, 255)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y,
                                                  (0, 0, 255)),
                                        sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y + 2*sqrsize,
                                                  (0, 0, 255)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y + 1*sqrsize,
                                                  (0, 0, 255)),
                                       sqrsize, sqrsize), 'L'),

        'T':make_tetrominoe(make_block(make_point(start_x + 1*sqrsize,
                                                  start_y + 1*sqrsize,
                                                  (255, 0, 0)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x, start_y,
                                                  (255, 0, 0)),
                                       sqrsize, sqrsize),
                            make_block(make_point(start_x - 1*sqrsize,
                                                  start_y + 1*sqrsize,
                                                  (255, 0, 0)),
                                        sqrsize, sqrsize),
                            make_block(make_point(start_x,
                                                        start_y + 1*sqrsize,
                                                        (255, 0, 0)),
                                        sqrsize, sqrsize), 'T')
        }
    return shapes[shape]


#####
#####
def random_shape(start_x=80, start_y=0):
    """return a random tetrominoe shape"""
    tets = ['S', 'O', 'I', 'L', 'T']
    return tetrominoe_shape(tets[random.randint(0, 4)], start_x, start_y)


##############################################################################
##############################################################################
def rotate(tetrominoe):
    """input<- tetrominoe shape
       ouput-> tetrominoe shape
       rotates a tetrominoe shape if possible about a reference block."""
    #global occupied_squares
    if get_tetname(tetrominoe) == 'O':
        return tetrominoe
    else:
        ref_point = get_point(get_refblock(tetrominoe)) 
        x_coord = point_x(ref_point)
        y_coord = point_y(ref_point)

        tetblock_coords = block_points(tetrominoe) 

        new_tet = make_tetrominoe(make_block(make_point(x_coord +
                                            y_coord-point_y(tetblock_coords[0]),
                            y_coord - (x_coord - point_x(tetblock_coords[0])),
                            point_color(ref_point)), sqrsize, sqrsize,
                                             ),

                        make_block(make_point(x_coord + y_coord -
                                              point_y(tetblock_coords[1]),
                            y_coord - (x_coord - point_x(tetblock_coords[1])),
                            point_color(ref_point)), sqrsize, sqrsize),

                        make_block(make_point(x_coord + y_coord -
                                              point_y(tetblock_coords[2]),
                            y_coord - (x_coord - point_x(tetblock_coords[2])),
                            point_color(ref_point)), sqrsize, sqrsize),

                        make_block(make_point(x_coord, y_coord,
                                              point_color(ref_point)),
                                   sqrsize, sqrsize),
                    get_tetname(tetrominoe))

        #if legal(new_tet):
        return new_tet
        #else:
         #   return tetrominoe
####
####
def drop_shape(shape):
    """drop a shape into postion on a column"""
    new_shape = move(shape, 'down')
    prev_shape, new_shape = shape, new_shape
    while legal(new_shape):
        prev_shape, new_shape = new_shape, move(new_shape, 'down')
    return prev_shape


####
####
def bubble_count(shape):
    """returns number of new empty spots generated when a shape is placed at a 
    legal point"""
    count = 0
    points = [(point_x(pt), point_y(pt)) for pt in block_points(shape)]
    board = [(point_x(pt), point_y(pt)) for pt in occupied_squares]
    for pt in points:
        for i in range(point_y(pt) + sqrsize, height, sqrsize):
            if (pt[0], i) in board or (pt[0], i) in points:
                break
            else:
                count += 1
    return count

####
###
def shape_lowest_row(shape):
    """return the lowest row of a shape"""
    points = [(point_x(pt), point_y(pt)) for pt in block_points(shape)]
    points = sorted(points, key=lambda point: point[1], reverse=True)
    return points[0]

#####
#####
def row_filln_column(shape):
    """return a list of columns, rows filled tuple for each column on the 
    board if there are n columns for which a shape dropped in column fills
    a row"""
    rows_filled = []
    shape_rotates = {'S':2, 'I':1, 'O':0, 'L':3, 'T':3}
    rotate_count = shape_rotates[shape]
    curr_cnt = 0
    while True:
        for col in range(0, width, sqrsize):
            board = copy.deepcopy(occupied_squares)
            tet_shape = tetrominoe_shape(shape, start_x=col, start_y=0)
            cnt = curr_cnt
            while cnt > 0:
                tet_shape = rotate(tet_shape)
                cnt -= 1

            if not legal(tet_shape): # check shape is in board sideways
                continue
            tet_shape = drop_shape(tet_shape)
            board.extend(block_points(tet_shape))
            rows = 0
            for row in range(height, 0, -sqrsize):
                if row_filled(row, board=board):
                    rows += 1
            if rows > 0:
                rows_filled.append((col, rows, curr_cnt))
            tet_shape = rotate(tet_shape)

        if rotate_count == curr_cnt:
            break
        curr_cnt += 1
    if rows_filled:
        return rows_filled
    return None

####
####
def next_best_columns(shape):
    """return list of columns which a shape can go into if the shape cannot
    fill any rows"""
    next_best = []
    shape_rotates = {'S':2, 'I':1, 'O':0, 'L':3, 'T':3}
    rotate_count = shape_rotates[shape]
    curr_cnt = 0
    while True:
        for col in range(0, width, sqrsize):
            board = copy.deepcopy(occupied_squares)
            tet_shape = tetrominoe_shape(shape, start_x=col, start_y=0)
            cnt = curr_cnt
            while cnt > 0:
                tet_shape = rotate(tet_shape)
                cnt -= 1

            if not legal(tet_shape):
                continue
            tet_shape = drop_shape(tet_shape)
            board.extend(block_points(tet_shape))
            bubble_cnt = bubble_count(tet_shape)
            next_best.append((col, bubble_cnt, shape_lowest_row(tet_shape)[1],
                             curr_cnt))
            #print col, bubble_cnt
        if rotate_count == curr_cnt:
            break
        curr_cnt += 1
    return next_best

#####
#####
def find_column(shape):
    """find column of best fit to drop down a tetrominoe shape from"""
    # search for if any rows can be filled up by shape
    rows_filled = row_filln_column(shape)
    if not rows_filled:
        next_best = next_best_columns(shape)
        next_best =  sorted(next_best, key=lambda col: col[2], reverse=True)
        cols = sorted(next_best, key=lambda col: col[1])
        return cols[0]
    rows_filled = sorted(rows_filled, key=lambda row_filled: row_filled[1])
    return rows_filled[-1] #col with most rows filled


if __name__ == '__main__':
    tetris()
\$\endgroup\$
6
\$\begingroup\$

A few general comments:

  • Python constants should be capitals, for e.g width, height etc should be capitalized in your code since they are constants (declared at the beginning).

  • The places where you have indicated "constructors and selectors" probably indicate that they aught to be in a separate class.

  • As a general thumb rule, more than one loop statement in a method is probably indicating that the method can be refactored. The method "tetris" is ripe for refactoring.

  • Same way, I would say more than 3 levels of indentation is probably a code-smell.

  • The method "tetrominoe_shape" may be refactored rather heavily. The difference between each shapes seems to be just one or two numbers. They may be factored out.

  • Try to factor out the objects for block,shape,point, controller etc, and your code will become much more tractable.

  • Also add a bit of information as to what each method does. Right now, it is rather hard to figure out what each method does.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.