diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..fabc202 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 28a3578..5c0047d 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,60 @@ +[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-718a45dd9cf7e7f842a935f5ebbe5719a5e09af4491e668f4dbf3b35d5cca122.svg)](https://classroom.github.com/online_ide?assignment_repo_id=14650182&assignment_repo_type=AssignmentRepo) :warning: Everything between << >> needs to be replaced (remove << >> after replacing) -# << Project Title >> -## CS110 Final Project << Semester, Year >> +# Deep Blue ! +## CS110 Final Project Spring, 2024 ## Team Members -<< List team member names >> +David Schramm *** ## Project Description -<< Give an overview of your project >> - +This project creates a game similar to Brick-Breaker, but ups the challenge by using a single ball which must be kept up with a controlled paddle to destroy all the bricks. If the ball is allowed to touch the ground, the game is lost. *** ## GUI Design ### Initial Design -![initial gui](assets/gui.jpg) +![initial gui](assets/initialballinfo.jpg) +- Originally had a stationary cannon shooting multiple balls but ran into troubles with too many interactions, this is initial printed balal values ### Final Design -![final gui](assets/finalgui.jpg) +![final gui](assets/BLUE.jpg) +![final main menu](assets/mainmenu.jpg) ## Program Design - +- Randomly generate bricks, continuously check for collisions between the ball and bricks or screen and if there are no bricks, end game ### Features -1. << Feature 1 >> -2. << Feature 2 >> -3. << Feature 3 >> -4. << Feature 4 >> -5. << Feature 5 >> +1. Moving objects +2. Object Collisions +3. Start Menu +4. Random brick generation +5. Win and Loss Condition ### Classes - -- << You should have a list of each of your classes with a description >> +- "Ball": Handles dynamics of ball in the game +- "Paddle": Relays player's inputs for paddle movement to interact with ball +- "Brick": Properties for each brick and their collision handling ## ATP -| Step |Procedure |Expected Results | -|----------------------|:--------------------:|----------------------------------:| -| 1 | Run Counter Program |GUI window appears with count = 0 | -| 2 | click count button | display changes to count = 1 | -etc... +| Step |Procedure |Expected Results | +|----------------------|:---------------------------------------: |----------------------------------------:| +| 1. | Run BLUE program with "python3 main.py". | GUI window appears with title screen | +| 2. | Press "S" to Start or "Q" to Quit title | "S" - Screen switches to game screen, | +| |screen. | bricks, ball, and paddle generated. | +| | | "Q" - closes window. | +| 3. | Move paddle under the ball by pressing | Paddle moves accordingly with individual| +| | the "right" or "left" arrow keys. | arrow key press. | +| 4. | When the ball collides with a brick, | Ball bounces off sides of screen and | +| | anticipate its path and keep it up with | paddle. When all bricks are removed(win)| +| | the paddle until all bricks are removed. | window is closed. If ball touches bottom| +| | | of screen(loss), window is closed. | +| | | | \ No newline at end of file diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..730efe7 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/BLUE.jpg b/assets/BLUE.jpg new file mode 100644 index 0000000..9577e7b Binary files /dev/null and b/assets/BLUE.jpg differ diff --git a/assets/Initialballinfo.jpg b/assets/Initialballinfo.jpg new file mode 100644 index 0000000..1136a9f Binary files /dev/null and b/assets/Initialballinfo.jpg differ diff --git a/assets/class_diagram.jpg b/assets/class_diagram.jpg deleted file mode 100644 index a20b5dd..0000000 Binary files a/assets/class_diagram.jpg and /dev/null differ diff --git a/assets/gui.jpg b/assets/gui.jpg deleted file mode 100644 index cf4630c..0000000 Binary files a/assets/gui.jpg and /dev/null differ diff --git a/assets/mainmenu.jpg b/assets/mainmenu.jpg new file mode 100644 index 0000000..677ed1d Binary files /dev/null and b/assets/mainmenu.jpg differ diff --git a/main.py b/main.py index a5c44c6..9e38765 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,10 @@ import pygame -#import your controller +from src.game import Game def main(): pygame.init() - #Create an instance on your controller object - #Call your mainloop + game = Game() + game.run() ###### NOTHING ELSE SHOULD GO IN main(), JUST THE ABOVE 3 LINES OF CODE ###### diff --git a/src/__pycache__/controller.cpython-310.pyc b/src/__pycache__/controller.cpython-310.pyc new file mode 100644 index 0000000..02ff800 Binary files /dev/null and b/src/__pycache__/controller.cpython-310.pyc differ diff --git a/src/__pycache__/game.cpython-310.pyc b/src/__pycache__/game.cpython-310.pyc new file mode 100644 index 0000000..77c2485 Binary files /dev/null and b/src/__pycache__/game.cpython-310.pyc differ diff --git a/src/__pycache__/level.cpython-310.pyc b/src/__pycache__/level.cpython-310.pyc new file mode 100644 index 0000000..d8f305e Binary files /dev/null and b/src/__pycache__/level.cpython-310.pyc differ diff --git a/src/__pycache__/models.cpython-310.pyc b/src/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..eaec7d8 Binary files /dev/null and b/src/__pycache__/models.cpython-310.pyc differ diff --git a/src/__pycache__/utility.cpython-310.pyc b/src/__pycache__/utility.cpython-310.pyc new file mode 100644 index 0000000..5ec0812 Binary files /dev/null and b/src/__pycache__/utility.cpython-310.pyc differ diff --git a/src/controller.py b/src/controller.py new file mode 100644 index 0000000..4f0fd22 --- /dev/null +++ b/src/controller.py @@ -0,0 +1,57 @@ +import pygame +from src.utility import draw_text, WHITE, WIDTH, HEIGHT + +def handle_game_events(ball, bricks, paddle): + """ + Process game events and update game + args: ball (Ball): Establishes loss condition of ball touching bottom of screen + return: str: Returns the game state ('quit' if game closes) + """ + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return 'quit' + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + return 'quit' + if ball.y - ball.radius >= HEIGHT: + return "game_over" + return "running" + +def game_over_screen(screen, font): + """ + Display game over screen and handle interaction + args: screen (pygame.Surface): display surface + font (pygame.font.Font): the font for text rendering + return: str: the action chosen by the player ("retry", "quit") + """ + screen.fill((0,0,0)) + draw_text("Game Over!", font, WHITE, screen, WIDTH // 2 - 100, HEIGHT // 2 - 50) + draw_text("press R to retry or Q to quit", font, WHITE, screen, WIDTH // 2 - 150), + pygame.display.update() + pygame.time.wait(3000) + return handle_game_events(None, None) + +def main_menu(screen,font): + """ + Display the main menu, handle user choices + args: screen (pygame.Surface): load surface + font (pygame.font.Font): font for text + draw_text (str): directed text to be drawn, with location and font + return: str: "start" if game should start, "quit" if it should close + """ + screen.fill((0,0,0)) + draw_text("Welcome to BLUE", font, WHITE, screen, WIDTH // 2 - 140, HEIGHT // 2 - 100) + draw_text("Press S to Start", font, WHITE, screen, WIDTH // 2 - 80, HEIGHT // 2) + draw_text("Press Q to Quit", font, WHITE, screen, WIDTH // 2 - 100, HEIGHT // 2 + 60) + pygame.display.update() + + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + return "quit" + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_s: + return "start" + elif event.key == pygame.K_q: + return "quit" + diff --git a/src/game.py b/src/game.py new file mode 100644 index 0000000..9da807b --- /dev/null +++ b/src/game.py @@ -0,0 +1,66 @@ +import pygame +from src.utility import WIDTH, HEIGHT,font, BLACK, WHITE +from src.models import Ball, Paddle +from src.level import create_level +from src.controller import main_menu, game_over_screen + +def Game(): + """ + Main function for running game loop + args: quit (pygame.quit): allows user to quit game + difficulty: + """ + pygame.init() + screen = pygame.display.set_mode((WIDTH, HEIGHT)) + pygame.display.set_caption("Brick Breaker") + clock = pygame.time.Clock() + running = True + font = pygame.font.Font(None, 74) + + if main_menu(screen, font) == "quit": + pygame.quit() + return + + + bricks = create_level() + ball = Ball(WIDTH // 2, HEIGHT - 50, 10, WHITE, 4) + paddle = Paddle(WIDTH // 2 - 50, HEIGHT - 30, 100, 10, 10, WHITE) + + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_LEFT: + paddle.move(-5) + elif event.key == pygame.K_RIGHT: + paddle.move(5) + elif event.type == pygame.KEYUP: + if event.key in [pygame.K_LEFT, pygame.K_RIGHT]: + paddle.move(0) + + if ball.update(screen, paddle) or not bricks: + if not bricks: + game_over_screen(screen, font, "You Win!") + else: + game_over_screen(screen, font, "Game Over") + pygame.time.wait(2000) + break + + + screen.fill(BLACK) + paddle.draw(screen) + + for brick in bricks: + brick.draw(screen) + if ball.check_collision(brick) and brick.health <= 0: + bricks.remove(brick) + ball.draw(screen) + pygame.display.update() + clock.tick(60) + + pygame.quit() + +if __name__ == "__main__": + Game() \ No newline at end of file diff --git a/src/level.py b/src/level.py new file mode 100644 index 0000000..13f9e65 --- /dev/null +++ b/src/level.py @@ -0,0 +1,23 @@ +import random +from src.models import Brick +from src.utility import RED, GREEN, GREY, WIDTH, HEIGHT + +def create_level(): + """ + Creates a level with randomly placed bricks (in the upper part of the screen) + args: + return: list: a list of Bricks""" + bricks = [] + cols = 10 + brick_width = WIDTH // cols + brick_height = 20 + rows = 5 + max_bricks = int(rows * cols * 0.5) + + for _ in range (max_bricks): + x = random.randint(0, WIDTH - brick_width) + y = random.randint(0, HEIGHT // 4 - brick_height) + color = (200, 0, 0) + health = 1 + bricks.append(Brick(x, y, brick_width, brick_height, color, health)) + return bricks diff --git a/src/models.py b/src/models.py new file mode 100644 index 0000000..40b6bcb --- /dev/null +++ b/src/models.py @@ -0,0 +1,133 @@ +import pygame +from src.utility import RED, GREEN, GREY, WHITE, WIDTH + +class Brick: + def __init__(self, x, y, width, height, color, health): + """Creates brick object + args: x(int): x position of brick + y(int): y position of brick + width(int): width of brick + height (int): height of brick + color(tuple): brick color + health (int): brick health + no return + """ + self.rect = pygame.Rect(x, y, width, height) + self.color = color + self.health = health + + def draw(self, screen): + """ + Draws brick on screen + args: screen (pygame.Surface): surface to be drawn on + no return + """ + pygame. draw.rect(screen, self.color, self.rect) + + def hit(self): + """Reduces brick health when hit by a ball + args: none""" + self.health -= 1 + +class Paddle: + def __init__(self, x, y, width, height, speed, color): + """ + Initialize the paddle (Paddle) for the game. + args: + x (int): Initial x-coordinate of the Paddle. + y (int): Initial y-coordinate of the Paddle. + width (int): Width of the Paddle. + height (int): Height of the Paddle. + speed (int): Movement speed of the Paddle. + color (tuple): RGB color tuple for the Paddle. + return: + None + """ + self.x = x + self.y = y + self.width = width + self.height = height + self.speed = speed + self.color = color + self.rect = pygame.Rect(x, y, width, height) + + def draw(self, screen): + """ + Draw the Paddle on the screen. + args: + screen (pygame.Surface): The display surface. + return: + None + """ + pygame.draw.rect(screen, self.color, self.rect) + + def move(self, direction): + """ + Move the Paddle left or right within the game window. + args: + direction (int): Direction of movement (-1 for left, 1 for right). + return: + None + """ + self.x += direction * self.speed + + self.x = max(0, min(WIDTH - self.width, self.x)) #Keep Paddle in screen + self.rect.x = self.x + +class Ball: + def __init__(self, x, y, radius, color, speed): + """ + Initialize Ball + args: x (int): Ball's x-position + y (int): ball's y-position + raidus (int): The radius of the ball + speed (int): Ball speed + no return""" + self.x = x + self.y = y + self.radius = radius + self.speed = speed + self.color = color + self.dx = speed + self.dy = -speed + + def draw (self, screen): + """ + Draws ball on screen + args: screen (pygame.Surface): Surface to draw on + no return""" + pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.radius) + + def update(self, screen, paddle): + """ + Update ball position based on speed + args: screen (pygame.Surface): Surface to draw on + paddle (Paddle): analyze collisions with paddle + """ + self.x += self.dx + self.y += self.dy + + if self.x - self.radius <= 0 or self.x + self.radius >= screen.get_width(): + self.dx *= -1 + if self.y - self.radius <= 0: + self.dy *= -1 + + if self.y + self.radius >= paddle.y and self.x > paddle.x and self.x < paddle.x + paddle.width: + if self.y + self.radius <= paddle.y + paddle.height: + self.dy *= -1 + + if self.y + self.radius >= screen.get_height(): + self.dy *= -abs(self.dy) + + def check_collision(self, brick): + """checks and handles collisions with a brick + arg: brick (Brick): calls brick to check collision against + return: bool: True if collision occurs, False otherwise + """ + ball_rect = pygame.Rect(self.x - self.radius, self.y - self.radius, 2 * self.radius, 2 * self.radius) + if ball_rect.colliderect(brick.rect): + self.dy *= -1 + brick.hit() + return True + return False + diff --git a/src/sample_controller.py b/src/sample_controller.py deleted file mode 100644 index 665b6ca..0000000 --- a/src/sample_controller.py +++ /dev/null @@ -1,33 +0,0 @@ - -class Controller: - - def __init__(self): - #setup pygame data - - def mainloop(self): - #select state loop - - - ### below are some sample loop states ### - - def menuloop(self): - - #event loop - - #update data - - #redraw - - def gameloop(self): - #event loop - - #update data - - #redraw - - def gameoverloop(self): - #event loop - - #update data - - #redraw diff --git a/src/utility.py b/src/utility.py new file mode 100644 index 0000000..caaaf17 --- /dev/null +++ b/src/utility.py @@ -0,0 +1,28 @@ + +import pygame +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +GREEN = (0, 255, 0) +RED = (255, 0, 0) +GREY = (128, 128, 128) + +WIDTH = 800 +HEIGHT = 600 + +pygame.font.init() +font = pygame.font.Font(None, 36) + +def draw_text(text, font, color, surface, x, y): + """ + Draw text on _____. + args: text (str): text to draw + font (pygame.font.Font): font of the text + surface(pygame.Surface): describes surface to draw on + color(tuple) RGB + x (int): X position of text + y (int): y coordinate of text + """ + text_obj = font.render(text, True, color) + text_rect = text_obj.get_rect() + text_rect.topleft = (x, y) + surface.blit(text_obj, text_rect)