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! (:
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
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.