My first Python game
This is a non data-related article. However, learning Python might be helpful to master for future data projects, especially when it comes to Data Science. Python has great data related libraries such as Pandas, Seaborn and Matplotlib. They can be used in tools such as Power BI, and I already noticed the advantages of knowing a bit of Data Science for Business Intelligence projects.
I wanted to learn Python with a fun project that no one ever did. It’s a game from my childhood. Here it is.
The “Satellite Game”
This project comes from a mini-game in a PlayStation 2 game called “Jak 3”. This mini-game is called “Satellite Game” and you can find videos in YouTube if you search this: “Jak 3 Satellite Game”. The game is quite simple: at the start, there are 4 blue circles swinging from the center of screen to the 4 edges. Shapes are falling from the edges to the center of the screen:
- Green triangles from the top
- Pink squares from left
- Red circles from right
- Blue crosses from the bottom
The player scores points whenever he clicks on the right controller button when the corresponding shapes overlaps with the circles. When the player hits the shape, it disappears. There are some rules for scoring points:
- 1 shape at a time = 1 point
- 2 shapes at a time = 5 points
- 3 shapes at a time = 10 points
When the player clicks on a controller button by mistake, meaning when there is no corresponding shape overlapping a circle, it increases by 1 the number of allowed “Misses”. When a shape reaches the center of the screen and the player couldn’t hit it in time, it counts as a miss too. The goal on the left of this screenshot is simply the number of points to reach to win the game. The game gets harder through time; more shapes are falling, and faster.
Here is how it looked, in 2004!

Organizing my code with GitHub
The game I coded is available on GitHub : https://github.com/PratsNathan/Jak-3-Satellite-Game.
I’ll go through the different commits I created. I didn’t code the game in 3 hours… I just reorganized my work with GitHub afterwards.

Moving Circles
The first step is to draw 4 circles and to swing them from the middle of the screen to the 4 edges.
Draw Circles
To draw circles on a screen, I used a Python library called Pygame. Its documentation is here : https://www.pygame.org/docs/.
First, we have to define what a circle is. I created a class to store this object.
class Circle: def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"): self.rect = pygame.Rect(0, 0, 2*radius, 2*radius) self.rect.centerx = x self.rect.centery = y self.radius = radius self.thick = thick self.color = color self.speed = speed self.position = position if speed >= 0: self.directionX = 'right' self.direction = 'up' else: self.directionX = 'left' self.direction = 'down'
From what I understood, a class stores a kind of object. I considered that a circle is an object. Later on, we’ll see that I created another class for falling shapes. You define what the object is in the __init__ definition. I gave the object Circle the following variables:
- X
- Y
- Radius
- Thick
- Color
- Speed
- Position
In Pygame, a circle is actually a rectangle object: https://www.pygame.org/docs/ref/rect.html
- Rect(left, top, width, height)
That’s why I have this line: self.rect = pygame.Rect(0, 0, 2*radius, 2*radius). The width and height of the circle is 2 times its radius. I define the position of the circles using X and Y. The Rect object has several virtual attributes which can be used to move and align the Rect. I used centerx and centery.
Regarding the draw definition, I followed the Pygame documentation on how to draw a circle :
- pygame.draw.circle(surface, color, center, radius, width=0)
So I added a draw definition in the circle class:
def draw(self, screen): pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
To draw the 4 circles perfectly in the middle of screen, I created the list of circles in the main() definition. I added and subtracted 2 times the circle radius on X and Y axis to make this happen. We’ll see later that when these circles will swing from the center to the edges of the screen, this allow me to have a synchronized swing. Going back to the code where I create the circles, it’s important to understand how Pygame coordinates work. The following image sums it up.

Swing Circles
Now that we have our 4 circles, we have to make them swing from the center to the edges. I added a constant called SPEED and modified the Circle class. I added the variable speed in __init__ and added a definition called swing. I also added an attribute called direction and directionX. If the speed is greater than 0, the direction = “Right” and the direction(Y) = “Up”, otherwise, the directions are left and down. The swing definition is ugly but I didn’t find better alternative. It does the job though. *
def swing(self): if self.position == "top": self.rect.y -= self.speed if self.rect.top <= 0 and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "bot": self.rect.y -= self.speed if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "left": self.rect.x -= self.speed if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed elif self.rect.left <= 420 and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed if self.position == "right": self.rect.x -= self.speed if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed
The principle is simple and we’ll take the example for the circle located at “Top” at the start. As we want the circle to move to the top at start, we write “self.rect.y -= self.speed”. Therefore, going up means Y decreases. When the circle reaches the top of the screen, we want it to change direction and go back to its initial position. That’s why we have:
The speed is going to be positive (going down), so we set the direction to down. Now that the circle is going down, we have to change its direction again when it reaches its initial position. The rest of the swing definition is simply the same principle applied for the other 3 circles.
Putting this all together, this code draws 4 circles and swing them perfectly from their initial position to the 4 edges of the screen:
# ---------- Packages and Inits ---------- import pygame from pygame.locals import * pygame.init() # ---------- Settings ---------- SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 FPS = 60 CIRCLE_RADIUS = 70 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN) clock = pygame.time.Clock() SPEED = 2 # ---------- Resources (Colors) ---------- BLACK = (000,000,000) WHITE = (255,255,255) # ---------- Classes ---------- class Circle: def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"): self.rect = pygame.Rect(0, 0, 2*radius, 2*radius) self.rect.centerx = x self.rect.centery = y self.radius = radius self.thick = thick self.color = color self.speed = speed self.position = position if speed >= 0: self.directionX = 'right' self.direction = 'up' else: self.directionX = 'left' self.direction = 'down' def draw(self, screen): pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick) def swing(self): if self.position == "top": self.rect.y -= self.speed if self.rect.top <= 0 and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "bot": self.rect.y -= self.speed if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "left": self.rect.x -= self.speed if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed elif self.rect.left <= 420 and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed if self.position == "right": self.rect.x -= self.speed if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed # ---------- Main ---------- def main(): # Settings screen_rect = screen.get_rect() game_over = False # Start with 4 circles all_circles = [ Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ), Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ), Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"), Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )] while not game_over: screen.fill(WHITE) # Circles for c in all_circles: c.draw(screen) # Place circles on the screen c.swing() # Move circles from center to edges, and from edges to center, back and forth pygame.display.update() clock.tick(FPS) main()
Dropping Enemies
Now that we have our 4 circles swinging properly, the next step is to drop the shapes I mentioned at the beginning:
- Green triangles from the top
- Pink squares from left
- Red circles from right
- Blue crosses from the bottom
These shapes have to drop from the 4 different edges of the screen, with the following rules to begin with:
- Once an enemy reaches the middle, it disappears
- Enemies are created every 400 ms
- Enemies position must be random
- On the screen, there should be a maximum of 4 enemies
Later, we’ll modify these variables based on the game difficulty. As the game gets harder, there will be more enemies at the same time and they’ll drop faster.
For this I added:
- a constant called ENEMY_SIZE
- a class called Enemies
- a for loop in the main() definition
- images of the 4 different shapes
Putting this all together, the following code will show 4 circles swinging from the middle to the edges, and some shapes falling from the 4 edges to the middle of the screen.
# ---------- Packages and Inits ---------- import pygame, random, math, sys, os, pickle, time from tkinter import * from pygame.locals import * from random import choice pygame.init() # ---------- Settings ---------- SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 FPS = 60 CIRCLE_RADIUS = 70 ENEMY_SIZE = 40 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN) clock = pygame.time.Clock() SPEED = 2 # ---------- Resources (Images & Sounds) ---------- if getattr(sys, 'frozen', False): current_path = os.path.dirname(sys.executable) else: current_path = os.path.dirname(os.path.realpath(__file__)) # PS2 images button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert() button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert() button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert() button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert() # ---------- Resources (Colors) ---------- BLACK = (000,000,000) WHITE = (255,255,255) # ---------- Classes ---------- class Enemies: def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"): self.rect = button_circle_red_tiny.get_rect() if ( x == 0 and y == 0 ): self.randomise() self.rect.centerx = x self.rect.centery = y self.speed = speed self.calcDirection() self.position = position def calcDirection( self ): self.x_float = 1.0 * self.rect.centerx self.y_float = 1.0 * self.rect.centery # Determine direction vector from (x,y) to the centre of the screen self.position_vector = pygame.math.Vector2( self.x_float, self.y_float ) self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float ) self.velocity_vector = self.velocity_vector.normalize() def update( self ): x_delta = self.speed * self.velocity_vector[0] y_delta = self.speed * self.velocity_vector[1] self.x_float += x_delta self.y_float += y_delta self.rect.centerx = int( self.x_float ) self.rect.centery = int( self.y_float ) def draw( self, screen): if self.position == "right": screen.blit(button_circle_red_tiny, self.rect ) elif self.position == "left": screen.blit(button_square_pink_tiny, self.rect ) elif self.position == "top": screen.blit(button_triangle_green_tiny, self.rect ) else: screen.blit(button_cross_blue_tiny, self.rect ) def reachedPoint( self, x, y ): return self.rect.collidepoint( x, y ) def randomise( self ): self.rect.centerx = SCREEN_WIDTH//2 self.rect.centery = SCREEN_HEIGHT//2 side = random.randint( 0, 4 ) if ( side == 0 ): self.rect.centery = SCREEN_HEIGHT self.position= "bot" elif ( side == 1 ): self.rect.centery = 0 self.position= "top" elif ( side == 2 ): self.rect.centerx = 420 self.position= "left" else: self.rect.centerx = SCREEN_WIDTH - 420 self.position= "right" self.calcDirection() class Circle: def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"): self.rect = pygame.Rect(0, 0, 2*radius, 2*radius) self.rect.centerx = x self.rect.centery = y self.radius = radius self.thick = thick self.color = color self.speed = speed self.position = position if speed >= 0: self.directionX = 'right' self.direction = 'up' else: self.directionX = 'left' self.direction = 'down' def draw(self, screen): pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick) def swing(self): if self.position == "top": self.rect.y -= self.speed if self.rect.top <= 0 and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "bot": self.rect.y -= self.speed if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "left": self.rect.x -= self.speed if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed elif self.rect.left <= 420 and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed if self.position == "right": self.rect.x -= self.speed if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed # ---------- Main ---------- def main(): # Settings screen_rect = screen.get_rect() game_over = False interval = 400 simultaneity = 4 pygame.time.set_timer(USEREVENT+1, interval) # We create an empty list of enemies, as we want them to drop randomly all_enemies = [] # Start with 4 circles all_circles = [ Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ), Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ), Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"), Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )] while not game_over: screen.fill(WHITE) # Circles for c in all_circles: c.draw(screen) # Place circles on the screen c.swing() # Move circles from center to edges, and from edges to center, back and forth # Enemies for e in all_enemies: e.draw(screen) # Place enemies on the screen e.update() # Move enemies from the edges of the screen towards the center if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated all_enemies.remove(e) # Scoring and lifepoints systems for event in pygame.event.get(): if event.type == USEREVENT+1 and len(all_enemies) < simultaneity: all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = BLACK, position = "top")) appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top for e in appended_enemies: e.randomise() pygame.display.update() clock.tick(FPS) main()
Scoring System and Lives
# ---------- Packages and Inits ---------- import pygame, random, math, sys, os, pickle, time from tkinter import * from pygame.locals import * from random import choice pygame.init() # ---------- Settings ---------- SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 FPS = 60 CIRCLE_RADIUS = 70 ENEMY_SIZE = 40 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN) clock = pygame.time.Clock() myFont = pygame.font.SysFont("monospace", 25 , bold = True) SPEED = 2 # ---------- Resources (Images & Sounds) ---------- if getattr(sys, 'frozen', False): current_path = os.path.dirname(sys.executable) else: current_path = os.path.dirname(os.path.realpath(__file__)) # PS2 images button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert() button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert() button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert() button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert() # ---------- Resources (Colors) ---------- RED = (255,000,000) BLUE = (000,000,255) YELLOW = (255,255,000) GREEN = (000,128,000) BLACK = (000,000,000) WHITE = (255,255,255) LIGHT_GREEN = (144,238,144) LIGHT_RED = (255,0,128) # ---------- Classes ---------- class Enemies: def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"): self.rect = button_circle_red_tiny.get_rect() if ( x == 0 and y == 0 ): self.randomise() self.rect.centerx = x self.rect.centery = y self.speed = speed self.calcDirection() self.position = position def calcDirection( self ): self.x_float = 1.0 * self.rect.centerx self.y_float = 1.0 * self.rect.centery # Determine direction vector from (x,y) to the centre of the screen self.position_vector = pygame.math.Vector2( self.x_float, self.y_float ) self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float ) self.velocity_vector = self.velocity_vector.normalize() def update( self ): x_delta = self.speed * self.velocity_vector[0] y_delta = self.speed * self.velocity_vector[1] self.x_float += x_delta self.y_float += y_delta self.rect.centerx = int( self.x_float ) self.rect.centery = int( self.y_float ) def draw( self, screen): if self.position == "right": screen.blit(button_circle_red_tiny, self.rect ) elif self.position == "left": screen.blit(button_square_pink_tiny, self.rect ) elif self.position == "top": screen.blit(button_triangle_green_tiny, self.rect ) else: screen.blit(button_cross_blue_tiny, self.rect ) def reachedPoint( self, x, y ): return self.rect.collidepoint( x, y ) def randomise( self ): self.rect.centerx = SCREEN_WIDTH//2 self.rect.centery = SCREEN_HEIGHT//2 side = random.randint( 0, 4 ) if ( side == 0 ): self.rect.centery = SCREEN_HEIGHT self.position= "bot" elif ( side == 1 ): self.rect.centery = 0 self.position= "top" elif ( side == 2 ): self.rect.centerx = 420 self.position= "left" else: self.rect.centerx = SCREEN_WIDTH - 420 self.position= "right" self.calcDirection() class Circle: def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"): self.rect = pygame.Rect(0, 0, 2*radius, 2*radius) self.rect.centerx = x self.rect.centery = y self.radius = radius self.thick = thick self.color = color self.speed = speed self.position = position if speed >= 0: self.directionX = 'right' self.direction = 'up' else: self.directionX = 'left' self.direction = 'down' def draw(self, screen): pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick) def swing(self): if self.position == "top": self.rect.y -= self.speed if self.rect.top <= 0 and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "bot": self.rect.y -= self.speed if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "left": self.rect.x -= self.speed if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed elif self.rect.left <= 420 and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed if self.position == "right": self.rect.x -= self.speed if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed def isCollision(self, enemyX, enemyY, circleX, circleY): distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2))) if distance < (CIRCLE_RADIUS+ENEMY_SIZE//2): return True else: return False # ---------- Main ---------- def main(): # Settings screen_rect = screen.get_rect() game_over = False score = 0 lifes = 5 interval = 400 simultaneity = 4 pygame.time.set_timer(USEREVENT+1, interval) # We create an empty list of enemies, as we want them to drop randomly all_enemies = [] # Start with 4 circles all_circles = [ Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ), Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ), Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"), Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )] while not game_over: screen.fill(WHITE) # Variables interval = 400 simultaneity = 4 # Circles for c in all_circles: c.draw(screen) # Place circles on the screen c.swing() # Move circles from center to edges, and from edges to center, back and forth # Enemies for e in all_enemies: e.draw(screen) # Place enemies on the screen e.update() # Move enemies from the edges of the screen towards the center if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated lifes -=1 all_enemies.remove(e) # Scoring and lifepoints systems for event in pygame.event.get(): # Cheats if event.type == pygame.KEYDOWN: if event.key == pygame.K_g: lifes += 5 if event.key == pygame.K_t: score -= 100 if event.type == USEREVENT+1 and len(all_enemies) < simultaneity: all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top")) appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top for e in appended_enemies: e.randomise() for c in all_circles: if event.type == pygame.KEYDOWN: # LEFT if event.key == pygame.K_LEFT and c.position == "left": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # RIGHT if event.key == pygame.K_RIGHT and c.position == "right": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # TOP if event.key == pygame.K_UP and c.position == "top": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # BOT if event.key == pygame.K_DOWN and c.position == "bot": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # Game Over condition if lifes < 0: game_over = True # Score / Lifes place_text = SCREEN_WIDTH-250 print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, BLACK) screen.blit(print_lifes, (place_text, 10)) print_score = myFont.render("Score:" + str(round(score)), 1, BLACK) screen.blit(print_score, (place_text, 50)) pygame.display.update() clock.tick(FPS) main()
Levels, Menus and Sounds
# ---------- Packages and Inits ---------- import pygame, random, math, sys, os, pickle, time from tkinter import * from pygame.locals import * from random import choice pygame.init() # ---------- Settings ---------- SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 FPS = 60 SPEED = 2 CIRCLE_RADIUS = 70 ENEMY_SIZE = 40 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN) clock = pygame.time.Clock() pause = False over = False myFont = pygame.font.SysFont("monospace", 25 , bold = True) # ---------- Resources (Images & Sounds) ---------- if getattr(sys, 'frozen', False): current_path = os.path.dirname(sys.executable) else: current_path = os.path.dirname(os.path.realpath(__file__)) # Background Images background_image_Start = pygame.image.load(os.path.join(current_path,"Start.jpg")).convert() background_image_Pause = pygame.image.load(os.path.join(current_path,"Pause.jpg")).convert() background_image_Game_Over = pygame.image.load(os.path.join(current_path,"Over.jpg")).convert() background_image_In_Game = pygame.image.load(os.path.join(current_path,"InGame.png")).convert() # Sounds Re5HealingSoundEffect = pygame.mixer.Sound('Re5HealingSoundEffect.wav') Halo3Deaths1 = pygame.mixer.Sound('Halo3Deaths1.wav') # PS2 images button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert() button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert() button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert() button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert() # ---------- Resources (Colors) ---------- RED = (255,000,000) BLUE = (000,000,255) YELLOW = (255,255,000) GREEN = (000,128,000) BLACK = (000,000,000) WHITE = (255,255,255) LIGHT_GREEN = (144,238,144) LIGHT_RED = (255,0,128) # ---------- Classes ---------- class Enemies: def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"): self.rect = button_circle_red_tiny.get_rect() if ( x == 0 and y == 0 ): self.randomise() self.rect.centerx = x self.rect.centery = y self.speed = speed self.calcDirection() self.position = position def calcDirection( self ): self.x_float = 1.0 * self.rect.centerx self.y_float = 1.0 * self.rect.centery # Determine direction vector from (x,y) to the centre of the screen self.position_vector = pygame.math.Vector2( self.x_float, self.y_float ) self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float ) self.velocity_vector = self.velocity_vector.normalize() def update( self ): x_delta = self.speed * self.velocity_vector[0] y_delta = self.speed * self.velocity_vector[1] self.x_float += x_delta self.y_float += y_delta self.rect.centerx = int( self.x_float ) self.rect.centery = int( self.y_float ) def draw( self, screen): if self.position == "right": screen.blit(button_circle_red_tiny, self.rect ) elif self.position == "left": screen.blit(button_square_pink_tiny, self.rect ) elif self.position == "top": screen.blit(button_triangle_green_tiny, self.rect ) else: screen.blit(button_cross_blue_tiny, self.rect ) def reachedPoint( self, x, y ): return self.rect.collidepoint( x, y ) def randomise( self ): self.rect.centerx = SCREEN_WIDTH//2 self.rect.centery = SCREEN_HEIGHT//2 side = random.randint( 0, 4 ) if ( side == 0 ): self.rect.centery = SCREEN_HEIGHT self.position= "bot" elif ( side == 1 ): self.rect.centery = 0 self.position= "top" elif ( side == 2 ): self.rect.centerx = 420 self.position= "left" else: self.rect.centerx = SCREEN_WIDTH - 420 self.position= "right" self.calcDirection() def set_speed( self, difficulty): if difficulty == 1 : self.speed = 2 elif difficulty == 2 : self.speed = 2.25 elif difficulty == 3 : self.speed = 2.5 elif difficulty == 4 : self.speed = 2.75 elif difficulty == 5 : self.speed = 3 elif difficulty == 6 : self.speed = 3.25 return self.speed class Circle: def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"): self.rect = pygame.Rect(0, 0, 2*radius, 2*radius) self.rect.centerx = x self.rect.centery = y self.radius = radius self.thick = thick self.color = color self.speed = speed self.position = position if speed >= 0: self.directionX = 'right' self.direction = 'up' else: self.directionX = 'left' self.direction = 'down' def draw(self, screen): pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick) def swing(self): if self.position == "top": self.rect.y -= self.speed if self.rect.top <= 0 and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "bot": self.rect.y -= self.speed if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "left": self.rect.x -= self.speed if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed elif self.rect.left <= 420 and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed if self.position == "right": self.rect.x -= self.speed if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed def isCollision(self, enemyX, enemyY, circleX, circleY): distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2))) if distance < (CIRCLE_RADIUS+ENEMY_SIZE//2): return True else: return False # ---------- Other Functions ---------- """ Difficulty sets 4 variables : enemy speed (in enemy class) interval between enemy drops maximum number of enemies drop in a row from the same lane simultaneity (drops at the same time from different lanes)""" def set_difficulty(score): if score < 25 : difficulty = 1 elif score < 50 : difficulty = 2 elif score < 100: difficulty = 3 elif score < 250: difficulty = 4 elif score < 500: difficulty = 5 else: difficulty = 6 return difficulty def set_interval(difficulty): if difficulty == 1 : interval = 400 elif difficulty == 2 : interval = 250 elif difficulty == 3 : interval = 200 elif difficulty == 4 : interval = 150 elif difficulty == 5 : interval = 100 elif difficulty == 6 : interval = 75 return interval def set_simultaneity(difficulty): if difficulty == 1 : simultaneity = 3 elif difficulty == 2 : simultaneity = 4 elif difficulty == 3 : simultaneity = 5 else : simultaneity = 6 return simultaneity # ---------- Welcome screen / Pause / Game Over ---------- def text_objects(text, font): textSurface = font.render(text, True, WHITE) return textSurface, textSurface.get_rect() def button(msg,x,y,w,h,ic,ac,action=None): mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() if x+w > mouse[0] > x and y+h > mouse[1] > y: pygame.draw.rect(screen, ac,(x,y,w,h)) if click[0] == 1 and action != None: action() else: pygame.draw.rect(screen, ic,(x,y,w,h)) smallText = pygame.font.SysFont("comicsansms",20) textSurf, textRect = text_objects(msg, smallText) textRect.center = ( (x+(w/2)), (y+(h/2)) ) screen.blit(textSurf, textRect) def game_intro(): intro = True # Music pygame.mixer.music.load('in_game.ogg') pygame.mixer.music.play() over == False while intro: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() screen.blit(background_image_Start, [0, 0]) button("Go !",700,700,100,50,GREEN,LIGHT_GREEN,main) button("Quit",1200,700,100,50,RED,LIGHT_RED,quit_game) print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE) screen.blit(print_myself, (10, 1050)) pygame.display.update() clock.tick(15) def unpause(): global pause pygame.mixer.music.unpause() pause = False def paused(): pygame.mixer.music.pause() screen.blit(background_image_Pause, [0, 0]) largeText = pygame.font.SysFont("comicsansms",50) TextSurf, TextRect = text_objects("Paused", largeText) TextRect.center = (380,200) screen.blit(TextSurf, TextRect) while pause: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() if event.type == pygame.KEYDOWN: if event.key == pygame.K_p: unpause() if event.key == pygame.K_a: pygame.quit() button("Continue",150,300,100,50,GREEN,LIGHT_GREEN,unpause) button("Quit",500,300,100,50,RED,LIGHT_RED,quit_game) pygame.display.update() clock.tick(15) def quit_game(): pygame.quit() # ---------- Main ---------- def main(): global pause # Settings screen_rect = screen.get_rect() game_over = False score = 0 lifes = 5 difficulty = set_difficulty(score) interval = set_interval(difficulty) pygame.time.set_timer(USEREVENT+1, interval) # We create an empty list of enemies, as we want them to drop randomly all_enemies = [] # Music music_end = pygame.USEREVENT+2 pygame.mixer.music.set_endevent(music_end) pygame.mixer.music.load('in_game.ogg') pygame.mixer.music.play() # Start with 4 circles all_circles = [ Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ), Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ), Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"), Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )] while not game_over: screen.blit(background_image_In_Game, [0, 0]) # Variables difficulty = set_difficulty(score) interval = set_interval(difficulty) simultaneity = set_simultaneity(difficulty) # Circles for c in all_circles: c.draw(screen) # Place circles on the screen c.swing() # Move circles from center to edges, and from edges to center, back and forth # Enemies for e in all_enemies: e.draw(screen) # Place enemies on the screen e.set_speed(difficulty) # Set speed difficulty for enemies e.update() # Move enemies from the edges of the screen towards the center if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated lifes -=1 all_enemies.remove(e) # Scoring and lifepoints systems for event in pygame.event.get(): # Music if event.type == music_end: pygame.mixer.music.load('Halo3OneFinalEffort.mp3') pygame.mixer.music.play() # Allow Pause and Quit if event.type == pygame.KEYDOWN: if event.key == pygame.K_p: pause = True paused() if event.key == pygame.K_a: quit_game() # Cheats if event.type == pygame.KEYDOWN: if event.key == pygame.K_g: pygame.mixer.Sound.play(Re5HealingSoundEffect) lifes += 5 if event.key == pygame.K_t: score -= 100 if event.type == USEREVENT+1 and len(all_enemies) < simultaneity: all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top")) appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top for e in appended_enemies: e.randomise() for c in all_circles: if event.type == pygame.KEYDOWN: # LEFT if event.key == pygame.K_LEFT and c.position == "left": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # RIGHT if event.key == pygame.K_RIGHT and c.position == "right": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # TOP if event.key == pygame.K_UP and c.position == "top": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # BOT if event.key == pygame.K_DOWN and c.position == "bot": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # Game Over condition if lifes < 0: pygame.mixer.Sound.play(Halo3Deaths1) you_lose() next_level = 25 if difficulty == 2 : next_level = 50 elif difficulty == 3 : next_level = 100 elif difficulty == 4 : next_level = 250 elif difficulty == 5 : next_level = 500 elif difficulty == 6 : next_level = 1000 # Score / Lifes / Number of Enemies / Next Level place_text = SCREEN_WIDTH-250 print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, BLACK) screen.blit(print_lifes, (place_text, 10)) print_score = myFont.render("Score:" + str(round(score)), 1, BLACK) screen.blit(print_score, (place_text, 50)) print_next_level = myFont.render("Next Level:" + str(round(next_level)), 1, BLACK) screen.blit(print_next_level, (place_text, 90)) pygame.display.update() clock.tick(FPS) def you_lose(): global over over = True pygame.mixer.music.load(os.path.join(current_path,"Re5DreamyLoops.mp3")) pygame.mixer.music.play(-1) while over: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() screen.blit(background_image_Game_Over, [0, 0]) y = SCREEN_HEIGHT-100 button("Retry !",1200,y,100,50,GREEN,LIGHT_GREEN,main) button("Menu",1350,y,100,50,BLUE,LIGHT_GREEN,game_intro) button("Quit",1500,y,100,50,RED,LIGHT_RED,quit_game) print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE) screen.blit(print_myself, (10, 1050)) pygame.display.update() clock.tick(15) game_intro()
Scores Leaderboard
This is the last step of this project. I wanted to add the ability to score your scores. Now, when you launch the game, you’re prompted your name. There is a scores file that will store the score of each player.
With this being done, the code is now complete. I ended up with this python file to run the entire game.
# ---------- Packages and Inits ---------- import pygame, random, math, sys, os, pickle, time from tkinter import * from pygame.locals import * from random import choice pygame.init() # ---------- Users & Scores ---------- """ This section allows me to : Prompt for a user name Display his best score Save his score if he beats his current best score """ scores_file = "scores" def get_scores(): if os.path.exists(scores_file): file_scores = open(scores_file, "rb") my_depickler = pickle.Unpickler(file_scores) scores = my_depickler.load() file_scores.close() else: scores = {} return scores def save_scores(scores): file_scores = open(scores_file, "wb") my_pickler = pickle.Pickler(file_scores) my_pickler.dump(scores) file_scores.close() def get_user(): my_user = input("Write your Name: ") my_user = my_user.capitalize() if not my_user.isalnum() or len(my_user)<4: print("This name is invalid.") return get_user() else: return my_user scores = get_scores() utilisateur = get_user() if utilisateur not in scores.keys(): scores[utilisateur] = 0 # ---------- Settings ---------- SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 FPS = 60 SPEED = 2 CIRCLE_RADIUS = 70 ENEMY_SIZE = 40 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.FULLSCREEN) clock = pygame.time.Clock() pause = False over = False myFont = pygame.font.SysFont("monospace", 25 , bold = True) last_score = 0 # ---------- Resources (Images & Sounds) ---------- if getattr(sys, 'frozen', False): current_path = os.path.dirname(sys.executable) else: current_path = os.path.dirname(os.path.realpath(__file__)) # Background Images background_image_Start = pygame.image.load(os.path.join(current_path,"Start.jpg")).convert() background_image_Pause = pygame.image.load(os.path.join(current_path,"Pause.jpg")).convert() background_image_Game_Over = pygame.image.load(os.path.join(current_path,"Over.jpg")).convert() background_image_In_Game = pygame.image.load(os.path.join(current_path,"InGame.png")).convert() # Sounds Re5HealingSoundEffect = pygame.mixer.Sound('Re5HealingSoundEffect.wav') Halo3Deaths1 = pygame.mixer.Sound('Halo3Deaths1.wav') # PS2 images button_circle_red_tiny = pygame.image.load('button_circle_red_tiny.png').convert() button_square_pink_tiny = pygame.image.load('button_square_pink_tiny.png').convert() button_triangle_green_tiny = pygame.image.load('button_triangle_green_tiny.png').convert() button_cross_blue_tiny = pygame.image.load('button_cross_blue_tiny.png').convert() # ---------- Resources (Colors) ---------- RED = (255,000,000) BLUE = (000,000,255) YELLOW = (255,255,000) GREEN = (000,128,000) BLACK = (000,000,000) WHITE = (255,255,255) LIGHT_GREEN = (144,238,144) LIGHT_RED = (255,0,128) # ---------- Classes ---------- class Enemies: def __init__( self, x, y, size=ENEMY_SIZE, thick=0, color=BLACK, speed=2, position="top"): self.rect = button_circle_red_tiny.get_rect() if ( x == 0 and y == 0 ): self.randomise() self.rect.centerx = x self.rect.centery = y self.speed = speed self.calcDirection() self.position = position def calcDirection( self ): self.x_float = 1.0 * self.rect.centerx self.y_float = 1.0 * self.rect.centery # Determine direction vector from (x,y) to the centre of the screen self.position_vector = pygame.math.Vector2( self.x_float, self.y_float ) self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float ) self.velocity_vector = self.velocity_vector.normalize() def update( self ): x_delta = self.speed * self.velocity_vector[0] y_delta = self.speed * self.velocity_vector[1] self.x_float += x_delta self.y_float += y_delta self.rect.centerx = int( self.x_float ) self.rect.centery = int( self.y_float ) def draw( self, screen): if self.position == "right": screen.blit(button_circle_red_tiny, self.rect ) elif self.position == "left": screen.blit(button_square_pink_tiny, self.rect ) elif self.position == "top": screen.blit(button_triangle_green_tiny, self.rect ) else: screen.blit(button_cross_blue_tiny, self.rect ) def reachedPoint( self, x, y ): return self.rect.collidepoint( x, y ) def randomise( self ): self.rect.centerx = SCREEN_WIDTH//2 self.rect.centery = SCREEN_HEIGHT//2 side = random.randint( 0, 4 ) if ( side == 0 ): self.rect.centery = SCREEN_HEIGHT self.position= "bot" elif ( side == 1 ): self.rect.centery = 0 self.position= "top" elif ( side == 2 ): self.rect.centerx = 420 self.position= "left" else: self.rect.centerx = SCREEN_WIDTH - 420 self.position= "right" self.calcDirection() def set_speed( self, difficulty): if difficulty == 1 : self.speed = 2 elif difficulty == 2 : self.speed = 2.25 elif difficulty == 3 : self.speed = 2.5 elif difficulty == 4 : self.speed = 2.75 elif difficulty == 5 : self.speed = 3 elif difficulty == 6 : self.speed = 3.25 return self.speed class Circle: def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=7, color=BLACK, speed=SPEED, position="top"): self.rect = pygame.Rect(0, 0, 2*radius, 2*radius) self.rect.centerx = x self.rect.centery = y self.radius = radius self.thick = thick self.color = color self.speed = speed self.position = position if speed >= 0: self.directionX = 'right' self.direction = 'up' else: self.directionX = 'left' self.direction = 'down' def draw(self, screen): pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick) def swing(self): if self.position == "top": self.rect.y -= self.speed if self.rect.top <= 0 and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "bot": self.rect.y -= self.speed if self.rect.top <= int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up': self.direction = 'down' self.speed = -self.speed elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down': self.direction = 'up' self.speed = -self.speed if self.position == "left": self.rect.x -= self.speed if self.rect.right >= int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed elif self.rect.left <= 420 and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed if self.position == "right": self.rect.x -= self.speed if self.rect.left <= int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right': self.directionX = 'left' self.speed = -self.speed elif self.rect.right >= SCREEN_WIDTH - 420 and self.directionX == 'left': self.directionX = 'right' self.speed = -self.speed def isCollision(self, enemyX, enemyY, circleX, circleY): distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2))) if distance < (CIRCLE_RADIUS+ENEMY_SIZE//2): return True else: return False # ---------- Other Functions ---------- """ Difficulty sets 4 variables : enemy speed (in enemy class) interval between enemy drops maximum number of enemies drop in a row from the same lane simultaneity (drops at the same time from different lanes)""" def set_difficulty(score): if score < 25 : difficulty = 1 elif score < 50 : difficulty = 2 elif score < 100: difficulty = 3 elif score < 250: difficulty = 4 elif score < 500: difficulty = 5 else: difficulty = 6 return difficulty def set_interval(difficulty): if difficulty == 1 : interval = 400 elif difficulty == 2 : interval = 250 elif difficulty == 3 : interval = 200 elif difficulty == 4 : interval = 150 elif difficulty == 5 : interval = 100 elif difficulty == 6 : interval = 75 return interval def set_simultaneity(difficulty): if difficulty == 1 : simultaneity = 3 elif difficulty == 2 : simultaneity = 4 elif difficulty == 3 : simultaneity = 5 else : simultaneity = 6 return simultaneity # ---------- Welcome screen / Pause / Game Over ---------- def text_objects(text, font): textSurface = font.render(text, True, WHITE) return textSurface, textSurface.get_rect() def button(msg,x,y,w,h,ic,ac,action=None): mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() if x+w > mouse[0] > x and y+h > mouse[1] > y: pygame.draw.rect(screen, ac,(x,y,w,h)) if click[0] == 1 and action != None: action() else: pygame.draw.rect(screen, ic,(x,y,w,h)) smallText = pygame.font.SysFont("comicsansms",20) textSurf, textRect = text_objects(msg, smallText) textRect.center = ( (x+(w/2)), (y+(h/2)) ) screen.blit(textSurf, textRect) def game_intro(): intro = True # Music pygame.mixer.music.load('in_game.ogg') pygame.mixer.music.play() over == False while intro: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() screen.blit(background_image_Start, [0, 0]) button("Go !",700,700,100,50,GREEN,LIGHT_GREEN,main) button("Quit",1200,700,100,50,RED,LIGHT_RED,quit_game) # Show the user best score print_score = myFont.render("Your best score : {0}".format(round(scores[utilisateur])), 1, WHITE) screen.blit(print_score, (10, 50)) print_user = myFont.render("Current Player : " + utilisateur, 1, WHITE) screen.blit(print_user, (10, 10)) print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE) screen.blit(print_myself, (10, 1050)) pygame.display.update() clock.tick(15) def unpause(): global pause pygame.mixer.music.unpause() pause = False def paused(): pygame.mixer.music.pause() screen.blit(background_image_Pause, [0, 0]) largeText = pygame.font.SysFont("comicsansms",50) TextSurf, TextRect = text_objects("Paused", largeText) TextRect.center = (380,200) screen.blit(TextSurf, TextRect) while pause: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() if event.type == pygame.KEYDOWN: if event.key == pygame.K_p: unpause() if event.key == pygame.K_a: pygame.quit() button("Continue",150,300,100,50,GREEN,LIGHT_GREEN,unpause) button("Quit",500,300,100,50,RED,LIGHT_RED,quit_game) pygame.display.update() clock.tick(15) def quit_game(): pygame.quit() # ---------- Main ---------- def main(): global pause global last_score # Settings screen_rect = screen.get_rect() game_over = False score = 0 lifes = 5 difficulty = set_difficulty(score) interval = set_interval(difficulty) pygame.time.set_timer(USEREVENT+1, interval) # We create an empty list of enemies, as we want them to drop randomly all_enemies = [] # Music music_end = pygame.USEREVENT+2 pygame.mixer.music.set_endevent(music_end) pygame.mixer.music.load('in_game.ogg') pygame.mixer.music.play() # Start with 4 circles all_circles = [ Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ), Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ), Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"), Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )] while not game_over: screen.blit(background_image_In_Game, [0, 0]) # Variables difficulty = set_difficulty(score) interval = set_interval(difficulty) simultaneity = set_simultaneity(difficulty) # Circles for c in all_circles: c.draw(screen) # Place circles on the screen c.swing() # Move circles from center to edges, and from edges to center, back and forth # Enemies for e in all_enemies: e.draw(screen) # Place enemies on the screen e.set_speed(difficulty) # Set speed difficulty for enemies e.update() # Move enemies from the edges of the screen towards the center if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated lifes -=1 all_enemies.remove(e) # Scoring and lifepoints systems for event in pygame.event.get(): # Music if event.type == music_end: pygame.mixer.music.load('Halo3OneFinalEffort.mp3') pygame.mixer.music.play() # Allow Pause and Quit if event.type == pygame.KEYDOWN: if event.key == pygame.K_p: pause = True paused() if event.key == pygame.K_a: quit_game() # Cheats if event.type == pygame.KEYDOWN: if event.key == pygame.K_g: pygame.mixer.Sound.play(Re5HealingSoundEffect) lifes += 5 if event.key == pygame.K_t: score -= 100 if event.type == USEREVENT+1 and len(all_enemies) < simultaneity: all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top")) appended_enemies = [e for e in all_enemies if e.y_float == 0] # Create a filtered list with all enemies at top for e in appended_enemies: e.randomise() for c in all_circles: if event.type == pygame.KEYDOWN: # LEFT if event.key == pygame.K_LEFT and c.position == "left": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # RIGHT if event.key == pygame.K_RIGHT and c.position == "right": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # TOP if event.key == pygame.K_UP and c.position == "top": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # BOT if event.key == pygame.K_DOWN and c.position == "bot": hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)] if not hits: lifes -=1 for e in hits: if len(hits) == 1: score +=1 if len(hits) == 2: score +=5/len(hits) if len(hits) == 3: score +=10/len(hits) all_enemies.remove(e) # Game Over condition if lifes < 0: pygame.mixer.Sound.play(Halo3Deaths1) if score > scores[utilisateur]: # Populate best score scores[utilisateur] = score last_score = score save_scores(scores) game_over = True you_lose() next_level = 25 if difficulty == 2 : next_level = 50 elif difficulty == 3 : next_level = 100 elif difficulty == 4 : next_level = 250 elif difficulty == 5 : next_level = 500 elif difficulty == 6 : next_level = 1000 # Score / Lifes / Number of Enemies / Next Level place_text = SCREEN_WIDTH-250 print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, BLACK) screen.blit(print_lifes, (place_text, 10)) print_score = myFont.render("Score:" + str(round(score)), 1, BLACK) screen.blit(print_score, (place_text, 50)) print_next_level = myFont.render("Next Level:" + str(round(next_level)), 1, BLACK) screen.blit(print_next_level, (place_text, 90)) pygame.display.update() clock.tick(FPS) def you_lose(): global over over = True pygame.mixer.music.load(os.path.join(current_path,"Re5DreamyLoops.mp3")) pygame.mixer.music.play(-1) while over: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() screen.blit(background_image_Game_Over, [0, 0]) y = SCREEN_HEIGHT-100 button("Retry !",1200,y,100,50,GREEN,LIGHT_GREEN,main) button("Menu",1350,y,100,50,BLUE,LIGHT_GREEN,game_intro) button("Quit",1500,y,100,50,RED,LIGHT_RED,quit_game) # Show the user best score print_score = myFont.render("Your best score : {0}".format(round(scores[utilisateur])), 1, WHITE) screen.blit(print_score, (10, 50)) print_user = myFont.render("Current Player : " + utilisateur, 1, WHITE) screen.blit(print_user, (10, 10)) print_last_score = myFont.render("You scored : " + str(round(last_score)), 1, WHITE) screen.blit(print_last_score, (10, 90)) print_myself = myFont.render("A game developed by : Nathan PRATS", 1, WHITE) screen.blit(print_myself, (10, 1050)) pygame.display.update() clock.tick(15) game_intro()
Creating the Executable
Now that the game is working properly, I needed a way to share it. I used a Python library called cx_Freeze to build it, which turned out to work well.
In the end I didn’t bother adding all files in the script. I added them manually later on the right folders.
I created a Setup.py file.
from cx_Freeze import setup, Executable includefiles = [ 'scores', 'InGame.png', 'Pause.jpg', 'Over.jpg', 'Start.jpg', 'in_game.ogg' ] includes = [] packages = ['pygame','random', 'math', 'sys','os','pickle'] target = Executable( script="SatelliteGame.py", icon="Icon.ico" ) setup( name = "Jak 3 - Satellite Game", author = 'Nathan PRATS', options = {'build_exe': {'includes':includes,'packages':packages,'include_files':includefiles}}, executables = [target] )
Then I ran a Windows Command: python Setup.py bdist_msi and migrated all resources to the build folder (audio, pictures) as it was tedious to write them in the Setup.py file.
At this point, I have now a folder with the executable. It’s available in the GitHub project. The game works well on any PC with a 1080p screen.