Creating a rect for each tile in Pygame to make collisions - python

I am trying to create a rect for each tile in Pygame in order to make collisions- so enabling me to walk on blocks when I jump and not being able to run through a block. My map is a 2D list and I render it on the screen using for loops. I need help with collisions and creating a rect for each tile- would I need to create a rect for the 2D list, I'm not sure.
each_block and next_row are passed in the parameter and both have a value of 0,0 in the while loop e.g. so player1.level1(0,0)
def level1(self,each_block,next_row): #ROWS #MOVE DOWN TO THE NEXT ROW
self.game_level = [['0','0','0','0','0','0','0','0','0','0'], #LOOKING AT THIS 2D LIST, THE DIMENSIONS ARE 10X10
['0','0','0','0','0','0','0','0','0','0'],
['0','0','0','0','2','2','0','0','0','0'],
['0','0','0','0','0','0','0','0','0','0'],
['0','0','0','0','0','0','0','0','0','0'],
['2','2','2','2','2','2','2','2','2','2'],
['1','1','1','1','1','1','1','1','1','1'],
['1','1','1','1','1','1','1','1','1','1'],
['1','1','1','1','1','1','1','1','1','1'],
['1','1','1','1','1','1','1','1','1','1']]
next_row = 0
for row in self.game_level: #LOOPS THROUGH EACH ROW
each_block = 0
for each_tile in row: #LOOPS THROUGH EVERY STRING IN THE ROW
if each_tile == '1':
WINDOW.blit(self.dirt_image,(each_block * 70, next_row * 70))
if each_tile == '2':
WINDOW.blit(self.grass_image,(each_block * 70, next_row * 70))
each_block += 1
next_row += 1
self.area1 = pygame.Rect(x,y,scale_width,scale_height) #This is my player rect which is in the constructor.
I was thinking of doing something like this, but I don't know if it is correct or complete. I think this is only for the jumping?
pr = self.area1
for t in tiles:
# assume each tile has a rect associated
# check is the player is within the bounds of tile.
if pr.right > t.rect.left and pr.left < t.rect.right:
# check if the player is above the tile and below the tile is velocity is applied
if pr.bottom < t.rect.top and pr.bottom + player.velocity >= t.rect.top:
player.velocity = 0
pr.bottom = t.rect.top

One way of doing this, is to make a rectangle for each tile as the level1 like function iterates over all the tiles. You know the cell-position of the tile in the map, so multiplying this by the tile dimensions gives the rectangle parameters:
`tile_rect = pygame.Rect( map_x * TILE_SIZE, map_y * TILE_SIZE, TILE_SIZE, TILE_SIZE )`
The problem is that gives you a lot of rectangles. It's better to only make rectangles for the types that can collide. There's no point testing collisions with a tile that's part of the background art.
So define a set of "platform" tile types:
`PLATFORM_TYPES = '21'`
And then check the tile-type code is a member of of that before making the tile:
tile_rect_list = []
if ( each_tile in PLATFORM_TYPES ):
# valid platform-making tile, create a rect
tile_rect_list.append( pygame.Rect( map_x * TILE_SIZE, ... ) )
If the code has a rectangle for the player, a collision calculation is as simple as calling the rectangle collision function pygame.Rect.colliderect(). Which would look something like this:
tile_rect = pygame.Rect( ... )
player_rect = pygame.Rect( player_x, player_y, player_width, player_height )
collides = player_rect.collide_rect( tile_rect )
Or if you have a list of tiles:
for tile_rect in tile_rect_list:
if ( player_rect.collide_rect( tile_rect ) ):
# Player has collided, do something about it here
PyGame Rectangles are a really useful module. You don't need to write code to check all the coordinates and lengths yourself.
One further optimisation is to create rectangles that span the rows of same-type tiles. So a block of 2-typed tiles 22222 creates a single rectangle that's 5 * tile_width long.
One way of doing this, is that when iterating through the rows of tiles, remember the previous tile-type. Then only output a rectangle when the tile-type changes to a new type.
import pygame
# Window size
WINDOW_WIDTH = 320
WINDOW_HEIGHT = 320
BLACK = ( 0, 0, 0)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
class Level():
TILE_SIZE = 32 # size of tiles in pixels
PLATFORM_TILES = '12' # tile types the player can walk on
def __init__( self ):
self.game_level = ['0000000000', #DIMENSIONS 10X10
'1000000001',
'0000220000',
'0000000011',
'0000000000',
'2022220220',
'1111111111',
'1111111111',
'1111111111',
'1111111110']
self.tile_rects = [] # holds all the tile rectangles
# Create a 2D list of tile-rects to match the map.
# Rects are horizontal blocks of same-tiles
for row_y, row in enumerate( self.game_level ):
prev_tile_type = '*' # no match
platform_start = -1 # start column of platform
rect_y = row_y * Level.TILE_SIZE
last_column = len( row ) -1
for col_x, tile_type in enumerate( row ):
# Match same tiles to make a longer rect
if ( platform_start != -1 and ( prev_tile_type != tile_type or col_x == last_column ) ): # tile changed
# The tile-type has changed, or it's the row-end, make the rectangle
# Handle the special case of the last cell in the row
if ( col_x == last_column and prev_tile_type == tile_type ):
col_x += 1
width = ( col_x - platform_start ) * Level.TILE_SIZE
rect_x = platform_start * Level.TILE_SIZE
if ( prev_tile_type in Level.PLATFORM_TILES ):
self.tile_rects.append( pygame.Rect( rect_x, rect_y, width, Level.TILE_SIZE ) )
prev_tile_type = '*'
platform_start = -1
elif ( tile_type in Level.PLATFORM_TILES ):
# If it's the last block in the row, make a rectangle, as there's no "next" block
if ( col_x == len( row ) - 1 ):
rect_x = col_x * Level.TILE_SIZE
self.tile_rects.append( pygame.Rect( rect_x, rect_y, Level.TILE_SIZE, Level.TILE_SIZE ) )
elif ( prev_tile_type != tile_type ):
# start a new row-block for the found tile-type
platform_start = col_x
prev_tile_type = tile_type
def getPlatforms( self ):
""" Get the list of platform-collision rects """
return self.tile_rects
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE )
pygame.display.set_caption("Tile Rects")
pygame.mixer.init()
level = Level()
# Main loop
clock = pygame.time.Clock()
running = True
while running:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
window.fill( BLACK )
for platform_rect in level.getPlatforms():
pygame.draw.rect( window, GREEN, platform_rect, 5 )
#print( "RECT: %d,%d w=%d h=%d" % ( platform_rect.x, platform_rect.y, platform_rect.width, platform_rect.height ) )
# Draw a grid for debugging sizes
for y in range( 10 ):
for x in range( 10 ):
pygame.draw.rect( window, RED, [ x*Level.TILE_SIZE, y*Level.TILE_SIZE, Level.TILE_SIZE, Level.TILE_SIZE ], 1 )
pygame.display.flip()
# Clamp FPS
clock.tick(3) # slow update
pygame.quit()

Related

PacMan Ghost Movement

So I've been trying to recreate the PacMan Game, I have been stuck on how to approach the idea of the Ghosts moving around the maze, I've heard of A* and Dijkstra's Algorithms, but is there a simpler way to implement ghosts moving around the maze? Aside from figuring out the different modes they can go into, Frightened, Chase, and Scatter, I just want to be able to understand whats the best way to get them to move randomly in the maze with the wall detection function in place.
import pygame
import time
#import random
import pickle
import math
pygame.init()
pygame.mixer.init()
pygame.display.set_caption("Pac-Man")
# Sets the size of the screen via (WIDTH, HEIGHT)
SCREEN_WIDTH = 478
SCREEN_HEIGHT = 608
# Speed of Characters
SPEED = 1
# Frames per second, how fast the game runs
FPS = 50
# Colors (RED,GREEN,BLUE)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
YELLOW = (255, 255, 0)
BLUE = (0, 0, 255)
# Sets the WIDTH and HEIGHT of the window
WINDOW = (SCREEN_WIDTH, SCREEN_HEIGHT)
# Displays the screen
SCREEN = pygame.display.set_mode(WINDOW)
CLOCK = pygame.time.Clock()
PacManStartSurface = pygame.transform.scale(pygame.image.load
("PacManStart.png"), (23, 23))
PacManStartSurface.convert()
PacManStartRect = PacManStartSurface.get_rect(topleft =
(((SCREEN_WIDTH - 25) // 2),
(SCREEN_HEIGHT + 144) // 2))
PacManSurface = pygame.transform.scale(pygame.image.load
("PacManRight.png"), (23, 23))
PacManSurface.convert()
PacManRect = PacManStartSurface.get_rect(topleft =
(((SCREEN_WIDTH - 125) // 2),
(SCREEN_HEIGHT + 144) // 2))
CurrentSurface = PacManStartSurface
CurrentRect = PacManStartRect
BackgroundSurface = pygame.image.load("Background.png").convert()
PinkGhostSurface = pygame.transform.scale(pygame.image.load("PinkGhost.png")
.convert(), (23, 23))
PinkGhostRect = PinkGhostSurface.get_rect()
YellowGhostSurface = pygame.transform.scale(pygame.image.load
("YellowGhost.png")
.convert(), (23, 23))
YellowGhostRect = YellowGhostSurface.get_rect()
RedGhostSurface = pygame.transform.scale(pygame.image.load("RedGhost.png")
.convert(), (23, 23))
RedGhostRect = RedGhostSurface.get_rect()
BlueGhostSurface = pygame.transform.scale(pygame.image.load("BlueGhost.png")
.convert(), (23, 23))
BlueGhostRect = BlueGhostSurface.get_rect()
pygame.mixer.music.load('power_pellet.wav')
Font = pygame.font.Font("emulogic.ttf", 15)
class PacMan():
def __init__(self):
self.LIVES = 3
class Maze():
def __init__(self):
self.DOTS = []
self.WALLS = []
self.ENERGIZER = []
self.GHOSTS = []
self.DECISION_NODES = []
self.BLOCK_WIDTH = 25
self.BLOCK_HEIGHT = 25
self.MAZE_OFFSET_X = 0
self.MAZE_OFFSET_Y = 50
self.MARGIN = 3
# 0 - Dots
# 1 - Walls
# 2 - Energizers
# 3 - Empty Spaces
# 4 - Ghosts
# 5 - Decision Nodes & will be added for intersections in maze
self.MATRIX = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], \
[1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], \
[1,2,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,2,1], \
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], \
[1,0,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,0,1], \
[1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1], \
[1,1,1,1,0,1,1,1,3,1,3,1,1,1,0,1,1,1,1], \
[3,3,3,1,0,1,3,3,5,4,5,3,3,1,0,1,3,3,3], \
[1,1,1,1,0,1,3,1,1,1,1,1,3,1,0,1,1,1,1], \
[0,0,0,0,0,3,5,1,4,4,4,1,5,3,0,0,0,0,0], \
[1,1,1,1,0,1,3,1,1,1,1,1,3,1,0,1,1,1,1], \
[3,3,3,1,0,1,5,3,3,3,3,3,5,1,0,1,3,3,3], \
[1,1,1,1,0,1,3,1,1,1,1,1,3,1,0,1,1,1,1], \
[1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], \
[1,2,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,2,1], \
[1,0,0,1,0,0,0,0,0,3,0,0,0,0,0,1,0,0,1], \
[1,1,0,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1], \
[1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1], \
[1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1], \
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], \
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
# BackgroundImage(X, Y, WIDTH, HEIGHT)
self.MAZE_X = self.BLOCK_WIDTH * (len(self.MATRIX[0])
+ self.MAZE_OFFSET_X)
self.MAZE_Y = self.BLOCK_HEIGHT * (len(self.MATRIX)
+ self.MAZE_OFFSET_Y)
self.MAZE_WIDTH = self.BLOCK_WIDTH * len(self.MATRIX[0])
self.MAZE_HEIGHT = self.BLOCK_HEIGHT * len(self.MATRIX)
def DrawMaze(self, MazeSurface):
for ROW in range(len(self.MATRIX)):
for COLUMN in range(len(self.MATRIX[0])):
# Only saves the position of each dot
if self.MATRIX[ROW][COLUMN] == 0:
self.DOTS.append([(self.BLOCK_WIDTH * COLUMN),
(self.BLOCK_HEIGHT * ROW), 4, 4])
if self.MATRIX[ROW][COLUMN] == 1:
self.WALLS.append(pygame.draw.rect(MazeSurface, WHITE,
[((self.BLOCK_WIDTH) * COLUMN),
((self.BLOCK_HEIGHT) * ROW),
self.BLOCK_WIDTH, self.BLOCK_HEIGHT]))
if self.MATRIX[ROW][COLUMN] == 2:
self.ENERGIZER.append([(self.BLOCK_WIDTH * COLUMN),
(self.BLOCK_HEIGHT * ROW), 14, 14])
if self.MATRIX[ROW][COLUMN] == 4:
self.GHOSTS.append([(self.BLOCK_WIDTH * COLUMN),
(self.BLOCK_HEIGHT * ROW), 23, 23])
if self.MATRIX[ROW][COLUMN] == 5:
self.DECISION_NODES.append([(self.BLOCK_WIDTH * COLUMN),
(self.BLOCK_HEIGHT * ROW), 4, 4])
class Main(Maze):
def __init__(self):
# Inherits Maze class
Maze.__init__(self)
self.TimeBetweenBites = 0.1
self.LastBiteTime = time.time()
self.MouthOpen = False
self.PacManDirection = ""
self.GhostDirection = ""
self.PreviousGhostDirection = ""
self.SCORE = 0
self.HIGH_SCORE = 0
def PacManMovement(self):
key = pygame.key.get_pressed()
if key[pygame.K_LEFT] and not key[pygame.K_UP] \
and not key[pygame.K_DOWN]:
self.PacManDirection = "LEFT"
elif key[pygame.K_RIGHT] and not key[pygame.K_UP] \
and not key[pygame.K_DOWN]:
self.PacManDirection = "RIGHT"
elif key[pygame.K_UP] and not key[pygame.K_LEFT] \
and not key[pygame.K_RIGHT]:
self.PacManDirection = "UP"
elif key[pygame.K_DOWN] and not key[pygame.K_LEFT] \
and not key[pygame.K_RIGHT]:
self.PacManDirection = "DOWN"
def ContinuePacManMovement(self):
if self.PacManDirection == "LEFT":
CurrentRect.x -= SPEED
self.PacManWallDetection(-1, 0, CurrentRect)
if self.PacManDirection == "RIGHT":
CurrentRect.x += SPEED
self.PacManWallDetection(1, 0, CurrentRect)
if self.PacManDirection == "UP":
CurrentRect.y -= SPEED
self.PacManWallDetection(0, -1, CurrentRect)
if self.PacManDirection == "DOWN":
CurrentRect.y += SPEED
self.PacManWallDetection(0, 1, CurrentRect)
def PacManTeleport(self):
if CurrentRect.right < 0:
CurrentRect.right = SCREEN_WIDTH + 20
if CurrentRect.left > SCREEN_WIDTH:
CurrentRect.right = 0
def GhostTeleport(self):
if PinkGhostRect.right < 0:
PinkGhostRect.right = SCREEN_WIDTH + 20
if PinkGhostRect.left > SCREEN_WIDTH:
PinkGhostRect.right = 0
def PacManWallDetection(self, x, y, CurrentRect):
CurrentRect.right += x
for WALL in self.WALLS:
COLLIDE = CurrentRect.colliderect(WALL)
if COLLIDE:
if x < 0:
CurrentRect.left = WALL.right
CurrentSurface = pygame.transform.rotate(PacManSurface, 180)
MazeSurface.blit(CurrentSurface, CurrentRect)
if x > 0:
CurrentRect.right = WALL.left
break
CurrentRect.top += y
for WALL in self.WALLS:
COLLIDE = CurrentRect.colliderect(WALL)
if COLLIDE:
if y < 0:
CurrentRect.top = WALL.bottom
if y > 0:
CurrentRect.bottom = WALL.top
break
def GhostWallDetection(self, x, y, PinkGhostRect):
PinkGhostRect.right += x
for WALL in self.WALLS:
COLLIDE = PinkGhostRect.colliderect(WALL)
if COLLIDE:
if x < 0:
PinkGhostRect.left = WALL.right
if x > 0:
PinkGhostRect.right = WALL.left
break
PinkGhostRect.top += y
for WALL in self.WALLS:
COLLIDE = PinkGhostRect.colliderect(WALL)
if COLLIDE:
if y < 0:
PinkGhostRect.top = WALL.bottom
if y > 0:
PinkGhostRect.bottom = WALL.top
break
def EatDots(self):
for ROW in range(len(self.MATRIX)):
for COLUMN in range(len(self.MATRIX[0])):
for DOT in self.DOTS:
CHOMP = CurrentRect.colliderect(DOT)
if CHOMP:
Main.PlaySound(self, 0)
self.DOTS.remove(DOT)
self.MATRIX[ROW][COLUMN] = 3
self.SCORE += 10
if self.SCORE > self.HIGH_SCORE:
self.HIGH_SCORE = self.SCORE
return str(self.SCORE), str(self.HIGH_SCORE)
def EatEnergizer(self):
for ROW in range(len(self.MATRIX)):
for COLUMN in range(len(self.MATRIX[0])):
for POWERUP in self.ENERGIZER:
CHOMP = CurrentRect.colliderect(POWERUP)
if CHOMP:
self.ENERGIZER.remove(POWERUP)
self.MATRIX[ROW][COLUMN] = 3
self.SCORE += 50
Main.PlaySound(self, 1)
if self.SCORE > self.HIGH_SCORE:
self.HIGH_SCORE = self.SCORE
return str(self.SCORE), str(self.HIGH_SCORE)
def EatGhosts(self):
pass
def DrawDots(self):
for POSITION in self.DOTS:
X = POSITION[0] + 13
Y = POSITION[1] + 13
WIDTH = POSITION[2]
HEIGHT = POSITION[3]
pygame.draw.circle(MazeSurface, YELLOW, (X, Y),
WIDTH // 2, HEIGHT // 2)
def DrawEnergizer(self):
for POSITION in self.ENERGIZER:
X = POSITION[0] + 13
Y = POSITION[1] + 13
WIDTH = POSITION[2]
HEIGHT = POSITION[3]
pygame.draw.circle(MazeSurface, YELLOW, (X, Y),
WIDTH // 2, HEIGHT // 2)
def DrawGhosts(self):
MazeSurface.blit(PinkGhostSurface, PinkGhostRect)
MazeSurface.blit(YellowGhostSurface, YellowGhostRect)
MazeSurface.blit(RedGhostSurface, RedGhostRect)
MazeSurface.blit(BlueGhostSurface, BlueGhostRect)
def GhostPosition(self):
X, Y, WIDTH, HEIGHT = self.GHOSTS[0]
PinkGhostRect.x = X
PinkGhostRect.y = Y
PinkGhostRect.width = WIDTH
PinkGhostRect.height = HEIGHT
X, Y, WIDTH, HEIGHT = self.GHOSTS[1]
YellowGhostRect.x = X
YellowGhostRect.y = Y
YellowGhostRect.width = WIDTH
YellowGhostRect.height = HEIGHT
X, Y, WIDTH, HEIGHT = self.GHOSTS[2]
RedGhostRect.x = X
RedGhostRect.y = Y
RedGhostRect.width = WIDTH
RedGhostRect.height = HEIGHT
X, Y, WIDTH, HEIGHT = self.GHOSTS[3]
BlueGhostRect.x = X
BlueGhostRect.y = Y
BlueGhostRect.width = WIDTH
BlueGhostRect.height = HEIGHT
def ChaseMode(self):
self.GhostDirection = "LEFT"
self.GhostWallDetection(-1, 0, PinkGhostRect)
if PinkGhostRect.x < CurrentRect.x:
self.GhostDirection = "RIGHT"
self.GhostWallDetection(1, 0, PinkGhostRect)
if PinkGhostRect.y > CurrentRect.y:
self.GhostDirection = "UP"
self.GhostWallDetection(0, -1, PinkGhostRect)
if PinkGhostRect.y < CurrentRect.y:
self.GhostDirection = "DOWN"
self.GhostWallDetection(0, 1, PinkGhostRect)
def ScatterMode(self):
pass
def FrightenedMode(self):
pass
def PlaySound(self, Track):
if Track == 0:
Eat = pygame.mixer.Sound("pacman_chomp.wav")
Eat.play()
pygame.mixer.fadeout(400)
if Track == 1:
EatPellet = pygame.mixer.Sound("pacman_eatghost.wav")
EatPellet.play()
pygame.mixer.music.play(7)
pygame.mixer.fadeout(400)
def ShowScore(self):
global Font
OneUpText = Font.render("1UP", True, WHITE)
OneUpTextRect = OneUpText.get_rect(center = (70, 10))
# Displays current score
OneUpScoreText = Font.render(str(self.SCORE), True, WHITE)
OneUpScoreRect = OneUpScoreText.get_rect(center =
((SCREEN_WIDTH - 290)
// 2, 26))
HighScoreText = Font.render("High Score", True, WHITE)
HighScoreTextRect = HighScoreText.get_rect(center =
(SCREEN_WIDTH // 2, 10))
# Displays High Score
HighScoreNumber = Font.render(str(self.HIGH_SCORE), True, WHITE)
HighScoreNumberRect = HighScoreNumber.get_rect(center =
((SCREEN_WIDTH + 90)
// 2, 26))
SCREEN.blit(OneUpText, OneUpTextRect)
SCREEN.blit(OneUpScoreText, OneUpScoreRect)
SCREEN.blit(HighScoreText, HighScoreTextRect)
SCREEN.blit(HighScoreNumber, HighScoreNumberRect)
def PacManBite(self):
global CurrentSurface
CurrentTime = time.time()
if CurrentTime - self.LastBiteTime >= self.TimeBetweenBites:
self.LastBiteTime = CurrentTime
if self.MouthOpen:
CurrentSurface = PacManStartSurface
else:
CurrentSurface = PacManSurface
self.MouthOpen = not self.MouthOpen
if self.PacManDirection == "LEFT":
CurrentSurface = pygame.transform.rotate(CurrentSurface, 180)
if self.PacManDirection == "RIGHT":
CurrentSurface = CurrentSurface
if self.PacManDirection == "UP":
CurrentSurface = pygame.transform.rotate(CurrentSurface, 90)
if self.PacManDirection == "DOWN":
CurrentSurface = pygame.transform.rotate(CurrentSurface, 270)
def PacManLives(self):
pass
Player = Main()
BackgroundSurface = pygame.transform.scale(BackgroundSurface,
(Player.MAZE_WIDTH,
Player.MAZE_HEIGHT))
BackgroundRect = BackgroundSurface.get_rect()
MazeSurface = pygame.Surface((Player.MAZE_WIDTH, Player.MAZE_HEIGHT))
MazeRect = MazeSurface.get_rect(topleft = (Player.MAZE_OFFSET_X,
Player.MAZE_OFFSET_Y))
Player.DrawMaze(MazeSurface)
Player.GhostPosition()
#Player.GhostMovement()
'''
Before the game starts ...
pregame = True
while pregame:
if key button pressed:
pregame = False
run = True
'''
run = True
while run:
SCREEN.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
Player.PacManMovement()
Player.PacManTeleport()
Player.ContinuePacManMovement()
MazeSurface.blit(BackgroundSurface, BackgroundRect)
Player.DrawDots()
Player.DrawEnergizer()
Player.DrawGhosts()
Player.GhostTeleport()
Player.EatDots()
Player.EatEnergizer()
Player.ChaseMode()
MazeSurface.blit(CurrentSurface, CurrentRect)
Player.PacManBite()
SCREEN.blit(MazeSurface, MazeRect)
Player.ShowScore()
pygame.display.update()
CLOCK.tick(FPS)
pygame.quit()
An easy algorithm for moving is simply looking at the available exits from this position and choose where to go.
Watching Pacman for a few minutes leads to a few movement rules:
The direction is only changed on exact map-grid boundaries (true for all entities).
Ghosts do not stop
Ghosts do not reverse direction
... unless becoming edible (out of scope for now)
So I implemented this in a simple heuristic:
Unless in a dead-end, do not reverse
Always move forward, but if we can turn, still prefer forward (60% of the time)
To code this, the GhostSprite simply remembers its current direction in self.direction, which is a compass bearing North, East, South, West. North is toward the top of the window (-Y), West to the left (-X).
This makes the Ghost's direction control reduce down to a single algorithm:
By consulting the map, list the available exits
Is the only available direction in reverse?
Yes: turn around
No:
60% of the time continue forward
40% of the time, choose a random direction for what's available
In my implementation the ghosts jump whole grid co-ordinates. In versions with per-pixel movement, the ghost needs only check that it on an exact grid-boundary before bothering to check for direction changes. Something like a self.rect.x % GRID_SIZE == 0 would achieve that quickly.
** Code **
import pygame
import random
# Window size
WINDOW_WIDTH = 420
WINDOW_HEIGHT = 465
GRID_SIZE = WINDOW_HEIGHT // 21
MAP_WIDTH = 19
MAP_HEIGHT = 21
MAP = [ "###################",
"# # #",
"# ## ### # ### ## #",
"# #",
"# ## # ##### # ## #",
"# # # # #",
"#### ### # ### ####",
"#### # # ####",
"#### # ## ## # ####",
"< # # >",
"#### # ##### # ####",
"#### # c # ####",
"#### # ##### # ####",
"# # #",
"# ## ### # ### ## #",
"# # # #",
"## # # ##### # # ##",
"# # # # #",
"# ###### # ###### #",
"# #",
"###################" ]
BLACK = ( 0, 0, 0)
YELLOW = (255, 255, 0)
BLUE = ( 0, 0, 254)
RED = (255, 0, 0)
LIGHTBLUE= (161, 255, 254)
PINK = (255, 192, 203)
ORANGE = (255, 165, 0)
def pixelPosToGridPos( pixel_x, pixel_y ):
""" Map a window-pixel position to a map-grid position """
return ( pixel_x // GRID_SIZE, pixel_y // GRID_SIZE )
def gridPosToPixelPos( grid_x, grid_y ):
""" Map a grid position to a window-position position """
return ( grid_x * GRID_SIZE, grid_y * GRID_SIZE )
def getMapColour( x, y ):
""" Convert map symbols into colours """
symbol = MAP[y][x]
if ( symbol == '#' ):
return BLUE
elif ( symbol == 'c' ):
return YELLOW
elif ( symbol == 'b' ): # "Shadow" / "Blinky"
return RED
elif ( symbol == 'p' ): # "Speedy" / "Pinky"
return PINK
elif ( symbol == 'i' ): # "Bashful" / "Inky"
return LIGHTBLUE
elif ( symbol == 'o' ): # "Pokey" / "Clyde"
return ORANGE
return BLACK
class GhostSprite( pygame.sprite.Sprite ):
""" A pacman-like ghost sprite """
def __init__( self, grid_x, grid_y, colour ):
super().__init__()
self.image = pygame.Surface( ( GRID_SIZE, GRID_SIZE), pygame.SRCALPHA )
self.image.fill( colour )
self.rect = self.image.get_rect()
self.rect.topleft = gridPosToPixelPos( grid_x, grid_y )
self.direction = random.choice( [ 'N', 'S', 'E', 'W' ] )
def moveToGrid( self, grid_x, grid_y ):
""" Allow position to be reset """
self.rect.topleft = gridPosToPixelPos( grid_x, grid_y )
def availableMoves( self ):
""" Consult the map to see where is good to go from here.
We only consider walls, not other NPCs """
map_x, map_y = pixelPosToGridPos( self.rect.x, self.rect.y )
exits = []
# handle wrap-around, where it's possible to go "off grid"
if ( map_x <= 0 or map_x >= MAP_WIDTH-1 ):
exits = [ 'E', 'W' ]
else:
# otherwise consult the map
if ( MAP[ map_y-1 ][ map_x ] != '#' ):
exits.append( 'N' )
if ( MAP[ map_y ][ map_x+1 ] != '#' ):
exits.append( 'E' )
if ( MAP[ map_y+1 ][ map_x ] != '#' ):
exits.append( 'S' )
if ( MAP[ map_y ][ map_x-1 ] != '#' ):
exits.append( 'W' )
return exits
def getOppositeDirection( self ):
""" Return the compass-opposite of our current movement direction """
opposites = { 'N':'S', 'S':'N', 'E':'W', 'W':'E' };
return opposites[ self.direction ]
def moveForward( self ):
""" Move in the current direction. Generally we use the map
to keep us in-bounds, but on the wrap-around we can get
close to the edge of the map, so use special handling for
warping """
# handle wrap-around avenue
map_x, map_y = pixelPosToGridPos( self.rect.x, self.rect.y )
if ( MAP[ map_y ][ map_x ] == '<' ):
self.direction = 'W'
self.rect.x = (MAP_WIDTH-1) * GRID_SIZE
elif ( MAP[ map_y ][ map_x ] == '>' ):
self.direction = 'E'
self.rect.x = 0
# Whichever direction we're moving in, go forward
if ( self.direction == 'N' ):
self.rect.y -= GRID_SIZE
elif ( self.direction == 'E' ):
self.rect.x += GRID_SIZE
elif ( self.direction == 'S' ):
self.rect.y += GRID_SIZE
else: # W
self.rect.x -= GRID_SIZE
def update( self ):
""" Move the ghost, mostly forward, never backwards (unless dead-end)
At an intersection, possibly turn """
exits = self.availableMoves()
# Generally: Keep moving in current direction, never u-turn
opposite = self.getOppositeDirection()
# 60% change of continuing forward at an intersection
if ( self.direction in exits and ( len( exits ) == 1 or random.randrange( 0,100 ) <= 60 ) ):
pass
elif ( self.direction not in exits and len( exits ) == 1 ):
self.direction = exits[0] # maybe u-turn
else: # more than 1 exit
if ( opposite in exits ):
exits.remove( opposite )
self.direction = random.choice( exits )
# Move-it- Move-it
self.moveForward()
###
### MAIN
###
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE )
pygame.display.set_caption("Pac Algorithm")
# Make background image of map
background = pygame.Surface( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.SRCALPHA )
for y in range( MAP_HEIGHT ):
for x in range( MAP_WIDTH ):
rect = pygame.Rect( x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE )
pygame.draw.rect( background, getMapColour( x, y ), rect )
# Make the Ghosts
blinky = GhostSprite( 9, 7, RED )
inky = GhostSprite( 8, 9, LIGHTBLUE )
pinky = GhostSprite( 9, 9, PINK )
pokey = GhostSprite(10, 9, ORANGE )
ghosts = pygame.sprite.Group()
ghosts.add( [ blinky, inky, pinky, pokey ] )
# Ghosts move periodically
next_ghost_movement = pygame.time.get_ticks() + 1000
# Main loop
clock = pygame.time.Clock()
running = True
while running:
time_now = pygame.time.get_ticks()
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
running = False
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_UP] ):
print("up")
elif ( keys[pygame.K_DOWN] ):
print("down")
elif ( keys[pygame.K_LEFT] ):
print("left")
elif ( keys[pygame.K_RIGHT] ):
print("right")
elif ( keys[pygame.K_ESCAPE] ):
# Reset the ghosts home
blinky.moveToGrid( 9, 7 )
inky.moveToGrid( 8, 9 )
pinky.moveToGrid( 9, 9 )
pokey.moveToGrid( 10, 9 )
next_ghost_movement = time_now + 1000
# move the ghosts
if ( time_now > next_ghost_movement ):
ghosts.update()
next_ghost_movement = time_now + 100
# Update the window, but not more than 60fps
window.blit( background, ( 0, 0 ) )
ghosts.draw( window )
pygame.display.flip()
# Clamp FPS
clock.tick(30)
pygame.quit()

Pygame - Return collision location

How can I get the exact location of a collision between two sprites?
And if there are multiple pixels overlapping, maybe getting a list of the pixels location.
I want to spawn particles at that point and would need it for other functions too.
Pseudo code:
obj1.rect..x += obj1.velocity
collision = collide(obj1, obj2):
if collision:
?
return collision_location
My code
def _bullet_collision(self):
for bullet in self.bullets:
hits = pygame.sprite.spritecollide(bullet, self.aliens, False, pygame.sprite.collide_mask)
if hits:
hit_str = 11
for alien in hits:
x, y = alien.x, alien.y
spawn_pos = (x, y)
self._hit_enemy(alien, x, y, hit_str)
bullet.bullet_hit(spawn_pos, hit_str)
Based on the answer: https://stackoverflow.com/a/57023183/1730895
It's fairly easy to calculate the intersection of the rectangles:
def rectIntersection( rect1, rect2 ):
""" Given two pygame.Rect intersecting rectangles, return the overlap rect """
left = max( rect1.left, rect2.left )
width = min( rect1.right, rect2.right ) - left
top = max( rect1.top, rect2.top )
height= min( rect1.bottom, rect2.bottom ) - top
return pygame.Rect( left, top, width, height )
So use pygame.rect.colliderect() (doco) first to determine if there's actually any overlap. Obviously this gives you an overlapping region, so to answer your question, it could be every pixel within this rectangular area. But perhaps you could just take the centroid for use as a "happening at" point.
import pygame
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
WINDOW_MAXFPS = 60
# Colours
DARK_BLUE = ( 3, 5, 54)
RED = (255, 0, 0)
YELLOW = (200, 200, 20)
GREEN = ( 20, 200, 20)
def rectIntersection( rect1, rect2 ):
""" Given two pygame.Rect intersecting rectangles, calculate the rectangle of intersection """
left = max( rect1.left, rect2.left )
width = min( rect1.right, rect2.right ) - left
top = max( rect1.top, rect2.top )
height= min( rect1.bottom, rect2.bottom ) - top
return pygame.Rect( left, top, width, height )
### Initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Rect-Rect Intersection")
central_rect = pygame.Rect( 175, 175, 50, 50 )
player_rect = pygame.Rect( 0, 0, 30, 30 )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEMOTION ): # move the player's rectangle with the mouse
mouse_pos = pygame.mouse.get_pos()
player_rect.center = mouse_pos
# Update the window
window.fill( DARK_BLUE )
pygame.draw.rect( window, RED, central_rect, 1 ) # centre rect
pygame.draw.rect( window, YELLOW, player_rect, 1 ) # player's mouse rect
# IFF the rectangles overlap, paint the overlap
if ( player_rect.colliderect( central_rect ) ):
overlap_rect = rectIntersection( player_rect, central_rect )
pygame.draw.rect( window, GREEN, overlap_rect )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop( WINDOW_MAXFPS )
pygame.quit()
If pixel-perfect collision is desired, use Surface.subsurface() with the overlap rectangle's co-ordinates on the bitmap mask of both sprites. This gives you two bit-masks (maskA and maskB) which you know overlap somehow.
I can't think of a fast way to find the exact pixels of overlap between these two sub-sections. However it's quite easy to iterate through each of the mask-pixels, finding maskA & maskB pairs that are both "on". Obviously you need to convert from screen co-ordinates to sprite co-ordinates during this step, but this is just a subtraction of the sprite's current screen x and y.
What you really want is the "edge" of the bitmap-intersection, but that's another question.

Problem with animating a sprite in pygame

i have a problem with this code, i am a new person with programming and been using the book "how to think like a computer scientist 3rd edition" and he did not solve exercise 2 of chapter 17 this given: "he deliberately left a mistake in the code to animate Duke. If you click on one of the checkerboard squares to the right of Duke, he salutes anyway. Why? Find a one-line solution to the error ", I've tried many forms but I have not succeeded, I leave you all the code and the images that I have used
PS: images must have the name: ball.png and duke_spritesheet.png
import pygame
gravity = 0.025
my_clock = pygame.time.Clock()
class QueenSprite:
def __init__(self, img, target_posn):
self.image = img
self.target_posn = target_posn
(x, y) = target_posn
self.posn = (x, 0) # Start ball at top of its column
self.y_velocity = 0 # with zero initial velocity
def update(self):
self.y_velocity += gravity
(x, y) = self.posn
new_y_pos = y + self.y_velocity
(target_x, target_y) = self.target_posn # Unpack the position
dist_to_go = target_y - new_y_pos # How far to our floor?
if dist_to_go < 0: # Are we under floor?
self.y_velocity = -0.65 * self.y_velocity # Bounce
new_y_pos = target_y + dist_to_go # Move back above floor
self.posn = (x, new_y_pos) # Set our new position.
def draw(self, target_surface): # Same as before.
target_surface.blit(self.image, self.posn)
def contains_point(self, pt):
""" Return True if my sprite rectangle contains point pt """
(my_x, my_y) = self.posn
my_width = self.image.get_width()
my_height = self.image.get_height()
(x, y) = pt
return ( x >= my_x and x < my_x + my_width and
y >= my_y and y < my_y + my_height)
def handle_click(self):
self.y_velocity += -2 # Kick it up
class DukeSprite:
def __init__(self, img, target_posn):
self.image = img
self.posn = target_posn
self.anim_frame_count = 0
self.curr_patch_num = 0
def update(self):
if self.anim_frame_count > 0:
self.anim_frame_count = (self.anim_frame_count + 1 ) % 60
self.curr_patch_num = self.anim_frame_count // 6
def draw(self, target_surface):
patch_rect = (self.curr_patch_num * 50, 0,
50, self.image.get_width())
target_surface.blit(self.image, self.posn, patch_rect)
def contains_point(self, pt):
""" Return True if my sprite rectangle contains pt """
(my_x, my_y) = self.posn
my_width = self.image.get_width()
my_height = self.image.get_height()
(x, y) = pt
return ( x >= my_x and x < my_x + my_width and
y >= my_y and y < my_y + my_height)
def handle_click(self):
if self.anim_frame_count == 0:
self.anim_frame_count = 5
def draw_board(the_board):
""" Draw a chess board with queens, as determined by the the_board. """
pygame.init()
colors = [(255,0,0), (0,0,0)] # Set up colors [red, black]
n = len(the_board) # This is an NxN chess board.
surface_sz = 480 # Proposed physical surface size.
sq_sz = surface_sz // n # sq_sz is length of a square.
surface_sz = n * sq_sz # Adjust to exactly fit n squares.
# Create the surface of (width, height), and its window.
surface = pygame.display.set_mode((surface_sz, surface_sz))
ball = pygame.image.load("ball.png")
# Use an extra offset to centre the ball in its square.
# If the square is too small, offset becomes negative,
# but it will still be centered :-)
ball_offset = (sq_sz-ball.get_width()) // 2
all_sprites = [] # Keep a list of all sprites in the game
# Create a sprite object for each queen, and populate our list.
for (col, row) in enumerate(the_board):
a_queen = QueenSprite(ball,
(col*sq_sz+ball_offset, row*sq_sz+ball_offset))
all_sprites.append(a_queen)
# Load the sprite sheet
duke_sprite_sheet = pygame.image.load("duke_spritesheet.png")
# Instantiate two duke instances, put them on the chessboard
duke1 = DukeSprite(duke_sprite_sheet,(sq_sz*2, 0))
duke2 = DukeSprite(duke_sprite_sheet,(sq_sz*5, sq_sz))
# Add them to the list of sprites which our game loop manages
all_sprites.append(duke1)
all_sprites.append(duke2)
while True:
# Look for an event from keyboard, mouse, etc.
ev = pygame.event.poll()
if ev.type == pygame.QUIT:
break;
if ev.type == pygame.KEYDOWN:
key = ev.dict["key"]
if key == 27: # On Escape key ...
break # leave the game loop.
if key == ord("r"):
colors[0] = (255, 0, 0) # Change to red + black.
elif key == ord("g"):
colors[0] = (0, 255, 0) # Change to green + black.
elif key == ord("b"):
colors[0] = (0, 0, 255) # Change to blue + black.
if ev.type == pygame.MOUSEBUTTONDOWN: # Mouse gone down?
posn_of_click = ev.dict["pos"] # Get the coordinates.
for sprite in all_sprites:
if sprite.contains_point(posn_of_click):
sprite.handle_click()
break
for sprite in all_sprites:
sprite.update()
# Draw a fresh background (a blank chess board)
for row in range(n): # Draw each row of the board.
c_indx = row % 2 # Alternate starting color
for col in range(n): # Run through cols drawing squares
the_square = (col*sq_sz, row*sq_sz, sq_sz, sq_sz)
surface.fill(colors[c_indx], the_square)
# Now flip the color index for the next square
c_indx = (c_indx + 1) % 2
# Ask every sprite to draw itself.
for sprite in all_sprites:
sprite.draw(surface)
my_clock.tick(60) # Waste time so that frame rate becomes 60 fps
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
draw_board([0, 5, 3, 1, 6, 4, 2]) # 7 x 7 to test window size
PS: I think the error is here but it did not succeed
return ( x >= my_x and x + my_width and y >= my_y and y < my_y + my_height)
The issue is caused by the face, that "duke_spritesheet.png" is a sprite sheet. When you define the rectangular region which is covered by the object, then you have to use the width of a single image, rather than the width of the entire sprite sheet:
my_width = self.image.get_width()
my_width = 50
Change this in the method contains_point of the class DukeSprite:
class DukeSprite:
# [...]
def contains_point(self, pt):
""" Return True if my sprite rectangle contains pt """
(my_x, my_y) = self.posn
my_width = 50
my_height = self.image.get_height()
(x, y) = pt
return ( x >= my_x and x < my_x + my_width and
y >= my_y and y < my_y + my_height)

How do I make images appear/disappear in pygame?

I am creating a battleship-type game. I am using .blit to display images that I load using the function pygame.image.load. I was wondering, is it possible to make images like this appear/disappear at different points?
My code is as follows:
import random, sys, pygame
from pygame.locals import *
# Set variables, like screen width and height
# globals
FPS = 60 #Determines the number of frames per second
REVEALSPEED = 2 #Determines the speed at which the squares reveals after being clicked
WINDOWWIDTH = 800 #Width of game window
WINDOWHEIGHT = 600 #Height of game window
TILESIZE = 40 #Size of the squares in each grid(tile)
MARKERSIZE = 40 #Size of the box which contatins the number that indicates how many ships in this row/col
BUTTONHEIGHT = 20 #Height of a standard button
BUTTONWIDTH = 40 #Width of a standard button
TEXT_HEIGHT = 25 #Size of the text
TEXT_LEFT_POSN = 10 #Where the text will be positioned
BOARDWIDTH = 6 #Number of grids horizontally
BOARDHEIGHT = 6 #Number of grids vertically
DISPLAYWIDTH = 200 #Width of the game board
EXPLOSIONSPEED = 10 #How fast the explosion graphics will play
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * TILESIZE) - DISPLAYWIDTH - MARKERSIZE) / 2) #x-position of the top left corner of board
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * TILESIZE) - MARKERSIZE) / 2) #y-position of the top left corner of board
#Colours which will be used by the game
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
GREEN = ( 0, 204, 0)
GRAY = ( 60, 60, 60)
BLUE = ( 0, 50, 255)
YELLOW = (255, 255, 0)
DARKGRAY =( 40, 40, 40)
transparent = (0, 0, 0, 0)
#Determine what to colour each element of the game
BGCOLOR = GRAY
BUTTONCOLOR = GREEN
TEXTCOLOR = WHITE
TILECOLOR = GREEN
BORDERCOLOR = BLUE
TEXTSHADOWCOLOR = BLUE
SHIPCOLOR = YELLOW
HIGHLIGHTCOLOR = BLUE
def main():
"""
The main function intializes the variables which will be used by the game.
"""
global DISPLAYSURF, FPSCLOCK, BASICFONT, HELP_SURF, HELP_RECT, NEW_SURF, \
NEW_RECT, SHOTS_SURF, SHOTS_RECT, BIGFONT, COUNTER_SURF, \
COUNTER_RECT, HBUTTON_SURF, EXPLOSION_IMAGES
pygame.init()
FPSCLOCK = pygame.time.Clock()
#Fonts used by the game
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
BASICFONT = pygame.font.Font('freesansbold.ttf', 20)
BIGFONT = pygame.font.Font('freesansbold.ttf', 50)
# Create and label the buttons
HELP_SURF = BASICFONT.render("HELP", True, WHITE)
HELP_RECT = HELP_SURF.get_rect()
HELP_RECT.topleft = (WINDOWWIDTH - 180, WINDOWHEIGHT - 350)
NEW_SURF = BASICFONT.render("NEW GAME", True, WHITE)
NEW_RECT = NEW_SURF.get_rect()
NEW_RECT.topleft = (WINDOWWIDTH - 200, WINDOWHEIGHT - 200)
# The 'Shots:' label at the top
SHOTS_SURF = BASICFONT.render("Shots: ", True, WHITE)
SHOTS_RECT = SHOTS_SURF.get_rect()
SHOTS_RECT.topleft = (WINDOWWIDTH - 750, WINDOWHEIGHT - 570)
# Load the explosion graphics from the /img folder
EXPLOSION_IMAGES = [
pygame.image.load("blowup1.png"), pygame.image.load("blowup2.png"),
pygame.image.load("blowup3.png"),pygame.image.load("blowup4.png"),
pygame.image.load("blowup5.png"),pygame.image.load("blowup6.png")]
# Set the title in the menu bar to 'Battleship'
pygame.display.set_caption('Battleship')
# Keep the game running at all times
while True:
shots_taken = run_game() #Run the game until it stops and save the result in shots_taken
show_gameover_screen(shots_taken) #Display a gameover screen by passing in shots_taken
def run_game():
greenButton = pygame.image.load('green-button-icon-png-13.png')
greenButton = pygame.transform.scale(greenButton, (75,75))
rect = greenButton.get_rect()
rect = rect.move((150, 475))
redButton = pygame.image.load('red-button-1426817_960_720.png')
redButton = pygame.transform.scale(redButton, (85,85))
rect2 = redButton.get_rect()
rect2 = rect2.move((400, 475))
"""
Function is executed while a game is running.
returns the amount of shots taken
"""
revealed_tiles = generate_default_tiles(False) #Contains the list of the tiles revealed by user
# main board object,
main_board = generate_default_tiles(None) #Contains the list of the ships which exists on board
ship_objs = ['raft'] # List of the ships available
main_board = add_ships_to_board(main_board, ship_objs) #call add_ships_to_board to add the list of ships to the main_board
mousex, mousey = 0, 0 #location of mouse
counter = [] #counter to track number of shots fired
while True:
# counter display (it needs to be here in order to refresh it)
COUNTER_SURF = BASICFONT.render(str(len(counter)), True, WHITE)
COUNTER_RECT = SHOTS_SURF.get_rect()
COUNTER_RECT.topleft = (WINDOWWIDTH - 680, WINDOWHEIGHT - 570)
# Fill background
DISPLAYSURF.fill(BGCOLOR)
# draw the buttons
DISPLAYSURF.blit(HELP_SURF, HELP_RECT)
DISPLAYSURF.blit(NEW_SURF, NEW_RECT)
DISPLAYSURF.blit(SHOTS_SURF, SHOTS_RECT)
DISPLAYSURF.blit(COUNTER_SURF, COUNTER_RECT)
DISPLAYSURF.blit(greenButton, rect)
DISPLAYSURF.blit(redButton, rect2)
greenButton.fill(transparent)
DISPLAYSURF.blit(greenButton, rect)
# Draw the tiles onto the board and their respective markers
draw_board(main_board, revealed_tiles)
mouse_clicked = False
check_for_quit()
#Check for pygame events
for event in pygame.event.get():
if event.type == MOUSEBUTTONUP:
if HELP_RECT.collidepoint(event.pos): #if the help button is clicked on
DISPLAYSURF.fill(BGCOLOR)
show_help_screen() #Show the help screen
elif NEW_RECT.collidepoint(event.pos): #if the new game button is clicked on
main() #goto main, which resets the game
else: #otherwise
mousex, mousey = event.pos #set mouse positions to the new position
mouse_clicked = True #mouse is clicked but not on a button
elif event.type == MOUSEMOTION: #Detected mouse motion
mousex, mousey = event.pos #set mouse positions to the new position
#Check if the mouse is clicked at a position with a ship piece
tilex, tiley = get_tile_at_pixel(mousex, mousey)
if tilex != None and tiley != None:
if not revealed_tiles[tilex][tiley]: #if the tile the mouse is on is not revealed
draw_highlight_tile(tilex, tiley) # draws the hovering highlight over the tile
if not revealed_tiles[tilex][tiley] and mouse_clicked: #if the mouse is clicked on the not revealed tile
reveal_tile_animation(main_board, [(tilex, tiley)])
revealed_tiles[tilex][tiley] = True #set the tile to now be revealed
if check_revealed_tile(main_board, [(tilex, tiley)]): # if the clicked position contains a ship piece
left, top = left_top_coords_tile(tilex, tiley)
blowup_animation((left, top))
if check_for_win(main_board, revealed_tiles): # check for a win
counter.append((tilex, tiley))
return len(counter) # return the amount of shots taken
counter.append((tilex, tiley))
pygame.display.update()
FPSCLOCK.tick(FPS)
def generate_default_tiles(default_value):
"""
Function generates a list of 10 x 10 tiles. The list will contain tuples
('shipName', boolShot) set to their (default_value).
default_value -> boolean which tells what the value to set to
returns the list of tuples
"""
default_tiles = [[default_value]*BOARDHEIGHT for i in range(BOARDWIDTH)]
return default_tiles
def blowup_animation(coord):
"""
Function creates the explosition played if a ship is shot.
coord -> tuple of tile coords to apply the blowup animation
"""
for image in EXPLOSION_IMAGES: # go through the list of images in the list of pictures and play them in sequence
#Determine the location and size to display the image
image = pygame.transform.scale(image, (TILESIZE+10, TILESIZE+10))
DISPLAYSURF.blit(image, coord)
pygame.display.flip()
FPSCLOCK.tick(EXPLOSIONSPEED) #Determine the delay to play the image with
def check_revealed_tile(board, tile):
"""
Function checks if a tile location contains a ship piece.
board -> the tiled board either a ship piece or none
tile -> location of tile
returns True if ship piece exists at tile location
"""
return board[tile[0][0]][tile[0][1]] != None
def reveal_tile_animation(board, tile_to_reveal):
"""
Function creates an animation which plays when the mouse is clicked on a tile, and whatever is
behind the tile needs to be revealed.
board -> list of board tile tuples ('shipName', boolShot)
tile_to_reveal -> tuple of tile coords to apply the reveal animation to
"""
for coverage in range(TILESIZE, (-REVEALSPEED) - 1, -REVEALSPEED): #Plays animation based on reveal speed
draw_tile_covers(board, tile_to_reveal, coverage)
def draw_tile_covers(board, tile, coverage):
"""
Function draws the tiles according to a set of variables.
board -> list; of board tiles
tile -> tuple; of tile coords to reveal
coverage -> int; amount of the tile that is covered
"""
left, top = left_top_coords_tile(tile[0][0], tile[0][1])
if check_revealed_tile(board, tile):
pygame.draw.rect(DISPLAYSURF, SHIPCOLOR, (left, top, TILESIZE,
TILESIZE))
else:
pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, TILESIZE,
TILESIZE))
if coverage > 0:
pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left, top, coverage,
TILESIZE))
pygame.display.update()
FPSCLOCK.tick(FPS)
def check_for_quit():
"""
Function checks if the user has attempted to quit the game.
"""
for event in pygame.event.get(QUIT):
pygame.quit()
sys.exit()
def check_for_win(board, revealed):
"""
Function checks if the current board state is a winning state.
board -> the board which contains the ship pieces
revealed -> list of revealed tiles
returns True if all the ships are revealed
"""
for tilex in range(BOARDWIDTH):
for tiley in range(BOARDHEIGHT):
if board[tilex][tiley] != None and not revealed[tilex][tiley]: # check if every board with a ship is revealed, return false if not
return False
return True
def draw_board(board, revealed):
"""
Function draws the game board.
board -> list of board tiles
revealed -> list of revealed tiles
"""
#draws the grids depending on its state
for tilex in range(BOARDWIDTH):
for tiley in range(BOARDHEIGHT):
left, top = left_top_coords_tile(tilex, tiley)
if not revealed[tilex][tiley]:
pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left, top, TILESIZE,
TILESIZE))
else:
if board[tilex][tiley] != None:
pygame.draw.rect(DISPLAYSURF, SHIPCOLOR, (left, top,
TILESIZE, TILESIZE))
else:
pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top,
TILESIZE, TILESIZE))
#draws the horizontal lines
for x in range(0, (BOARDWIDTH + 1) * TILESIZE, TILESIZE):
pygame.draw.line(DISPLAYSURF, DARKGRAY, (x + XMARGIN + MARKERSIZE,
YMARGIN + MARKERSIZE), (x + XMARGIN + MARKERSIZE,
WINDOWHEIGHT - YMARGIN))
#draws the vertical lines
for y in range(0, (BOARDHEIGHT + 1) * TILESIZE, TILESIZE):
pygame.draw.line(DISPLAYSURF, DARKGRAY, (XMARGIN + MARKERSIZE, y +
YMARGIN + MARKERSIZE), (WINDOWWIDTH - (DISPLAYWIDTH + MARKERSIZE *
2), y + YMARGIN + MARKERSIZE))
def add_ships_to_board(board, ships):
"""
Function goes through a list of ships and add them randomly into a board.
board -> list of board tiles
ships -> list of ships to place on board
returns list of board tiles with ships placed on certain tiles
"""
new_board = board[:]
ship_length = 0
for ship in ships: #go through each ship declared in the list
#Randomly find a valid position that fits the ship
valid_ship_position = False
while not valid_ship_position:
xStartpos = random.randint(0, (BOARDHEIGHT-1))
yStartpos = random.randint(0, (BOARDHEIGHT-1))
isHorizontal = random.randint(0, 1) #vertical or horizontal positioning
#Type of ship and their respective length
if 'battleship' in ship:
ship_length = 5
elif 'destroyer' in ship:
ship_length = 4
elif 'cruiser'in ship:
ship_length = 3
elif 'submarine' in ship:
ship_length = 2
elif 'raft' in ship:
ship_length = 1
#check if position is valid
valid_ship_position, ship_coords = make_ship_position(new_board,
xStartpos, yStartpos, isHorizontal, ship_length, ship)
#add the ship if it is valid
if valid_ship_position:
for coord in ship_coords:
new_board[coord[0]][coord[1]] = ship
return new_board
def make_ship_position(board, xPos, yPos, isHorizontal, length, ship):
"""
Function makes a ship on a board given a set of variables
board -> list of board tiles
xPos -> x-coordinate of first ship piece
yPos -> y-coordinate of first ship piece
isHorizontal -> True if ship is horizontal
length -> length of ship
returns tuple: True if ship position is valid and list ship coordinates
"""
ship_coordinates = [] #the coordinates the ship will occupy
if isHorizontal:
for i in range(length):
if (i+xPos > (BOARDHEIGHT-1)) or (board[i+xPos][yPos] != None) or \
hasAdjacent(board, i+xPos, yPos, ship): #if the ship goes out of bound, hits another ship, or is adjacent to another ship
return (False, ship_coordinates) #then return false
else:
ship_coordinates.append((i+xPos, yPos))
else:
for i in range(length):
if (i+yPos > (BOARDHEIGHT-1)) or (board[xPos][i+yPos] != None) or \
hasAdjacent(board, xPos, i+yPos, ship): #if the ship goes out of bound, hits another ship, or is adjacent to another ship
return (False, ship_coordinates) #then return false
else:
ship_coordinates.append((xPos, i+yPos))
return (True, ship_coordinates) #ship is successfully added
def hasAdjacent(board, xPos, yPos, ship):
"""
Funtion checks if a ship has adjacent ships
board -> list of board tiles
xPos -> x-coordinate of first ship piece
yPos -> y-coordinate of first ship piece
ship -> the ship being checked for adjacency
returns true if there are adjacent ships and false if there are no adjacent ships
"""
for x in range(xPos-1,xPos+2):
for y in range(yPos-1,yPos+2):
if (x in range (BOARDHEIGHT)) and (y in range (BOARDHEIGHT)) and \
(board[x][y] not in (ship, None)):
return True
return False
def left_top_coords_tile(tilex, tiley):
"""
Function calculates and returns the pixel of the tile in the top left corner
tilex -> int; x position of tile
tiley -> int; y position of tile
returns tuple (int, int) which indicates top-left pixel coordinates of tile
"""
left = tilex * TILESIZE + XMARGIN + MARKERSIZE
top = tiley * TILESIZE + YMARGIN + MARKERSIZE
return (left, top)
def get_tile_at_pixel(x, y):
"""
Function finds the corresponding tile coordinates of pixel at top left, defaults to (None, None) given a coordinate.
x -> int; x position of pixel
y -> int; y position of pixel
returns tuple (tilex, tiley)
"""
for tilex in range(BOARDWIDTH):
for tiley in range(BOARDHEIGHT):
left, top = left_top_coords_tile(tilex, tiley)
tile_rect = pygame.Rect(left, top, TILESIZE, TILESIZE)
if tile_rect.collidepoint(x, y):
return (tilex, tiley)
return (None, None)
def draw_highlight_tile(tilex, tiley):
"""
Function draws the hovering highlight over the tile.
tilex -> int; x position of tile
tiley -> int; y position of tile
"""
left, top = left_top_coords_tile(tilex, tiley)
pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR,
(left, top, TILESIZE, TILESIZE), 4)
def show_help_screen():
"""
Function display a help screen until any button is pressed.
"""
line1_surf, line1_rect = make_text_objs('Press a key to return to the game',
BASICFONT, TEXTCOLOR)
line1_rect.topleft = (TEXT_LEFT_POSN, TEXT_HEIGHT)
DISPLAYSURF.blit(line1_surf, line1_rect)
line2_surf, line2_rect = make_text_objs(
'This is a battleship puzzle game. Your objective is ' \
'to sink all the ships in as few', BASICFONT, TEXTCOLOR)
line2_rect.topleft = (TEXT_LEFT_POSN, TEXT_HEIGHT * 3)
DISPLAYSURF.blit(line2_surf, line2_rect)
line3_surf, line3_rect = make_text_objs('shots as possible. The markers on'\
' the edges of the game board tell you how', BASICFONT, TEXTCOLOR)
line3_rect.topleft = (TEXT_LEFT_POSN, TEXT_HEIGHT * 4)
DISPLAYSURF.blit(line3_surf, line3_rect)
line4_surf, line4_rect = make_text_objs('many ship pieces are in each'\
' column and row. To reset your game click on', BASICFONT, TEXTCOLOR)
line4_rect.topleft = (TEXT_LEFT_POSN, TEXT_HEIGHT * 5)
DISPLAYSURF.blit(line4_surf, line4_rect)
line5_surf, line5_rect = make_text_objs('the "New Game" button.',
BASICFONT, TEXTCOLOR)
line5_rect.topleft = (TEXT_LEFT_POSN, TEXT_HEIGHT * 6)
DISPLAYSURF.blit(line5_surf, line5_rect)
while check_for_keypress() == None: #Check if the user has pressed keys, if so go back to the game
pygame.display.update()
FPSCLOCK.tick()
def check_for_keypress():
"""
Function checks for any key presses by pulling out all KEYDOWN and KEYUP events from queue.
returns any KEYUP events, otherwise return None
"""
for event in pygame.event.get([KEYDOWN, KEYUP, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION]):
if event.type in (KEYDOWN, MOUSEBUTTONUP, MOUSEBUTTONDOWN, MOUSEMOTION):
continue
return event.key
return None
def make_text_objs(text, font, color):
"""
Function creates a text.
text -> string; content of text
font -> Font object; face of font
color -> tuple of color (red, green blue); colour of text
returns the surface object, rectangle object
"""
surf = font.render(text, True, color)
return surf, surf.get_rect()
def show_gameover_screen(shots_fired):
"""
Function display a gameover screen when the user has successfully shot at every ship pieces.
shots_fired -> the number of shots taken before game is over
"""
DISPLAYSURF.fill(BGCOLOR)
titleSurf, titleRect = make_text_objs('Congrats! Puzzle solved in:',
BIGFONT, TEXTSHADOWCOLOR)
titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
DISPLAYSURF.blit(titleSurf, titleRect)
titleSurf, titleRect = make_text_objs('Congrats! Puzzle solved in:',
BIGFONT, TEXTCOLOR)
titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2) - 3)
DISPLAYSURF.blit(titleSurf, titleRect)
titleSurf, titleRect = make_text_objs(str(shots_fired) + ' shots',
BIGFONT, TEXTSHADOWCOLOR)
titleRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2 + 50))
DISPLAYSURF.blit(titleSurf, titleRect)
titleSurf, titleRect = make_text_objs(str(shots_fired) + ' shots',
BIGFONT, TEXTCOLOR)
titleRect.center = (int(WINDOWWIDTH / 2) - 3, int(WINDOWHEIGHT / 2 + 50) - 3)
DISPLAYSURF.blit(titleSurf, titleRect)
pressKeySurf, pressKeyRect = make_text_objs(
'Press a key to try to beat that score.', BASICFONT, TEXTCOLOR)
pressKeyRect.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2) + 100)
DISPLAYSURF.blit(pressKeySurf, pressKeyRect)
while check_for_keypress() == None: #Check if the user has pressed keys, if so start a new game
pygame.display.update()
FPSCLOCK.tick()
if __name__ == "__main__": #This calls the game loop
main()
Generally there's two ways of doing this.
The more common way is to re-paint the entire screen on each iteration of the main loop.
For example:
### Main Loop
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEBUTTONUP ):
mouse_position = pygame.mouse.get_pos() # Location of mouse-click
player_moves.append ( PlayerMove( mouse_position ) ) # Make a new move
# Re-paint the screen
window.fill( OCEAN_BLUE_COLOUR ) # clear the screen
# Paint each of the players turns
for m in player_moves:
m.draw( window ) # paints a hit or miss icon
pygame.display.flip()
Alternatively, instead of re-painting everything, only change the items that have updated, or when events happen. This is close to the "dirty-rectangles" method of updating.
# Initially paint the screen
window.fill( OCEAN_BLUE_COLOUR ) # clear the screen
### Main Loop
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEBUTTONUP ):
mouse_position = pygame.mouse.get_pos() # Location of mouse-click
move = playerMove( mouse_position )
move.draw( window )
pygame.display.flip()
The difficulty of the second method, is that the program needs to clean up after the movement of on-screen images (otherwise they will leave a trail). Obviously in a battleship game, no on-screen elements move - but things like re-drawing scores or starting a new game will need to somehow erase the background. I'm not sure if this will also re-paint the window after it has been occluded by another window.
If you are a beginner programmer, I would use the first method. It's much simpler, and a lot of games are written this way.

I'm having trouble loading a image for it to be displayed

im having issues for loading an image to a certain location in which I choose to the gameboard
EDIT: OCR'd text from linked image.
def drawPieces (gameBoard):
global goblin1a
global goblin1b
global goblin2a
global goblin2b, goblin3a, goblin3b, goblin4a, goblin4b
global xcoordinate
global ycoordinate
global i
global j
for x in range (0,20):
for y in range (0,20):
xcoordinate.append(((margin+width) * x + margin+32)+xDistanceFromEdge)
ycoordinate.append((margintheight) * y + margin+33
#if gameBoard [x] [y]=="NormalBlack"
goblin1a=xcoordinate[2]#goblin 1
goblin1b=ycoordinate[2]#goblin 1
goblin2a=xcoordinate[3]#goblin 1
goblin2b=ycoordinate[3]#goblin 1
goblin3a=xcoordinate[7]#goblin 1
goblin3b=ycoordinate[5]#goblin 1
goblin4a=xcoordinate[9]#goblin 1
goblin4b=ycoordinate[2]#goblin 1
screen.blit(walkLeft, (goblin1a, goblin1b))
print (xcoordinate)
drawPieces (gameBoard)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygameMOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
The code is getting bogged in all those co-ordinates!
What if there was some way of storing everything about a Goblin together, to keep it tidy, and allow it to be passed as a parameter or stored in a list.
So what do we know about a Goblin ... well it has:
an game-grid co-ordinate
an image (PyGame surface)
This could easily be put into a python list (like an array):
goblin1 = [ 0, 0, walkLeft ]
goblin2 = [ 1, 0, walkLeft ]
...
These can be kept in another list:
goblins = [ goblin1, goblin2, ... ]
Making the drawPieces() simpler:
# window border constants
X_BORDER = 40
Y_BORDER = 40
drawPieces( screen, goblin_list ):
global X_BORDER, Y_BORDER
# paint background white
screen.fill( ( 255,255,255 ) )
# draw each goblin in the list
for single_goblin in goblin_list:
# copy the three parts of the goblin's info from the list
board_x, board_y, bitmap = single_goblin
# convert game grid-position to screen-position
screen_x = X_BORDER + ( board_x * 32 )
screen_y = Y_BORDER + ( board_y * 33 )
screen.blit( bitmap, ( screen_x, screen_y ) )
# flush the updates to the display
pygame.display.flip()
There's also PyGame sprites, which are software objects, so they encapsulate both the variables for the object, and functions to work with it.
class GoblinSprite( pygame.sprite.Sprite ):
def __init__( self, bitmap, board_position ):
self.position = board_position
self.image = bitmap
self.rect = self.image.get_rect()
self.setScreenPosition()
def setScreenPosition( self ):
global X_BORDER, Y_BORDER
screen_x = X_BORDER + ( self.position[0] * 32 )
screen_y = Y_BORDER + ( self.position[1] * 33 )
self.rect.x = screen_x
self.rect.y = screen_y
return (screen_x, screen_y)
There's a short sprite tutorial on the PyGame Website.

Categories

Resources