این بخش، بخش پایانی این قسمت و هم چنین این سری آموزشی است.در این بخش دو بازی دیگر را معرفی کرده و کد آن ها را نیز می توانید ببینید و استفاده کنید.
بازی Connect Four از یک صفحه 6 در 7 است که بازیکنان به نوبت از بالای صفحه مهره های خود را در صفحه قرار می دهد. مهره ها از بالای هر ستون پایین می آیند و در پایین صفحه یا بالای، بالاترین مهره در آن ستون قرار می گیرند. بازیکن زمانی برنده می شود که چهار مهره او در یک سطر به صورت افقی، عمودی یا مورب قرار بگیرند. این بازی از هوش مصنوعی بسیار خوبی برخوردار است. هر حرکت ممکن را همانند سازی می کند، و سپس هر حرکت احتمالی را که بازیکن می تواند در پاسخ به آن انجام دهد، شبیه سازی می کند.
از آن جا که هفت حرکت احتمالی وجود دارد که می توانید در نوبت خود انجام دهید (مگر این که برخی از ستون ها پر باشند)، و هفت حرکت احتمالی دیگر که حریف می تواند انجام دهد، و هفت دیگر حرکت در پاسخ به آن، و هفت حرکت در پاسخ به آن، به این معنی است که در هرنوبت کامپیوتر 2401 = 7 * 7 * 7 * 7 * 7 * 7 * 7 حرکت احتمالی را در نظر دارد. می توان مقدار DIFFICULTY را بیش تر هم کرد، اما وقتی مقدار را بیش تر از 2 تنظیم می کنیم، محاسبه نوبت زمان زیادی را صرف می کند. هم چنین می توان مقدار DIFFICULTY را یک در نظر گرفت. در این حالت کامپیوتر حرکات خود و پاسخ های احتمالی بازیکن به آن حرکات را در نظر می گیرد. اگر DIFFICULTY را روی 0 تنظیم کنیم، کامپیوتر تمام هوش خود را از دست می دهد و به سادگی حرکات تصادفی را انجام می دهد.
کد بازی را می توانید از نشانی دانلود کنید. فایل های تصویری را می توانید از نشانی دانلود کنید.کد کامل بازی را در زیر آمده است.
# Four-In-A-Row (a Connect Four clone) # By Al Sweigart al@inventwithpython.com # http://inventwithpython.com/pygame # Released under a "Simplified BSD" license import random, copy, sys, pygame from pygame.locals import * BOARDWIDTH = 7 # how many spaces wide the board is BOARDHEIGHT = 6 # how many spaces tall the board is assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.' DIFFICULTY = 2 # how many moves to look ahead. (>2 is usually too much) SPACESIZE = 50 # size of the tokens and individual board spaces in pixels FPS = 30 # frames per second to update the screen WINDOWWIDTH = 640 # width of the program's window, in pixels WINDOWHEIGHT = 480 # height in pixels XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2) YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) BRIGHTBLUE = (0, 50, 255) WHITE = (255, 255, 255) BGCOLOR = BRIGHTBLUE TEXTCOLOR = WHITE RED = 'red' BLACK = 'black' EMPTY = None HUMAN = 'human' COMPUTER = 'computer' def main(): global FPSCLOCK, DISPLAYSURF, REDPILERECT, BLACKPILERECT, REDTOKENIMG global BLACKTOKENIMG, BOARDIMG, ARROWIMG, ARROWRECT, HUMANWINNERIMG global COMPUTERWINNERIMG, WINNERRECT, TIEWINNERIMG pygame.init() FPSCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Four in a Row') REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE) BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE) REDTOKENIMG = pygame.image.load('4row_red.png') REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE)) BLACKTOKENIMG = pygame.image.load('4row_black.png') BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE)) BOARDIMG = pygame.image.load('4row_board.png') BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE)) HUMANWINNERIMG = pygame.image.load('4row_humanwinner.png') COMPUTERWINNERIMG = pygame.image.load('4row_computerwinner.png') TIEWINNERIMG = pygame.image.load('4row_tie.png') WINNERRECT = HUMANWINNERIMG.get_rect() WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) ARROWIMG = pygame.image.load('4row_arrow.png') ARROWRECT = ARROWIMG.get_rect() ARROWRECT.left = REDPILERECT.right + 10 ARROWRECT.centery = REDPILERECT.centery isFirstGame = True while True: runGame(isFirstGame) isFirstGame = False def runGame(isFirstGame): if isFirstGame: # Let the computer go first on the first game, so the player # can see how the tokens are dragged from the token piles. turn = COMPUTER showHelp = True else: # Randomly choose who goes first. if random.randint(0, 1) == 0: turn = COMPUTER else: turn = HUMAN showHelp = False # Set up a blank board data structure. mainBoard = getNewBoard() while True: # main game loop if turn == HUMAN: # Human player's turn. getHumanMove(mainBoard, showHelp) if showHelp: # turn off help arrow after the first move showHelp = False if isWinner(mainBoard, RED): winnerImg = HUMANWINNERIMG break turn = COMPUTER # switch to other player's turn else: # Computer player's turn. column = getComputerMove(mainBoard) animateComputerMoving(mainBoard, column) makeMove(mainBoard, BLACK, column) if isWinner(mainBoard, BLACK): winnerImg = COMPUTERWINNERIMG break turn = HUMAN # switch to other player's turn if isBoardFull(mainBoard): # A completely filled board means it's a tie. winnerImg = TIEWINNERIMG break while True: # Keep looping until player clicks the mouse or quits. drawBoard(mainBoard) DISPLAYSURF.blit(winnerImg, WINNERRECT) pygame.display.update() FPSCLOCK.tick() 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 == MOUSEBUTTONUP: return def makeMove(board, player, column): lowest = getLowestEmptySpace(board, column) if lowest != -1: board[column][lowest] = player def drawBoard(board, extraToken=None): DISPLAYSURF.fill(BGCOLOR) # draw tokens spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE)) if board[x][y] == RED: DISPLAYSURF.blit(REDTOKENIMG, spaceRect) elif board[x][y] == BLACK: DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect) # draw the extra token if extraToken != None: if extraToken['color'] == RED: DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE)) elif extraToken['color'] == BLACK: DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE)) # draw board over the tokens for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE)) DISPLAYSURF.blit(BOARDIMG, spaceRect) # draw the red and black tokens off to the side DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) # red on the left DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) # black on the right def getNewBoard(): board = [] for x in range(BOARDWIDTH): board.append([EMPTY] * BOARDHEIGHT) return board def getHumanMove(board, isFirstMove): draggingToken = False tokenx, tokeny = None, None while True: for event in pygame.event.get(): # event handling loop if event.type == QUIT: pygame.quit() sys.exit() elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos): # start of dragging on red token pile. draggingToken = True tokenx, tokeny = event.pos elif event.type == MOUSEMOTION and draggingToken: # update the position of the red token being dragged tokenx, tokeny = event.pos elif event.type == MOUSEBUTTONUP and draggingToken: # let go of the token being dragged if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN: # let go at the top of the screen. column = int((tokenx - XMARGIN) / SPACESIZE) if isValidMove(board, column): animateDroppingToken(board, column, RED) board[column][getLowestEmptySpace(board, column)] = RED drawBoard(board) pygame.display.update() return tokenx, tokeny = None, None draggingToken = False if tokenx != None and tokeny != None: drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED}) else: drawBoard(board) if isFirstMove: # Show the help arrow for the player's first move. DISPLAYSURF.blit(ARROWIMG, ARROWRECT) pygame.display.update() FPSCLOCK.tick() def animateDroppingToken(board, column, color): x = XMARGIN + column * SPACESIZE y = YMARGIN - SPACESIZE dropSpeed = 1.0 lowestEmptySpace = getLowestEmptySpace(board, column) while True: y += int(dropSpeed) dropSpeed += 0.5 if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace: return drawBoard(board, {'x':x, 'y':y, 'color':color}) pygame.display.update() FPSCLOCK.tick() def animateComputerMoving(board, column): x = BLACKPILERECT.left y = BLACKPILERECT.top speed = 1.0 # moving the black tile up while y > (YMARGIN - SPACESIZE): y -= int(speed) speed += 0.5 drawBoard(board, {'x':x, 'y':y, 'color':BLACK}) pygame.display.update() FPSCLOCK.tick() # moving the black tile over y = YMARGIN - SPACESIZE speed = 1.0 while x > (XMARGIN + column * SPACESIZE): x -= int(speed) speed += 0.5 drawBoard(board, {'x':x, 'y':y, 'color':BLACK}) pygame.display.update() FPSCLOCK.tick() # dropping the black tile animateDroppingToken(board, column, BLACK) def getComputerMove(board): potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) # get the best fitness from the potential moves bestMoveFitness = -1 for i in range(BOARDWIDTH): if potentialMoves[i] > bestMoveFitness and isValidMove(board, i): bestMoveFitness = potentialMoves[i] # find all potential moves that have this best fitness bestMoves = [] for i in range(len(potentialMoves)): if potentialMoves[i] == bestMoveFitness and isValidMove(board, i): bestMoves.append(i) return random.choice(bestMoves) def getPotentialMoves(board, tile, lookAhead): if lookAhead == 0 or isBoardFull(board): return [0] * BOARDWIDTH if tile == RED: enemyTile = BLACK else: enemyTile = RED # Figure out the best move to make. potentialMoves = [0] * BOARDWIDTH for firstMove in range(BOARDWIDTH): dupeBoard = copy.deepcopy(board) if not isValidMove(dupeBoard, firstMove): continue makeMove(dupeBoard, tile, firstMove) if isWinner(dupeBoard, tile): # a winning move automatically gets a perfect fitness potentialMoves[firstMove] = 1 break # don't bother calculating other moves else: # do other player's counter moves and determine best one if isBoardFull(dupeBoard): potentialMoves[firstMove] = 0 else: for counterMove in range(BOARDWIDTH): dupeBoard2 = copy.deepcopy(dupeBoard) if not isValidMove(dupeBoard2, counterMove): continue makeMove(dupeBoard2, enemyTile, counterMove) if isWinner(dupeBoard2, enemyTile): # a losing move automatically gets the worst fitness potentialMoves[firstMove] = -1 break else: # do the recursive call to getPotentialMoves() results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1) potentialMoves[firstMove] += (sum(results) / BOARDWIDTH) / BOARDWIDTH return potentialMoves def getLowestEmptySpace(board, column): # Return the row number of the lowest empty row in the given column. for y in range(BOARDHEIGHT-1, -1, -1): if board[column][y] == EMPTY: return y return -1 def isValidMove(board, column): # Returns True if there is an empty space in the given column. # Otherwise returns False. if column < 0 or column >= (BOARDWIDTH) or board[column][0] != EMPTY: return False return True def isBoardFull(board): # Returns True if there are no empty spaces anywhere on the board. for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if board[x][y] == EMPTY: return False return True def isWinner(board, tile): # check horizontal spaces for x in range(BOARDWIDTH - 3): for y in range(BOARDHEIGHT): if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile: return True # check vertical spaces for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT - 3): if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile: return True # check / diagonal spaces for x in range(BOARDWIDTH - 3): for y in range(3, BOARDHEIGHT): if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile: return True # check \ diagonal spaces for x in range(BOARDWIDTH - 3): for y in range(BOARDHEIGHT - 3): if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile: return True return False if __name__ == '__main__': main()
GemGem یا بازی جواهرات یک بازی است که در آن جواهرها برای پر کردن یک صفحه از بالای صفحه به پایین می آیند. بازیکن می تواند جای هر دو جواهری که در کنار هم هستند را عوض کند تا سه جواهر با یک شکل و رنگ در یک سطر (عمودی یا افقی، مورب) قرار بگیرند. پس ار قرارگیری سه جواهر در یک خط، جواهرات همسان ناپدید می شوند و راه را برای سقوط جواهرات جدید از بالا ایجاد می کنند. تطبیق بیش از سه جواهر یا ایجاد واکنش زنجیره ای، امتیاز بیشتری را به همراه خواهد داشت.
کد بازی را می توانید از نشانی دانلود کنید.فایل های تصویری را می توانید از نشانی دانلود کنید.کد کامل در زیر آمده است.
# 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()
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.