Hi, guys, good morning, good noon and good evening,
Through the study of Pygame in the first two chapters, we understand its basic use, and attach the link:
1. Python can't play games? Make a game in an hour!
2. Python can't play games? Do you know the objects in Pygame?
Now, let's really start writing a game.
The name of the game is: ink spill, Chinese Name: ink overflow. This is a very typical game in Python. Let's first look at what the game looks like and how to play it:
After watching it, the little friends should understand how to play the game. Now, let's consider how to make this game from the perspective of "designer".
Three aspects should be considered in game production:
- Game props (pictures, sound effects, etc.)
- Logic control (design of game state)
- UI design (game interface design)
Game props are mainly obtained in two ways: one is to load pictures, and the other is to draw directly with code. In this game, the following pictures are needed:
The rest are drawn directly with code.
Analyze the game interface, which is mainly composed of the following parts:
- Large grid in the center of the interface
- Life meter on the left of the interface
- Six different color palettes directly below the interface
- The "re game" button and the "Settings" button at the bottom right of the interface
Before writing a game, we consider a question: how to design the difficulty of the game?
The first thing we think of must be to control the number of small grids. The more the number, the more difficult it is.
In addition, as for the difficulty of the game, we can also write a boxtochange variable to randomly change the surrounding lattices of a small lattice into the same color, and the number of such lattices is described by boxtochange. In this way, the value of boxtochange will be set higher for "simple difficulty" (the more the same color), and the value of boxtochange will be set lower for "difficult difficulty" (make fewer of the same color) (or set it directly to 0).
First, let's take a look at what variables are needed to store what game states in this game (you can think about it yourself first):
# According to the setting interface, there are different overall grid sizes, number of small grids and life # Small grid size SMALL_BOX_SIZE = 60 # Size in pixels MEDIUM_BOX_SIZE = 20 LARGE_BOX_SIZE = 11 # The size of the entire grid SMALL_BOARD_SIZE = 6 # The size is in a small grid MEDIUM_BOARD_SIZE = 17 LARGE_BOARD_SIZE = 30 # Maximum number of operations (life) SMALL_MAX_LIFE = 10 MEDIUM_MAX_LIFE = 30 LARGE_MAX_LIFE = 64 FPS = 30 WINDOW_WIDTH = 640 WINDOW_HEIGHT = 480 boxSize = MEDIUM_BOX_SIZE PALETTE_GAP_SIZE = 10 # Palette interval size PALETTE_SIZE = 45 EASY = 0 # Difficulty: simple MEDIUM = 1 # Difficulty: medium HARD = 2 # Difficulty: difficulty difficulty = MEDIUM # The game starts in "medium" mode maxLife = MEDIUM_MAX_LIFE boardWidth = MEDIUM_BOARD_SIZE boardHeight = MEDIUM_BOARD_SIZE # 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, and the next six are palette colors. COLOR_SCHEMES = (((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))) # Treatment of color for i in range(len(COLOR_SCHEMES)): assert len(COLOR_SCHEMES[i]) == 7, 'Color scheme %s No 7 colors!.' % (i) # Background color, palette setting default color bgColor = COLOR_SCHEMES[0][0] paletteColors = COLOR_SCHEMES[0][1:]
You will see that there are so many variables just to record the game state. But don't worry, little friends. We can easily deduce the function of this variable according to the name of the variable. The subsequent game logic control is actually changing the value of these variables.
Next, let's consider writing the framework of the whole game:
def main(): # Variables other than functions are required global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE # initialization pygame.init() # Control frame rate FPS_CLOCK = pygame.time.Clock() # create a window DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) # Load picture LOGO_IMAGE = pygame.image.load('inkspilllogo.png') SPOT_IMAGE = pygame.image.load('inkspillspot.png') SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png') SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png') RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png') # Set window title pygame.display.set_caption('Ink overflow') # Generate interface mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife # Record the color of the palette at the bottom of the last click interface lastPaletteClicked = None while True: # Main game loop paletteClicked = None resetGame = False # Draw screen DISPLAY_SURF.fill(bgColor) # Load icons and related buttons drawLogoAndButtons() # Load the large grid in the center and randomly initialize the color of each small grid drawBoard(mainBoard) # Load the life meter on the left drawLifeMeter(life) # Loads the six color palettes at the bottom of the screen drawPalettes() # Determine whether the player wants to quit the game checkForQuit()
There are many functions in the code, such as
generateRandomBoard(),
We haven't implemented them yet. Let's implement them:
I generateRandomBoard() :
Generate the main game window - the whole large grid in the center:
def generateRandomBoard(width, height, difficulty=MEDIUM): # Create a data structure with random colors for each small grid in the whole large grid. board = [] for x in range(width): column = [] for y in range(height): column.append(random.randint(0, len(paletteColors) - 1)) board.append(column) # By setting some small grids to the same color as adjacent grids, it is easier to make the whole large grid with the same color. # Determine the number of cells to change. if difficulty == EASY: if boxSize == SMALL_BOX_SIZE: boxesToChange = 100 else: boxesToChange = 1500 elif difficulty == MEDIUM: if boxSize == SMALL_BOX_SIZE: boxesToChange = 5 else: boxesToChange = 200 else: boxesToChange = 0 # Color of hanging grid: for i in range(boxesToChange): # Randomly select the box whose color you want to copy x = random.randint(1, width - 2) y = random.randint(1, height - 2) # Randomly select the neighbor grid to change direction = random.randint(0, 3) if direction == 0: # Left, down board[x - 1][y] = board[x][y] board[x][y - 1] = board[x][y] elif direction == 1: # Right, up board[x + 1][y] = board[x][y] board[x][y + 1] = board[x][y] elif direction == 2: # Left, down board[x][y - 1] = board[x][y] board[x + 1][y] = board[x][y] else: # Left, up board[x][y + 1] = board[x][y] board[x - 1][y] = board[x][y] return board
II drawLogoAndButtons() :
Add some buttons and icons to the interface:
def drawLogoAndButtons(): # Draw the ink overflow logo, set and reset buttons. # The bait() function is used to load the image onto the Surface object of distribute_surf DISPLAY_SURF.blit(LOGO_IMAGE, (WINDOW_WIDTH - LOGO_IMAGE.get_width(), 0)) DISPLAY_SURF.blit(SETTINGS_BUTTON_IMAGE, (WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height())) DISPLAY_SURF.blit(RESET_BUTTON_IMAGE, (WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height()))
A large number of coordinate operations are used in the code to control the position of the picture. Please feel it carefully.
III drawBoard(mainBoard) :
Load the large grid in the center of the interface and randomly initialize the color of each small grid:
def drawBoard(board, transparency=255): # The transparency setting defaults to 255 # The colored squares will be drawn to the temporary surface and then to the DISPLAY_SURF surface. In this way, we can draw a transparent square at the top of DISPLAY_SURF. tempSurf = pygame.Surface(DISPLAY_SURF.get_size()) # Get window size tempSurf = tempSurf.convert_alpha() # Support transparency tempSurf.fill((0, 0, 0, 0)) # First, all are treated as black for x in range(boardWidth): for y in range(boardHeight): # Get the coordinates of the upper left corner of each small grid 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) # In order to prevent the color of the small grid on the edge from being the same as the background color of the window, a layer of thin black lines with a thickness of 1px is wrapped outside the whole large grid pygame.draw.rect(tempSurf, BLACK, (left - 1, top - 1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1) DISPLAY_SURF.blit(tempSurf, (0, 0))
In code, leftTopPixelCoordOfBox(x,y) is called.
def leftTopPixelCoordOfBox(boxx, boxy): # Returns the x and y of the top left pixel of a small grid. # Notice that the entire grid is in the middle of the window x_margin = int((WINDOW_WIDTH - (boardWidth * boxSize)) / 2) # x_margin is the distance between the leftmost of the whole large grid and the leftmost of the window y_margin = int((WINDOW_HEIGHT - (boardHeight * boxSize)) / 2) # y_margin is the distance between the top edge of the whole large grid and the top edge of the window return boxx * boxSize + x_margin, boxy * boxSize + y_margin
IV drawLifeMeter(life) :
Draw the life "count" on the left of the game interface and perform a simple life "logical calculation":
def drawLifeMeter(currentLife): # The 'life meter' is placed vertically, 20px away from the upper and lower edges lifeBoxSize = int((WINDOW_HEIGHT - 40) / maxLife) # Draw the background color of the "life meter". The upper left corner of the "life meter" is located at the coordinates (20,20), with a width of 20px and a height of 20 + (maxLife * lifeBoxSize) px pygame.draw.rect(DISPLAY_SURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize))) for i in range(maxLife): if currentLife >= (maxLife - i): # Draw a solid red box pygame.draw.rect(DISPLAY_SURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize)) # Add 1px white border pygame.draw.rect(DISPLAY_SURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1)
We have used a lot of coordinate operations in the positioning of graphics. Please feel it carefully.
V drawPalettes() :
Add six color palettes directly below the interface and carry out simple logic control:
def drawPalettes(): # Draw six color palettes at the bottom of the screen numColors = len(paletteColors) x_margin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2) for i in range(numColors): left = x_margin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE) top = WINDOW_HEIGHT - PALETTE_SIZE - 10 pygame.draw.rect(DISPLAY_SURF, paletteColors[i], (left, top, PALETTE_SIZE, PALETTE_SIZE)) # For beauty, 2px is added to the corresponding color area with 2px thickness outside the grid pygame.draw.rect(DISPLAY_SURF, bgColor, (left + 2, top + 2, PALETTE_SIZE - 4, PALETTE_SIZE - 4), 2)
Vi checkForQuit():
What happens when a player wants to exit the game:
def checkForQuit(): # If there are any exit events, terminate the program for event in pygame.event.get(QUIT): # Get all exit events pygame.quit() # Terminate if there are any exit events sys.exit() for event in pygame.event.get(KEYUP): # Get all KEYUP events if event.key == K_ESCAPE: pygame.quit() # If the KEYUP event is for the Esc key, it terminates sys.exit() pygame.event.post(event) # Replace the other KEYUP event objects
Next, let's consider the main logical control of the game.
Since we did not create a button, when the "mouse click" Event occurs, we try to obtain the location coordinates of its click (using the coordinates of the Event object) pos Attribute: mousex, mousey = event.pos), compare this coordinate with the position area of an icon on the interface (use pygame.rest.colleendpoint (mousex, mousey) function). If it is in this area, it means that the player wants to click the icon to realize the corresponding function (for example, clicking the "reset" icon with the mouse means that the player wants to restart the game), We can write code to respond. Based on this idea, we can complete the main() function:
def main(): global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE pygame.init() FPS_CLOCK = pygame.time.Clock() DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) # Load picture LOGO_IMAGE = pygame.image.load('inkspilllogo.png') SPOT_IMAGE = pygame.image.load('inkspillspot.png') SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png') SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png') RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png') # Set window title pygame.display.set_caption('Ink overflow') mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife lastPaletteClicked = None while True: # Main game loop paletteClicked = None resetGame = False # Draw screen DISPLAY_SURF.fill(bgColor) # Load icons and related buttons drawLogoAndButtons() # Load the large grid in the center and randomly initialize the color of each small grid drawBoard(mainBoard) # Load the life meter on the left drawLifeMeter(life) # Loads the six color palettes at the bottom of the screen drawPalettes() # Determine whether the player wants to quit the game checkForQuit() for event in pygame.event.get(): # Event handling loop if event.type == MOUSEBUTTONUP: # If the event is a mouse click mousex, mousey = event.pos # Get the coordinates of mouse click # If you click "SETTINGS" if pygame.Rect(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height(), SETTINGS_BUTTON_IMAGE.get_width(), SETTINGS_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey): # Display the setting interface resetGame = showSettingsScreen() # showSettingsScreen() to be implemented # If you click "RESET" elif pygame.Rect(WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height(), RESET_BUTTON_IMAGE.get_width(), RESET_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey): # Start the game again resetGame = True else: # Check whether the palette button is clicked. If so, return to the index of the color clicked in the six palettes paletteClicked = getColorOfPaletteAt(mousex, mousey) # getColorOfPaletteAt() to be implemented if paletteClicked is not None and paletteClicked != lastPaletteClicked: # The palette button you clicked is different from the one you clicked last time, preventing the mouse from accidentally clicking the same palette twice lastPaletteClicked = paletteClicked # Fill the large grid with color, and floodAnimation() is to be implemented floodAnimation(mainBoard, paletteClicked) # Click once to reduce "life" by 1 life -= 1 resetGame = False if hasWon(mainBoard): # If you win, hasWon() will be implemented for i in range(4): # Successful interface effect: flash the border 4 times flashBorderAnimation(WHITE, mainBoard) # flashBorderAnimation() to be implemented # After "flash", restart the game resetGame = True # Pause for 2s before starting the game pygame.time.wait(2000) elif life == 0: # Life is reduced to zero and the player fails drawLifeMeter(0) # Update interface pygame.display.update() # Wait for 0.4s pygame.time.wait(400) # Failed end effect: use black "flash" 4 times for i in range(4): flashBorderAnimation(BLACK, mainBoard). # flashBorderAnimation() to be implemented resetGame = True # Restart the game after a pause of 2s pygame.time.wait(2000) if resetGame: # Restart the game mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife lastPaletteClicked = None # Update interface pygame.display.update() # Control frame rate FPS_CLOCK.tick(FPS)
In the above main logic control codes, some functions are not implemented:
showSettingsScreen(): displays the "Settings" interface and provides corresponding logic control:
def showSettingsScreen(): # Get global variables global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor # The pixel coordinates in this function are obtained by loading the inkspearsettings.png image into the graphics editor and reading the pixel coordinates from it origDifficulty = difficulty origBoxSize = boxSize screenNeedsRedraw = True while True: if screenNeedsRedraw: DISPLAY_SURF.fill(bgColor) # Load settings picture DISPLAY_SURF.blit(SETTINGS_IMAGE, (0, 0)) # Place the ink picture marker to the left of the selected color if difficulty == EASY: DISPLAY_SURF.blit(SPOT_IMAGE, (30, 4)) if difficulty == MEDIUM: DISPLAY_SURF.blit(SPOT_IMAGE, (8, 41)) if difficulty == HARD: DISPLAY_SURF.blit(SPOT_IMAGE, (30, 76)) # Places an ink marker next to the selected size if boxSize == SMALL_BOX_SIZE: DISPLAY_SURF.blit(SPOT_IMAGE, (22, 150)) if boxSize == MEDIUM_BOX_SIZE: DISPLAY_SURF.blit(SPOT_IMAGE, (11, 185)) if boxSize == LARGE_BOX_SIZE: DISPLAY_SURF.blit(SPOT_IMAGE, (24, 220)) # The color selection box on the right of the loading setting interface for i in range(len(COLOR_SCHEMES)): drawColorSchemeBoxes(500, i * 60 + 30, i) # To be realized # Update interface pygame.display.update() screenNeedsRedraw = False # By default, the screen is not redrawn # Event handling loop for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYUP: if event.key == K_ESCAPE: # Set the Esc key on the screen to return to the game return not (origDifficulty == difficulty and origBoxSize == boxSize) elif event.type == MOUSEBUTTONUP: screenNeedsRedraw = True # The screen should be redrawn mousex, mousey = event.pos # Coordinates of mouse click # Check for a click on the difficulty button 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 whether there is a click on the size button elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey): # Small board size setting: boxSize = SMALL_BOX_SIZE boardWidth = SMALL_BOARD_SIZE boardHeight = SMALL_BOARD_SIZE maxLife = SMALL_MAX_LIFE elif pygame.Rect(52, 192, 106, 32).collidepoint(mousex, mousey): # Middle plate size setting: boxSize = MEDIUM_BOX_SIZE boardWidth = MEDIUM_BOARD_SIZE boardHeight = MEDIUM_BOARD_SIZE maxLife = MEDIUM_MAX_LIFE elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey): # Large plate size setting: boxSize = LARGE_BOX_SIZE boardWidth = LARGE_BOARD_SIZE boardHeight = LARGE_BOARD_SIZE maxLife = LARGE_MAX_LIFE elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey): # Click the "Back To Game" button return not (origDifficulty == difficulty and origBoxSize == boxSize) for i in range(len(COLOR_SCHEMES)): # Click the color scheme button if pygame.Rect(500, 30 + i * 60, MEDIUM_BOX_SIZE * 3, MEDIUM_BOX_SIZE * 2).collidepoint(mousex, mousey): bgColor = COLOR_SCHEMES[i][0] paletteColors = COLOR_SCHEMES[i][1:]
The above code is called. drawColorSchemeBoxes(): the implementation is as follows:
def drawColorSchemeBoxes(x, y, schemeNum): # Draws the color scheme box displayed on the settings screen for boxy in range(2): for boxx in range(3): pygame.draw.rect(DISPLAY_SURF, COLOR_SCHEMES[schemeNum][3 * boxy + boxx + 1], (x + MEDIUM_BOX_SIZE * boxx, y + MEDIUM_BOX_SIZE * boxy, MEDIUM_BOX_SIZE, MEDIUM_BOX_SIZE)) if paletteColors == COLOR_SCHEMES[schemeNum][1:]: # Place the ink picture next to the selected color scheme DISPLAY_SURF.blit(SPOT_IMAGE, (x - 50, y))
getColorOfPaletteAt(mousex, mousey): get the index of the clicked palette color:
def getColorOfPaletteAt(x, y): # Returns the index of the palette color covered by the x and y parameters numColors = len(paletteColors) xmargin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2) # The bottom of the palette is 10px from the bottom of the window top = WINDOW_HEIGHT - PALETTE_SIZE - 10 for i in range(numColors): # Coordinates of the upper left corner of the six palettes: (left,top) left = xmargin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE) r = pygame.Rect(left, top, PALETTE_SIZE, PALETTE_SIZE) # Find out if the mouse click area is in any palette if r.collidepoint(x, y): return i # If yes, returns the sequence number of the clicked palette # If x and y are not on any palette, return None return None
hasWon(): Judge whether the player wins or not, and realize the following:
def hasWon(board): # If the color of the whole chessboard is the same, it means that the player wins for x in range(boardWidth): for y in range(boardHeight): # As long as you find a color different from the color in the upper left corner, the player has not won yet if board[x][y] != board[0][0]: return False return True
Flood animation (mainBoard, paletteClicked): fill the mainBoard (large grid in the center) with the color corresponding to paletteClicked:
def floodAnimation(board, paletteClicked, animationSpeed=25): origBoard = copy.deepcopy(board) # Deep copy of the whole grid # Start the same color filling from the grid in the upper left corner, and the floodFill() will be implemented floodFill(board, board[0][0], paletteClicked, 0, 0) for transparency in range(0, 255, animationSpeed): # The "new" big grid slowly becomes opaque on the big grid drawBoard(origBoard) # Update transparency drawBoard(board, transparency) # Update interface pygame.display.update() # Control frame rate FPS_CLOCK.tick(FPS)
floodFill(): the filling algorithm is implemented as follows:
def floodFill(board, oldColor, newColor, x, y): # Flood filling algorithm if oldColor == newColor or board[x][y] != oldColor: # If the color in the upper left corner is not adjacent to the click color, return directly return board[x][y] = newColor # Change the color of the current grid # Recursively call any adjacent lattice: if x > 0: floodFill(board, oldColor, newColor, x - 1, y) if x < boardWidth - 1: floodFill(board, oldColor, newColor, x + 1, y) if y > 0: floodFill(board, oldColor, newColor, x, y - 1) if y < boardHeight - 1: floodFill(board, oldColor, newColor, x, y + 1)
Flashborder animation (color, mainboard): end screen of game victory or failure:
def flashBorderAnimation(color, board, animationSpeed=30): # After the "flash" effect ends, you need to return to the original interface (the game has just ended). Here, save the original interface first origSurf = DISPLAY_SURF.copy() flashSurf = pygame.Surface(DISPLAY_SURF.get_size()) flashSurf = flashSurf.convert_alpha() # Achieve "flash" effect for start, end, step in ((0, 256, 1), (255, 0, -1)): # The first iteration of the outer loop sets the transparency of the inner loop from 0 to 255, and the second iteration sets the transparency from 255 to 0. This is "flash". for transparency in range(start, end, animationSpeed * step): DISPLAY_SURF.blit(origSurf, (0, 0)) r, g, b = color flashSurf.fill((r, g, b, transparency)) DISPLAY_SURF.blit(flashSurf, (0, 0)) drawBoard(board) # Draw the board at the top of the transparent layer # Update interface pygame.display.update() # Control frame rate FPS_CLOCK.tick(FPS) # Draw the original interface (just after the game) DISPLAY_SURF.blit(origSurf, (0, 0))
...... I guess the little friends may have been confused. When playing games, they will consider a lot of things. We can really make our own games only after we have a good understanding of every detail.
Finally, attach the complete game code:
import random, sys, copy, pygame from pygame.locals import * # Import all Pygame constants # According to the setting interface, there are different large grid sizes, small grid numbers and service life # Small grid size SMALL_BOX_SIZE = 60 # Size in pixels MEDIUM_BOX_SIZE = 20 LARGE_BOX_SIZE = 11 # The size of the entire grid SMALL_BOARD_SIZE = 6 # The size is in a small grid MEDIUM_BOARD_SIZE = 17 LARGE_BOARD_SIZE = 30 # Maximum number of operations (life) SMALL_MAX_LIFE = 10 MEDIUM_MAX_LIFE = 30 LARGE_MAX_LIFE = 64 FPS = 30 WINDOW_WIDTH = 640 WINDOW_HEIGHT = 480 boxSize = MEDIUM_BOX_SIZE PALETTE_GAP_SIZE = 10 # Palette interval size PALETTE_SIZE = 45 EASY = 0 # Difficulty: simple MEDIUM = 1 # Difficulty: medium HARD = 2 # Difficulty: difficulty difficulty = MEDIUM # The game starts in "medium" mode maxLife = MEDIUM_MAX_LIFE boardWidth = MEDIUM_BOARD_SIZE boardHeight = MEDIUM_BOARD_SIZE # 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, and the next six are palette colors. COLOR_SCHEMES = (((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))) # Treatment of color for i in range(len(COLOR_SCHEMES)): assert len(COLOR_SCHEMES[i]) == 7, 'Color scheme %s No 7 colors!.' % (i) # Background color, palette setting default color bgColor = COLOR_SCHEMES[0][0] paletteColors = COLOR_SCHEMES[0][1:] def main(): global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE pygame.init() FPS_CLOCK = pygame.time.Clock() DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) # Load picture LOGO_IMAGE = pygame.image.load('inkspilllogo.png') SPOT_IMAGE = pygame.image.load('inkspillspot.png') SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png') SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png') RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png') # Set window title pygame.display.set_caption('Ink overflow') mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife lastPaletteClicked = None while True: # Main game loop paletteClicked = None resetGame = False # Draw screen DISPLAY_SURF.fill(bgColor) # Load icons and related buttons drawLogoAndButtons() # Load the large grid in the center and randomly initialize the color of each small grid drawBoard(mainBoard) # Load the life meter on the left drawLifeMeter(life) # Loads the six color palettes at the bottom of the screen drawPalettes() # Determine whether the player wants to quit the game checkForQuit() for event in pygame.event.get(): # Event handling loop if event.type == MOUSEBUTTONUP: # If the event is a mouse click mousex, mousey = event.pos # Get the coordinates of mouse click # If you click "SETTINGS" if pygame.Rect(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height(), SETTINGS_BUTTON_IMAGE.get_width(), SETTINGS_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey): # Display the setting interface resetGame = showSettingsScreen() # If you click "RESET" elif pygame.Rect(WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height(), RESET_BUTTON_IMAGE.get_width(), RESET_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey): # Start the game again resetGame = True else: # Check that the palette button is clicked paletteClicked = getColorOfPaletteAt(mousex, mousey) if paletteClicked is not None and paletteClicked != lastPaletteClicked: # The palette button you clicked is different from the one you clicked last time, preventing the player from accidentally clicking the same palette twice lastPaletteClicked = paletteClicked # fill color floodAnimation(mainBoard, paletteClicked) # Click once to reduce "life" by 1 life -= 1 resetGame = False if hasWon(mainBoard): # If you win for i in range(4): # Successful interface effect: flash the border 4 times flashBorderAnimation(WHITE, mainBoard) # After "flash", restart the game resetGame = True # Pause for 2s before starting the game pygame.time.wait(2000) elif life == 0: # Life is reduced to zero and the player fails drawLifeMeter(0) # Update interface pygame.display.update() # Wait for 0.4s pygame.time.wait(400) # Failed end effect: use black "flash" 4 times for i in range(4): flashBorderAnimation(BLACK, mainBoard) resetGame = True # Restart the game after a pause of 2s pygame.time.wait(2000) if resetGame: # Restart the game mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty) life = maxLife lastPaletteClicked = None # Update interface pygame.display.update() # Control frame rate FPS_CLOCK.tick(FPS) def checkForQuit(): # If there are any exit events, terminate the program for event in pygame.event.get(QUIT): # Get all exit events pygame.quit() # Terminate if there are any exit events sys.exit() for event in pygame.event.get(KEYUP): # Get all KEYUP events if event.key == K_ESCAPE: pygame.quit() # If the KEYUP event is for the Esc key, it terminates sys.exit() pygame.event.post(event) # Replace the other KEYUP event objects def hasWon(board): # If the color of the whole chessboard is the same, it means that the player wins for x in range(boardWidth): for y in range(boardHeight): # As long as you find a color different from the color in the upper left corner, the player has not won yet if board[x][y] != board[0][0]: return False return True def showSettingsScreen(): # Get global variables global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor # The pixel coordinates in this function are obtained by loading the inkspearsettings.png image into the graphics editor and reading the pixel coordinates from it origDifficulty = difficulty origBoxSize = boxSize screenNeedsRedraw = True while True: if screenNeedsRedraw: DISPLAY_SURF.fill(bgColor) # Load settings picture DISPLAY_SURF.blit(SETTINGS_IMAGE, (0, 0)) # Place the ink picture marker to the left of the selected color if difficulty == EASY: DISPLAY_SURF.blit(SPOT_IMAGE, (30, 4)) if difficulty == MEDIUM: DISPLAY_SURF.blit(SPOT_IMAGE, (8, 41)) if difficulty == HARD: DISPLAY_SURF.blit(SPOT_IMAGE, (30, 76)) # Places an ink marker next to the selected size if boxSize == SMALL_BOX_SIZE: DISPLAY_SURF.blit(SPOT_IMAGE, (22, 150)) if boxSize == MEDIUM_BOX_SIZE: DISPLAY_SURF.blit(SPOT_IMAGE, (11, 185)) if boxSize == LARGE_BOX_SIZE: DISPLAY_SURF.blit(SPOT_IMAGE, (24, 220)) # The color selection box on the right of the loading setting interface for i in range(len(COLOR_SCHEMES)): drawColorSchemeBoxes(500, i * 60 + 30, i) # Update interface pygame.display.update() screenNeedsRedraw = False # By default, the screen is not redrawn # Event handling loop for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() elif event.type == KEYUP: if event.key == K_ESCAPE: # Set the Esc key on the screen to return to the game return not (origDifficulty == difficulty and origBoxSize == boxSize) elif event.type == MOUSEBUTTONUP: screenNeedsRedraw = True # The screen should be redrawn mousex, mousey = event.pos # Coordinates of mouse click # Check for a click on the difficulty button 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 whether there is a click on the size button elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey): # Small board size setting: boxSize = SMALL_BOX_SIZE boardWidth = SMALL_BOARD_SIZE boardHeight = SMALL_BOARD_SIZE maxLife = SMALL_MAX_LIFE elif pygame.Rect(52, 192, 106, 32).collidepoint(mousex, mousey): # Middle plate size setting: boxSize = MEDIUM_BOX_SIZE boardWidth = MEDIUM_BOARD_SIZE boardHeight = MEDIUM_BOARD_SIZE maxLife = MEDIUM_MAX_LIFE elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey): # Large plate size setting: boxSize = LARGE_BOX_SIZE boardWidth = LARGE_BOARD_SIZE boardHeight = LARGE_BOARD_SIZE maxLife = LARGE_MAX_LIFE elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey): # Click the "Back To Game" button return not (origDifficulty == difficulty and origBoxSize == boxSize) for i in range(len(COLOR_SCHEMES)): # Click the color scheme button if pygame.Rect(500, 30 + i * 60, MEDIUM_BOX_SIZE * 3, MEDIUM_BOX_SIZE * 2).collidepoint(mousex, mousey): bgColor = COLOR_SCHEMES[i][0] paletteColors = COLOR_SCHEMES[i][1:] def drawColorSchemeBoxes(x, y, schemeNum): # Draws the color scheme box displayed on the settings screen for boxy in range(2): for boxx in range(3): pygame.draw.rect(DISPLAY_SURF, COLOR_SCHEMES[schemeNum][3 * boxy + boxx + 1], (x + MEDIUM_BOX_SIZE * boxx, y + MEDIUM_BOX_SIZE * boxy, MEDIUM_BOX_SIZE, MEDIUM_BOX_SIZE)) if paletteColors == COLOR_SCHEMES[schemeNum][1:]: # Place the ink picture next to the selected color scheme DISPLAY_SURF.blit(SPOT_IMAGE, (x - 50, y)) def flashBorderAnimation(color, board, animationSpeed=30): # After the "flash" effect ends, you need to return to the original interface (the game has just ended). Here, save the original interface first origSurf = DISPLAY_SURF.copy() flashSurf = pygame.Surface(DISPLAY_SURF.get_size()) flashSurf = flashSurf.convert_alpha() # Achieve "flash" effect for start, end, step in ((0, 256, 1), (255, 0, -1)): # The first iteration of the outer loop sets the transparency of the inner loop from 0 to 255, and the second iteration sets the transparency from 255 to 0. This is "flash". for transparency in range(start, end, animationSpeed * step): DISPLAY_SURF.blit(origSurf, (0, 0)) r, g, b = color flashSurf.fill((r, g, b, transparency)) DISPLAY_SURF.blit(flashSurf, (0, 0)) drawBoard(board) # Draw the board at the top of the transparent layer # Update interface pygame.display.update() # Control frame rate FPS_CLOCK.tick(FPS) # Draw the original interface (just after the game) DISPLAY_SURF.blit(origSurf, (0, 0)) def floodAnimation(board, paletteClicked, animationSpeed=25): origBoard = copy.deepcopy(board) # Deep copy of the whole grid # Fill in the same color from the grid in the upper left corner floodFill(board, board[0][0], paletteClicked, 0, 0) for transparency in range(0, 255, animationSpeed): # The "new" big grid slowly becomes opaque on the big grid drawBoard(origBoard) # Update transparency drawBoard(board, transparency) # Update interface pygame.display.update() # Control frame rate FPS_CLOCK.tick(FPS) def generateRandomBoard(width, height, difficulty=MEDIUM): # Create a data structure with random colors for each small grid in the whole large grid. board = [] for x in range(width): column = [] for y in range(height): column.append(random.randint(0, len(paletteColors) - 1)) board.append(column) # By setting some small grids to the same color as adjacent grids, it is easier to solve the whole large grid. # Determine the number of cells to change. if difficulty == EASY: if boxSize == SMALL_BOX_SIZE: boxesToChange = 100 else: boxesToChange = 1500 elif difficulty == MEDIUM: if boxSize == SMALL_BOX_SIZE: boxesToChange = 5 else: boxesToChange = 200 else: boxesToChange = 0 # Color of hanging grid: for i in range(boxesToChange): # Randomly select the box whose color you want to copy x = random.randint(1, width - 2) y = random.randint(1, height - 2) # Randomly select the neighbor grid to change direction = random.randint(0, 3) if direction == 0: # Left, down board[x - 1][y] = board[x][y] board[x][y - 1] = board[x][y] elif direction == 1: # Right, up board[x + 1][y] = board[x][y] board[x][y + 1] = board[x][y] elif direction == 2: # Left, down board[x][y - 1] = board[x][y] board[x + 1][y] = board[x][y] else: # Left, up board[x][y + 1] = board[x][y] board[x - 1][y] = board[x][y] return board def drawLogoAndButtons(): # Draw the ink overflow logo, set and reset buttons. DISPLAY_SURF.blit(LOGO_IMAGE, (WINDOW_WIDTH - LOGO_IMAGE.get_width(), 0)) DISPLAY_SURF.blit(SETTINGS_BUTTON_IMAGE, (WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height())) DISPLAY_SURF.blit(RESET_BUTTON_IMAGE, (WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(), WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height())) def drawBoard(board, transparency=255): # The transparency setting defaults to 255 # Colored squares are drawn to the temporary surface and then to DISPLAY_SURF surface. So we can display_ The top of surf draws a transparent square. tempSurf = pygame.Surface(DISPLAY_SURF.get_size()) # Get window size tempSurf = tempSurf.convert_alpha() # Support transparency tempSurf.fill((0, 0, 0, 0)) # First, all are treated as black 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) # In order to prevent the color of the small grid on the edge from being the same as the background color of the window, a layer of thin black lines with a thickness of 1px is wrapped outside the whole large grid pygame.draw.rect(tempSurf, BLACK, (left - 1, top - 1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1) DISPLAY_SURF.blit(tempSurf, (0, 0)) def drawPalettes(): # Draw six color palettes at the bottom of the screen numColors = len(paletteColors) x_margin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2) for i in range(numColors): left = x_margin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE) top = WINDOW_HEIGHT - PALETTE_SIZE - 10 pygame.draw.rect(DISPLAY_SURF, paletteColors[i], (left, top, PALETTE_SIZE, PALETTE_SIZE)) # For beauty, 2px is added to the corresponding color area with 2px thickness outside the grid pygame.draw.rect(DISPLAY_SURF, bgColor, (left + 2, top + 2, PALETTE_SIZE - 4, PALETTE_SIZE - 4), 2) def drawLifeMeter(currentLife): # The 'life meter' is placed vertically, 20px away from the upper and lower edges lifeBoxSize = int((WINDOW_HEIGHT - 40) / maxLife) # Draw the background color of the "life meter". The upper left corner of the "life meter" is located at the coordinates (20,20), with a width of 20px and a height of 20 + (maxLife * lifeBoxSize) px pygame.draw.rect(DISPLAY_SURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize))) for i in range(maxLife): if currentLife >= (maxLife - i): # Draw a solid red box pygame.draw.rect(DISPLAY_SURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize)) # Add 1px white border pygame.draw.rect(DISPLAY_SURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1) def getColorOfPaletteAt(x, y): # Returns the index of the palette color covered by the x and y parameters numColors = len(paletteColors) xmargin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2) # The bottom of the palette is 10px from the bottom of the window top = WINDOW_HEIGHT - PALETTE_SIZE - 10 for i in range(numColors): # Coordinates of the upper left corner of the six palettes: (left,top) left = xmargin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE) r = pygame.Rect(left, top, PALETTE_SIZE, PALETTE_SIZE) # Find out if the mouse click area is in any palette if r.collidepoint(x, y): return i # If yes, returns the sequence number of the clicked palette # If x and y are not on any palette, return None return None def floodFill(board, oldColor, newColor, x, y): # Flood filling algorithm if oldColor == newColor or board[x][y] != oldColor: # If the color in the upper left corner is not adjacent to the click color, return directly return board[x][y] = newColor # Change the color of the current grid # Recursively call any adjacent lattice: if x > 0: floodFill(board, oldColor, newColor, x - 1, y) if x < boardWidth - 1: floodFill(board, oldColor, newColor, x + 1, y) if y > 0: floodFill(board, oldColor, newColor, x, y - 1) if y < boardHeight - 1: floodFill(board, oldColor, newColor, x, y + 1) def leftTopPixelCoordOfBox(boxx, boxy): # Returns the x and y of the top left pixel of a small grid. # Notice that the entire grid is in the middle of the window x_margin = int((WINDOW_WIDTH - (boardWidth * boxSize)) / 2) # x_margin is the distance between the leftmost of the whole large grid and the leftmost of the window y_margin = int((WINDOW_HEIGHT - (boardHeight * boxSize)) / 2) # y_margin is the distance between the top edge of the whole large grid and the top edge of the window return boxx * boxSize + x_margin, boxy * boxSize + y_margin if __name__ == '__main__': main()
Like the little friends point a praise, encourage and support it ~