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 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:
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()