added a ton of comments

This commit is contained in:
ayabusa 2024-10-26 08:23:07 +02:00
parent 46923152e4
commit 0a00482562
2 changed files with 57 additions and 24 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 KiB

After

Width:  |  Height:  |  Size: 934 KiB

77
game.py
View File

@ -1,9 +1,8 @@
# Example file showing a basic pygame "game loop" # Here we import everything we need
import pygame import pygame
import math import math
import csv import csv
import random import random
import datetime
# pygame setup # pygame setup
pygame.init() pygame.init()
@ -12,11 +11,9 @@ clock = pygame.time.Clock()
running = True running = True
dt = 0 dt = 0
player_sprite = pygame.Rect(100,100,50,50)
mur = pygame.Rect(500,100,100,100)
class Waypoint: class Waypoint:
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
"""Create the corresponding waypoint according to the letter specified in name: str"""
self.name = name self.name = name
match name.capitalize(): match name.capitalize():
case "A": case "A":
@ -53,10 +50,12 @@ class Waypoint:
self.connection = ("A", "G") self.connection = ("A", "G")
def get_new_connected(self)-> object: def get_new_connected(self)-> object:
"""Return a new waypoint connected to the current one"""
letter = random.choice(self.connection) letter = random.choice(self.connection)
return Waypoint(letter) return Waypoint(letter)
def get_direction(self, pointA: object, pointB: object)-> str: def get_direction(self, pointA: object, pointB: object)-> str:
"""Helper that returns the direction: str from pointA: Waypoint, to pointB: Waypoint"""
a = pointA.name a = pointA.name
b = pointB.name b = pointB.name
if (a == "H" and b == "A") or (a == "G" and b == "E") or (a == "E" and b == "B") or (a == "F" and b == "D") or (a == "D" and b == "C"): if (a == "H" and b == "A") or (a == "G" and b == "E") or (a == "E" and b == "B") or (a == "F" and b == "D") or (a == "D" and b == "C"):
@ -68,10 +67,18 @@ class Waypoint:
elif (a == "B" and b == "A") or (a == "C" and b == "B") or (a == "D" and b == "E") or (a == "G" and b == "H") or (a == "F" and b == "G"): elif (a == "B" and b == "A") or (a == "C" and b == "B") or (a == "D" and b == "E") or (a == "G" and b == "H") or (a == "F" and b == "G"):
return "west" return "west"
else: else:
# If the direction is impossible
raise Exception("crash: POINT A and B impossible") raise Exception("crash: POINT A and B impossible")
class Pnj: class Pnj:
def __init__(self, x: int, y: int, sprite_path: str, direction: str, objectif: Waypoint, speed: int) -> None: def __init__(self, x: int, y: int, sprite_path: str, direction: str, objectif: Waypoint, speed: int) -> None:
"""Create a single pnj with the following attributes:
- x: int, x coordinate
- y: int, y coordinate
- sprite_path: str, the path to the sprite of the pnj
- direction: str, the direction can be "north", "south", "east", or "west"
- objectif: Waypoint, the goal of the pnj
- speed: int, the desired speed of the pnj"""
self.x = x self.x = x
self.y = y self.y = y
self.sprite = pygame.transform.scale_by(pygame.image.load(sprite_path), 3.0) self.sprite = pygame.transform.scale_by(pygame.image.load(sprite_path), 3.0)
@ -103,7 +110,7 @@ class Pnj:
def check_objectif(self): def check_objectif(self):
""" """
Vérifie que l'objectif n'est pas atteint et renvoie true si un l'est, s'occupe aussi de réassigner un nouvel objectig Vérifie que l'objectif n'est pas atteint et renvoie true si un l'est, s'occupe aussi de réassigner un nouvel objectif
""" """
match self.direction: match self.direction:
case "north": case "north":
@ -133,6 +140,7 @@ class Pnj:
return False return False
def kill(self): def kill(self):
"""Kill the PNJ by replacing it with a blood splatter"""
self.sprite = pygame.transform.scale_by(pygame.image.load("assets/blood.png"), 3.0) self.sprite = pygame.transform.scale_by(pygame.image.load("assets/blood.png"), 3.0)
self.alive = False self.alive = False
player.killcounter += 1 player.killcounter += 1
@ -146,32 +154,36 @@ class Pnj:
class Village: class Village:
def __init__(self, nb_pnj: int) -> None: def __init__(self, nb_pnj: int) -> None:
"""This class defines the village of pnj in it's globality and nb_pnj (int) determines how many pnj should be generated"""
self.liste_pnj = [] self.liste_pnj = []
# here we generate each pnj randomly and store it in liste_pnj
for i in range(nb_pnj): for i in range(nb_pnj):
start_waypoint = Waypoint(random.choice(("A", "B", "C", "D", "E", "F", "G", "H"))) start_waypoint = Waypoint(random.choice(("A", "B", "C", "D", "E", "F", "G", "H")))
objective_waypoint = start_waypoint.get_new_connected() objective_waypoint = start_waypoint.get_new_connected()
self.liste_pnj.append(Pnj(start_waypoint.x, start_waypoint.y, "assets/pnj/pnj"+str(random.randint(1,8))+".png", start_waypoint.get_direction(start_waypoint, objective_waypoint), objective_waypoint, random.randint(1, 4))) self.liste_pnj.append(Pnj(start_waypoint.x, start_waypoint.y, "assets/pnj/pnj"+str(random.randint(1,8))+".png", start_waypoint.get_direction(start_waypoint, objective_waypoint), objective_waypoint, random.randint(1, 4)))
def ajouter_pnj_random(self)-> None:
pass
def update_pnj(self)-> None: def update_pnj(self)-> None:
"""Make each pnj move a little bit"""
for p in self.liste_pnj: for p in self.liste_pnj:
p.avance() p.avance()
def get_village_sprites(self)->list: def get_village_sprites(self)->list:
"""Method that returns a list of tuple containing the coordinates and sprite of a pnj
The tuple is in this shape: (x, y, sprite)"""
village_sprites = [] village_sprites = []
for p in self.liste_pnj: for p in self.liste_pnj:
village_sprites.append((p.x, p.y, p.sprite)) village_sprites.append((p.x, p.y, p.sprite))
return village_sprites return village_sprites
def check_kill(self)->None: def check_kill(self)->None:
"""Check for each pnj if he is being killed by the player"""
player_rect = pygame.Rect(player.x-50, player.y-50, 100, 100) player_rect = pygame.Rect(player.x-50, player.y-50, 100, 100)
for p in self.liste_pnj: for p in self.liste_pnj:
if p.alive and player_rect.collidepoint(p.x, p.y): if p.alive and player_rect.collidepoint(p.x, p.y):
p.kill() p.kill()
def final_boss(self)->None: def final_boss(self)->None:
"""Called when all original pnj where killed, it create an army of really fast knight"""
for i in ("A", "B", "C", "D", "E", "F", "G", "H"): for i in ("A", "B", "C", "D", "E", "F", "G", "H"):
start_waypoint = Waypoint(i) start_waypoint = Waypoint(i)
objective_waypoint = start_waypoint.get_new_connected() objective_waypoint = start_waypoint.get_new_connected()
@ -179,6 +191,8 @@ class Village:
class Player: class Player:
def __init__(self)-> None: def __init__(self)-> None:
"""This class defines how the player can behave,
it should be created as soon as possible and only once in the game"""
self.x = 800 self.x = 800
self.y = 900 self.y = 900
self.mov_speed = 8 self.mov_speed = 8
@ -187,9 +201,11 @@ class Player:
self.killcounter = 0 self.killcounter = 0
def rotate(self, angle: math.degrees)-> None: def rotate(self, angle: math.degrees)-> None:
"""Rotate the player to the desired angle"""
self.angle += angle*self.rotate_speed self.angle += angle*self.rotate_speed
def move(self, mov: int)-> None: def move(self, mov: int)-> None:
"""Move the player in the direction he is looking or backward if mov is negative"""
new_x = self.x + self.mov_speed*mov*math.cos(math.radians(self.angle)) new_x = self.x + self.mov_speed*mov*math.cos(math.radians(self.angle))
new_y = self.y + self.mov_speed*mov*math.sin(math.radians(self.angle)) new_y = self.y + self.mov_speed*mov*math.sin(math.radians(self.angle))
if(game.test_collision(pygame.Rect(new_x-50, new_y-50, 100, 100), game.collisions) == False): if(game.test_collision(pygame.Rect(new_x-50, new_y-50, 100, 100), game.collisions) == False):
@ -203,6 +219,8 @@ class Player:
class Game: class Game:
def __init__(self): def __init__(self):
"""The main class that defines the game and it's behaviour,
it should be created only once during the game, as soon as possible"""
self.is_paused = False self.is_paused = False
self.pnj_number = 20 self.pnj_number = 20
pygame.font.init() pygame.font.init()
@ -211,23 +229,30 @@ class Game:
self.load_collisions() self.load_collisions()
def load_sprites(self): def load_sprites(self):
"""Useful to load sprites in memory, should be called as soon as possible to avoid errors"""
self.perso_sprite = pygame.image.load("assets/Horse2.png") self.perso_sprite = pygame.image.load("assets/Horse2.png")
self.map_sprite = pygame.image.load("assets/map3.png") self.map_sprite = pygame.image.load("assets/map3.png")
def load_collisions(self): def load_collisions(self):
"""Load all collisions stored in the sheets located in assets/collisions.csv,
should be called as soon as possible to avoid errors"""
self.collisions = [] self.collisions = []
with open('assets/collisions.csv', 'r', newline='') as file: with open('assets/collisions.csv', 'r', newline='') as file:
reader = csv.DictReader(file) reader = csv.DictReader(file)
for row in reader: for row in reader:
self.collisions.append(pygame.Rect(float(row["starting_point_x"]), float(row["starting_point_y"]), float(row["len_x"]), float(row["len_y"]))) self.collisions.append(pygame.Rect(float(row["starting_point_x"]), float(row["starting_point_y"]), float(row["len_x"]), float(row["len_y"])))
def test_collision(self, objet: pygame.Rect, l_collisions) -> bool: def test_collision(self, objet: pygame.Rect, l_collisions: list) -> bool:
"""This is an helper to check if there is collision between an object (pygame.Rect) and a list
of collisions containing pygame.Rect"""
for col in l_collisions: for col in l_collisions:
if objet.colliderect(col): if objet.colliderect(col):
return True return True
return False return False
def display_debug_text(self): def display_debug_text(self):
"""This is only for debuging and should be called in display_all() if in debug mode,
but not on the final game"""
angle_surface = self.font.render("angle: "+str(player.angle), False, (0, 0, 0)) angle_surface = self.font.render("angle: "+str(player.angle), False, (0, 0, 0))
x_surface = self.font.render("x: "+str(player.x), False, (0, 0, 0)) x_surface = self.font.render("x: "+str(player.x), False, (0, 0, 0))
y_surface = self.font.render("y: "+str(player.y), False, (0, 0, 0)) y_surface = self.font.render("y: "+str(player.y), False, (0, 0, 0))
@ -239,7 +264,10 @@ class Game:
#pygame.draw.rect(screen, "blue",pygame.Rect(player.x-50, player.y-50, 100, 100)) #pygame.draw.rect(screen, "blue",pygame.Rect(player.x-50, player.y-50, 100, 100))
def display_ui(self): def display_ui(self):
"""Handle the display of the killcounter and time,
should be called at each frames"""
color = "black" color = "black"
if player.killcounter < game.pnj_number: if player.killcounter < game.pnj_number:
kill_surface = self.font.render("Kills: "+str(player.killcounter)+"\\"+str(game.pnj_number), False, color) kill_surface = self.font.render("Kills: "+str(player.killcounter)+"\\"+str(game.pnj_number), False, color)
else: else:
@ -255,6 +283,7 @@ class Game:
screen.blit(time_surface, (740,0)) screen.blit(time_surface, (740,0))
def check_input(self): def check_input(self):
"""Here we check all possible user input and execut and action acordingly"""
keys = pygame.key.get_pressed() keys = pygame.key.get_pressed()
if keys[pygame.K_z]: if keys[pygame.K_z]:
_ = player.move(1) _ = player.move(1)
@ -266,31 +295,33 @@ class Game:
player.rotate(1) player.rotate(1)
def draw_player(self): def draw_player(self):
#pygame.draw.rect(screen, "red", pygame.Rect(player.x, player.y, 50, 50)) """Draw the player after applying a rotozoom in the center of the screen"""
img = pygame.transform.rotozoom(self.perso_sprite, -player.angle-90, 1) img = pygame.transform.rotozoom(self.perso_sprite, -player.angle-90, 1)
screen.blit(img, (540-img.get_rect().centerx, 360-img.get_rect().centery)) screen.blit(img, (540-img.get_rect().centerx, 360-img.get_rect().centery))
def draw_village(self): def draw_village(self):
"""Draw each sprite of the village"""
for s in village.get_village_sprites(): for s in village.get_village_sprites():
screen.blit(s[2], (540-player.x + s[0] - s[2].get_rect().centerx, 360-player.y + s[1] - s[2].get_rect().centery)) screen.blit(s[2], (540-player.x + s[0] - s[2].get_rect().centerx, 360-player.y + s[1] - s[2].get_rect().centery))
def display_all(self): def display_all(self):
# fill the screen with a color to wipe away anything from last frame """This is a helper that will render everything needed on the screen"""
# fill the screen with a color to wipe away anything from last frame and draw the map
screen.fill("gray") screen.fill("gray")
screen.blit(self.map_sprite, (540-player.x, 360-player.y)) screen.blit(self.map_sprite, (540-player.x, 360-player.y))
self.draw_village() self.draw_village()
self.draw_player() self.draw_player()
# We display it at the end so it's on top of all # We display them at the end so it's on top of all
# self.display_debug_text() #self.display_debug_text()
self.display_ui() self.display_ui()
# flip() the display to put your work on screen # flip() the display to put our work on screen
pygame.display.flip() pygame.display.flip()
def game_over(self)->None: def game_over(self)->None:
"""Create a gameover loop displaying the scoreboard and an image"""
screen.blit(pygame.image.load("assets/gameover.png"), (0,0)) screen.blit(pygame.image.load("assets/gameover.png"), (0,0))
seconds = pygame.time.get_ticks()/1000 seconds = pygame.time.get_ticks()/1000
seconds = seconds % (24 * 3600) seconds = seconds % (24 * 3600)
@ -308,32 +339,34 @@ class Game:
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
exit() exit()
# Here we create the principal objects that will always be used during the game
player = Player() player = Player()
game = Game() game = Game()
#pnj = Pnj(800, 900, "assets/MiniPeasant.png", "south", Waypoint("H"), 3)
village = Village(game.pnj_number) village = Village(game.pnj_number)
# start music # start music with -1 meaning infinite loop
pygame.mixer.music.load("assets/music.mp3") pygame.mixer.music.load("assets/music.mp3")
pygame.mixer.music.play(-1) pygame.mixer.music.play(-1)
# Infinite game loop
while running: while running:
# poll for events # poll for events
# pygame.QUIT event means the user clicked X to close your window # pygame.QUIT event means the user clicked X on the window
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
running = False running = False
# make all the pnj move and do their pnj stuff # make all the pnj move and do their pnj stuff
village.update_pnj() village.update_pnj()
# Here we check all inputs from the user and do what is requiered
game.check_input() game.check_input()
# Here we display everything to the screen
game.display_all() game.display_all()
# limits FPS to 60 # limits FPS to 60
# dt is delta time in seconds since last frame, used for framerate- # dt is delta time in seconds since last frame
# independent physics.
dt = clock.tick(60) / 1000 dt = clock.tick(60) / 1000
# cleanly quit the pygame instance
pygame.quit() pygame.quit()