Untitled

mail@pastecode.io avatar
unknown
plain_text
6 months ago
9.6 kB
3
Indexable
Never
import pygame
import random
from typing import Dict, List, Tuple

#úkoly
#1. postava se může vydat za hranice mapy, napravte to
#2. protivník se může narodit velice blízko či přímo na hráčovi, napravte to
#3. postava se pohybuje jen při stisku klávesy a ne při podržení, napravte to
#4. předělejtu hru tak, aby postavy byly kruhy místo čtverců
#5. pokud větší objekt koliduje s menším, tak větší absorbuje menší a povyroste o jeho obsah
#6. dejte hráčovi tři životy, představované barvami v pořadí zelená, oranžová, červená
#7. dejte možnost hru opakovat pokud hráč prohraje
#8. přidejte možnost hru pozastavit (pause game)
#9. přidejte možnost ovládat hru myší
#10. předělejte hru do OOP, aktuálně není s rozšířeními příliš udržitelná

#colors
COLORS = {
    "white": (255, 255, 255),
    "red": (255, 0, 0),
    "green": (0, 255, 0),
    "blue": (0, 0, 255),
    "black": (0, 0, 0),
    "orange": (255,165,0)
}

#game visualization
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
WINDOW_COLOR = COLORS["white"]
SCORE_COORDINATES = (50, 50)
SCORE_FONT_SIZE = 50
SCORE_COLOR = COLORS["black"]
TITLE = "Square? Beware!"
FPS = 30

#character settings
PLAYER_COLOR = COLORS["blue"]
PLAYER_CHARACTER_WIDTH = 10
PLAYER_CHARACTER_HEIGHT = 10

#enemy settings
ENEMY_COLOR = COLORS["black"]
ENEMY_WIDTH = 5
ENEMY_HEIGHT = 5
ENEMY_DELTA_X = 5
ENEMY_DELTA_Y = 5
SPAWN_EVERY_MILISECONDS = 5000
SPAWN_ENEMY = pygame.USEREVENT
ENEMY_MOVE_EVERY = 500
ENEMY_MOVE = pygame.USEREVENT

#player control
DELTA_X = 5
DELTA_Y = 5
KEY_MAPPING = {
    pygame.K_UP: "move_up",
    pygame.K_DOWN: "move_down",
    pygame.K_LEFT: "move_left",
    pygame.K_RIGHT: "move_right",
    pygame.K_q: "quit_game",
    pygame.K_p: "pause_game"
}


def print_score(canvas: pygame.Surface, collided_with: int, main_character: Dict[str, int], 
                score_font_renderer: pygame.font.Font) -> None:
    """Recalcutes and prints actual score (number of collided object with) of player.

    Args:
        canvas (pygame.Surface): pygame surface where score will be rendered on.
        collided_with (int): index of object that player collided with. Negative if no collision detected.
        main_character (Dict[str, int]): player state for score update.
        score_font_renderer(pygame.font.Font): font renderer for player score.
    """
    main_character["score"] += 1 if collided_with >= 0 else 0
    text = f"Score: {main_character['score']} object eaten."
    message = score_font_renderer.render(text, True, SCORE_COLOR)
    canvas.blit(source=message, dest=SCORE_COORDINATES)


def remove_enemy(enemies: List[Dict[str, int]], collided_with: int) -> None:
    """Removes enemy that player collided with.

    Args:
        enemies (List[Dict[str, int]]): list od enemies with their state
        collided_with (int): index of enemy that player collided with. If -1, then no collision have been detected.
    """
    if collided_with != -1:
        enemies.pop(collided_with)


def collision_detection(main_character: Dict[str, int], enemies: List[Dict[str, int]]) -> int:
    """Detects collision of player character with enemy.

    Args:
        main_character (Dict[str, int]): player character state
        enemies (List[Dict[str, int]]): list of enemies and their states

    Returns:
        int: index of firt enemy that player collided with.
    """
    character_collider = pygame.Rect(main_character["x"], main_character["y"], 
                                     PLAYER_CHARACTER_WIDTH, PLAYER_CHARACTER_HEIGHT)
    enemy_colliders = [pygame.Rect(enemy["x"], enemy["y"], ENEMY_WIDTH, ENEMY_HEIGHT) 
                                   for enemy in enemies]
    return character_collider.collidelist(enemy_colliders)


def enemy_movement(enemies: List[Dict[str, int]]) -> None:
    """Moves enemies in random direction.

    Args:
        enemies (List[Dict[str, int]]): list of enemies with their states.
    """
    for enemy in enemies:
        enemy["x"] += random.uniform(-ENEMY_DELTA_X, -ENEMY_DELTA_X)
        enemy["y"] += random.uniform(-ENEMY_DELTA_Y, ENEMY_DELTA_Y)


def generate_enemy(enemies: List[Dict[str, int]]) -> None:
    """Generates new enemy to list of enemies on random position in game canvas.

    Args:
        enemies (List[Dict[str, int]]): List of enemy states that new enemy with its state will be added.
    """
    random_position = [random.uniform(0, WINDOW_WIDTH), random.uniform(0, WINDOW_HEIGHT)]
    enemies.append(dict(zip(["x", "y"], random_position)))


def control_character(player_action: str, main_character: Dict[str, int], game_state: Dict[str, bool]) -> None:
    """Updates game state or player state based upon keyboard input.

    Args:
        player_action (str): keymapped action that player inputted
        main_character (Dict[str, int]): state of player character
        game_state (Dict[str, bool]): state of game
    """
    if player_action == "move_up":
        main_character["y"] -= DELTA_Y
    elif player_action == "move_down":
        main_character["y"] += DELTA_Y
    elif player_action == "move_left":
        main_character["x"] -= DELTA_X
    elif player_action == "move_right":
        main_character["x"] += DELTA_X
    elif player_action == "pause_game":
        game_state["pause"] = not game_state["pause"]
    elif player_action == "quit_game":
        game_state["gameover"] = True


def get_game_event() -> Tuple[str, str]:
    """Gets pygame events that happened in actual frame.

    Returns:
        Tuple[str, str]: event type and concrete event action that happened.
    """
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            return ("state_change", "quit_game")
        elif event.type == pygame.KEYDOWN:
            return ("user_action", KEY_MAPPING.get(event.key))
        elif event.type == SPAWN_ENEMY:
            return ("enemy_event", "spawn_enemy")
        elif event.type == ENEMY_MOVE:
            return ("enemy_event", "move_enemy")
    return ("no_event", None)

        

def render_game(game_canvas: pygame.Surface, main_character: Dict[str, int], enemies: List[Dict[str, int]]) -> None:
    """Renders player character and enemies on pygame Surface object.

    Args:
        game_canvas (pygame.Surface): surface that objects will be rendered on.
        main_character (Dict[str, int]): player character state to be rendered.
        enemies (List[Dict[str, int]]): list of enemies with their states to be rendered.
    """
    game_canvas.fill(WINDOW_COLOR)
    pygame.draw.rect(
        surface=game_canvas,
        color=PLAYER_COLOR,
        rect=[main_character["x"], main_character["y"], PLAYER_CHARACTER_WIDTH, PLAYER_CHARACTER_HEIGHT]
    )
    for enemy in enemies:
        pygame.draw.rect(
            surface=game_canvas,
            color=ENEMY_COLOR,
            rect=[enemy["x"], enemy["y"], ENEMY_WIDTH, ENEMY_HEIGHT]
    )


def gameloop() -> None:
    """Game loop that repeats until quit event.
    """
    
    #init game state
    game_size = {"width": WINDOW_WIDTH, "height": WINDOW_HEIGHT}
    game_canvas = pygame.display.set_mode(size=(WINDOW_WIDTH, WINDOW_HEIGHT))
    score_font_renderer = pygame.font.SysFont(None, SCORE_FONT_SIZE)
    #List of available fonts: pygame.font.get_fonts()
    main_character = {"x": game_size["width"]//2, "y": game_size["height"]//2, "score": 0}
    enemies = []
    game_clock = pygame.time.Clock()
    game_state = {"gameover": False, "pause": False}

    #start gameloop
    while not game_state["gameover"]:

        #1. render state of game
        render_game(game_canvas, main_character, enemies)

        #2. get events that happened
        event_type, event_action = get_game_event()

        #3. update game state based upon event
        if event_type == "user_action":
            control_character(event_action, main_character, game_state)
        elif event_type == "enemy_event" and event_action == "spawn_enemy":
            generate_enemy(enemies)
        elif event_type == "enemy_event" and event_action == "move_enemy":
            enemy_movement(enemies)
        elif event_type == "state_change" and event_action == "quit_game":
            game_state["gameover"] = True

        #4. calculate collisions
        collided_with = collision_detection(main_character, enemies)

        #5. update enemies states based on collisions
        remove_enemy(enemies, collided_with)

        #6. update player state based on collisions
        print_score(game_canvas, collided_with, main_character, score_font_renderer)
        
        #wait for demanded FPS and render new frame
        game_clock.tick(FPS)
        pygame.display.update()

        #FPS calculation explanation
        #   frame = one iteration of gameloop (concretly pygame.display update)
        #   T = time that one iteration (frame) took
        #   1/T = number of frames per second (frequency of pygame.display update)
        #   FPS = how many frames per second we actually want
        #   tick(FPS) = force iteration to sleep to reach demanded FPS 


def main() -> None:
    """Program entry point.
    """

    #game initialization with window settings
    pygame.init()
    pygame.display.set_caption(TITLE)
    
    #events registration
    pygame.time.set_timer(ENEMY_MOVE, ENEMY_MOVE_EVERY)
    pygame.time.set_timer(SPAWN_ENEMY, SPAWN_EVERY_MILISECONDS)
    
    #start game loop
    gameloop()

    #quit game when gameloop ends via return statement
    pygame.quit()
    quit()
    

if __name__ == "__main__":
    main()
Leave a Comment