Flood-it! Game in Python 2.7 with Pygame

it has been quite a long while since i last posted a game tutorial. well i did not in any way intended this blog to be one, but the only project i’m dealing with right now has to be confidential so there’s no way it’s going out here in my blog. i’m also currently taking a short online course in game theory under Stanford and that’s what gets me occupied most of the time so it’s not ideal to discuss something rather complex as an actual IT project. so here i am with another game tutorial.

a couple of weeks ago (or maybe a month ago), i’ve encountered the game Flood-it! while checking out some games in Google Plus. i was idle then. i could have actually checked out some other games with better graphics, complex goals and all that, but i was at the office and thought i should not try those games that would catch too much attention from people who would probably pass by my work station. so i just chose Flood-it! and well it was okay to pass one’s time. the goal was very simple yet challenging, and Google Plus added a way for you and your friends to compete against each other. that was added coolness for those who are really competitive when it comes to games. the goal was simple, one has to fill/flood the 14×14 grid with only one color in 25 moves by selecting one of the six colors on the palette. you can check out an online sample here.

since i was idle and looking for something fun to do, i decided i would create a version of the game in python. the graphics needed was not very sophisticated and the logic was simple so i thought it was the perfect game to code while waiting for better tasks. now i know that sounds a little nerdy but i already said before that i’m learning python and it would be good to code using it in simple yet not ridiculous applications.

finally, let’s get to coding. i did not have time to refactor the codes so i have everything in a single file but i will try to discuss everything in detail.

we of course start with the essential initialization as shown below.

import pygame
import pygame._view
from pygame import *
import random

pygame.init()

DEFAULT_SCREEN_SIZE = [608, 448]
DEFAULT_TILE_SIZE = [32, 32]
DEFAULT_STEP_SIZE = 32
screen = pygame.display.set_mode(DEFAULT_SCREEN_SIZE)
display.set_caption('Flood-it!')

like the other tutorials, we start by importing pygame, and the random class library. we initialize the game by calling pygame.init() then we set the default values for the screen size, tile size, and the step size to use when iterating across the grid. finally we set the display mode and the caption to use in the window.

we then declare all variables the global variables needed in the game.

#declares all global variables
watchlist = None       #list of coordinates of all "flooded" tiles
checkedlist = None     #list of coordinates of tiles already updated
tiles = None           #list of coordinates of all tiles in the game
is_done = False        #tells whether game is done
update = False         #tells whether display must update
for_restart = False    #tells whether player opted to restart
movecount = None       #counts moves taken by the player
btncol = None          #list of control buttons

next, we create the essential functions. following are a few short ones.

#returns a color RGB values given a number from 1-6
def _getcolor(colornum):
    if colornum == 1:
        return [255, 105, 180] #hot pink
    elif colornum == 2:
        return [138, 43, 226] #blue violet
    elif colornum == 3:
        return [255, 255, 0] #yellow
    elif colornum == 4:
        return [255, 69, 0] #orange red
    elif colornum == 5:
        return [110, 139, 61] #dark olive green
    elif colornum == 6:
        return [0, 191, 255] #deep skyblue

#tells whether the two colors are the same
def _issamecolor(color1, color2):
    if color1[0] == color2[0] and color1[1] == color2[1] and color1[2] == color2[2]:
        return True
    return False

#adds a tile's coordinate to list of flooded tiles
def _addtowatchlist(coord, color):
    global tiles
    global watchlist
    adjcolor = tiles[str(coord[0])+"-"+str(coord[1])]
    if _issamecolor(color, adjcolor):
        _fill([coord[0], coord[1]], color)

#updates the color of the flooded tiles to the newly selected one
def _colorwatchlist(color):
    global checkedlist
    global watchlist
    for i in range(len(watchlist)):
        screen.blit(color, watchlist[i])
        checkedlist.remove(watchlist[i])
    pygame.display.update()

a built-in function for determining intersection of two lists exists but i took the long way of telling whether the two colors are the same because the built-in function got an issue with RGB values equal to 0.

next we create the function that will update the look of the grid after a user selects a color. the function basically checks if a tile must be flooded or not, adds it to the watch list if it must, then proceed to check adjacent tiles.

def _fill(coord, color):
    global checkedlist
    global tiles
    global watchlist

    #stops updates of already checked coordinates
    if checkedlist.__contains__(coord):
        return
    else:
        checkedlist.append(coord)
        #adds coordinate to watchlist if not existing
        if not watchlist.__contains__(coord):
            watchlist.append(coord)

    tile = pygame.Surface(DEFAULT_TILE_SIZE)
    tile.fill(color)
    screen.blit(tile, coord)
    if coord[0] - DEFAULT_STEP_SIZE >= 0:
        X = coord[0] - DEFAULT_STEP_SIZE
        Y = coord[1]
        _addtowatchlist([X, Y], color)

    if coord[0] + DEFAULT_STEP_SIZE < DEFAULT_SCREEN_SIZE[1]:         X = coord[0] + DEFAULT_STEP_SIZE         Y = coord[1]         _addtowatchlist([X, Y], color)     if coord[1] - DEFAULT_STEP_SIZE >= 0:
        X = coord[0]
        Y = coord[1] - DEFAULT_STEP_SIZE
        _addtowatchlist([X, Y], color)

    if coord[1] + DEFAULT_STEP_SIZE < DEFAULT_SCREEN_SIZE[1]:
        X = coord[0]
        Y = coord[1] + DEFAULT_STEP_SIZE
        _addtowatchlist([X, Y], color)

we then create the function that will render the game’s controls which are the six color buttons. the function basically draws the buttons on the screen and stores their location for later reference.

def _render_controls():
    #button initialization
    global btncol
    btncol = [dict(), dict(), dict(), dict(), dict(), dict()]

    #button color initialization
    btncol[0]['color'] = _getcolor(1)
    btncol[1]['color'] = _getcolor(2)
    btncol[2]['color'] = _getcolor(3)
    btncol[3]['color'] = _getcolor(4)
    btncol[4]['color'] = _getcolor(5)
    btncol[5]['color'] = _getcolor(6)

    #button position initialization
    btncol[0]['position'] = [512, 21]
    btncol[1]['position'] = [512, 95]
    btncol[2]['position'] = [512, 169]
    btncol[3]['position'] = [512, 243]
    btncol[4]['position'] = [512, 317]
    btncol[5]['position'] = [512, 391]

    #button bounds initialization
    btncol[0]['bounds'] = pygame.Rect(512, 21, 32, 32)
    btncol[1]['bounds'] = pygame.Rect(512, 95, 32, 32)
    btncol[2]['bounds'] = pygame.Rect(512, 169, 32, 32)
    btncol[3]['bounds'] = pygame.Rect(512, 243, 32, 32)
    btncol[4]['bounds'] = pygame.Rect(512, 317, 32, 32)
    btncol[5]['bounds'] = pygame.Rect(512, 391, 32, 32)

    for i in range(len(btncol)):
        pygame.draw.circle(screen, btncol[i]['color'] \
            , [btncol[i]['position'][0]+16, btncol[i]['position'][1]+16] \
            , 16)

    pygame.display.update()

the following functions are not really necessary but i wanted to add a little something to the game, rage faces! yes i’m at times pretty shallow that i enjoy rage faces. because of that, i created a function that will display a random rage face if a user wins or loses.

def _gameover():
    shade = pygame.Surface(DEFAULT_SCREEN_SIZE)
    shade.fill([0, 0, 0])
    shade.set_alpha(200)
    screen.blit(shade, [0, 0])

    imggameover = pygame.image.load('gameover.png')
    tilenum = random.randint(1, 4)
    rectangle = None
    if tilenum == 1:
        rectangle = pygame.Rect(0, 0, 320, 240)
    elif tilenum == 2:
        rectangle = pygame.Rect(320, 0, 320, 240)
    elif tilenum == 3:
        rectangle = pygame.Rect(0, 240, 320, 240)
    elif tilenum == 4:
        rectangle = pygame.Rect(320, 240, 320, 240)

    screen.blit(imggameover, [64, 104], rectangle)

    pygame.display.update()

def _success():
    shade = pygame.Surface(DEFAULT_SCREEN_SIZE)
    shade.fill([0, 0, 0])
    shade.set_alpha(200)
    screen.blit(shade, [0, 0])

    imgwin = pygame.image.load('win.png')
    tilenum = random.randint(1, 4)
    rectangle = None
    if tilenum == 1:
        rectangle = pygame.Rect(0, 0, 320, 240)
    elif tilenum == 2:
        rectangle = pygame.Rect(320, 0, 320, 240)
    elif tilenum == 3:
        rectangle = pygame.Rect(0, 240, 320, 240)
    elif tilenum == 4:
        rectangle = pygame.Rect(320, 240, 320, 240)

    screen.blit(imgwin, [64, 104], rectangle)

    pygame.display.update()

lastly, for the set of functions, we have the _init function that is supposed to start the game. it starts with initializing global variables then it proceeds to fill the grid with randomly colored tiles before. it then assigns the starting tile, then renders the controls.

def _init():
    global checkedlist
    global watchlist
    global tiles
    global movecount
    global for_restart
    checkedlist  = list()
    watchlist = list()
    tiles = dict()
    movecount = 0
    for_restart = False

    screen.fill(0) #color screen black

    #fills the grid with randomly colored tiles
    for i in range(14):
        for j in range(14):
            #sets coordinates
            X = DEFAULT_STEP_SIZE*i
            Y = DEFAULT_STEP_SIZE*j

            #gets random tile
            colornum = random.randint(1, 6)
            tile = pygame.Surface(DEFAULT_TILE_SIZE)
            color = _getcolor(colornum)
            tile.fill(color)
            tiles[str(X)+"-"+str(Y)] = color
            screen.blit(tile, [X, Y])

    #adds starting tile to watch list
    _fill([0, 0], tiles['0-0'])

    #initializes first time coloring of watch list
    tile = pygame.Surface(DEFAULT_TILE_SIZE)
    tile.fill(tiles['0-0'])
    _colorwatchlist(tile)

    #renders the controls
    _render_controls()

finally, the _init function has to be called before the game begins. then program will enter the loop that contains the logic of the game as shown below.

_init()

while is_done == False:
    global is_done
    global update
    global movecount
    global for_restart
    global btncol

    #checks for changes in direction and validates it
    for e in event.get():
        color = None

        if e.type == MOUSEBUTTONDOWN:
            for i in range(len(btncol)):
                if btncol[i]['bounds'].collidepoint(e.pos):
                    color = _getcolor(i+1)
        elif e.type == QUIT:
            is_done = True
        elif e.type == KEYUP:
            update = True
            if e.key == K_ESCAPE:
                is_done = True
            elif e.key == K_r:
                _init()
            elif e.key == K_a:
                color = _getcolor(1)
            elif e.key == K_s:
                color = _getcolor(2)
            elif e.key == K_d:
                color = _getcolor(3)
            elif e.key == K_z:
                color = _getcolor(4)
            elif e.key == K_x:
                color = _getcolor(5)
            elif e.key == K_c:
                color = _getcolor(6)

        if color != None and movecount < 25:
            if not for_restart:
                movecount += 1
                tile = pygame.Surface(DEFAULT_TILE_SIZE)
                tile.fill(color)
                for i in range(len(watchlist)):
                    _fill(watchlist[i], color)
                _colorwatchlist(tile)
                display.set_caption('Flood-it! '+str(movecount)+'/25')

        if len(watchlist) == 196:
            if not for_restart:
                _success()
                for_restart = True
            display.set_caption('Flood-it! Congratulations. You won!')

        if movecount == 25 and len(watchlist) != 196:
            if not for_restart:
                _gameover()
                for_restart = True
            display.set_caption('Flood-it! GAME OVER!')

    if update == True:
        #TODO: call update function
        update = False

first, the program will go through the events that might have occurred. a valid event could be a mouse-click on a button or pressing of any of the dedicated keys to any color. the program will then attempt to update the grid then see if user has reached the move limit or the goal of flooding the whole grid was already achieved.

whew, that was quite long. i hope you will get to enjoy this game and add your own enhancements (better tiles perhaps). you can download the codes and the images here. again, make sure you have python 2.7 and the compatible pygame library. until next time! (:

2 thoughts on “Flood-it! Game in Python 2.7 with Pygame

  1. I love this tutorial and would like to learn python (pygame) programming. I have a silly question about python-pygame. Am I have to master python first before learning pygame or just go head with pygame with little knowledge of python?.

    FYI, I’m beginner in this area. Currently I’m still play around with python 2.7.2 in Mac OSX Lion and just installed Pygame in there.

    Need your advice.
    TQ

    1. honestly, i don’t think mastery of the python language is necessary before using pygame. i am of course assuming that you want to start with simple games. knowledge of the basics such as simple data structures, conditions, and loops can already get you far. not all enjoyable games are programmatically complex anyway, the idea matters more. as you go on learning things, you will eventually know the things you need to study. that’s the time when you would need to read python documentations and other materials. for now, you are good to go with basic python.

      thanks for commenting, by the way. it is much appreciated.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s