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.