diff --git a/README.md b/README.md index 28a3578..ff5b415 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ +[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-718a45dd9cf7e7f842a935f5ebbe5719a5e09af4491e668f4dbf3b35d5cca122.svg)](https://classroom.github.com/online_ide?assignment_repo_id=14913552&assignment_repo_type=AssignmentRepo) :warning: Everything between << >> needs to be replaced (remove << >> after replacing) -# << Project Title >> -## CS110 Final Project << Semester, Year >> +# Tower Rescue: +## CS110 Final Project Spring, 2024 ## Team Members -<< List team member names >> +Carol Zhang +Ashley Mera -*** +*** ## Project Description -<< Give an overview of your project >> +Tower Rescue. For this project, there will be a girl in a tower that needs to be saved. Your character is supposed to save the girl but there is a evil witch at the top of the tower trying to chase you. This is a 2 player game so either person can win. The witch will win if she gets the prince before he can catch Rapunzel and the prince will win if he gets to Rapunzel without getting caught. *** @@ -29,21 +31,93 @@ ## Program Design ### Features - -1. << Feature 1 >> -2. << Feature 2 >> -3. << Feature 3 >> -4. << Feature 4 >> -5. << Feature 5 >> +1. "Press start" +2. Moving characters +3. Witch with her apple +4. Tower background +5. "You win!" +======= ### Classes +Character class: represents the main characters and the character can move in various directions and perform actions like jumping +Tower class: represents the tower and checks for collisions with the character to determine if the character has reached the top +Witch class: the witch can throw apples at the character + +- Character Class: Represents the main character of the game, tasked with saving the girl from the tower. The character can move in various directions and perform actions like jumping. +- Tower Class: Represents the tower where the girl is held captive. It checks for collisions with the character to determine if the character has reached the top of the tower. +-Witch Class: Represents the evil witch at the top of the tower. The witch can chase the prince and when they collide the witch will win. +-Apple Class: Represents the apples that is used with the witch. +-class StartMenu: Allows buttons to play +-class Highscore: Keeps track of time +-class Clouds: extra decorations for visuals +-class Rapunzel: The person who needs to be saved, if touched by prince, game is won. +-class button: Allows retry + + +- Controller Class: Manages the game loop, handles events, updates game logic, and controls the flow of the game. -- << You should have a list of each of your classes with a description >> ## 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... +TEST CASE 1: Player Movement for the Prince +Test Description: Verify that the player can move left, right and up. +Test Steps: +1. Start the game +2. Press the left arrow key on keyboard +3. Verify the player moves left +4. Press the right key arrow key on keyboard +5. Verify the player moves right +6. Press the Up key arrow key on keyboard +7. Verify the player moves up +Expected Outcome: The player should move left, right or up using keyboard keys. + +TEST CASE 2: Player Movement for the Witch +Test Description: Verify that the player can move left, right and up. +Test Steps: +1. Start the game +2. Press "1" on keyboard to move left +3. Verify the player moves left +4. Press "2" on keyboard +5. Verify the player moves right +6. Press "4" on keyboard to move down +7. Verify the player moves down +8. Press "3" on keyboard to move up +9. Verify the player moves up +Expected Outcome: The player should move left, right or up and down using keyboard numbers. + + +TEST CASE 3: Collision detected +Test Description: Ensure that collisions between the witch and prince are detected correctly. You are playing the witch (Keys for witch are 1 and 2, to move left and right and 3, 4 are to move up and down check test case 2 for verification) +Test Steps: +1.Start the game. +2. If you are playing the witch, chase the prince until collistion +3.Verify that the witch with apple hits the prince. It will say "Game Over" +4. If you are playing with prine, move the prince until he arrives to rapunzel +5. Verify that no collision is detected. The prince will win and screen will say "you win!" +Expected Outcome: Collision of characters should determine winning or losing outcome. + + + +TEST CASE 4: Win/Lose Navigation +Test Description: Test the navigation through the game's menu +Test Steps: +1. Run the Game +2. Check to see if a "play button" appears +3. Click "PLAY" +4. Verify that game begins +5. Play the game +Expected Outcome: The beginning button should start the game + +TEST CASE 5: Game Over/Retry Condition +1. Start the game +2. Play the game +3. If playing prince, get hit by witch +4. Verify that game displays "Game Over" +5. Click "retry" +6. Verify you can play again +7. If playing prince, get to rapunzel +8. Verify that game displays "You win" +9. Click "retry" +10. Verify you can play again + +Expected Outcome: The game should display a message when the player loses or winsand you should get the option to play again. \ No newline at end of file diff --git a/assets/Untitled design.jpg b/assets/Untitled design.jpg new file mode 100644 index 0000000..d6dc271 Binary files /dev/null and b/assets/Untitled design.jpg differ diff --git a/assets/apple.png b/assets/apple.png new file mode 100644 index 0000000..5f8ceee Binary files /dev/null and b/assets/apple.png differ diff --git a/assets/clouds.png b/assets/clouds.png new file mode 100644 index 0000000..6410737 Binary files /dev/null and b/assets/clouds.png differ diff --git a/assets/finalgui.jpg b/assets/finalgui.jpg new file mode 100644 index 0000000..1b3612c Binary files /dev/null and b/assets/finalgui.jpg differ diff --git a/assets/foldercontents.txt b/assets/foldercontents.txt index a429bf2..f071422 100644 --- a/assets/foldercontents.txt +++ b/assets/foldercontents.txt @@ -1 +1,2 @@ all of your media (images/music/video) go in this folder + diff --git a/assets/gameover.png b/assets/gameover.png new file mode 100644 index 0000000..3658642 Binary files /dev/null and b/assets/gameover.png differ diff --git a/assets/gui.jpg b/assets/gui.jpg index cf4630c..b87815a 100644 Binary files a/assets/gui.jpg and b/assets/gui.jpg differ diff --git a/assets/prince.png b/assets/prince.png new file mode 100644 index 0000000..053c472 Binary files /dev/null and b/assets/prince.png differ diff --git a/assets/quit.png b/assets/quit.png new file mode 100644 index 0000000..3e7f073 Binary files /dev/null and b/assets/quit.png differ diff --git a/assets/rapunzel.png b/assets/rapunzel.png new file mode 100644 index 0000000..16b1516 Binary files /dev/null and b/assets/rapunzel.png differ diff --git a/assets/retry.png b/assets/retry.png new file mode 100644 index 0000000..87c9cc5 Binary files /dev/null and b/assets/retry.png differ diff --git a/assets/startgame.png b/assets/startgame.png new file mode 100644 index 0000000..ba4ff6f Binary files /dev/null and b/assets/startgame.png differ diff --git a/assets/tower.png b/assets/tower.png new file mode 100644 index 0000000..4747baf Binary files /dev/null and b/assets/tower.png differ diff --git a/assets/witch.png b/assets/witch.png new file mode 100644 index 0000000..1ba7b1e Binary files /dev/null and b/assets/witch.png differ diff --git a/assets/youwin.png b/assets/youwin.png new file mode 100644 index 0000000..2c85d73 Binary files /dev/null and b/assets/youwin.png differ diff --git a/main.py b/main.py index a5c44c6..951f522 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,12 @@ import pygame #import your controller +from src.controller import Controller def main(): pygame.init() + + games=Controller() + games.mainloop() #Create an instance on your controller object #Call your mainloop diff --git a/src/__pycache__/controller.cpython-310.pyc b/src/__pycache__/controller.cpython-310.pyc new file mode 100644 index 0000000..898ad58 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..d422355 Binary files /dev/null and b/src/__pycache__/game.cpython-310.pyc differ diff --git a/src/controller.py b/src/controller.py new file mode 100644 index 0000000..faf014f --- /dev/null +++ b/src/controller.py @@ -0,0 +1,233 @@ +import pygame +from src.game import Character +from src.game import Tower +from src.game import Witch +#from src.game import Apple +from src.game import StartButton +from src.game import Rapunzel +from src.game import Apple +from src.game import Cloud +from src.game import Highscore + +#from src.game import RetryButton + + +class Controller: + def __init__(self): + pygame.init() + self.start_button = StartButton(300, 150, .60, 'assets/startgame.png') + self.retry_button = StartButton(400, 400, .60, 'assets/retry.png') # Add retry button THIS IS NEW, DELETE IF DONT WORK + self.start_button_visible = True + self.clock = pygame.time.Clock() + self.game_over = False + + + self.you_win_image = pygame.image.load('assets/youwin.png') + self.you_win_rect = self.you_win_image.get_rect() + self.you_win_rect.center = (400, 300) + + self.game_over_image = pygame.image.load('assets/gameover.png') + self.game_over_rect = self.game_over_image.get_rect() + self.game_over_rect.center = (400, 300) + + self.retry_image = pygame.image.load('assets/retry.png') + self.retry_rect = self.retry_image.get_rect() + self.retry_rect.center = (400, 300) + + + def reset_game(self): + + + + + self.cloud = Cloud(50, 150 , .90, 'assets/clouds.png') + self.prince = Character(100, 400, .25, 'assets/prince.png') + self.tower = Tower(300, 50 , .55, 'assets/tower.png') + self.witch = Witch(100, 20, .02,'assets/witch.png', ) + self.apple = Apple(100, 50, .005, 'assets/apple.png') + self.rapunzel = Rapunzel (350, 150 , .30, 'assets/rapunzel.png') + self.score = 0 + + + self.charactergroup= pygame.sprite.Group(self.prince) + self.witchgroup= pygame.sprite.Group(self.witch) + self.towergroup= pygame.sprite.Group(self.tower) + #self.startbuttongroup = pygame.sprite.Group(self.start_button) + self.rapunzelgroup = pygame.sprite.Group(self.rapunzel) + self.apple_group = pygame.sprite.Group(self.apple) + self.cloud_group = pygame.sprite.Group(self.cloud) + + + + + + def mainloop(self): + + screen = pygame.display.set_mode((800, 600)) + clock = pygame.time.Clock() + self.reset_game() + #CONSTANTS + INTRO=0 + GAME=1 + RETRY=2 + + + cloud = Cloud(50, 150 , .90, 'assets/clouds.png') + prince = Character(100, 400, .25, 'assets/prince.png') + tower = Tower(300, 50 , .55, 'assets/tower.png') + witch = Witch(100, 20, .02,'assets/witch.png', ) + apple = Apple(100, 50, .005, 'assets/apple.png') + rapunzel = Rapunzel (350, 150 , .30, 'assets/rapunzel.png') + + charactergroup= pygame.sprite.Group(prince) + witchgroup= pygame.sprite.Group(witch) + towergroup= pygame.sprite.Group(tower) + startbuttongroup = pygame.sprite.Group(self.start_button) + rapunzelgroup = pygame.sprite.Group(rapunzel) + apple_group = pygame.sprite.Group(apple) + cloud_group = pygame.sprite.Group(cloud) + + + + + + running = True + start_game = INTRO + game_over= False + score = 0 + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + + running = False + elif event.type == pygame.MOUSEBUTTONDOWN and start_game == INTRO: + if self.start_button.is_clicked(event.pos): + #if self.start_button.rect.collidepoint(event.pos): + start_game = GAME + score = 0 + #self.startbuttongroup.empty() + + + elif event.type == pygame.MOUSEBUTTONDOWN and game_over: + #if self.retry_button.rect.collidepoint(event.pos): + if self.retry_button.is_clicked(event.pos): # Check for retry button click NEW STUFFY + print(self.score) + self.reset_game() + game_over = False + start_game= GAME + + + + if start_game == GAME: + key=pygame.key.get_pressed() + if key[pygame.K_LEFT]: + self.prince.move_left() + if key[pygame.K_RIGHT]: + self.prince.move_right() + if key[pygame.K_UP]: + self.prince.move_up() + if key[pygame.K_DOWN]: + self.prince.move_down() + if key[pygame.K_1]: + self.witch.move_left() + if key[pygame.K_2]: + self.witch.move_right() + if key[pygame.K_1]: + self.apple.move_left() + if key[pygame.K_2]: + self.apple.move_right() + if key[pygame.K_3]: + self.apple.move_up() + self.witch.move_up() + if key[pygame.K_4]: + self.apple.move_down() + self.witch.move_down() + + + + + + #prince.move_up() # Example movement, adjust as needed + #apple.move() # Example apple movement, adjust as needed + + # Check collisions + #if tower.check_collision(prince): + # Handle collision logic + pass + + + self.charactergroup.update() + self.witchgroup.update() + self.towergroup.update() + #self.startbuttongroup.update() + self.rapunzelgroup.update() + self.apple_group.update() + self.cloud_group.update() + # Draw everything + screen.fill((" sky blue")) # White background + # Draw characters, tower, apples, etc. + self.cloud_group.draw(screen) + self.towergroup.draw(screen) + self.charactergroup.draw(screen) + self.witchgroup.draw(screen) + self.rapunzelgroup.draw(screen) + self.apple_group.draw(screen) + + + + if pygame.sprite.collide_rect(self.prince, self.apple): + screen.blit(self.game_over_image, self.game_over_rect) + pygame.display.flip() + pygame.time.delay(3000) # Display for 3 seconds before quitting + start_game = RETRY + game_over= True + self.score=score + + if pygame.sprite.collide_rect(self.prince, self.rapunzel): + screen.blit(self.you_win_image, self.you_win_rect) + pygame.display.flip() + pygame.time.delay(3000) # Display for 3 seconds before quitting + start_game = RETRY + game_over=True + self.score=score + + + #THIS IS NEW STUFF + if pygame.sprite.collide_rect(self.prince, self.apple) or pygame.sprite.collide_rect(self.prince, self.rapunzel): + #screen.blit(self.retry_image, self.retry_rect) + self.retry_button.draw(screen) + pygame.display.flip() + pygame.time.delay(3000) # Display for 3 seconds before quitting + start_game = RETRY + game_over= True + self.score=score + + + + + + + #if pygame.sprite.spritecollide(prince, apple_group, True): + #self.game_over = True + #print("Prince hit by apple! Witch wins.") + + elif start_game == INTRO: + screen.fill("sky blue") + #self.startbuttongroup.draw(screen) + self.start_button.draw(screen) + + + #else: + + #screen.fill("sky blue") + #startbuttongroup.draw(screen) + + pygame.display.flip() + score += clock.tick(60) + + pygame.quit() + +if __name__ == "__main__": + controller = Controller() + controller.mainloop() diff --git a/src/game.py b/src/game.py new file mode 100644 index 0000000..b5a8a34 --- /dev/null +++ b/src/game.py @@ -0,0 +1,238 @@ +import pygame +import json + +class Character(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image= pygame.image.load(img_file) + self.image=pygame.transform.scale_by(self.image, scale) + self.rect=self.image.get_rect() + + def update(self): + self.rect.x=self.x + self.rect.y=self.y + + def move_up(self): + self.y -= 1 + + def move_down(self): + self.y += 1 + + def move_left(self): + self.x -= 1 + + def move_right(self): + self.x += 1 + + def jump(self): + self.y -= 10 + +class Tower(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image= pygame.image.load(img_file) + self.image=pygame.transform.scale_by(self.image, scale) + self.rect=self.image.get_rect() + self.rect.x=x + self.rect.y=y + + def update(self): + self.rect.x=self.x + self.rect.y=self.y + pass + + + def check_collision(self, character): + if (character.x >= self.x and character.x <= self.x + 100) and \ + (character.y >= self.y and character.y <= self.y + 200): + return True + return False +pass + +class Witch(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image= pygame.image.load(img_file) + self.image=pygame.transform.scale_by(self.image, scale) + self.rect=self.image.get_rect() + self.rect.x=x + self.rect.y=y + + def update(self): + self.rect.x=self.x + self.rect.y=self.y + pass + + def move_left(self): + self.x -= 1 + + def move_right(self): + self.x += 1 + + def move_up(self): + self.y -= 1 + + def move_down(self): + self.y += 1 + + +class Apple(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image= pygame.image.load(img_file) + self.image=pygame.transform.scale_by(self.image, scale) + self.rect=self.image.get_rect() + self.rect.x=x + self.rect.y=y + + def update(self): + self.rect.x=self.x + self.rect.y=self.y + pass + + def move_left(self): + self.x -= 1 + + def move_right(self): + self.x += 1 + + def move_up(self): + self.y -= 1 + + def move_down(self): + self.y += 1 + + def check_collision(self, character): + if (character.x >= self.x and character.x <= self.x + 100) and \ + (character.y >= self.y and character.y <= self.y + 200): + return True + return False + + + + + + + +class StartButton(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image = pygame.image.load(img_file) + self.image = pygame.transform.scale_by(self.image, scale) + self.rect = self.image.get_rect() + self.rect.x = x + self.rect.y = y + + def draw(self, screen): + screen.blit(self.image, self.rect) + + def is_clicked(self, mouse_pos): + return self.rect.collidepoint(mouse_pos) + +class Rapunzel(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image= pygame.image.load(img_file) + self.image=pygame.transform.scale_by(self.image, scale) + self.rect=self.image.get_rect() + self.rect.x=x + self.rect.y=y + + def update(self): + self.rect.x=self.x + self.rect.y=self.y + + def check_collision(self, character): + if (character.x >= self.x and character.x <= self.x + 100) and \ + (character.y >= self.y and character.y <= self.y + 200): + return True + return False + +class Cloud(pygame.sprite.Sprite): + def __init__(self, x, y, scale, img_file): + pygame.sprite.Sprite.__init__(self) + self.x = x + self.y = y + self.img_file = img_file + self.image= pygame.image.load(img_file) + self.image=pygame.transform.scale_by(self.image, scale) + self.rect=self.image.get_rect() + self.rect.x=x + self.rect.y=y + + def update(self): + self.rect.x=self.x + self.rect.y=self.y + +class Highscore: + def __init__(self): + self.filename = "assets/highscore.json" + + def getscore(self): + try: + with open(self.filename, "r") as file: + data=json.load(file) + return data["topscore"] + except FileNotFoundError: + return 0 + + def setscore(self, score): + data = {"topscore":score} + with open(self.filename, "w") as file: + json.dump(data,file) + + + + + + + def move(self): + self.y += 1 + +class StartMenu: + def __init__(self, screen, image_path): + self.screen = screen + self.image = pygame.image.load(image_path) + self.image_rect = self.image.get_rect(center=(screen.get_width() // 2, screen.get_height() // 2)) + self.rect.x=x + self.rect.y=y + + + def display_menu(self): + self.screen.blit(self.image, self.image_rect) + + def handle_events(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + elif event.type == pygame.MOUSEBUTTONDOWN: + return True + + def run(self): + while True: + self.screen.fill((0, 0, 0)) + self.display_menu() + pygame.display.flip() + + start_game = self.handle_events() + if start_game: + break