# Ink Spill (Flood It Clone)
# http://inventwithpython.com/blog
# By Al Sweigart al@inventwithpython.com
# Creative Commons US BY-NC-SA 3.0

# Ubuntu, Mint : sudo apt install python-pygame

import random
import time
import sys
import pygame
from pygame.locals import *
import webbrowser



SMALLBOXSIZE = 60
MEDIUMBOXSIZE = 20
LARGEBOXSIZE = 11

SMALLCOLSROWS = 6
MEDIUMCOLSROWS = 17
LARGECOLSROWS = 30

SMALLMAXLIFE = 10
MEDIUMMAXLIFE = 30
LARGEMAXLIFE = 64

FPS = 30
WINDOWWIDTH = 640
WINDOWHEIGHT = 480
BOXSIZE = 20
PALETTEGAPSIZE = 10
PALETTESIZE = 45
MAXLIFE = 30
COLS = 17
ROWS = 17
DIFFICULTY = 1


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 = (150, 200, 255)

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)))

def main():
    global MAINCLOCK, MAINSURF

    pygame.init()
    MAINCLOCK = pygame.time.Clock()
    MAINSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    loadImages()

    pygame.display.set_caption('Ink Spill')
    mousex = 0
    mousey = 0
    mainBoard = generateRandomBoard(COLS, ROWS, DIFFICULTY)
    life = MAXLIFE
    lastPaletteClicked = None

    # Main game loop:
    while True:
        paletteClicked = None
        resetGame = False

        # Draw the screen.
        MAINSURF.fill(BGCOLOR)
        drawLogo()
        drawBoard(mainBoard)
        drawLifeMeter(life)
        drawPalettes()

        # Handle any events.
        for event in pygame.event.get():
            if event.type == QUIT:
                terminate()

            if event.type == MOUSEMOTION:
                mousex, mousey = event.pos
            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()
                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
                else:
                    paletteClicked = getClickedPalette(mousex, mousey)
            if event.type == KEYUP:
                if event.key == K_ESCAPE:
                    terminate()

        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
                time.sleep(2)
            elif life == 0:
                drawLifeMeter(0)
                pygame.display.update()
                time.sleep(0.4)
                for i in range(4):
                    flashBorderAnimation(BLACK, mainBoard)
                resetGame = True
                time.sleep(2)

        if resetGame:
            mainBoard = generateRandomBoard(COLS, ROWS, DIFFICULTY)
            life = MAXLIFE
            lastPaletteClicked = None

        pygame.display.update()
        MAINCLOCK.tick(FPS)

def loadImages():
    global LOGOIMAGE, NOLOGO, SPOTIMAGE, SETTINGSIMAGE, SETTINGSBUTTONIMAGE, RESETBUTTONIMAGE

    NOLOGO = False
    try:
        LOGOIMAGE = pygame.image.load('inkspilllogo.png')
    except pygame.error:
        NOLOGO = True
    SPOTIMAGE = pygame.image.load('inkspillspot.png')
    SETTINGSIMAGE = pygame.image.load('inkspillsettings.png')
    SETTINGSBUTTONIMAGE = pygame.image.load('inkspillsettingsbutton.png')
    RESETBUTTONIMAGE = pygame.image.load('inkspillresetbutton.png')

def terminate():
    pygame.quit()
    sys.exit()

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 showSettingsScreen():
    global DIFFICULTY, BOXSIZE, COLS, ROWS, MAXLIFE, COLORS, BGCOLOR

    origDifficulty = DIFFICULTY
    origBoxSize = BOXSIZE

    while True:
        MAINSURF.fill(BGCOLOR)
        MAINSURF.blit(SETTINGSIMAGE, (0,0))

        if DIFFICULTY == 0:
            MAINSURF.blit(SPOTIMAGE, (30, 4))
        if DIFFICULTY == 1:
            MAINSURF.blit(SPOTIMAGE, (8, 41))
        if DIFFICULTY == 2:
            MAINSURF.blit(SPOTIMAGE, (30, 76))

        if BOXSIZE == SMALLBOXSIZE:
            MAINSURF.blit(SPOTIMAGE, (22, 150))
        if BOXSIZE == MEDIUMBOXSIZE:
            MAINSURF.blit(SPOTIMAGE, (11, 185))
        if BOXSIZE == LARGEBOXSIZE:
            MAINSURF.blit(SPOTIMAGE, (24, 220))

        for i in range(len(COLORSCHEMES)):
            drawColorSchemeBoxes(500, i * 60 + 30, i)

        pygame.display.update()

        for event in pygame.event.get():
            if event.type == QUIT:
                terminate()
            if event.type == KEYUP:
                if event.key == K_ESCAPE:
                    return not (origDifficulty == DIFFICULTY and origBoxSize == BOXSIZE)
            if event.type == MOUSEBUTTONUP:
                mousex, mousey = event.pos
                if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey):
                    DIFFICULTY = 0
                if pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey):
                    DIFFICULTY = 1
                if pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey):
                    DIFFICULTY = 2
                if pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey):
                    BOXSIZE = SMALLBOXSIZE
                    COLS = SMALLCOLSROWS
                    ROWS = SMALLCOLSROWS
                    MAXLIFE = SMALLMAXLIFE
                if pygame.Rect(52, 192, 106,32).collidepoint(mousex, mousey):
                    BOXSIZE = MEDIUMBOXSIZE
                    COLS = MEDIUMCOLSROWS
                    ROWS = MEDIUMCOLSROWS
                    MAXLIFE = MEDIUMMAXLIFE
                if pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey):
                    BOXSIZE = LARGEBOXSIZE
                    COLS = LARGECOLSROWS
                    ROWS = LARGECOLSROWS
                    MAXLIFE = LARGEMAXLIFE

                for i in range(len(COLORSCHEMES)):
                    if pygame.Rect(500, 30 + i * 60, MEDIUMBOXSIZE * 3, MEDIUMBOXSIZE * 2).collidepoint(mousex, mousey):
                        COLORS = COLORSCHEMES[i][1:]
                        BGCOLOR = COLORSCHEMES[i][0]

                if pygame.Rect(14, 299, 371, 97).collidepoint(mousex, mousey):
                    webbrowser.open('http://inventwithpython.com')
                if pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey):
                    return not (origDifficulty == DIFFICULTY and origBoxSize == BOXSIZE)




def drawColorSchemeBoxes(x, y, schemeNum):
    for boxy in range(2):
        for boxx in range(3):
            pygame.draw.rect(MAINSURF, COLORSCHEMES[schemeNum][3 * boxy + boxx + 1], (x + MEDIUMBOXSIZE * boxx, y + MEDIUMBOXSIZE * boxy, MEDIUMBOXSIZE, MEDIUMBOXSIZE))
            if COLORS == COLORSCHEMES[schemeNum][1:]:
                MAINSURF.blit(SPOTIMAGE, (x - 50, y))

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, difficulty=1):
    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.
    if difficulty == 0:
        difficulty = 1500
    elif difficulty == 1:
        difficulty = 200
    if difficulty == 0 and BOXSIZE == SMALLBOXSIZE:
        difficulty = 100
    elif difficulty == 1 and BOXSIZE == SMALLBOXSIZE:
        difficulty = 5
    elif BOXSIZE == LARGEBOXSIZE:
        difficulty *= 8
    else:
        difficulty = 0



    for i in range(difficulty):
        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 drawLogo():
    if not NOLOGO:
        MAINSURF.blit(LOGOIMAGE, (WINDOWWIDTH - LOGOIMAGE.get_width(), 0))
    MAINSURF.blit(SETTINGSBUTTONIMAGE, (WINDOWWIDTH - SETTINGSBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height()))
    MAINSURF.blit(RESETBUTTONIMAGE, (WINDOWWIDTH - RESETBUTTONIMAGE.get_width(), WINDOWHEIGHT - SETTINGSBUTTONIMAGE.get_height() - RESETBUTTONIMAGE.get_height()))

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))
    left, top = leftTopOfBox(0, 0)
    pygame.draw.rect(tempSurf, BLACK, (left-1, top-1, BOXSIZE * COLS + 1, BOXSIZE * ROWS + 1), 1)
    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()