# Ink Spill (Flood It Clone) # http://inventwithpython.com/blog # By Al Sweigart al@inventwithpython.com import random import time import sys import pygame from pygame.locals import * FPS = 30 WINDOWWIDTH = 640 WINDOWHEIGHT = 480 BOXSIZE = 20 PALETTEGAPSIZE = 10 PALETTESIZE = 45 MAXLIFE = 30 COLS = 17 ROWS = 17 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) COLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE) BGCOLOR = DARKGRAY def main(): global MAINCLOCK, MAINSURF pygame.init() MAINCLOCK = pygame.time.Clock() MAINSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Ink Spill') mousex = 0 mousey = 0 mainBoard = generateRandomBoard(COLS, ROWS, 100) life = MAXLIFE lastPaletteClicked = None # Main game loop: while True: paletteClicked = None # Draw the screen. MAINSURF.fill(BGCOLOR) drawBoard(mainBoard) drawLifeMeter(life) drawPalettes() # Handle any events. for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == MOUSEMOTION: mousex, mousey = event.pos if event.type == MOUSEBUTTONUP: mousex, mousey = event.pos paletteClicked = getClickedPalette(mousex, mousey) if paletteClicked != None and paletteClicked != lastPaletteClicked: lastPaletteClicked = paletteClicked floodAnimation(mainBoard, paletteClicked) life -= 1 resetGame = False if hasWon(mainBoard): for i in range(4): flashBorderAnimation(WHITE, mainBoard) resetGame = True elif life == 0: drawLifeMeter(0) pygame.display.update() time.sleep(0.4) for i in range(4): flashBorderAnimation(BLACK, mainBoard) resetGame = True if resetGame: time.sleep(2) mainBoard = generateRandomBoard(COLS, ROWS, 100) life = MAXLIFE lastPaletteClicked = None pygame.display.update() MAINCLOCK.tick(FPS) def hasWon(board): color = board[0][0] for x in range(COLS): for y in range(ROWS): if board[x][y] != color: return False return True def flashBorderAnimation(color, board, animationSpeed=30): origSurf = MAINSURF.copy() flashSurf = pygame.Surface(MAINSURF.get_size()) flashSurf = flashSurf.convert_alpha() for start, end, step in ((0, 256, 1), (255, 0, -1)): for transparency in range(start, end, animationSpeed * step): MAINSURF.blit(origSurf, (0, 0)) r, g, b = color flashSurf.fill((r, g, b, transparency)) MAINSURF.blit(flashSurf, (0, 0)) drawBoard(board) pygame.display.update() MAINCLOCK.tick(FPS) MAINSURF.blit(origSurf, (0, 0)) def getBoardCopy(board): dupe = [] for x in range(COLS): column = [] for y in range(ROWS): column.append(board[x][y]) dupe.append(column) return dupe def floodAnimation(board, paletteClicked, animationSpeed=25): origBoard = getBoardCopy(board) flood(board, board[0][0], paletteClicked, 0, 0) for transparency in range(0, 255, animationSpeed): drawBoard(origBoard) drawBoard(board, transparency) pygame.display.update() MAINCLOCK.tick(FPS) def generateRandomBoard(width, height, easy=0): board = [] for x in range(width): column = [] for y in range(height): column.append(random.randint(0, len(COLORS) - 1)) board.append(column) # Make the board easier by setting some boxes to be the same color as their neighbor. for i in range(easy): x = random.randint(1, width-2) y = random.randint(1, height-2) direction = random.randint(0, 3) if direction == 0: board[x-1][y] == board[x][y] board[x][y-1] == board[x][y] elif direction == 1: board[x+1][y] == board[x][y] board[x][y+1] == board[x][y] elif direction == 2: board[x][y-1] == board[x][y] board[x+1][y] == board[x][y] else: board[x][y+1] == board[x][y] board[x-1][y] == board[x][y] return board def drawBoard(board, transparency=255): tempSurf = pygame.Surface(MAINSURF.get_size()) tempSurf = tempSurf.convert_alpha() tempSurf.fill((0, 0, 0, 0)) for x in range(COLS): for y in range(ROWS): left, top = leftTopOfBox(x, y) r, g, b = COLORS[board[x][y]] pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, BOXSIZE, BOXSIZE)) MAINSURF.blit(tempSurf, (0, 0)) def drawPalettes(): numColors = len(COLORS) 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(MAINSURF, COLORS[i], (left, top, PALETTESIZE, PALETTESIZE)) pygame.draw.rect(MAINSURF, BGCOLOR, (left + 2, top + 2, PALETTESIZE - 4, PALETTESIZE - 4), 2) def drawLifeMeter(currentLife): lifeBoxSize = int((WINDOWHEIGHT - 40) / MAXLIFE) # Draw background of life box. pygame.draw.rect(MAINSURF, BGCOLOR, (20, 20, 20, 20 + (MAXLIFE * lifeBoxSize))) for i in range(MAXLIFE): if currentLife >= (MAXLIFE - i): pygame.draw.rect(MAINSURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize)) pygame.draw.rect(MAINSURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1) def getClickedPalette(x, y): numColors = len(COLORS) xmargin = int((WINDOWWIDTH - ((PALETTESIZE * numColors) + (PALETTEGAPSIZE * (numColors - 1)))) / 2) top = WINDOWHEIGHT - PALETTESIZE - 10 for i in range(numColors): # Determine if the xy coordinates of 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 def flood(board, oldColorNum, newColorNum, x, y): if oldColorNum == newColorNum or board[x][y] != oldColorNum: return board[x][y] = newColorNum # change the color of the current box # Make the recursive call for any neighboring boxes: if x > 0: flood(board, oldColorNum, newColorNum, x - 1, y) if x < COLS - 1: flood(board, oldColorNum, newColorNum, x + 1, y) if y > 0: flood(board, oldColorNum, newColorNum, x, y - 1) if y < ROWS - 1: flood(board, oldColorNum, newColorNum, x, y + 1) def leftTopOfBox(boxx, boxy): # Determine size of the margins for each side. xmargin = int((WINDOWWIDTH - (COLS * BOXSIZE)) / 2) ymargin = int((WINDOWHEIGHT - (ROWS * BOXSIZE)) / 2) return (boxx * BOXSIZE + xmargin, boxy * BOXSIZE + ymargin) if __name__ == '__main__': main()