Making Wormy Game with Python - Part 1

in voilk •  4 months ago

    Making Wormy Game with Python

    In the snake game, the player has to control the movements of a small snake that constantly rotates around the screen. The player cannot stop or slow the snake, but can determine which direction it will go. A red apple is randomly displayed on the screen and the player must move the snake to eat the apple. Each time the snake eats the apple, it grows one section longer and a new apple appears randomly on the screen. If the worm hits itself or the edges of the screen, the game ends. The picture of the game is below.

    Making Wormy Game with Python

    snake game code

    The complete game code is below.

    # Wormy
    import random
    import pygame
    import sys
    from pygame.locals import *
    FPS = 10
    WINDOWWIDTH = 640
    WINDOWHEIGHT = 480
    CELLSIZE = 20
    assert WINDOWWIDTH % CELLSIZE == 0, "Window width must be a multiple of cell size."
    assert WINDOWHEIGHT % CELLSIZE == 0, "Window height must be a multiple of cell size."
    CELLWIDTH = int(WINDOWWIDTH / CELLSIZE)
    CELLHEIGHT = int(WINDOWHEIGHT / CELLSIZE)
    #             R    G    B
    WHITE = (255, 255, 255)
    BLACK = (0,   0,   0)
    RED = (255,   0,   0)
    GREEN = (0, 255,   0)
    DARKGREEN = (0, 155,   0)
    DARKGRAY = (40,  40,  40)
    BGCOLOR = BLACK
    UP = 'up'
    DOWN = 'down'
    LEFT = 'left'
    RIGHT = 'right'
    HEAD = 0  # syntactic sugar: index of the worm's head
    def main():
        global FPSCLOCK, DISPLAYSURF, BASICFONT
        pygame.init()
        FPSCLOCK = pygame.time.Clock()
        DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
        BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
        pygame.display.set_caption('Wormy')
        showStartScreen()
        while True:
            runGame()
            showGameOverScreen()
    def runGame():
        # Set a random start point.
        startx = random.randint(5, CELLWIDTH - 6)
        starty = random.randint(5, CELLHEIGHT - 6)
        wormCoords = [{'x': startx,     'y': starty},
                      {'x': startx - 1, 'y': starty},
                      {'x': startx - 2, 'y': starty}]
        direction = RIGHT
        # Start the apple in a random place.
        apple = getRandomLocation()
        while True:  # main game loop
            for event in pygame.event.get():  # event handling loop
                if event.type == QUIT:
                    terminate()
                elif event.type == KEYDOWN:
                    if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT:
                        direction = LEFT
                    elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:
                        direction = RIGHT
                    elif (event.key == K_UP or event.key == K_w) and direction != DOWN:
                        direction = UP
                    elif (event.key == K_DOWN or event.key == K_s) and direction != UP:
                        direction = DOWN
                    elif event.key == K_ESCAPE:
                        terminate()
            # check if the worm has hit itself or the edge
            if wormCoords[HEAD]['x'] == -1 or wormCoords[HEAD]['x'] == CELLWIDTH or wormCoords[HEAD]['y'] == -1 or wormCoords[HEAD]['y'] == CELLHEIGHT:
                return  # game over
            for wormBody in wormCoords[1:]:
                if wormBody['x'] == wormCoords[HEAD]['x'] and wormBody['y'] == wormCoords[HEAD]['y']:
                    return  # game over
            # check if worm has eaten an apply
            if wormCoords[HEAD]['x'] == apple['x'] and wormCoords[HEAD]['y'] == apple['y']:
                # don't remove worm's tail segment
                apple = getRandomLocation()  # set a new apple somewhere
            else:
                del wormCoords[-1]  # remove worm's tail segment
            # move the worm by adding a segment in the direction it is moving
            if direction == UP:
                newHead = {'x': wormCoords[HEAD]['x'],
                           'y': wormCoords[HEAD]['y'] - 1}
            elif direction == DOWN:
                newHead = {'x': wormCoords[HEAD]['x'],
                           'y': wormCoords[HEAD]['y'] + 1}
            elif direction == LEFT:
                newHead = {'x': wormCoords[HEAD]
                           ['x'] - 1, 'y': wormCoords[HEAD]['y']}
            elif direction == RIGHT:
                newHead = {'x': wormCoords[HEAD]
                           ['x'] + 1, 'y': wormCoords[HEAD]['y']}
            wormCoords.insert(0, newHead)
            DISPLAYSURF.fill(BGCOLOR)
            drawGrid()
            drawWorm(wormCoords)
            drawApple(apple)
            drawScore(len(wormCoords) - 3)
            pygame.display.update()
            FPSCLOCK.tick(FPS)
    def drawPressKeyMsg():
        pressKeySurf = BASICFONT.render('Press a key to play.', True, DARKGRAY)
        pressKeyRect = pressKeySurf.get_rect()
        pressKeyRect.topleft = (WINDOWWIDTH - 200, WINDOWHEIGHT - 30)
        DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
    def checkForKeyPress():
        if len(pygame.event.get(QUIT)) > 0:
            terminate()
        keyUpEvents = pygame.event.get(KEYUP)
        if len(keyUpEvents) == 0:
            return None
        if keyUpEvents[0].key == K_ESCAPE:
            terminate()
        return keyUpEvents[0].key
    def showStartScreen():
        titleFont = pygame.font.Font('freesansbold.ttf', 100)
        titleSurf1 = titleFont.render('Wormy!', True, WHITE, DARKGREEN)
        titleSurf2 = titleFont.render('Wormy!', True, GREEN)
        degrees1 = 0
        degrees2 = 0
        while True:
            DISPLAYSURF.fill(BGCOLOR)
            rotatedSurf1 = pygame.transform.rotate(titleSurf1, degrees1)
            rotatedRect1 = rotatedSurf1.get_rect()
            rotatedRect1.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)
            DISPLAYSURF.blit(rotatedSurf1, rotatedRect1)
            rotatedSurf2 = pygame.transform.rotate(titleSurf2, degrees2)
            rotatedRect2 = rotatedSurf2.get_rect()
            rotatedRect2.center = (WINDOWWIDTH / 2, WINDOWHEIGHT / 2)
            DISPLAYSURF.blit(rotatedSurf2, rotatedRect2)
            drawPressKeyMsg()
            if checkForKeyPress():
                pygame.event.get()  # clear event queue
                return
            pygame.display.update()
            FPSCLOCK.tick(FPS)
            degrees1 += 3  # rotate by 3 degrees each frame
            degrees2 += 7  # rotate by 7 degrees each frame
    def terminate():
        pygame.quit()
        sys.exit()
    def getRandomLocation():
        return {'x': random.randint(0, CELLWIDTH - 1), 'y': random.randint(0, CELLHEIGHT - 1)}
    def showGameOverScreen():
        gameOverFont = pygame.font.Font('freesansbold.ttf', 150)
        gameSurf = gameOverFont.render('Game', True, WHITE)
        overSurf = gameOverFont.render('Over', True, WHITE)
        gameRect = gameSurf.get_rect()
        overRect = overSurf.get_rect()
        gameRect.midtop = (WINDOWWIDTH / 2, 10)
        overRect.midtop = (WINDOWWIDTH / 2, gameRect.height + 10 + 25)
        DISPLAYSURF.blit(gameSurf, gameRect)
        DISPLAYSURF.blit(overSurf, overRect)
        drawPressKeyMsg()
        pygame.display.update()
        pygame.time.wait(500)
        checkForKeyPress()  # clear out any key presses in the event queue
        while True:
            if checkForKeyPress():
                pygame.event.get()  # clear event queue
                return
    def drawScore(score):
        scoreSurf = BASICFONT.render('Score: %s' % (score), True, WHITE)
        scoreRect = scoreSurf.get_rect()
        scoreRect.topleft = (WINDOWWIDTH - 120, 10)
        DISPLAYSURF.blit(scoreSurf, scoreRect)
    def drawWorm(wormCoords):
        for coord in wormCoords:
            x = coord['x'] * CELLSIZE
            y = coord['y'] * CELLSIZE
            wormSegmentRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)
            pygame.draw.rect(DISPLAYSURF, DARKGREEN, wormSegmentRect)
            wormInnerSegmentRect = pygame.Rect(
                x + 4, y + 4, CELLSIZE - 8, CELLSIZE - 8)
            pygame.draw.rect(DISPLAYSURF, GREEN, wormInnerSegmentRect)
    def drawApple(coord):
        x = coord['x'] * CELLSIZE
        y = coord['y'] * CELLSIZE
        appleRect = pygame.Rect(x, y, CELLSIZE, CELLSIZE)
        pygame.draw.rect(DISPLAYSURF, RED, appleRect)
    def drawGrid():
        for x in range(0, WINDOWWIDTH, CELLSIZE):  # draw vertical lines
            pygame.draw.line(DISPLAYSURF, DARKGRAY, (x, 0), (x, WINDOWHEIGHT))
        for y in range(0, WINDOWHEIGHT, CELLSIZE):  # draw horizontal lines
            pygame.draw.line(DISPLAYSURF, DARKGRAY, (0, y), (WINDOWWIDTH, y))
    if __name__ == '__main__':
        main()

    If you play the game, you'll notice that the apple and the snake's body always fit in a grid of lines. We call the squares in this grid a cell. The cells have their own Cartesian coordinate system, which is (0, 0) the upper left cell and (23, 31) the lower right cell.

    Initial settings

    # Wormy
    import random
    import pygame
    import sys
    from pygame.locals import *
    FPS = 10
    WINDOWWIDTH = 640
    WINDOWHEIGHT = 480
    CELLSIZE = 20
    assert WINDOWWIDTH % CELLSIZE == 0, "Window width must be a multiple of cell size."
    assert WINDOWHEIGHT % CELLSIZE == 0, "Window height must be a multiple of cell size."
    CELLWIDTH = int(WINDOWWIDTH / CELLSIZE)
    CELLHEIGHT = int(WINDOWHEIGHT / CELLSIZE)

    The start of the program is to set some constants. The height and width of the cells are stored in CELLSIZE. The asserts ensure that the cells fit perfectly in the window. For example, if CELLSIZE has a value of 10 and the WINDOWWIDTH or WINDOWHEIGHT constants have a value of 15, then only 1.5 cells can fit on the page. asserts ensure that only the correct number of cells fit into the window.

    #             R    G    B
    WHITE = (255, 255, 255)
    BLACK = (0,   0,   0)
    RED = (255,   0,   0)
    GREEN = (0, 255,   0)
    DARKGREEN = (0, 155,   0)
    DARKGRAY = (40,  40,  40)
    BGCOLOR = BLACK
    UP = 'up'
    DOWN = 'down'
    LEFT = 'left'
    RIGHT = 'right'
    HEAD = 0  # syntactic sugar: index of the worm's head

    main Function

    def main():
        global FPSCLOCK, DISPLAYSURF, BASICFONT
        pygame.init()
        FPSCLOCK = pygame.time.Clock()
        DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
        BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
        pygame.display.set_caption('Wormy')
        showStartScreen()
        while True:
            runGame()
            showGameOverScreen()

    In the wormy game, I have put the main part of the code in a function called runGame. The reason for this is that I want to show the "start screen" only at the start of the game (this screen is an animation written by Wormy, and I do this by calling the showStartScreen function). Then I want to call the runGame function to start the Wormy game. This function returns when the snake hits a wall or itself and ends the game. At this stage, the game is over by calling showGameOverScreen or I will show game over on the screen. When the function call returns, loop control returns to the beginning and calls runGame again. The for loop in line 44 continues until the end of the program.

    runGame Function

    def runGame():
        # Set a random start point.
        startx = random.randint(5, CELLWIDTH - 6)
        starty = random.randint(5, CELLHEIGHT - 6)
        wormCoords = [{'x': startx,     'y': starty},
                      {'x': startx - 1, 'y': starty},
                      {'x': startx - 2, 'y': starty}]
        direction = RIGHT
        # Start the apple in a random place.
        apple = getRandomLocation()

    At the start of the game, I want the snake to start moving at a random position (but not too close to the edges of the screen) so I store a random coordinate in startx and starty. (Remember that CELLWIDTH and CELLHEIGHT are the number of cells that fit on either side of the window, not the width and height in pixels). The snake body is stored in a dictionary. There will be a dictionary value for each part of the snake's body. The dictionary considers the keys 'x' and 'y' for the XY coordinates of a part of the snake's body. The snake head will be in startx and starty. The other two parts of the body will be two cells to the left of the head. The snake's head will always be at wormCoords[0]. To make this code more readable, I set the HEAD constant to 0 on line 32 so that I can use wormCoords[HEAD] instead of wormCoords[0].

    Event management loop

    while True:  # main game loop
            for event in pygame.event.get():  # event handling loop
                if event.type == QUIT:
                    terminate()
                elif event.type == KEYDOWN:
                    if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT:
                        direction = LEFT
                    elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT:
                        direction = RIGHT
                    elif (event.key == K_UP or event.key == K_w) and direction != DOWN:
                        direction = UP
                    elif (event.key == K_DOWN or event.key == K_s) and direction != UP:
                        direction = DOWN
                    elif event.key == K_ESCAPE:
                        terminate()

    Line 61 is the start of the main game loop and line 62 is the start of the event loop. If the event is QUIT, then I call terminate (which is defined as the terminate function in previous games). Otherwise, if the event is KEYDOWN, I check if the key pressed is an arrow key or a WASD key. I would like an additional check to check if the cream is working or not. For example, if the worm moves to the left and at the same time the player accidentally presses the right key, the worm will immediately start moving to the right and the worm will collide with itself. That's why this review for the current value of the direction variable. That way, if the player suddenly causes the worm to hit a keypress, I'll ignore the keypress.

    Find a collision

    # check if the worm has hit itself or the edge
    if wormCoords[HEAD]['x'] == -1 or wormCoords[HEAD]['x'] == CELLWIDTH or wormCoords[HEAD]['y'] == -1 or wormCoords[HEAD]['y'] == CELLHEIGHT:
        return  # game over
    for wormBody in wormCoords[1:]:
        if wormBody['x'] == wormCoords[HEAD]['x'] and wormBody['y'] == wormCoords[HEAD]['y']:
            return  # game over

    When the head leaves the web or moves over a cell that is already occupied by another body part, then the snake has hit. By seeing that the X coordinate of the head (stored in wormCoords [HEAD] ['x'] ) we can check if the head has crossed the edge of the grid (that is past the left edge of the grid) or is equal to CELLWIDTH is (which is past the right edge, because the maximum coordinate of cell X is one less than CELLWIDTH).

    If the Y coordinate of the head (stored in wormCoords [HEAD] ['y']) is -1, the head has crossed the edge of the mesh (which has passed the top edge) or CELLHEIGHT (which has passed the bottom). All we have to do to end the current game is return from runGame. When runGame() returns to the function call in main, the line after runGame is called, which itself calls the showGameOverScreen function and causes the large Game Over text to appear on the screen. For this reason, we use return in line 79. Line 80 loops through each body part in the wormCoords located after head. Line 80 loops through each body part. The head is at index 0, that's why the for loop iterates over wormCoords [1:] instead of wormCoords. If the 'x' and 'y' values of the body are equal to the 'x' and 'y' of the head, then we end the game by returning from the runGame function.

      Authors get paid when people like you upvote their post.
      If you enjoyed what you read here, create your account today and start earning FREE VOILK!