11.8: Source Code for Gemgem
- Page ID
- 14690
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)This source code can be downloaded from http://invpy.com/gemgem.py.
The image files that Gemgem uses can be downloaded from http://invpy.com/gemgemimages.zip.
# Gemgem (a Bejeweled clone) # By Al Sweigart al@inventwithpython.com # http://inventwithpython.com/pygame # Released under a "Simplified BSD" license """ This program has "gem data structures", which are basically dictionaries with the following keys: 'x' and 'y' - The location of the gem on the board. 0,0 is the top left. There is also a ROWABOVEBOARD row that 'y' can be set to, to indicate that it is above the board. 'direction' - one of the four constant variables UP, DOWN, LEFT, RIGHT. This is the direction the gem is moving. 'imageNum' - The integer index into GEMIMAGES to denote which image this gem uses. """ import random, time, pygame, sys, copy from pygame.locals import * FPS = 30 # frames per second to update the screen WINDOWWIDTH = 600 # width of the program's window, in pixels WINDOWHEIGHT = 600 # height in pixels BOARDWIDTH = 8 # how many columns in the board BOARDHEIGHT = 8 # how many rows in the board GEMIMAGESIZE = 64 # width & height of each space in pixels # NUMGEMIMAGES is the number of gem types. You will need .png image # files named gem0.png, gem1.png, etc. up to gem(N-1).png. NUMGEMIMAGES = 7 assert NUMGEMIMAGES >= 5 # game needs at least 5 types of gems to work # NUMMATCHSOUNDS is the number of different sounds to choose from when # a match is made. The .wav files are named match0.wav, match1.wav, etc. NUMMATCHSOUNDS = 6 MOVERATE = 25 # 1 to 100, larger num means faster animations DEDUCTSPEED = 0.8 # reduces score by 1 point every DEDUCTSPEED seconds. # R G B PURPLE = (255, 0, 255) LIGHTBLUE = (170, 190, 255) BLUE = ( 0, 0, 255) RED = (255, 100, 100) BLACK = ( 0, 0, 0) BROWN = ( 85, 65, 0) HIGHLIGHTCOLOR = PURPLE # color of the selected gem's border BGCOLOR = LIGHTBLUE # background color on the screen GRIDCOLOR = BLUE # color of the game board GAMEOVERCOLOR = RED # color of the "Game over" text. GAMEOVERBGCOLOR = BLACK # background color of the "Game over" text. SCORECOLOR = BROWN # color of the text for the player's score # The amount of space to the sides of the board to the edge of the window # is used several times, so calculate it once here and store in variables. XMARGIN = int((WINDOWWIDTH - GEMIMAGESIZE * BOARDWIDTH) / 2) YMARGIN = int((WINDOWHEIGHT - GEMIMAGESIZE * BOARDHEIGHT) / 2) # constants for direction values UP = 'up' DOWN = 'down' LEFT = 'left' RIGHT = 'right' EMPTY_SPACE = -1 # an arbitrary, nonpositive value ROWABOVEBOARD = 'row above board' # an arbitrary, noninteger value def main(): global FPSCLOCK, DISPLAYSURF, GEMIMAGES, GAMESOUNDS, BASICFONT, BOARDRECTS # Initial set up. pygame.init() FPSCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Gemgem') BASICFONT = pygame.font.Font('freesansbold.ttf', 36) # Load the images GEMIMAGES = [] for i in range(1, NUMGEMIMAGES+1): gemImage = pygame.image.load('gem%s.png' % i) if gemImage.get_size() != (GEMIMAGESIZE, GEMIMAGESIZE): gemImage = pygame.transform.smoothscale(gemImage, (GEMIMAGESIZE, GEMIMAGESIZE)) GEMIMAGES.append(gemImage) # Load the sounds. GAMESOUNDS = {} GAMESOUNDS['bad swap'] = pygame.mixer.Sound('badswap.wav') GAMESOUNDS['match'] = [] for i in range(NUMMATCHSOUNDS): GAMESOUNDS['match'].append(pygame.mixer.Sound('match%s.wav' % i)) # Create pygame.Rect objects for each board space to # do board-coordinate-to-pixel-coordinate conversions. BOARDRECTS = [] for x in range(BOARDWIDTH): BOARDRECTS.append([]) for y in range(BOARDHEIGHT): r = pygame.Rect((XMARGIN + (x * GEMIMAGESIZE), YMARGIN + (y * GEMIMAGESIZE), GEMIMAGESIZE, GEMIMAGESIZE)) BOARDRECTS[x].append(r) while True: runGame() def runGame(): # Plays through a single game. When the game is over, this function returns. # initalize the board gameBoard = getBlankBoard() score = 0 fillBoardAndAnimate(gameBoard, [], score) # Drop the initial gems. # initialize variables for the start of a new game firstSelectedGem = None lastMouseDownX = None lastMouseDownY = None gameIsOver = False lastScoreDeduction = time.time() clickContinueTextSurf = None while True: # main game loop clickedSpace = None for event in pygame.event.get(): # event handling loop if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): pygame.quit() sys.exit() elif event.type == KEYUP and event.key == K_BACKSPACE: return # start a new game elif event.type == MOUSEBUTTONUP: if gameIsOver: return # after games ends, click to start a new game if event.pos == (lastMouseDownX, lastMouseDownY): # This event is a mouse click, not the end of a mouse drag. clickedSpace = checkForGemClick(event.pos) else: # this is the end of a mouse drag firstSelectedGem = checkForGemClick((lastMouseDownX, lastMouseDownY)) clickedSpace = checkForGemClick(event.pos) if not firstSelectedGem or not clickedSpace: # if not part of a valid drag, deselect both firstSelectedGem = None clickedSpace = None elif event.type == MOUSEBUTTONDOWN: # this is the start of a mouse click or mouse drag lastMouseDownX, lastMouseDownY = event.pos if clickedSpace and not firstSelectedGem: # This was the first gem clicked on. firstSelectedGem = clickedSpace elif clickedSpace and firstSelectedGem: # Two gems have been clicked on and selected. Swap the gems. firstSwappingGem, secondSwappingGem = getSwappingGems(gameBoard, firstSelectedGem, clickedSpace) if firstSwappingGem == None and secondSwappingGem == None: # If both are None, then the gems were not adjacent firstSelectedGem = None # deselect the first gem continue # Show the swap animation on the screen. boardCopy = getBoardCopyMinusGems(gameBoard, (firstSwappingGem, secondSwappingGem)) animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score) # Swap the gems in the board data structure. gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = secondSwappingGem['imageNum'] gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = firstSwappingGem['imageNum'] # See if this is a matching move. matchedGems = findMatchingGems(gameBoard) if matchedGems == []: # Was not a matching move; swap the gems back GAMESOUNDS['bad swap'].play() animateMovingGems(boardCopy, [firstSwappingGem, secondSwappingGem], [], score) gameBoard[firstSwappingGem['x']][firstSwappingGem['y']] = firstSwappingGem['imageNum'] gameBoard[secondSwappingGem['x']][secondSwappingGem['y']] = secondSwappingGem['imageNum'] else: # This was a matching move. scoreAdd = 0 while matchedGems != []: # Remove matched gems, then pull down the board. # points is a list of dicts that tells fillBoardAndAnimate() # where on the screen to display text to show how many # points the player got. points is a list because if # the playergets multiple matches, then multiple points text should appear. points = [] for gemSet in matchedGems: scoreAdd += (10 + (len(gemSet) - 3) * 10) for gem in gemSet: gameBoard[gem[0]][gem[1]] = EMPTY_SPACE points.append({'points': scoreAdd, 'x': gem[0] * GEMIMAGESIZE + XMARGIN, 'y': gem[1] * GEMIMAGESIZE + YMARGIN}) random.choice(GAMESOUNDS['match']).play() score += scoreAdd # Drop the new gems. fillBoardAndAnimate(gameBoard, points, score) # Check if there are any new matches. matchedGems = findMatchingGems(gameBoard) firstSelectedGem = None if not canMakeMove(gameBoard): gameIsOver = True # Draw the board. DISPLAYSURF.fill(BGCOLOR) drawBoard(gameBoard) if firstSelectedGem != None: highlightSpace(firstSelectedGem['x'], firstSelectedGem['y']) if gameIsOver: if clickContinueTextSurf == None: # Only render the text once. In future iterations, just # use the Surface object already in clickContinueTextSurf clickContinueTextSurf = BASICFONT.render('Final Score: %s (Click to continue)' % (score), 1, GAMEOVERCOLOR, GAMEOVERBGCOLOR) clickContinueTextRect = clickContinueTextSurf.get_rect() clickContinueTextRect.center = int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) DISPLAYSURF.blit(clickContinueTextSurf, clickContinueTextRect) elif score > 0 and time.time() - lastScoreDeduction > DEDUCTSPEED: # score drops over time score -= 1 lastScoreDeduction = time.time() drawScore(score) pygame.display.update() FPSCLOCK.tick(FPS) def getSwappingGems(board, firstXY, secondXY): # If the gems at the (X, Y) coordinates of the two gems are adjacent, # then their 'direction' keys are set to the appropriate direction # value to be swapped with each other. # Otherwise, (None, None) is returned. firstGem = {'imageNum': board[firstXY['x']][firstXY['y']], 'x': firstXY['x'], 'y': firstXY['y']} secondGem = {'imageNum': board[secondXY['x']][secondXY['y']], 'x': secondXY['x'], 'y': secondXY['y']} highlightedGem = None if firstGem['x'] == secondGem['x'] + 1 and firstGem['y'] == secondGem['y']: firstGem['direction'] = LEFT secondGem['direction'] = RIGHT elif firstGem['x'] == secondGem['x'] - 1 and firstGem['y'] == secondGem['y']: firstGem['direction'] = RIGHT secondGem['direction'] = LEFT elif firstGem['y'] == secondGem['y'] + 1 and firstGem['x'] == secondGem['x']: firstGem['direction'] = UP secondGem['direction'] = DOWN elif firstGem['y'] == secondGem['y'] - 1 and firstGem['x'] == secondGem['x']: firstGem['direction'] = DOWN secondGem['direction'] = UP else: # These gems are not adjacent and can't be swapped. return None, None return firstGem, secondGem def getBlankBoard(): # Create and return a blank board data structure. board = [] for x in range(BOARDWIDTH): board.append([EMPTY_SPACE] * BOARDHEIGHT) return board def canMakeMove(board): # Return True if the board is in a state where a matching # move can be made on it. Otherwise return False. # The patterns in oneOffPatterns represent gems that are configured # in a way where it only takes one move to make a triplet. oneOffPatterns = (((0,1), (1,0), (2,0)), ((0,1), (1,1), (2,0)), ((0,0), (1,1), (2,0)), ((0,1), (1,0), (2,1)), ((0,0), (1,0), (2,1)), ((0,0), (1,1), (2,1)), ((0,0), (0,2), (0,3)), ((0,0), (0,1), (0,3))) # The x and y variables iterate over each space on the board. # If we use + to represent the currently iterated space on the # board, then this pattern: ((0,1), (1,0), (2,0))refers to identical # gems being set up like this: # # +A # B # C # # That is, gem A is offset from the + by (0,1), gem B is offset # by (1,0), and gem C is offset by (2,0). In this case, gem A can # be swapped to the left to form a vertical three-in-a-row triplet. # # There are eight possible ways for the gems to be one move # away from forming a triple, hence oneOffPattern has 8 patterns. for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): for pat in oneOffPatterns: # check each possible pattern of "match in next move" to # see if a possible move can be made. if (getGemAt(board, x+pat[0][0], y+pat[0][1]) == \ getGemAt(board, x+pat[1][0], y+pat[1][1]) == \ getGemAt(board, x+pat[2][0], y+pat[2][1]) != None) or \ (getGemAt(board, x+pat[0][1], y+pat[0][0]) == \ getGemAt(board, x+pat[1][1], y+pat[1][0]) == \ getGemAt(board, x+pat[2][1], y+pat[2][0]) != None): return True # return True the first time you find a pattern return False def drawMovingGem(gem, progress): # Draw a gem sliding in the direction that its 'direction' key # indicates. The progress parameter is a number from 0 (just # starting) to 100 (slide complete). movex = 0 movey = 0 progress *= 0.01 if gem['direction'] == UP: movey = -int(progress * GEMIMAGESIZE) elif gem['direction'] == DOWN: movey = int(progress * GEMIMAGESIZE) elif gem['direction'] == RIGHT: movex = int(progress * GEMIMAGESIZE) elif gem['direction'] == LEFT: movex = -int(progress * GEMIMAGESIZE) basex = gem['x'] basey = gem['y'] if basey == ROWABOVEBOARD: basey = -1 pixelx = XMARGIN + (basex * GEMIMAGESIZE) pixely = YMARGIN + (basey * GEMIMAGESIZE) r = pygame.Rect( (pixelx + movex, pixely + movey, GEMIMAGESIZE, GEMIMAGESIZE) ) DISPLAYSURF.blit(GEMIMAGES[gem['imageNum']], r) def pullDownAllGems(board): # pulls down gems on the board to the bottom to fill in any gaps for x in range(BOARDWIDTH): gemsInColumn = [] for y in range(BOARDHEIGHT): if board[x][y] != EMPTY_SPACE: gemsInColumn.append(board[x][y]) board[x] = ([EMPTY_SPACE] * (BOARDHEIGHT - len(gemsInColumn))) + gemsInColumn def getGemAt(board, x, y): if x < 0 or y < 0 or x >= BOARDWIDTH or y >= BOARDHEIGHT: return None else: return board[x][y] def getDropSlots(board): # Creates a "drop slot" for each column and fills the slot with a # number of gems that that column is lacking. This function assumes # that the gems have been gravity dropped already. boardCopy = copy.deepcopy(board) pullDownAllGems(boardCopy) dropSlots = [] for i in range(BOARDWIDTH): dropSlots.append([]) # count the number of empty spaces in each column on the board for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT-1, -1, -1): # start from bottom, going up if boardCopy[x][y] == EMPTY_SPACE: possibleGems = list(range(len(GEMIMAGES))) for offsetX, offsetY in ((0, -1), (1, 0), (0, 1), (-1, 0)): # Narrow down the possible gems we should put in the # blank space so we don't end up putting an two of # the same gems next to each other when they drop. neighborGem = getGemAt(boardCopy, x + offsetX, y + offsetY) if neighborGem != None and neighborGem in possibleGems: possibleGems.remove(neighborGem) newGem = random.choice(possibleGems) boardCopy[x][y] = newGem dropSlots[x].append(newGem) return dropSlots def findMatchingGems(board): gemsToRemove = [] # a list of lists of gems in matching triplets that should be removed boardCopy = copy.deepcopy(board) # loop through each space, checking for 3 adjacent identical gems for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): # look for horizontal matches if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x + 1, y) == getGemAt(boardCopy, x + 2, y) and getGemAt(boardCopy, x, y) != EMPTY_SPACE: targetGem = boardCopy[x][y] offset = 0 removeSet = [] while getGemAt(boardCopy, x + offset, y) == targetGem: # keep checking if there's more than 3 gems in a row removeSet.append((x + offset, y)) boardCopy[x + offset][y] = EMPTY_SPACE offset += 1 gemsToRemove.append(removeSet) # look for vertical matches if getGemAt(boardCopy, x, y) == getGemAt(boardCopy, x, y + 1) == getGemAt(boardCopy, x, y + 2) and getGemAt(boardCopy, x, y) != EMPTY_SPACE: targetGem = boardCopy[x][y] offset = 0 removeSet = [] while getGemAt(boardCopy, x, y + offset) == targetGem: # keep checking, in case there's more than 3 gems in a row removeSet.append((x, y + offset)) boardCopy[x][y + offset] = EMPTY_SPACE offset += 1 gemsToRemove.append(removeSet) return gemsToRemove def highlightSpace(x, y): pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, BOARDRECTS[x][y], 4) def getDroppingGems(board): # Find all the gems that have an empty space below them boardCopy = copy.deepcopy(board) droppingGems = [] for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT - 2, -1, -1): if boardCopy[x][y + 1] == EMPTY_SPACE and boardCopy[x][y] != EMPTY_SPACE: # This space drops if not empty but the space below it is droppingGems.append( {'imageNum': boardCopy[x][y], 'x': x, 'y': y, 'direction': DOWN} ) boardCopy[x][y] = EMPTY_SPACE return droppingGems def animateMovingGems(board, gems, pointsText, score): # pointsText is a dictionary with keys 'x', 'y', and 'points' progress = 0 # progress at 0 represents beginning, 100 means finished. while progress < 100: # animation loop DISPLAYSURF.fill(BGCOLOR) drawBoard(board) for gem in gems: # Draw each gem. drawMovingGem(gem, progress) drawScore(score) for pointText in pointsText: pointsSurf = BASICFONT.render(str(pointText['points']), 1, SCORECOLOR) pointsRect = pointsSurf.get_rect() pointsRect.center = (pointText['x'], pointText['y']) DISPLAYSURF.blit(pointsSurf, pointsRect) pygame.display.update() FPSCLOCK.tick(FPS) progress += MOVERATE # progress the animation a little bit more for the next frame def moveGems(board, movingGems): # movingGems is a list of dicts with keys x, y, direction, imageNum for gem in movingGems: if gem['y'] != ROWABOVEBOARD: board[gem['x']][gem['y']] = EMPTY_SPACE movex = 0 movey = 0 if gem['direction'] == LEFT: movex = -1 elif gem['direction'] == RIGHT: movex = 1 elif gem['direction'] == DOWN: movey = 1 elif gem['direction'] == UP: movey = -1 board[gem['x'] + movex][gem['y'] + movey] = gem['imageNum'] else: # gem is located above the board (where new gems come from) board[gem['x']][0] = gem['imageNum'] # move to top row def fillBoardAndAnimate(board, points, score): dropSlots = getDropSlots(board) while dropSlots != [[]] * BOARDWIDTH: # do the dropping animation as long as there are more gems to drop movingGems = getDroppingGems(board) for x in range(len(dropSlots)): if len(dropSlots[x]) != 0: # cause the lowest gem in each slot to begin moving in the DOWN direction movingGems.append({'imageNum': dropSlots[x][0], 'x': x, 'y': ROWABOVEBOARD, 'direction': DOWN}) boardCopy = getBoardCopyMinusGems(board, movingGems) animateMovingGems(boardCopy, movingGems, points, score) moveGems(board, movingGems) # Make the next row of gems from the drop slots # the lowest by deleting the previous lowest gems. for x in range(len(dropSlots)): if len(dropSlots[x]) == 0: continue board[x][0] = dropSlots[x][0] del dropSlots[x][0] def checkForGemClick(pos): # See if the mouse click was on the board for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if BOARDRECTS[x][y].collidepoint(pos[0], pos[1]): return {'x': x, 'y': y} return None # Click was not on the board. def drawBoard(board): for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): pygame.draw.rect(DISPLAYSURF, GRIDCOLOR, BOARDRECTS[x][y], 1) gemToDraw = board[x][y] if gemToDraw != EMPTY_SPACE: DISPLAYSURF.blit(GEMIMAGES[gemToDraw], BOARDRECTS[x][y]) def getBoardCopyMinusGems(board, gems): # Creates and returns a copy of the passed board data structure, # with the gems in the "gems" list removed from it. # # Gems is a list of dicts, with keys x, y, direction, imageNum boardCopy = copy.deepcopy(board) # Remove some of the gems from this board data structure copy. for gem in gems: if gem['y'] != ROWABOVEBOARD: boardCopy[gem['x']][gem['y']] = EMPTY_SPACE return boardCopy def drawScore(score): scoreImg = BASICFONT.render(str(score), 1, SCORECOLOR) scoreRect = scoreImg.get_rect() scoreRect.bottomleft = (10, WINDOWHEIGHT - 6) DISPLAYSURF.blit(scoreImg, scoreRect) if __name__ == '__main__': main()