Wednesday, August 22, 2012

python: yat (yet another tetris)


Who doesn't want to write its own version of one of the most popular games in the video game history, huh?

Here is YAT, my tetris version written  in python using the popular game developing module pygame.

Source code is available here.

Screenshots


About surfaces:

I think, besides the game logic, the most interesting thing here is the use of two different surfaces to show the blocks and the game information on the screen (including the next block).
This is managed in the main game file following. Basically I'm updating the game information surface with the "updateInfo" function and the blocks screen with the table class method "show". Both functions update the game surface "screen" with their own surfaces by themselves.

.tempfile
  1 #======================================================================#
  2 # This file is part of YAT (Yet Another Tetris).                       #
  3 #                                                                      #
  4 # YAT is free software: you can redistribute it and/or modify          #
  5 # it under the terms of the GNU General Public License as published by #
  6 # the Free Software Foundation, either version 3 of the License, or    #
  7 # (at your option) any later version.                                  #
  8 #                                                                      #
  9 # YAT is distributed in the hope that it will be useful,               #
 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of       #
 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
 12 # GNU General Public License for more details.                         #
 13 #                                                                      #
 14 # You should have received a copy of the GNU General Public License    #
 15 # along with YAT.     If not, see <http://www.gnu.org/licenses/>.      #
 16 #======================================================================#
 17 
 18 #==========================================================================#
 19 # Name       : yat.py (Yet Another Tetris)                                 #
 20 # Description: Main game file for the tetris like game "yat"               #
 21 # Author     : Adrian Antonana                                             #
 22 # Date       : 17.08.2012                                                  #
 23 # Copyright  : Adrian Antonana 2012                                        #
 24 #==========================================================================#
 25 import pygame as pg
 26 import table  as tb
 27 import blocks as bk
 28 import colors as col
 29 
 30 #==========================================================================#
 31 #                     Global Variables and Constants                       #
 32 #==========================================================================#
 33 GAME_SPEED     = 500
 34 SPEED_INC_TICK = 50
 35 LINES_INC_TICK = 10
 36 LEVEL          = 1
 37 REMOVED_LINES  = 0
 38 MAX_LEVEL      = 10
 39 FPS            = 100
 40 
 41 #==========================================================================#
 42 #                           Function Definitions                           #
 43 #==========================================================================#
 44 
 45 #-------------------- Checks when blocks have to move down ----------------#
 46 def delay(ticks):
 47   return (ticks % GAME_SPEED) >= GAME_SPEED-10
 48 
 49 #------------------ Increases the Game Level and Game Speed ---------------#
 50 def incSpeed(remlines):
 51   global GAME_SPEED
 52   global LEVEL
 53 
 54   if LEVEL < MAX_LEVEL:
 55     if remlines / (LEVEL*LINES_INC_TICK) == 1:
 56       LEVEL += 1
 57       GAME_SPEED -= SPEED_INC_TICK
 58       return True
 59   return False
 60 
 61 #---------------------- Updates the information surface -------------------#
 62 def updateInfo(nb):
 63   global LEVEL_NUM_TEXT
 64   global LINES_NUM_TEXT
 65   global infosurface
 66 
 67   LEVEL_NUM_TEXT = font.render(str(LEVEL),True,col.WHITE)
 68   LINES_NUM_TEXT = font.render(str(REMOVED_LINES),True,col.WHITE)
 69   infosurface.fill(col.GREY_DARK)
 70   infosurface.blit(LEVEL_TEXT,LEVEL_TEXT_OFFSET)
 71   infosurface.blit(LEVEL_NUM_TEXT,LEVEL_NUM_TEXT_OFFSET)
 72   infosurface.blit(LINES_TEXT,LINES_TEXT_OFFSET)
 73   infosurface.blit(LINES_NUM_TEXT,LINES_NUM_TEXT_OFFSET)
 74   nb.show(infosurface,NEXT_BLOCK_OFFSET,20,INF_BLOCK_SIZE)
 75 
 76 
 77 #==========================================================================#
 78 #              Initialize pygame (display,mixer and clock)                 #
 79 #==========================================================================#
 80 pg.init()
 81 pg.mixer.init()
 82 sndblockplaced = pg.mixer.Sound("sounds/block_placed.wav")
 83 sndblockrotate = pg.mixer.Sound("sounds/block_rotate.wav")
 84 sndremovelines = pg.mixer.Sound("sounds/remove_lines.wav")
 85 sndlevelup     = pg.mixer.Sound("sounds/level_up.wav")
 86 sndgameover    = pg.mixer.Sound("sounds/game_over.wav")
 87 clock          = pg.time.Clock()
 88 pg.display.set_caption("yat - yet another tetris")
 89 pg.key.set_repeat(10,50)
 90 
 91 #==========================================================================#
 92 #                         Information surface                              #
 93 #==========================================================================#
 94 INFO_SURFACE_HEIGHT   = 105
 95 FONT_SIZE             = 30
 96 FONT_SIZE_GAME_OVER   = 60
 97 UPPER_OFFSET          = 20
 98 LEFT_OFFSET           = 10
 99 INF_BLOCK_SIZE        = 20
100 font                  = pg.font.SysFont(pg.font.get_default_font(),FONT_SIZE)
101 font_game_over        = pg.font.SysFont(pg.font.get_default_font(),
                            FONT_SIZE_GAME_OVER)
102 LEVEL_TEXT            = font.render("Level : ",True,col.WHITE)
103 LINES_TEXT            = font.render("Lines : ",True,col.WHITE)
104 LEVEL_TEXT_OFFSET     = (LEFT_OFFSET,UPPER_OFFSET)
105 LEVEL_NUM_TEXT_OFFSET = (70+LEFT_OFFSET,UPPER_OFFSET)
106 LINES_TEXT_OFFSET     = (LEFT_OFFSET,INFO_SURFACE_HEIGHT-40)
107 LINES_NUM_TEXT_OFFSET = (70+LEFT_OFFSET,INFO_SURFACE_HEIGHT-40)
108 NEXT_BLOCK_OFFSET     = tb.BLOCK_SIZE*tb.WIDTH - INF_BLOCK_SIZE * 5
109 
110 #==========================================================================#
111 #                            Game over text                                #
112 #==========================================================================#
113 GAME_OVER_TEXT        = font_game_over.render("GAME OVER",True,col.WHITE)
114 GAME_OVER_TEXT_OFFSET = ((tb.BLOCK_SIZE*tb.WIDTH/2)-120,(tb.BLOCK_SIZE*tb.
                            HEIGHT/2)-50)
115 
116 #==========================================================================#
117 #                         Initialize surfaces                              #
118 #==========================================================================#
119 screen         = pg.display.set_mode((tb.BLOCK_SIZE*tb.WIDTH,tb.
                     BLOCK_SIZE*tb.HEIGHT+INFO_SURFACE_HEIGHT))
120 tablesurface   = screen.subsurface((0,INFO_SURFACE_HEIGHT,tb.BLOCK_SIZE*tb.
                     WIDTH,tb.BLOCK_SIZE*tb.HEIGHT))
121 infosurface    = screen.subsurface((0,0,tb.BLOCK_SIZE*tb.WIDTH,
                     INFO_SURFACE_HEIGHT))
122 
123 #==========================================================================#
124 #                        Block spawn position                              #
125 #==========================================================================#
126 BLOCK_SPAWN_POS = (0,(tb.WIDTH/2)-1)
127 
128 #==========================================================================#
129 #               Create the table and an initial block                      #
130 #==========================================================================#
131 t     = tb.table(tablesurface)
132 b     = bk.block(BLOCK_SPAWN_POS)
133 nextb = bk.block(BLOCK_SPAWN_POS)
134 
135 #==========================================================================#
136 #                 Draw initial information surface                         #
137 #==========================================================================#
138 updateInfo(nextb)
139 
140 #==========================================================================#
141 #                            Main loop                                     #
142 #==========================================================================#
143 running = True
144 
145 while running:
146   clock.tick_busy_loop(FPS)
147   t.adBlock(b.getPosList(),b.getType())
148   t.show()
149 
150   # check if the fall delay has been reached. If yes, move block down.
151   if delay(pg.time.get_ticks()):
152     if b.canMovDown(t.getHeight(),t.getOcupPosList(b.getPosList())):
153       t.adBlock(b.getPosList(),bk.E)
154       b.movDown()
155     else:
156       # if the block can't move down, spawn a new block
157       t.adBlock(b.getPosList(),b.getType())
158       sndblockplaced.play()
159       retval = t.delFullLines()
160       if retval != 0:
161         sndremovelines.play()
162         REMOVED_LINES += retval
163         if incSpeed(REMOVED_LINES):
164           sndlevelup.play()
165       b.__init__(BLOCK_SPAWN_POS,nextb.getType())
166       nextb = bk.block(BLOCK_SPAWN_POS)
167       updateInfo(nextb)
168 
169       # check if the game is over
170       if t.gameOver(b.getPosList()):
171         t.adBlock(b.getPosList(),b.getType())
172         t.show()
173         running = False
174 
175   # get one event from the queue and perform action
176   event = pg.event.poll()
177   if event.type == pg.KEYDOWN:
178     key = event.key
179     if key == pg.K_ESCAPE:
180       running = False
181     elif key == pg.K_DOWN:
182       if b.canMovDown(t.getHeight(),t.getOcupPosList(b.getPosList())):
183         t.adBlock(b.getPosList(),bk.E)
184         b.movDown()
185       else:
186         # if the block can't move down, spawn a new block
187         t.adBlock(b.getPosList(),b.getType())
188         sndblockplaced.play()
189         retval = t.delFullLines()
190         if retval != 0:
191           sndremovelines.play()
192           REMOVED_LINES += retval
193           if incSpeed(REMOVED_LINES):
194             sndlevelup.play()
195         b.__init__(BLOCK_SPAWN_POS,nextb.getType())
196         nextb = bk.block(BLOCK_SPAWN_POS)
197         updateInfo(nextb)
198 
199         # check if the game is over
200         if t.gameOver(b.getPosList()):
201           t.adBlock(b.getPosList(),b.getType())
202           t.show()
203           running = False
204 
205     # move/rotate block left/right
206     elif key == pg.K_LEFT:
207       if b.canMovLeft(t.getWidth(),t.getOcupPosList(b.getPosList())):
208         t.adBlock(b.getPosList(),bk.E)
209         b.movLeft()
210     elif key == pg.K_RIGHT:
211       if b.canMovRight(t.getWidth(),t.getOcupPosList(b.getPosList())):
212         t.adBlock(b.getPosList(),bk.E)
213         b.movRight()
214     elif key == pg.K_LCTRL:
215       t.adBlock(b.getPosList(),bk.E)
216       if b.rotLeft(t.getHeight(),t.getWidth(),t.getOcupPosList(b.getPosList()
                       )):
217         sndblockrotate.play()
218     elif key == pg.K_LALT:
219       t.adBlock(b.getPosList(),bk.E)
220       if b.rotRight(t.getHeight(),t.getWidth(),t.getOcupPosList(b.getPosList(
                        ))):
221         sndblockrotate.play()
222 
223 #==========================================================================#
224 #                           The game is over                               #
225 #==========================================================================#
226 tablesurface.fill(col.BLACK)
227 t.setSurfAlpha(60)
228 t.show()
229 tablesurface.blit(GAME_OVER_TEXT,GAME_OVER_TEXT_OFFSET)
230 pg.display.flip()
231 sndgameover.play()
232 
233 quit = False
234 while not quit:
235   event = pg.event.wait()
236   if  event.type == pg.QUIT:
237     quit = True
238   if event.type == pg.KEYDOWN:
239     if event.key == pg.K_ESCAPE:
240       quit = True

Wednesday, August 1, 2012

python: snake game

Here is my second try to write a simple game in python using pygame. I'm not posting here the whole source code since I don't want the post to be too long. The complete source code can be downloaded from this link.

First lets take a look at the game...

Snake Game Screen Cast:


 

Snake Class: 

Besides pygame, im using three self made classes on the game:
  • snake.py
  • food.py 
  • colors.py
The source code has plenty of comments which should make it quite easy to understand what is going on.
snake.py
  1 #==============================================================================================#
  2 # Name        : snake.py                                                                       #
  3 # Description : The snake class definition for the snake game.                                 #
  4 # Author      : Adrian Antonana                                                                #
  5 # Date        : 29.07.2012                                                                     #
  6 #==============================================================================================#
  7 
  8 # imports
  9 import pygame as pg
 10 from colors import *
 11 
 12 # motion direction constants
 13 UP    = 0
 14 DOWN  = 1
 15 LEFT  = 2
 16 RIGHT = 3
 17 
 18 # block sizes
 19 BLOCK_SIZE       = 30
 20 BLOCK_SIZE_INNER = 20
 21 
 22 # snake class definition
 23 class snake:
 24 
 25   # constructor
 26   def __init__(self,surface,headposx=10,headposy=10):
 27     self.surface = surface
 28     self.length  = 10
 29     self.poslist = [(headposx,y) for y in reversed(range(headposy-self.length+1,headposy+1))]
 30     self.motdir  = RIGHT
 31     self.crashed = False
 32 
 33     # for drawing the snake
 34     self.snakeblock = pg.Surface((BLOCK_SIZE,BLOCK_SIZE))
 35     self.snakeblock.set_alpha(255)
 36     self.snakeblock.fill(GREEN)
 37     self.snakeblockdark = pg.Surface((BLOCK_SIZE_INNER,BLOCK_SIZE_INNER))
 38     self.snakeblockdark.set_alpha(255)
 39     self.snakeblockdark.fill(GREEN_DARK)
 40 
 41     # for removing the snake
 42     self.backblock = pg.Surface((BLOCK_SIZE,BLOCK_SIZE))
 43     self.backblock.set_alpha(255)
 44     self.backblock.fill(BLACK)
 45 
 46   # get snake's head position
 47   def getHeadPos(self):
 48     return (self.poslist[0])
 49 
 50   # get the motion direction
 51   def getMotionDir(self):
 52     return self.motdir
 53 
 54   # get the snake positions list
 55   def getPosList(self):
 56     return self.poslist
 57 
 58   # set the motion direction
 59   def setMotionDir(self,motdir):
 60     self.motdir = motdir
 61 
 62   # increase the snake length by one
 63   def incLength(self):
 64     self.length += 1
 65 
 66   # move the snake updates the positions list and checks if the snake has crashed
 67   def move(self):
 68     motdir = self.getMotionDir()
 69     headpos = self.getHeadPos()
 70 
 71     # update positions
 72     if motdir == UP:
 73       poslist = [(headpos[0]-1,headpos[1])]
 74     elif motdir == DOWN:
 75       poslist = [(headpos[0]+1,headpos[1])]
 76     elif motdir == LEFT:
 77       poslist = [(headpos[0],headpos[1]-1)]
 78     elif motdir == RIGHT:
 79       poslist = [(headpos[0],headpos[1]+1)]
 80 
 81     poslist.extend(self.poslist[:-1])
 82     self.poslist = poslist
 83 
 84     # check if crashed
 85     if self.getHeadPos() in self.getPosList()[1:]:
 86       self.crashed = True
 87 
 88   # check if the snake has crashed
 89   def chrashed(self):
 90     return self.crashed
 91 
 92   # grow the snake. add a new position at the end
 93   def grow(self):
 94     lastpos = self.getPosList()[-1]
 95     self.length += 1
 96     self.poslist.append((lastpos[0]-1,lastpos[1]))
 97 
 98   # draw the snake
 99   def draw(self):
100     skb = self.snakeblock
101     skbd = self.snakeblockdark
102     sf = self.surface
103 
104     for blockpos in self.getPosList():
105       sf.blit(skb,(blockpos[1]*BLOCK_SIZE,blockpos[0]*BLOCK_SIZE))
106       sf.blit(skbd,(blockpos[1]*BLOCK_SIZE+5,blockpos[0]*BLOCK_SIZE+5))
107 
108   # delete the snake
109   def remove(self):
110     bkb = self.backblock
111     sf = self.surface
112 
113     # draw block for every snake position
114     for blockpos in self.getPosList():
115       sf.blit(bkb,(blockpos[1]*BLOCK_SIZE,blockpos[0]*BLOCK_SIZE))