Skip to main content
Engineering LibreTexts

6.2: Source Code to Simulate

  • Page ID
    13591
  • This source code can be downloaded from http://invpy.com/simulate.py. If you get any error messages, look at the line number that is mentioned in the error message and check your code for any typos. You can also copy and paste your code into the web form at http://invpy.com/diff/simulate to see if the differences between your code and the code in the book.

    You can download the four sound files that this program uses from:

    # Simulate (a Simon clone)
    # By Al Sweigart al@inventwithpython.com
    # http://inventwithpython.com/pygame
    # Released under a "Simplified BSD" license
    
    import random, sys, time, pygame
    from pygame.locals import *
    
    FPS = 30
    WINDOWWIDTH = 640
    WINDOWHEIGHT = 480
    FLASHSPEED = 500 # in milliseconds
    FLASHDELAY = 200 # in milliseconds
    BUTTONSIZE = 200
    BUTTONGAPSIZE = 20
    TIMEOUT = 4 # seconds before game over if no button is pushed.
    
    #                R    G    B
    WHITE        = (255, 255, 255)
    BLACK        = (  0,   0,   0)
    BRIGHTRED    = (255,   0,   0)
    RED          = (155,   0,   0)
    BRIGHTGREEN  = (  0, 255,   0)
    GREEN        = (  0, 155,   0)
    BRIGHTBLUE   = (  0,   0, 255)
    BLUE         = (  0,   0, 155)
    BRIGHTYELLOW = (255, 255,   0)
    YELLOW       = (155, 155,   0)
    DARKGRAY     = ( 40,  40,  40)
    bgColor = BLACK
    
    XMARGIN = int((WINDOWWIDTH - (2 * BUTTONSIZE) - BUTTONGAPSIZE) / 2)
    YMARGIN = int((WINDOWHEIGHT - (2 * BUTTONSIZE) - BUTTONGAPSIZE) / 2)
    
    # Rect objects for each of the four buttons
    YELLOWRECT = pygame.Rect(XMARGIN, YMARGIN, BUTTONSIZE, BUTTONSIZE)
    BLUERECT   = pygame.Rect(XMARGIN + BUTTONSIZE + BUTTONGAPSIZE, YMARGIN, BUTTONSIZE, BUTTONSIZE)
    REDRECT    = pygame.Rect(XMARGIN, YMARGIN + BUTTONSIZE + BUTTONGAPSIZE, BUTTONSIZE, BUTTONSIZE)
    GREENRECT  = pygame.Rect(XMARGIN + BUTTONSIZE + BUTTONGAPSIZE, YMARGIN + BUTTONSIZE + BUTTONGAPSIZE, BUTTONSIZE, BUTTONSIZE)
    
    def main():
        global FPSCLOCK, DISPLAYSURF, BASICFONT, BEEP1, BEEP2, BEEP3, BEEP4
    
        pygame.init()
        FPSCLOCK = pygame.time.Clock()
        DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
        pygame.display.set_caption('Simulate')
    
        BASICFONT = pygame.font.Font('freesansbold.ttf', 16)
        infoSurf = BASICFONT.render('Match the pattern by clicking on the button or using the Q, W, A, S keys.', 1, DARKGRAY)
        infoRect = infoSurf.get_rect()
        infoRect.topleft = (10, WINDOWHEIGHT - 25)
    
        # load the sound files
        BEEP1 = pygame.mixer.Sound('beep1.ogg')
        BEEP2 = pygame.mixer.Sound('beep2.ogg')
        BEEP3 = pygame.mixer.Sound('beep3.ogg')
        BEEP4 = pygame.mixer.Sound('beep4.ogg')
    
        # Initialize some variables for a new game
        pattern = [] # stores the pattern of colors
        currentStep = 0 # the color the player must push next
        lastClickTime = 0 # timestamp of the player's last button push
        score = 0
        # when False, the pattern is playing. when True, waiting for the player to click a colored button:
        waitingForInput = False
    
        while True: # main game loop
            clickedButton = None # button that was clicked (set to YELLOW, RED, GREEN, or BLUE)
            DISPLAYSURF.fill(bgColor)
            drawButtons()
    
            scoreSurf = BASICFONT.render('Score: ' + str(score), 1, WHITE)
            scoreRect = scoreSurf.get_rect()
            scoreRect.topleft = (WINDOWWIDTH - 100, 10)
            DISPLAYSURF.blit(scoreSurf, scoreRect)
    
            DISPLAYSURF.blit(infoSurf, infoRect)
    
            checkForQuit()
            for event in pygame.event.get(): # event handling loop
                if event.type == MOUSEBUTTONUP:
                    mousex, mousey = event.pos
                    clickedButton = getButtonClicked(mousex, mousey)
                elif event.type == KEYDOWN:
                    if event.key == K_q:
                        clickedButton = YELLOW
                    elif event.key == K_w:
                        clickedButton = BLUE
                    elif event.key == K_a:
                        clickedButton = RED
                    elif event.key == K_s:
                        clickedButton = GREEN
    
    
    
            if not waitingForInput:
                # play the pattern
                pygame.display.update()
                pygame.time.wait(1000)
                pattern.append(random.choice((YELLOW, BLUE, RED, GREEN)))
                for button in pattern:
                    flashButtonAnimation(button)
                    pygame.time.wait(FLASHDELAY)
                waitingForInput = True
            else:
                # wait for the player to enter buttons
                if clickedButton and clickedButton == pattern[currentStep]:
                    # pushed the correct button
                    flashButtonAnimation(clickedButton)
                    currentStep += 1
                    lastClickTime = time.time()
    
                    if currentStep == len(pattern):
                        # pushed the last button in the pattern
                        changeBackgroundAnimation()
                        score += 1
                        waitingForInput = False
                        currentStep = 0 # reset back to first step
    
                elif (clickedButton and clickedButton != pattern[currentStep]) or (currentStep != 0 and time.time() - TIMEOUT > lastClickTime):
                    # pushed the incorrect button, or has timed out
                    gameOverAnimation()
                    # reset the variables for a new game:
                    pattern = []
                    currentStep = 0
                    waitingForInput = False
                    score = 0
                    pygame.time.wait(1000)
                    changeBackgroundAnimation()
    
            pygame.display.update()
            FPSCLOCK.tick(FPS)
    
    
    def terminate():
        pygame.quit()
        sys.exit()
    
    
    def checkForQuit():
        for event in pygame.event.get(QUIT): # get all the QUIT events
            terminate() # terminate if any QUIT events are present
        for event in pygame.event.get(KEYUP): # get all the KEYUP events
            if event.key == K_ESCAPE:
                terminate() # terminate if the KEYUP event was for the Esc key
            pygame.event.post(event) # put the other KEYUP event objects back
    
    
    def flashButtonAnimation(color, animationSpeed=50):
        if color == YELLOW:
            sound = BEEP1
            flashColor = BRIGHTYELLOW
            rectangle = YELLOWRECT
        elif color == BLUE:
            sound = BEEP2
            flashColor = BRIGHTBLUE
            rectangle = BLUERECT
        elif color == RED:
            sound = BEEP3
            flashColor = BRIGHTRED
            rectangle = REDRECT
        elif color == GREEN:
            sound = BEEP4
            flashColor = BRIGHTGREEN
            rectangle = GREENRECT
    
        origSurf = DISPLAYSURF.copy()
        flashSurf = pygame.Surface((BUTTONSIZE, BUTTONSIZE))
        flashSurf = flashSurf.convert_alpha()
        r, g, b = flashColor
        sound.play()
        for start, end, step in ((0, 255, 1), (255, 0, -1)): # animation loop
            for alpha in range(start, end, animationSpeed * step):
                checkForQuit()
                DISPLAYSURF.blit(origSurf, (0, 0))
                flashSurf.fill((r, g, b, alpha))
                DISPLAYSURF.blit(flashSurf, rectangle.topleft)
                pygame.display.update()
                FPSCLOCK.tick(FPS)
        DISPLAYSURF.blit(origSurf, (0, 0))
    
    
    def drawButtons():
        pygame.draw.rect(DISPLAYSURF, YELLOW, YELLOWRECT)
        pygame.draw.rect(DISPLAYSURF, BLUE,   BLUERECT)
        pygame.draw.rect(DISPLAYSURF, RED,    REDRECT)
        pygame.draw.rect(DISPLAYSURF, GREEN,  GREENRECT)
    
    
    def changeBackgroundAnimation(animationSpeed=40):
        global bgColor
        newBgColor = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    
        newBgSurf = pygame.Surface((WINDOWWIDTH, WINDOWHEIGHT))
        newBgSurf = newBgSurf.convert_alpha()
        r, g, b = newBgColor
        for alpha in range(0, 255, animationSpeed): # animation loop
            checkForQuit()
            DISPLAYSURF.fill(bgColor)
    
            newBgSurf.fill((r, g, b, alpha))
            DISPLAYSURF.blit(newBgSurf, (0, 0))
    
            drawButtons() # redraw the buttons on top of the tint
    
            pygame.display.update()
            FPSCLOCK.tick(FPS)
        bgColor = newBgColor
    
    
    def gameOverAnimation(color=WHITE, animationSpeed=50):
        # play all beeps at once, then flash the background
        origSurf = DISPLAYSURF.copy()
        flashSurf = pygame.Surface(DISPLAYSURF.get_size())
        flashSurf = flashSurf.convert_alpha()
        BEEP1.play() # play all four beeps at the same time, roughly.
        BEEP2.play()
        BEEP3.play()
        BEEP4.play()
        r, g, b = color
        for i in range(3): # do the flash 3 times
            for start, end, step in ((0, 255, 1), (255, 0, -1)):
                # The first iteration in this loop sets the following for loop
                # to go from 0 to 255, the second from 255 to 0.
                for alpha in range(start, end, animationSpeed * step): # animation loop
                    # alpha means transparency. 255 is opaque, 0 is invisible
                    checkForQuit()
                    flashSurf.fill((r, g, b, alpha))
                    DISPLAYSURF.blit(origSurf, (0, 0))
                    DISPLAYSURF.blit(flashSurf, (0, 0))
                    drawButtons()
                    pygame.display.update()
                    FPSCLOCK.tick(FPS)
    
    
    
    def getButtonClicked(x, y):
        if YELLOWRECT.collidepoint( (x, y) ):
            return YELLOW
        elif BLUERECT.collidepoint( (x, y) ):
            return BLUE
        elif REDRECT.collidepoint( (x, y) ):
            return RED
        elif GREENRECT.collidepoint( (x, y) ):
            return GREEN
        return None
    
    
    if __name__ == '__main__':
        main()