Merge pull request #5 from BreizhHardware/dev

Dev to main
This commit is contained in:
Clément Hervouet
2025-03-26 13:07:56 +01:00
committed by GitHub
14 changed files with 581 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

102
main.py Normal file
View File

@@ -0,0 +1,102 @@
import pygame
import sys
from pygame.locals import *
# Import from pygame_basics
from src.game import (
initialize_game,
)
from src.constant import displaysurface, FramePerSec, font, FPS, WIDTH, HEIGHT
# Import from menu
from src.Menu.Menu import Menu
from src.Menu.Leaderboard import Leaderboard
def main():
# Game states
MENU = 0
PLAYING = 1
INFINITE = 2
LEADERBOARD = 3
# Initialize game state and objects
current_state = MENU
menu = Menu()
leaderboard = Leaderboard()
# Initialize game components
P1, PT1, platforms, all_sprites = initialize_game()
# Main game loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
if current_state in [PLAYING, INFINITE]:
current_state = MENU
else:
pygame.quit()
sys.exit()
# Handle menu events
if current_state == MENU:
action = menu.handle_event(event)
if action == "play":
current_state = PLAYING
elif action == "infinite":
current_state = INFINITE
elif action == "leaderboard":
current_state = LEADERBOARD
elif action == "quit":
pygame.quit()
sys.exit()
# Handle leaderboard events
elif current_state == LEADERBOARD:
action = leaderboard.handle_event(event)
if action == "menu":
current_state = MENU
# Clear screen
displaysurface.fill((0, 0, 0))
# Draw appropriate screen based on state
if current_state == MENU:
menu.draw(displaysurface)
elif current_state == PLAYING:
# Regular game code
P1.move()
P1.update()
for entity in all_sprites:
displaysurface.blit(entity.surf, entity.rect)
# Display FPS and coordinates
fps = int(FramePerSec.get_fps())
fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255))
displaysurface.blit(fps_text, (10, 10))
pos_text = font.render(
f"X: {int(P1.pos.x)}, Y: {int(P1.pos.y)}", True, (255, 255, 255)
)
displaysurface.blit(pos_text, (10, 40))
elif current_state == INFINITE:
# Placeholder for infinite mode
text = font.render("Mode Infini - À implémenter", True, (255, 255, 255))
displaysurface.blit(text, (WIDTH // 2 - text.get_width() // 2, HEIGHT // 2))
elif current_state == LEADERBOARD:
leaderboard.draw(displaysurface)
pygame.display.update()
FramePerSec.tick(FPS)
if __name__ == "__main__":
main()

View File

@@ -1 +1,7 @@
black==25.1.0
click==8.1.8
mypy-extensions==1.0.0
packaging==24.2
pathspec==0.12.1
platformdirs==4.3.7
pygame==2.6.1

28
src/Entity/Entity.py Normal file
View File

@@ -0,0 +1,28 @@
import pygame
from pygame.math import Vector2 as vec
class Entity(pygame.sprite.Sprite):
def __init__(self, pos=(0, 0), size=(30, 30), color=(255, 255, 255)):
super().__init__()
self.pos = vec(pos)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
# Default surface
self.surf = pygame.Surface(size)
self.surf.fill(color)
self.rect = self.surf.get_rect()
self.update_rect()
def update_rect(self):
"""Update rect position based on entity position"""
self.rect.midbottom = self.pos
def update(self):
"""Update entity state - to be overridden by child classes"""
pass
def move(self):
"""Handle movement - to be overridden by child classes"""
pass

10
src/Entity/Platform.py Normal file
View File

@@ -0,0 +1,10 @@
import pygame
from src.Entity.Entity import Entity
from src.constant import WIDTH, HEIGHT
class Platform(Entity):
def __init__(self, width, height, x, y, color=(255, 0, 0)):
super().__init__(pos=(x, y), size=(width, height), color=color)
# Override rect setting for platforms if needed
self.rect = self.surf.get_rect(center=(x, y))

171
src/Entity/Player.py Normal file
View File

@@ -0,0 +1,171 @@
from src.Entity.Entity import Entity
from src.constant import WIDTH, vec, ACC, FRIC, platforms
from pygame import *
import pygame
import os
import time
class Player(Entity):
def __init__(self, width=120, height=120, x=10, y=385):
super().__init__(pos=(x, y), size=(width, height), color=(128, 255, 40))
# Animation variables
self.animation_frames = []
self.jump_frames = []
self.dash_frames = []
self.current_frame = 0
self.animation_speed = 0.1
self.last_update = time.time()
self.static_image = None
self.moving = False
self.dashing = False
self.jumping = False
# Load images
self.load_images()
# Override initial surface if images are loaded
if self.static_image:
self.surf = self.static_image
elif self.animation_frames:
self.surf = self.animation_frames[0]
def load_images(self):
try:
# Load static image
if os.path.isfile("assets/player/Sanic Base.png"):
self.static_image = pygame.image.load(
"assets/player/Sanic Base.png"
).convert_alpha()
self.static_image = pygame.transform.scale(
self.static_image, (120, 120)
)
# Load regular animation sprite sheet
if os.path.isfile("assets/player/Sanic Annimate.png"):
sprite_sheet = pygame.image.load(
"assets/player/Sanic Annimate.png"
).convert_alpha()
# Extract the 4 frames
frame_width = sprite_sheet.get_height()
for i in range(4):
# Cut out a region of the sprite sheet
frame = sprite_sheet.subsurface(
(i * 2207, 0, frame_width, frame_width)
)
# Resize the frame
frame = pygame.transform.scale(frame, (120, 120))
self.animation_frames.append(frame)
# Load jump animation sprite sheet
if os.path.isfile("assets/player/Sanic Boule.png"):
self.jump_frames.append(
pygame.transform.scale(
pygame.image.load(
"assets/player/Sanic Boule.png"
).convert_alpha(),
(120, 120),
)
)
# Load dash animation sprite sheet
if os.path.isfile("assets/player/Sanic Boule Annimate.png"):
dash_sheet = pygame.image.load(
"assets/player/Sanic Boule Annimate.png"
).convert_alpha()
# Extract the frames with 2000px gap
dash_frame_height = dash_sheet.get_height()
for i in range(4): # Assuming 4 frames
frame = dash_sheet.subsurface(
(i * 2000, 0, dash_frame_height, dash_frame_height)
)
frame = pygame.transform.scale(frame, (120, 120))
self.dash_frames.append(frame)
except Exception as e:
print(f"Error loading player images: {e}")
def update_animation(self):
current_time = time.time()
# Priority: Dashing > Jumping > Moving > Static
if self.dashing and self.dash_frames:
if current_time - self.last_update > self.animation_speed:
self.current_frame = (self.current_frame + 1) % len(self.dash_frames)
self.surf = self.dash_frames[self.current_frame]
self.last_update = current_time
elif self.jumping and self.jump_frames:
self.surf = self.jump_frames[0] # Use jump frame
elif self.moving and self.animation_frames:
if current_time - self.last_update > self.animation_speed:
self.current_frame = (self.current_frame + 1) % len(
self.animation_frames
)
self.surf = self.animation_frames[self.current_frame]
self.last_update = current_time
elif self.static_image:
self.surf = self.static_image
def dash(self, acc):
self.acc.x = 5 * acc
self.dashing = True # Set dashing flag
def move(self):
self.acc = vec(0, 1) # Gravity
# Reset flags
self.moving = False
self.dashing = False
pressed_keys = pygame.key.get_pressed()
if pressed_keys[K_q]:
self.acc.x = -ACC
self.moving = True
if pressed_keys[K_a]:
self.dash(-ACC)
if pressed_keys[K_d]:
self.acc.x = ACC
self.moving = True
if pressed_keys[K_a]:
self.dash(ACC)
# Also consider the player moving if they have significant horizontal velocity
if abs(self.vel.x) > 0.5:
self.moving = True
# Jumping logic
if pressed_keys[K_SPACE] and not self.jumping:
self.vel.y = -30
self.jumping = True
# Apply friction
self.acc.y += self.vel.y * FRIC
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
# Prevent the player from moving off-screen horizontally
if self.pos.x > WIDTH - self.rect.width / 2:
self.pos.x = WIDTH - self.rect.width / 2
self.vel.x = 0
if self.pos.x < self.rect.width / 2:
self.pos.x = self.rect.width / 2
self.vel.x = 0
self.rect.midbottom = self.pos
# Update animation frame
self.update_animation()
def update(self):
hits = pygame.sprite.spritecollide(self, platforms, False)
if hits:
if self.vel.y > 0:
self.pos.y = hits[0].rect.top
self.vel.y = 0
self.jumping = False

46
src/Menu/Button.py Normal file
View File

@@ -0,0 +1,46 @@
from pygame.locals import *
import pygame
from src.constant import font
class Button:
def __init__(self, text, x, y, width, height, action=None):
self.text = text
self.x = x
self.y = y
self.width = width
self.height = height
self.action = action
self.hover = False
def draw(self, surface):
# Button colors
color = (100, 149, 237) if self.hover else (65, 105, 225)
border_color = (255, 255, 255)
# Draw button with border
pygame.draw.rect(surface, color, (self.x, self.y, self.width, self.height))
pygame.draw.rect(
surface, border_color, (self.x, self.y, self.width, self.height), 2
)
# Draw text
text_surf = font.render(self.text, True, (255, 255, 255))
text_rect = text_surf.get_rect(
center=(self.x + self.width / 2, self.y + self.height / 2)
)
surface.blit(text_surf, text_rect)
def is_hover(self, pos):
return (
self.x <= pos[0] <= self.x + self.width
and self.y <= pos[1] <= self.y + self.height
)
def handle_event(self, event):
if event.type == MOUSEMOTION:
self.hover = self.is_hover(event.pos)
elif event.type == MOUSEBUTTONDOWN:
if self.hover and self.action:
return self.action
return None

57
src/Menu/Leaderboard.py Normal file
View File

@@ -0,0 +1,57 @@
import pygame
from src.constant import WIDTH, HEIGHT, font
from src.Menu.Button import Button
class Leaderboard:
def __init__(self):
self.tabs = ["Mode Normal", "Mode Infini"]
self.current_tab = 0
self.scores = {
0: [("Player1", 1000), ("Player2", 800), ("Player3", 600)],
1: [("Player1", 2000), ("Player2", 1500), ("Player3", 1200)],
}
self.back_button = Button("Retour", 20, HEIGHT - 70, 120, 50, "menu")
tab_width = 150
self.tab_buttons = [
Button(self.tabs[0], WIDTH // 2 - tab_width, 80, tab_width, 40, "tab_0"),
Button(self.tabs[1], WIDTH // 2, 80, tab_width, 40, "tab_1"),
]
def draw(self, surface):
# Draw title
title = pygame.font.SysFont("Arial", 48).render(
"Classement", True, (0, 191, 255)
)
title_rect = title.get_rect(center=(WIDTH // 2, 40))
surface.blit(title, title_rect)
# Draw tabs
for i, button in enumerate(self.tab_buttons):
if i == self.current_tab:
pygame.draw.rect(
surface,
(100, 149, 237),
(button.x, button.y, button.width, button.height),
)
button.draw(surface)
# Draw scores
y_pos = 150
for i, (name, score) in enumerate(self.scores[self.current_tab]):
rank_text = font.render(f"{i+1}. {name}: {score}", True, (255, 255, 255))
surface.blit(rank_text, (WIDTH // 2 - rank_text.get_width() // 2, y_pos))
y_pos += 40
self.back_button.draw(surface)
def handle_event(self, event):
action = self.back_button.handle_event(event)
if action:
return action
for i, button in enumerate(self.tab_buttons):
action = button.handle_event(event)
if action and action.startswith("tab_"):
self.current_tab = int(action.split("_")[1])
return None

79
src/Menu/Menu.py Normal file
View File

@@ -0,0 +1,79 @@
import pygame
from src.constant import HEIGHT, WIDTH
from src.Menu.Button import Button
class Menu:
def __init__(self):
self.buttons = []
button_width = 250
button_height = 60
button_spacing = 20
start_y = HEIGHT // 2 - 100
# Create buttons centered horizontally
self.buttons.append(
Button(
"Jouer",
WIDTH // 2 - button_width // 2,
start_y,
button_width,
button_height,
"play",
)
)
start_y += button_height + button_spacing
self.buttons.append(
Button(
"Jouer en mode infini",
WIDTH // 2 - button_width // 2,
start_y,
button_width,
button_height,
"infinite",
)
)
start_y += button_height + button_spacing
self.buttons.append(
Button(
"Classement",
WIDTH // 2 - button_width // 2,
start_y,
button_width,
button_height,
"leaderboard",
)
)
start_y += button_height + button_spacing
self.buttons.append(
Button(
"Quitter",
WIDTH // 2 - button_width // 2,
start_y,
button_width,
button_height,
"quit",
)
)
def draw(self, surface):
# Draw title
title = pygame.font.SysFont("Arial", 72).render(
"Project Sanic", True, (0, 191, 255)
)
title_rect = title.get_rect(center=(WIDTH // 2, HEIGHT // 4))
surface.blit(title, title_rect)
# Draw buttons
for button in self.buttons:
button.draw(surface)
def handle_event(self, event):
for button in self.buttons:
action = button.handle_event(event)
if action:
return action
return None

20
src/constant.py Normal file
View File

@@ -0,0 +1,20 @@
import pygame
pygame.init()
FPS = 60
ACC = 0.5
FRIC = -0.12
WIDTH = 800
HEIGHT = 600
platforms = pygame.sprite.Group()
vec = pygame.math.Vector2
displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
FramePerSec = pygame.time.Clock()
all_sprites = pygame.sprite.Group()
try:
font = pygame.font.SysFont("Arial", 20)
except:
font = pygame.font.Font(None, 20)

62
src/game.py Normal file
View File

@@ -0,0 +1,62 @@
import pygame
import sys
from pygame.locals import *
from src.Entity.Platform import Platform
from src.Entity.Player import Player
from src.constant import displaysurface, FramePerSec, font, FPS, platforms, all_sprites
def initialize_game():
# Clear previous sprites if any
platforms.empty()
all_sprites.empty()
# Create new game objects
PT1 = Platform(1200, 20, 200, 400)
P1 = Player()
# Add them to the groups
platforms.add(PT1)
all_sprites.add(PT1)
all_sprites.add(P1)
return P1, PT1, platforms, all_sprites
def run_game(P1, all_sprites):
"""Run the main game loop without menu system"""
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
displaysurface.fill((0, 0, 0))
P1.move()
P1.update()
for entity in all_sprites:
displaysurface.blit(entity.surf, entity.rect)
# Display FPS
fps = int(FramePerSec.get_fps())
fps_text = font.render(f"FPS: {fps}", True, (255, 255, 255))
displaysurface.blit(fps_text, (10, 10))
# Display player coordinates
pos_text = font.render(
f"X: {int(P1.pos.x)}, Y: {int(P1.pos.y)}", True, (255, 255, 255)
)
displaysurface.blit(pos_text, (10, 40))
pygame.display.update()
FramePerSec.tick(FPS)
if __name__ == "__main__":
P1, PT1, platforms, all_sprites = initialize_game()
run_game(P1, all_sprites)