در این قسمت ساخت چهار بازی جدید را یاد خواهیم گرفت. در بخش یک دو بازی و در بخش پایانی دو بازی آخر آموزش داده می شوند. تفاوت این قسمت با قسمت های پیشین این است که آموزش آن ها کاملا تشریحی و خط به خط نیست. این بازی ها عبارتند از:
کد کامل بازی ها را می توانید از نشانی های زیر دانلود کنید:
اتلو یا همان Flippy، که با نام Reversi نیز شناخته می شود، از یک صفحه 8*8 با خانه های سیاه و سفید تشکیل شده است. صفحه شروع مانند شکل زیر است. هر بازیکن در نوبت خود مهره جدیدی را در صفحه قرار می دهد. هر یک از مهره های حریف که بین مهره جدید و دیگر مهره های آن رنگ قرار دارد، برگردانده می شود. هدف از این بازی این است که تا آن جا که ممکن است تعداد مهره های رنگ خود را بیش تر و بیش تر کنیم.
به عنوان مثال، اگر بازیکن سفید، یک مهره سفید رنگ جدید را در فضای 5، 6 قرار دهد، شکل زیر را خواهیم داشت:
مهره مشکی در (5,5) میان مهره سفید جدید و مهره سفید موجود در 5، 4 قرار دارد. آن مهره سیاه بر می گردد و به یک مهره سفید جدید تبدیل می شود و صفحه را شبیه شکل زیر می کند. سیاه حرکت بعدی را انجام می دهد، مهره مشکی را روی 4، 6 قرار می دهد که مهره سفید را در 4، 5 می چرخاند. در نهایت صفحه ای مانند شکل زیر پدیدار می شود.
مهره ها در همه جهات تا زمانی که بین مهره جدید بازیکن و مهره موجود باشند، بر می گردند. در شکل زیر، بازیکن سفید یک مهره را در 3، 6 قرار می دهد و مهره های سیاه را در هر دو جهت کی چرخاند (با خطوط مشخص می شود). نتیجه در شکل زیر آمده است:
همان طور که مشاهده می کنید، هر بازیکن فقط در یک یا دو حرکت می تواند همه مهره های صفحه را بدست آورد. بازیکنان باید حرکتی انجام دهند که بتوانند حداقل یک مهره را به دست آورند.وقتی بازی تمام می شود که بازیکن نتواند حرکتی انجام دهد یا صفحه کاملا پر باشد. بازیکنی که بیشترین مهره را در صفحه دارد، برنده است.برای آشنایی بیش تر با بازی Flippy می توانید به نشانی سر بزنید.
کد این بازی را می توانید از نشانی دانلود کنید. فایل های تصویری که Flippy استفاده می کند را می توانید از نشانی بارگیری کنید. کد کامل بازی در زیر آمده است.
# Flippy (an Othello or Reversi clone) # By Al Sweigart al@inventwithpython.com # http://inventwithpython.com/pygame # Released under a "Simplified BSD" license # Based on the "reversi.py" code that originally appeared in "Invent # Your Own Computer Games with Python", chapter 15: # http://inventwithpython.com/chapter15.html import random, sys, pygame, time, copy from pygame.locals import * FPS = 10 # frames per second to update the screen WINDOWWIDTH = 640 # width of the program's window, in pixels WINDOWHEIGHT = 480 # height in pixels SPACESIZE = 50 # width & height of each space on the board, in pixels BOARDWIDTH = 8 # how many columns of spaces on the game board BOARDHEIGHT = 8 # how many rows of spaces on the game board WHITE_TILE = 'WHITE_TILE' # an arbitrary but unique value BLACK_TILE = 'BLACK_TILE' # an arbitrary but unique value EMPTY_SPACE = 'EMPTY_SPACE' # an arbitrary but unique value HINT_TILE = 'HINT_TILE' # an arbitrary but unique value ANIMATIONSPEED = 25 # integer from 1 to 100, higher is faster animation # Amount of space on the left & right side (XMARGIN) or above and below # (YMARGIN) the game board, in pixels. XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * SPACESIZE)) / 2) YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * SPACESIZE)) / 2) # R G B WHITE = (255, 255, 255) BLACK = ( 0, 0, 0) GREEN = ( 0, 155, 0) BRIGHTBLUE = ( 0, 50, 255) BROWN = (174, 94, 0) TEXTBGCOLOR1 = BRIGHTBLUE TEXTBGCOLOR2 = GREEN GRIDLINECOLOR = BLACK TEXTCOLOR = WHITE HINTCOLOR = BROWN def main(): global MAINCLOCK, DISPLAYSURF, FONT, BIGFONT, BGIMAGE pygame.init() MAINCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Flippy') FONT = pygame.font.Font('freesansbold.ttf', 16) BIGFONT = pygame.font.Font('freesansbold.ttf', 32) # Set up the background image. boardImage = pygame.image.load('flippyboard.png') # Use smoothscale() to stretch the board image to fit the entire board: boardImage = pygame.transform.smoothscale(boardImage, (BOARDWIDTH * SPACESIZE, BOARDHEIGHT * SPACESIZE)) boardImageRect = boardImage.get_rect() boardImageRect.topleft = (XMARGIN, YMARGIN) BGIMAGE = pygame.image.load('flippybackground.png') # Use smoothscale() to stretch the background image to fit the entire window: BGIMAGE = pygame.transform.smoothscale(BGIMAGE, (WINDOWWIDTH, WINDOWHEIGHT)) BGIMAGE.blit(boardImage, boardImageRect) # Run the main game. while True: if runGame() == False: break def runGame(): # Plays a single game of reversi each time this function is called. # Reset the board and game. mainBoard = getNewBoard() resetBoard(mainBoard) showHints = False turn = random.choice(['computer', 'player']) # Draw the starting board and ask the player what color they want. drawBoard(mainBoard) playerTile, computerTile = enterPlayerTile() # Make the Surface and Rect objects for the "New Game" and "Hints" buttons newGameSurf = FONT.render('New Game', True, TEXTCOLOR, TEXTBGCOLOR2) newGameRect = newGameSurf.get_rect() newGameRect.topright = (WINDOWWIDTH - 8, 10) hintsSurf = FONT.render('Hints', True, TEXTCOLOR, TEXTBGCOLOR2) hintsRect = hintsSurf.get_rect() hintsRect.topright = (WINDOWWIDTH - 8, 40) while True: # main game loop # Keep looping for player and computer's turns. if turn == 'player': # Player's turn: if getValidMoves(mainBoard, playerTile) == []: # If it's the player's turn but they # can't move, then end the game. break movexy = None while movexy == None: # Keep looping until the player clicks on a valid space. # Determine which board data structure to use for display. if showHints: boardToDraw = getBoardWithValidMoves(mainBoard, playerTile) else: boardToDraw = mainBoard checkForQuit() for event in pygame.event.get(): # event handling loop if event.type == MOUSEBUTTONUP: # Handle mouse click events mousex, mousey = event.pos if newGameRect.collidepoint( (mousex, mousey) ): # Start a new game return True elif hintsRect.collidepoint( (mousex, mousey) ): # Toggle hints mode showHints = not showHints # movexy is set to a two-item tuple XY coordinate, or None value movexy = getSpaceClicked(mousex, mousey) if movexy != None and not isValidMove(mainBoard, playerTile, movexy[0], movexy[1]): movexy = None # Draw the game board. drawBoard(boardToDraw) drawInfo(boardToDraw, playerTile, computerTile, turn) # Draw the "New Game" and "Hints" buttons. DISPLAYSURF.blit(newGameSurf, newGameRect) DISPLAYSURF.blit(hintsSurf, hintsRect) MAINCLOCK.tick(FPS) pygame.display.update() # Make the move and end the turn. makeMove(mainBoard, playerTile, movexy[0], movexy[1], True) if getValidMoves(mainBoard, computerTile) != []: # Only set for the computer's turn if it can make a move. turn = 'computer' else: # Computer's turn: if getValidMoves(mainBoard, computerTile) == []: # If it was set to be the computer's turn but # they can't move, then end the game. break # Draw the board. drawBoard(mainBoard) drawInfo(mainBoard, playerTile, computerTile, turn) # Draw the "New Game" and "Hints" buttons. DISPLAYSURF.blit(newGameSurf, newGameRect) DISPLAYSURF.blit(hintsSurf, hintsRect) # Make it look like the computer is thinking by pausing a bit. pauseUntil = time.time() + random.randint(5, 15) * 0.1 while time.time() < pauseUntil: pygame.display.update() # Make the move and end the turn. x, y = getComputerMove(mainBoard, computerTile) makeMove(mainBoard, computerTile, x, y, True) if getValidMoves(mainBoard, playerTile) != []: # Only set for the player's turn if they can make a move. turn = 'player' # Display the final score. drawBoard(mainBoard) scores = getScoreOfBoard(mainBoard) # Determine the text of the message to display. if scores[playerTile] > scores[computerTile]: text = 'You beat the computer by %s points! Congratulations!' % \ (scores[playerTile] - scores[computerTile]) elif scores[playerTile] < scores[computerTile]: text = 'You lost. The computer beat you by %s points.' % \ (scores[computerTile] - scores[playerTile]) else: text = 'The game was a tie!' textSurf = FONT.render(text, True, TEXTCOLOR, TEXTBGCOLOR1) textRect = textSurf.get_rect() textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) DISPLAYSURF.blit(textSurf, textRect) # Display the "Play again?" text with Yes and No buttons. text2Surf = BIGFONT.render('Play again?', True, TEXTCOLOR, TEXTBGCOLOR1) text2Rect = text2Surf.get_rect() text2Rect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 50) # Make "Yes" button. yesSurf = BIGFONT.render('Yes', True, TEXTCOLOR, TEXTBGCOLOR1) yesRect = yesSurf.get_rect() yesRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 90) # Make "No" button. noSurf = BIGFONT.render('No', True, TEXTCOLOR, TEXTBGCOLOR1) noRect = noSurf.get_rect() noRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 90) while True: # Process events until the user clicks on Yes or No. checkForQuit() for event in pygame.event.get(): # event handling loop if event.type == MOUSEBUTTONUP: mousex, mousey = event.pos if yesRect.collidepoint( (mousex, mousey) ): return True elif noRect.collidepoint( (mousex, mousey) ): return False DISPLAYSURF.blit(textSurf, textRect) DISPLAYSURF.blit(text2Surf, text2Rect) DISPLAYSURF.blit(yesSurf, yesRect) DISPLAYSURF.blit(noSurf, noRect) pygame.display.update() MAINCLOCK.tick(FPS) def translateBoardToPixelCoord(x, y): return XMARGIN + x * SPACESIZE + int(SPACESIZE / 2), YMARGIN + y * SPACESIZE + int(SPACESIZE / 2) def animateTileChange(tilesToFlip, tileColor, additionalTile): # Draw the additional tile that was just laid down. (Otherwise we'd # have to completely redraw the board & the board info.) if tileColor == WHITE_TILE: additionalTileColor = WHITE else: additionalTileColor = BLACK additionalTileX, additionalTileY = translateBoardToPixelCoord(additionalTile[0], additionalTile[1]) pygame.draw.circle(DISPLAYSURF, additionalTileColor, (additionalTileX, additionalTileY), int(SPACESIZE / 2) - 4) pygame.display.update() for rgbValues in range(0, 255, int(ANIMATIONSPEED * 2.55)): if rgbValues > 255: rgbValues = 255 elif rgbValues < 0: rgbValues = 0 if tileColor == WHITE_TILE: color = tuple([rgbValues] * 3) # rgbValues goes from 0 to 255 elif tileColor == BLACK_TILE: color = tuple([255 - rgbValues] * 3) # rgbValues goes from 255 to 0 for x, y in tilesToFlip: centerx, centery = translateBoardToPixelCoord(x, y) pygame.draw.circle(DISPLAYSURF, color, (centerx, centery), int(SPACESIZE / 2) - 4) pygame.display.update() MAINCLOCK.tick(FPS) checkForQuit() def drawBoard(board): # Draw background of board. DISPLAYSURF.blit(BGIMAGE, BGIMAGE.get_rect()) # Draw grid lines of the board. for x in range(BOARDWIDTH + 1): # Draw the horizontal lines. startx = (x * SPACESIZE) + XMARGIN starty = YMARGIN endx = (x * SPACESIZE) + XMARGIN endy = YMARGIN + (BOARDHEIGHT * SPACESIZE) pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy)) for y in range(BOARDHEIGHT + 1): # Draw the vertical lines. startx = XMARGIN starty = (y * SPACESIZE) + YMARGIN endx = XMARGIN + (BOARDWIDTH * SPACESIZE) endy = (y * SPACESIZE) + YMARGIN pygame.draw.line(DISPLAYSURF, GRIDLINECOLOR, (startx, starty), (endx, endy)) # Draw the black & white tiles or hint spots. for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): centerx, centery = translateBoardToPixelCoord(x, y) if board[x][y] == WHITE_TILE or board[x][y] == BLACK_TILE: if board[x][y] == WHITE_TILE: tileColor = WHITE else: tileColor = BLACK pygame.draw.circle(DISPLAYSURF, tileColor, (centerx, centery), int(SPACESIZE / 2) - 4) if board[x][y] == HINT_TILE: pygame.draw.rect(DISPLAYSURF, HINTCOLOR, (centerx - 4, centery - 4, 8, 8)) def getSpaceClicked(mousex, mousey): # Return a tuple of two integers of the board space coordinates where # the mouse was clicked. (Or returns None not in any space.) for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if mousex > x * SPACESIZE + XMARGIN and \ mousex < (x + 1) * SPACESIZE + XMARGIN and \ mousey > y * SPACESIZE + YMARGIN and \ mousey < (y + 1) * SPACESIZE + YMARGIN: return (x, y) return None def drawInfo(board, playerTile, computerTile, turn): # Draws scores and whose turn it is at the bottom of the screen. scores = getScoreOfBoard(board) scoreSurf = FONT.render("Player Score: %s Computer Score: %s %s's Turn" % (str(scores[playerTile]), str(scores[computerTile]), turn.title()), True, TEXTCOLOR) scoreRect = scoreSurf.get_rect() scoreRect.bottomleft = (10, WINDOWHEIGHT - 5) DISPLAYSURF.blit(scoreSurf, scoreRect) def resetBoard(board): # Blanks out the board it is passed, and sets up starting tiles. for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): board[x][y] = EMPTY_SPACE # Add starting pieces to the center board[3][3] = WHITE_TILE board[3][4] = BLACK_TILE board[4][3] = BLACK_TILE board[4][4] = WHITE_TILE def getNewBoard(): # Creates a brand new, empty board data structure. board = [] for i in range(BOARDWIDTH): board.append([EMPTY_SPACE] * BOARDHEIGHT) return board def isValidMove(board, tile, xstart, ystart): # Returns False if the player's move is invalid. If it is a valid # move, returns a list of spaces of the captured pieces. if board[xstart][ystart] != EMPTY_SPACE or not isOnBoard(xstart, ystart): return False board[xstart][ystart] = tile # temporarily set the tile on the board. if tile == WHITE_TILE: otherTile = BLACK_TILE else: otherTile = WHITE_TILE tilesToFlip = [] # check each of the eight directions: for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]: x, y = xstart, ystart x += xdirection y += ydirection if isOnBoard(x, y) and board[x][y] == otherTile: # The piece belongs to the other player next to our piece. x += xdirection y += ydirection if not isOnBoard(x, y): continue while board[x][y] == otherTile: x += xdirection y += ydirection if not isOnBoard(x, y): break # break out of while loop, continue in for loop if not isOnBoard(x, y): continue if board[x][y] == tile: # There are pieces to flip over. Go in the reverse # direction until we reach the original space, noting all # the tiles along the way. while True: x -= xdirection y -= ydirection if x == xstart and y == ystart: break tilesToFlip.append([x, y]) board[xstart][ystart] = EMPTY_SPACE # make space empty if len(tilesToFlip) == 0: # If no tiles flipped, this move is invalid return False return tilesToFlip def isOnBoard(x, y): # Returns True if the coordinates are located on the board. return x >= 0 and x < BOARDWIDTH and y >= 0 and y < BOARDHEIGHT def getBoardWithValidMoves(board, tile): # Returns a new board with hint markings. dupeBoard = copy.deepcopy(board) for x, y in getValidMoves(dupeBoard, tile): dupeBoard[x][y] = HINT_TILE return dupeBoard def getValidMoves(board, tile): # Returns a list of (x,y) tuples of all valid moves. validMoves = [] for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if isValidMove(board, tile, x, y) != False: validMoves.append((x, y)) return validMoves def getScoreOfBoard(board): # Determine the score by counting the tiles. xscore = 0 oscore = 0 for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if board[x][y] == WHITE_TILE: xscore += 1 if board[x][y] == BLACK_TILE: oscore += 1 return {WHITE_TILE:xscore, BLACK_TILE:oscore} def enterPlayerTile(): # Draws the text and handles the mouse click events for letting # the player choose which color they want to be. Returns # [WHITE_TILE, BLACK_TILE] if the player chooses to be White, # [BLACK_TILE, WHITE_TILE] if Black. # Create the text. textSurf = FONT.render('Do you want to be white or black?', True, TEXTCOLOR, TEXTBGCOLOR1) textRect = textSurf.get_rect() textRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2)) xSurf = BIGFONT.render('White', True, TEXTCOLOR, TEXTBGCOLOR1) xRect = xSurf.get_rect() xRect.center = (int(WINDOWWIDTH / 2) - 60, int(WINDOWHEIGHT / 2) + 40) oSurf = BIGFONT.render('Black', True, TEXTCOLOR, TEXTBGCOLOR1) oRect = oSurf.get_rect() oRect.center = (int(WINDOWWIDTH / 2) + 60, int(WINDOWHEIGHT / 2) + 40) while True: # Keep looping until the player has clicked on a color. checkForQuit() for event in pygame.event.get(): # event handling loop if event.type == MOUSEBUTTONUP: mousex, mousey = event.pos if xRect.collidepoint( (mousex, mousey) ): return [WHITE_TILE, BLACK_TILE] elif oRect.collidepoint( (mousex, mousey) ): return [BLACK_TILE, WHITE_TILE] # Draw the screen. DISPLAYSURF.blit(textSurf, textRect) DISPLAYSURF.blit(xSurf, xRect) DISPLAYSURF.blit(oSurf, oRect) pygame.display.update() MAINCLOCK.tick(FPS) def makeMove(board, tile, xstart, ystart, realMove=False): # Place the tile on the board at xstart, ystart, and flip tiles # Returns False if this is an invalid move, True if it is valid. tilesToFlip = isValidMove(board, tile, xstart, ystart) if tilesToFlip == False: return False board[xstart][ystart] = tile if realMove: animateTileChange(tilesToFlip, tile, (xstart, ystart)) for x, y in tilesToFlip: board[x][y] = tile return True def isOnCorner(x, y): # Returns True if the position is in one of the four corners. return (x == 0 and y == 0) or \ (x == BOARDWIDTH and y == 0) or \ (x == 0 and y == BOARDHEIGHT) or \ (x == BOARDWIDTH and y == BOARDHEIGHT) def getComputerMove(board, computerTile): # Given a board and the computer's tile, determine where to # move and return that move as a [x, y] list. possibleMoves = getValidMoves(board, computerTile) # randomize the order of the possible moves random.shuffle(possibleMoves) # always go for a corner if available. for x, y in possibleMoves: if isOnCorner(x, y): return [x, y] # Go through all possible moves and remember the best scoring move bestScore = -1 for x, y in possibleMoves: dupeBoard = copy.deepcopy(board) makeMove(dupeBoard, computerTile, x, y) score = getScoreOfBoard(dupeBoard)[computerTile] if score > bestScore: bestMove = [x, y] bestScore = score return bestMove def checkForQuit(): for event in pygame.event.get((QUIT, KEYUP)): # event handling loop if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): pygame.quit() sys.exit() if __name__ == '__main__': main()
بازی پخش جوهر از صفحه ای با خانه های رنگی تشکیل شده است. در هر نوبت، بازیکن رنگ جدیدی را برای رنگ آمیزی خانه سمت چپ بالا و هر خانه مجاور آن با همان رنگ انتخاب می کند. این بازی از الگوریتم انباشتن سیلابی (flood fill) استفاده می کند. اگر دوست دارید که بیشتر با این الگوریتم آشنا شوید به نشانی نگاهی بیندازید. هدف از این بازی این است که پیش از تمام شدن نوبت تان، همه صفحه را با یک رنگ پر کنید.این بازی هم چنین دارای یک صفحه تنظیمات است که در آن بازیکن می تواند اندازه صفحه و میزان دشواری بازی را تغییر دهد.
کد بازی را می توانید از نشانی دانلود کنید. فایل های تصویری بازی را می توانید از نشانی بارگیری کنید. کد کامل بازی در زیر آمده است.
# Ink Spill (a Flood It clone) # http://inventwithpython.com/pygame # By Al Sweigart al@inventwithpython.com # Released under a "Simplified BSD" license import random, sys, webbrowser, copy, pygame from pygame.locals import * # There are different box sizes, number of boxes, and # life depending on the "board size" setting selected. SMALLBOXSIZE = 60 # size is in pixels MEDIUMBOXSIZE = 20 LARGEBOXSIZE = 11 SMALLBOARDSIZE = 6 # size is in boxes MEDIUMBOARDSIZE = 17 LARGEBOARDSIZE = 30 SMALLMAXLIFE = 10 # number of turns MEDIUMMAXLIFE = 30 LARGEMAXLIFE = 64 FPS = 30 WINDOWWIDTH = 640 WINDOWHEIGHT = 480 boxSize = MEDIUMBOXSIZE PALETTEGAPSIZE = 10 PALETTESIZE = 45 EASY = 0 # arbitrary but unique value MEDIUM = 1 # arbitrary but unique value HARD = 2 # arbitrary but unique value difficulty = MEDIUM # game starts in "medium" mode maxLife = MEDIUMMAXLIFE boardWidth = MEDIUMBOARDSIZE boardHeight = MEDIUMBOARDSIZE # R G B WHITE = (255, 255, 255) DARKGRAY = ( 70, 70, 70) BLACK = ( 0, 0, 0) RED = (255, 0, 0) GREEN = ( 0, 255, 0) BLUE = ( 0, 0, 255) YELLOW = (255, 255, 0) ORANGE = (255, 128, 0) PURPLE = (255, 0, 255) # The first color in each scheme is the background color, the next six are the palette colors. COLORSCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE), ((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45), (241, 109, 149)), ((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0), (197, 97, 211)), ((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)), ((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7), (88, 155, 213)), ((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227), (212, 86, 185))) for i in range(len(COLORSCHEMES)): assert len(COLORSCHEMES[i]) == 7, 'Color scheme %s does not have exactly 7 colors.' % (i) bgColor = COLORSCHEMES[0][0] paletteColors = COLORSCHEMES[0][1:] def main(): global FPSCLOCK, DISPLAYSURF, LOGOIMAGE, SPOTIMAGE, SETTINGSIMAGE, SETTINGSBUTTONIMAGE, RESETBUTTONIMAGE pygame.init() FPSCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) # Load images LOGOIMAGE = pygame.image.load('inkspilllogo.png') SPOTIMAGE = pygame.image.load('inkspillspot.png') SETTINGSIMAGE = pygame.image.load('inkspillsettings.png') SETTINGSBUTTONIMAGE = pygame.image.load('inkspillsettingsbutton.png') RESETBUTTONIMAGE = pygame.image.load('inkspillresetbutton.png') pygame.display.set_caption('Ink Spill') mousex = 0 mousey = 0 mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife lastPaletteClicked = None while True: # main game loop paletteClicked = None resetGame = False # Draw the screen. DISPLAYSURF.fill(bgColor) drawLogoAndButtons() drawBoard(mainBoard) drawLifeMeter(life) drawPalettes() checkForQuit() for event in pygame.event.get(): # event handling loop if event.type == MOUSEBUTTONUP: mousex, mousey = event.pos if pygame.Rect(WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height(), SETTINGSBUTTONIMAGE.get_width(), SETTINGSBUTTONIMAGE.get_height()).collidepoint(mousex, mousey): resetGame = showSettingsScreen() # clicked on Settings button elif pygame.Rect(WINDOWWIDTH - RESETBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height(), RESETBUTTONIMAGE.get_width(), RESETBUTTONIMAGE.get_height()).collidepoint(mousex, mousey): resetGame = True # clicked on Reset button else: # check if a palette button was clicked paletteClicked = getColorOfPaletteAt(mousex, mousey) if paletteClicked != None and paletteClicked != lastPaletteClicked: # a palette button was clicked that is different from the # last palette button clicked (this check prevents the player # from accidentally clicking the same palette twice) lastPaletteClicked = paletteClicked floodAnimation(mainBoard, paletteClicked) life -= 1 resetGame = False if hasWon(mainBoard): for i in range(4): # flash border 4 times flashBorderAnimation(WHITE, mainBoard) resetGame = True pygame.time.wait(2000) # pause so the player can bask in victory elif life == 0: # life is zero, so player has lost drawLifeMeter(0) pygame.display.update() pygame.time.wait(400) for i in range(4): flashBorderAnimation(BLACK, mainBoard) resetGame = True pygame.time.wait(2000) # pause so the player can suffer in their defeat if resetGame: # start a new game mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife lastPaletteClicked = None pygame.display.update() FPSCLOCK.tick(FPS) def checkForQuit(): # Terminates the program if there are any QUIT or escape key events. for event in pygame.event.get(QUIT): # get all the QUIT events pygame.quit() # terminate if any QUIT events are present sys.exit() for event in pygame.event.get(KEYUP): # get all the KEYUP events if event.key == K_ESCAPE: pygame.quit() # terminate if the KEYUP event was for the Esc key sys.exit() pygame.event.post(event) # put the other KEYUP event objects back def hasWon(board): # if the entire board is the same color, player has won for x in range(boardWidth): for y in range(boardHeight): if board[x][y] != board[0][0]: return False # found a different color, player has not won return True def showSettingsScreen(): global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor # The pixel coordinates in this function were obtained by loading # the inkspillsettings.png image into a graphics editor and reading # the pixel coordinates from there. Handy trick. origDifficulty = difficulty origBoxSize = boxSize screenNeedsRedraw = True while True: if screenNeedsRedraw: DISPLAYSURF.fill(bgColor) DISPLAYSURF.blit(SETTINGSIMAGE, (0,0)) # place the ink spot marker next to the selected difficulty if difficulty == EASY: DISPLAYSURF.blit(SPOTIMAGE, (30, 4)) if difficulty == MEDIUM: DISPLAYSURF.blit(SPOTIMAGE, (8, 41)) if difficulty == HARD: DISPLAYSURF.blit(SPOTIMAGE, (30, 76)) # place the ink spot marker next to the selected size if boxSize == SMALLBOXSIZE: DISPLAYSURF.blit(SPOTIMAGE, (22, 150)) if boxSize == MEDIUMBOXSIZE: DISPLAYSURF.blit(SPOTIMAGE, (11, 185)) if boxSize == LARGEBOXSIZE: DISPLAYSURF.blit(SPOTIMAGE, (24, 220)) for i in range(len(COLORSCHEMES)): drawColorSchemeBoxes(500, i * 60 + 30, i) pygame.display.update() screenNeedsRedraw = False # by default, don't redraw the screen for event in pygame.event.get(): # event handling loop if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYUP: if event.key == K_ESCAPE: # Esc key on settings screen goes back to game return not (origDifficulty == difficulty and origBoxSize == boxSize) elif event.type == MOUSEBUTTONUP: screenNeedsRedraw = True # screen should be redrawn mousex, mousey = event.pos # syntactic sugar # check for clicks on the difficulty buttons if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey): difficulty = EASY elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey): difficulty = MEDIUM elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey): difficulty = HARD # check for clicks on the size buttons elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey): # small board size setting: boxSize = SMALLBOXSIZE boardWidth = SMALLBOARDSIZE boardHeight = SMALLBOARDSIZE maxLife = SMALLMAXLIFE elif pygame.Rect(52, 192, 106,32).collidepoint(mousex, mousey): # medium board size setting: boxSize = MEDIUMBOXSIZE boardWidth = MEDIUMBOARDSIZE boardHeight = MEDIUMBOARDSIZE maxLife = MEDIUMMAXLIFE elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey): # large board size setting: boxSize = LARGEBOXSIZE boardWidth = LARGEBOARDSIZE boardHeight = LARGEBOARDSIZE maxLife = LARGEMAXLIFE elif pygame.Rect(14, 299, 371, 97).collidepoint(mousex, mousey): # clicked on the "learn programming" ad webbrowser.open('http://inventwithpython.com') # opens a web browser elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey): # clicked on the "back to game" button return not (origDifficulty == difficulty and origBoxSize == boxSize) for i in range(len(COLORSCHEMES)): # clicked on a color scheme button if pygame.Rect(500, 30 + i * 60, MEDIUMBOXSIZE * 3, MEDIUMBOXSIZE * 2).collidepoint(mousex, mousey): bgColor = COLORSCHEMES[i][0] paletteColors = COLORSCHEMES[i][1:] def drawColorSchemeBoxes(x, y, schemeNum): # Draws the color scheme boxes that appear on the "Settings" screen. for boxy in range(2): for boxx in range(3): pygame.draw.rect(DISPLAYSURF, COLORSCHEMES[schemeNum][3 * boxy + boxx + 1], (x + MEDIUMBOXSIZE * boxx, y + MEDIUMBOXSIZE * boxy, MEDIUMBOXSIZE, MEDIUMBOXSIZE)) if paletteColors == COLORSCHEMES[schemeNum][1:]: # put the ink spot next to the selected color scheme DISPLAYSURF.blit(SPOTIMAGE, (x - 50, y)) def flashBorderAnimation(color, board, animationSpeed=30): origSurf = DISPLAYSURF.copy() flashSurf = pygame.Surface(DISPLAYSURF.get_size()) flashSurf = flashSurf.convert_alpha() for start, end, step in ((0, 256, 1), (255, 0, -1)): # the first iteration on the outer loop will set the inner loop # to have transparency go from 0 to 255, the second iteration will # have it go from 255 to 0. This is the "flash". for transparency in range(start, end, animationSpeed * step): DISPLAYSURF.blit(origSurf, (0, 0)) r, g, b = color flashSurf.fill((r, g, b, transparency)) DISPLAYSURF.blit(flashSurf, (0, 0)) drawBoard(board) # draw board ON TOP OF the transparency layer pygame.display.update() FPSCLOCK.tick(FPS) DISPLAYSURF.blit(origSurf, (0, 0)) # redraw the original surface def floodAnimation(board, paletteClicked, animationSpeed=25): origBoard = copy.deepcopy(board) floodFill(board, board[0][0], paletteClicked, 0, 0) for transparency in range(0, 255, animationSpeed): # The "new" board slowly become opaque over the original board. drawBoard(origBoard) drawBoard(board, transparency) pygame.display.update() FPSCLOCK.tick(FPS) def generateRandomBoard(width, height, difficulty=MEDIUM): # Creates a board data structure with random colors for each box. board = [] for x in range(width): column = [] for y in range(height): column.append(random.randint(0, len(paletteColors) - 1)) board.append(column) # Make board easier by setting some boxes to same color as a neighbor. # Determine how many boxes to change. if difficulty == EASY: if boxSize == SMALLBOXSIZE: boxesToChange = 100 else: boxesToChange = 1500 elif difficulty == MEDIUM: if boxSize == SMALLBOXSIZE: boxesToChange = 5 else: boxesToChange = 200 else: boxesToChange = 0 # Change neighbor's colors: for i in range(boxesToChange): # Randomly choose a box whose color to copy x = random.randint(1, width-2) y = random.randint(1, height-2) # Randomly choose neighbors to change. direction = random.randint(0, 3) if direction == 0: # change left and up neighbor board[x-1][y] = board[x][y] board[x][y-1] = board[x][y] elif direction == 1: # change right and down neighbor board[x+1][y] = board[x][y] board[x][y+1] = board[x][y] elif direction == 2: # change right and up neighbor board[x][y-1] = board[x][y] board[x+1][y] = board[x][y] else: # change left and down neighbor board[x][y+1] = board[x][y] board[x-1][y] = board[x][y] return board def drawLogoAndButtons(): # draw the Ink Spill logo and Settings and Reset buttons. DISPLAYSURF.blit(LOGOIMAGE, (WINDOWWIDTH - LOGOIMAGE.get_width(), 0)) DISPLAYSURF.blit(SETTINGSBUTTONIMAGE, (WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height())) DISPLAYSURF.blit(RESETBUTTONIMAGE, (WINDOWWIDTH - RESETBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height())) def drawBoard(board, transparency=255): # The colored squares are drawn to a temporary surface which is then # drawn to the DISPLAYSURF surface. This is done so we can draw the # squares with transparency on top of DISPLAYSURF as it currently is. tempSurf = pygame.Surface(DISPLAYSURF.get_size()) tempSurf = tempSurf.convert_alpha() tempSurf.fill((0, 0, 0, 0)) for x in range(boardWidth): for y in range(boardHeight): left, top = leftTopPixelCoordOfBox(x, y) r, g, b = paletteColors[board[x][y]] pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize)) left, top = leftTopPixelCoordOfBox(0, 0) pygame.draw.rect(tempSurf, BLACK, (left-1, top-1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1) DISPLAYSURF.blit(tempSurf, (0, 0)) def drawPalettes(): # Draws the six color palettes at the bottom of the screen. numColors = len(paletteColors) xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2) for i in range(numColors): left = xmargin + (i * PALETTESIZE) + (i * PALETTEGAPSIZE) top = WINDOWHEIGHT - PALETTESIZE - 10 pygame.draw.rect(DISPLAYSURF, paletteColors[i], (left, top, PALETTESIZE, PALETTESIZE)) pygame.draw.rect(DISPLAYSURF, bgColor, (left + 2, top + 2, PALETTESIZE - 4, PALETTESIZE - 4), 2) def drawLifeMeter(currentLife): lifeBoxSize = int((WINDOWHEIGHT - 40) / maxLife) # Draw background color of life meter. pygame.draw.rect(DISPLAYSURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize))) for i in range(maxLife): if currentLife >= (maxLife - i): # draw a solid red box pygame.draw.rect(DISPLAYSURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize)) pygame.draw.rect(DISPLAYSURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1) # draw white outline def getColorOfPaletteAt(x, y): # Returns the index of the color in paletteColors that the x and y parameters # are over. Returns None if x and y are not over any palette. numColors = len(paletteColors) xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2) top = WINDOWHEIGHT - PALETTESIZE - 10 for i in range(numColors): # Find out if the mouse click is inside any of the palettes. left = xmargin + (i * PALETTESIZE) + (i * PALETTEGAPSIZE) r = pygame.Rect(left, top, PALETTESIZE, PALETTESIZE) if r.collidepoint(x, y): return i return None # no palette exists at these x, y coordinates def floodFill(board, oldColor, newColor, x, y): # This is the flood fill algorithm. if oldColor == newColor or board[x][y] != oldColor: return board[x][y] = newColor # change the color of the current box # Make the recursive call for any neighboring boxes: if x > 0: floodFill(board, oldColor, newColor, x - 1, y) # on box to the left if x < boardWidth - 1: floodFill(board, oldColor, newColor, x + 1, y) # on box to the right if y > 0: floodFill(board, oldColor, newColor, x, y - 1) # on box to up if y < boardHeight - 1: floodFill(board, oldColor, newColor, x, y + 1) # on box to down def leftTopPixelCoordOfBox(boxx, boxy): # Returns the x and y of the left-topmost pixel of the xth & yth box. xmargin = int((WINDOWWIDTH - (boardWidth * boxSize)) / 2) ymargin = int((WINDOWHEIGHT - (boardHeight * boxSize)) / 2) return (boxx * boxSize + xmargin, boxy * boxSize + ymargin) if __name__ == '__main__': main()
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.