I would really appreciate a review of my first game. It is a Bejeweled-like game written in Python with Pygame:
import pygame, random, time, sys
from pygame.locals import *
pygame.init()
NUM_SHAPES = 7 #7
PUZZLE_COLUMNS = 6 #6
PUZZLE_ROWS = 12 #12
SHAPE_WIDTH = 50 #50
SHAPE_HEIGHT = 50 #50
FPS = 15
WINDOW_WIDTH = PUZZLE_COLUMNS * SHAPE_WIDTH
WINDOW_HEIGHT = PUZZLE_ROWS * SHAPE_HEIGHT + 75
BACKGROUND = pygame.image.load("images/bg.png")
CIRCLE = pygame.image.load("images/circle.png")
DIAMOND = pygame.image.load("images/diamond.png")
HEXAGON = pygame.image.load("images/hexagon.png")
SQUARE = pygame.image.load("images/square.png")
STAR = pygame.image.load("images/star.png")
STAR2 = pygame.image.load("images/star2.png")
TRIANGLE = pygame.image.load("images/triangle.png")
SHAPES_LIST = [CIRCLE, DIAMOND, HEXAGON, SQUARE, STAR, STAR2, TRIANGLE]
for x in xrange(len(SHAPES_LIST) - NUM_SHAPES):
del(SHAPES_LIST[0])
EXPLOSION_1 = pygame.image.load("images/explosion1.png")
EXPLOSION_2 = pygame.image.load("images/explosion2.png")
EXPLOSION_3 = pygame.image.load("images/explosion3.png")
EXPLOSION_4 = pygame.image.load("images/explosion4.png")
EXPLOSION_5 = pygame.image.load("images/explosion5.png")
EXPLOSION_6 = pygame.image.load("images/explosion6.png")
EXPLOSION_LIST = [EXPLOSION_1, EXPLOSION_2, EXPLOSION_3, EXPLOSION_4, EXPLOSION_5, EXPLOSION_6]
BLANK = pygame.image.load("images/blank.png")
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
FONT_SIZE = 36
TEXT_OFFSET = 5
MINIMUM_MATCH = 3
SINGLE_POINTS = .9
DOUBLE_POINTS = 3
TRIPLE_POINTS = 9
EXTRA_LENGTH_POINTS = .1
RANDOM_POINTS = .3
DELAY_PENALTY_SECONDS = 10
DELAY_PENALTY_POINTS = .5
FPS_CLOCK = pygame.time.Clock()
DISPLAY_SURFACE = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), DOUBLEBUF)
pygame.display.set_caption("Bilging Puzzle")
def main():
global score
global selector
bilgeBoard = generate_random_board()
selector = (0, 0)
score = 0.0
lastMoveTime = pygame.time.get_ticks()
blit_board(bilgeBoard)
draw_selector(selector)
remove_matches(bilgeBoard, selector)
blit_board(bilgeBoard)
blit_score(score)
blit_time(0)
draw_selector(selector)
while True:
for event in pygame.event.get():
if event.type == KEYUP:
if event.key == K_RIGHT and selector[0] < (PUZZLE_COLUMNS - 2):
selector = (selector[0] + 1, selector[1])
if event.key == K_LEFT and selector [0] > 0:
selector = (selector[0] - 1, selector[1])
if event.key == K_DOWN and selector[1] < (PUZZLE_ROWS - 1):
selector = (selector[0], selector[1] + 1)
if event.key == K_UP and selector[1] > 0:
selector = (selector[0], selector[1] - 1)
if event.key == K_SPACE:
lastMoveTime = pygame.time.get_ticks()
score -= 1
swap_pieces(selector, bilgeBoard)
remove_matches(bilgeBoard, selector)
if moveDelay / (DELAY_PENALTY_SECONDS * 1000) >= 1:
score -= DELAY_PENALTY_POINTS * (moveDelay / (DELAY_PENALTY_SECONDS * 1000))
if event.type == QUIT:
pygame.quit()
sys.exit()
moveDelay = pygame.time.get_ticks() - lastMoveTime
blit_board(bilgeBoard)
blit_score(score)
blit_time(moveDelay / 1000)
draw_selector(selector)
pygame.display.update()
FPS_CLOCK.tick(FPS)
def generate_random_board():
return[ [SHAPES_LIST[random.randrange(0, len(SHAPES_LIST))] for i in range(PUZZLE_COLUMNS)] for x in range(PUZZLE_ROWS) ]
#Time in seconds since last move
def blit_time(time):
font = pygame.font.Font(None, FONT_SIZE)
text = font.render("Move Timer: " + str(time / 60) + ":" + str(time % 60).zfill(2), True, BLACK)
textPosition = text.get_rect()
DISPLAY_SURFACE.blit(text, (TEXT_OFFSET, WINDOW_HEIGHT - (FONT_SIZE * 2)))
def blit_score(score):
font = pygame.font.Font(None, FONT_SIZE)
text = font.render("Score: " + str(score), True, BLACK)
textPosition = text.get_rect()
DISPLAY_SURFACE.blit(text, (TEXT_OFFSET, WINDOW_HEIGHT - FONT_SIZE))
def blit_board(board):
DISPLAY_SURFACE.blit(BACKGROUND, (0, 0))
rowNum = 0
for row in board:
columnNum = 0
for shape in row:
DISPLAY_SURFACE.blit(shape, (SHAPE_WIDTH * columnNum, SHAPE_HEIGHT * rowNum))
columnNum += 1
rowNum += 1
#Accepts a tuple indicating the position of the left shape in the selector relative to the board (as an array) (row, column)
def draw_selector(position):
topLeft = (position[0] * SHAPE_WIDTH, position[1] * SHAPE_HEIGHT)
topRight = (topLeft[0] + SHAPE_WIDTH * 2, topLeft[1])
bottomLeft = (topLeft[0], topLeft[1] + SHAPE_HEIGHT)
bottomRight = (topRight[0], topRight[1] + SHAPE_HEIGHT)
pygame.draw.lines(DISPLAY_SURFACE, WHITE, True, [topLeft, topRight, bottomRight, bottomLeft], 3)
#Accepts a tuple indicating the position of the selector
def swap_pieces(position, board):
x, y = position
board[y][x + 1], board[y][x] = board[y][x], board[y][x + 1]
def remove_matches(board, selector):
matches = find_matches(board)
while matches:
explosion_animation(board, matches)
score_matches(board, selector, matches)
clear_matches(board, matches)
refill_columns(board)
matches = find_matches(board)
selector = (0, 0) #So subsequent matches won't be counted as player matches
def score_matches(board, selector, matches):
global score
playerMatches = []
selector = (selector[1], selector[0])
for match in matches:
for position in match:
if (position == selector or position == (selector[0], selector[1] + 1)) and (not match in playerMatches):
playerMatches.append(match)
if len(playerMatches) == 1:
score += SINGLE_POINTS
elif len(playerMatches) == 2:
score += DOUBLE_POINTS
elif len(playerMatches) == 3:
score += TRIPLE_POINTS
for match in playerMatches:
score += (len(match) - MINIMUM_MATCH) * EXTRA_LENGTH_POINTS
for match in matches:
if not match in playerMatches:
score += RANDOM_POINTS
def find_matches(board):
clearList = []
#First scan the columns for matches
for column in xrange(PUZZLE_COLUMNS):
length = 1
for row in xrange(1, PUZZLE_ROWS):
if board[row][column] == board[row - 1][column]:
length += 1
if not board[row][column] == board[row - 1][column]:
if length >= MINIMUM_MATCH:
match = []
for clearRow in xrange(row - length, row):
match.append((clearRow, column))
clearList.append(match)
length = 1
if row == PUZZLE_ROWS - 1:
if length >= MINIMUM_MATCH:
match = []
for clearRow in xrange(row - (length - 1), row + 1):
match.append((clearRow, column))
clearList.append(match)
#Next scan the rows for matches
for row in xrange(PUZZLE_ROWS):
length = 1
for column in xrange(1, PUZZLE_COLUMNS):
if board[row][column] == board[row][column - 1]:
length += 1
if not board[row][column] == board[row][column - 1]:
if length >= MINIMUM_MATCH:
match = []
for clearColumn in xrange(column - length, column):
match.append((row, clearColumn))
clearList.append(match)
length = 1
if column == PUZZLE_COLUMNS - 1:
if length >= MINIMUM_MATCH:
match = []
for clearColumn in xrange(column - (length - 1), column + 1):
match.append((row, clearColumn))
clearList.append(match)
return clearList
#Accepts list of positions to clear
def clear_matches(board, matches):
for match in matches:
for position in match:
row, column = position
board[row][column] = BLANK
def refill_columns(board):
for column in xrange(PUZZLE_COLUMNS):
for row in xrange(PUZZLE_ROWS):
if board[row][column] == BLANK:
test = 0
length = 0
#Determine how long the clear is
while row + test < PUZZLE_ROWS and board[row + test][column] == BLANK:
length += 1
test += 1
for blankRow in xrange(row, PUZZLE_ROWS):
try:
board[blankRow][column] = board[blankRow + length][column]
except:
board[blankRow][column] = SHAPES_LIST[random.randrange(0, len(SHAPES_LIST))]
def explosion_animation(board, matches):
for frame in EXPLOSION_LIST:
for match in matches:
for position in match:
row, column = position
board[row][column] = frame
blit_board(board)
pygame.display.update()
FPS_CLOCK.tick(FPS)
if __name__ == '__main__':
main()
Here is a zip with the code and assets: http://dl.dropbox.com/u/1541103/game.zip