diff --git a/assets/gameover.png b/assets/gameover.png index 4624aaa..89e02fb 100644 Binary files a/assets/gameover.png and b/assets/gameover.png differ diff --git a/game.py b/game.py index 9827214..c237791 100644 --- a/game.py +++ b/game.py @@ -1,9 +1,8 @@ -# Example file showing a basic pygame "game loop" +# Here we import everything we need import pygame import math import csv import random -import datetime # pygame setup pygame.init() @@ -12,11 +11,9 @@ clock = pygame.time.Clock() running = True dt = 0 -player_sprite = pygame.Rect(100,100,50,50) -mur = pygame.Rect(500,100,100,100) - class Waypoint: def __init__(self, name: str) -> None: + """Create the corresponding waypoint according to the letter specified in name: str""" self.name = name match name.capitalize(): case "A": @@ -53,10 +50,12 @@ class Waypoint: self.connection = ("A", "G") def get_new_connected(self)-> object: + """Return a new waypoint connected to the current one""" letter = random.choice(self.connection) return Waypoint(letter) def get_direction(self, pointA: object, pointB: object)-> str: + """Helper that returns the direction: str from pointA: Waypoint, to pointB: Waypoint""" a = pointA.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"): @@ -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"): return "west" else: + # If the direction is impossible raise Exception("crash: POINT A and B impossible") class Pnj: 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.y = y self.sprite = pygame.transform.scale_by(pygame.image.load(sprite_path), 3.0) @@ -103,7 +110,7 @@ class Pnj: 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: case "north": @@ -133,6 +140,7 @@ class Pnj: return False 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.alive = False player.killcounter += 1 @@ -146,32 +154,36 @@ class Pnj: class Village: 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 = [] + # here we generate each pnj randomly and store it in liste_pnj for i in range(nb_pnj): start_waypoint = Waypoint(random.choice(("A", "B", "C", "D", "E", "F", "G", "H"))) 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))) - - def ajouter_pnj_random(self)-> None: - pass - + def update_pnj(self)-> None: + """Make each pnj move a little bit""" for p in self.liste_pnj: p.avance() 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 = [] - for p in self.liste_pnj: + for p in self.liste_pnj: village_sprites.append((p.x, p.y, p.sprite)) return village_sprites 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) for p in self.liste_pnj: if p.alive and player_rect.collidepoint(p.x, p.y): p.kill() 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"): start_waypoint = Waypoint(i) objective_waypoint = start_waypoint.get_new_connected() @@ -179,6 +191,8 @@ class Village: class Player: 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.y = 900 self.mov_speed = 8 @@ -187,9 +201,11 @@ class Player: self.killcounter = 0 def rotate(self, angle: math.degrees)-> None: + """Rotate the player to the desired angle""" self.angle += angle*self.rotate_speed 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_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): @@ -203,6 +219,8 @@ class Player: class Game: 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.pnj_number = 20 pygame.font.init() @@ -211,23 +229,30 @@ class Game: self.load_collisions() 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.map_sprite = pygame.image.load("assets/map3.png") 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 = [] with open('assets/collisions.csv', 'r', newline='') as file: reader = csv.DictReader(file) 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"]))) - 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: if objet.colliderect(col): return True return False 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)) 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)) @@ -239,7 +264,10 @@ class Game: #pygame.draw.rect(screen, "blue",pygame.Rect(player.x-50, player.y-50, 100, 100)) def display_ui(self): + """Handle the display of the killcounter and time, + should be called at each frames""" color = "black" + if player.killcounter < game.pnj_number: kill_surface = self.font.render("Kills: "+str(player.killcounter)+"\\"+str(game.pnj_number), False, color) else: @@ -255,6 +283,7 @@ class Game: screen.blit(time_surface, (740,0)) def check_input(self): + """Here we check all possible user input and execut and action acordingly""" keys = pygame.key.get_pressed() if keys[pygame.K_z]: _ = player.move(1) @@ -266,31 +295,33 @@ class Game: player.rotate(1) 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) screen.blit(img, (540-img.get_rect().centerx, 360-img.get_rect().centery)) def draw_village(self): + """Draw each sprite of the village""" 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)) 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.blit(self.map_sprite, (540-player.x, 360-player.y)) self.draw_village() self.draw_player() - # We display it at the end so it's on top of all - # self.display_debug_text() - + # We display them at the end so it's on top of all + #self.display_debug_text() self.display_ui() - # flip() the display to put your work on screen + # flip() the display to put our work on screen pygame.display.flip() 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)) seconds = pygame.time.get_ticks()/1000 seconds = seconds % (24 * 3600) @@ -308,32 +339,34 @@ class Game: if event.type == pygame.QUIT: exit() +# Here we create the principal objects that will always be used during the game player = Player() game = Game() -#pnj = Pnj(800, 900, "assets/MiniPeasant.png", "south", Waypoint("H"), 3) village = Village(game.pnj_number) -# start music +# start music with -1 meaning infinite loop pygame.mixer.music.load("assets/music.mp3") pygame.mixer.music.play(-1) - +# Infinite game loop while running: # 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(): if event.type == pygame.QUIT: running = False # make all the pnj move and do their pnj stuff village.update_pnj() + # Here we check all inputs from the user and do what is requiered game.check_input() + # Here we display everything to the screen game.display_all() # limits FPS to 60 - # dt is delta time in seconds since last frame, used for framerate- - # independent physics. + # dt is delta time in seconds since last frame dt = clock.tick(60) / 1000 +# cleanly quit the pygame instance pygame.quit() \ No newline at end of file