diff --git a/FlappyNerd.py b/FlappyNerd.py new file mode 100644 index 00000000..34b7ea27 --- /dev/null +++ b/FlappyNerd.py @@ -0,0 +1,273 @@ +import pygame +import time +import pygame.locals +import pygame.mixer +from random import randint + +pygame.mixer.init() +pygame.mixer.music.load('Ray1.wav') +pygame.mixer.init(44100, -16,2,2048) +#pygame.mixer.music.load('Pop.wav') + + + +class SpaceShip(pygame.sprite.Sprite): + """ A spaceship sprite. + params: size, which is a tuple of the width an height of the + window in pixels. + """ + + def __init__(self, size): + super().__init__() + self.image = pygame.transform.scale( + pygame.image.load('spaceship.png'), (60, 40)) + self.rect = self.image.get_rect() + self.g = 1 / 9 + self.size = size + self.vy = 0 + + def update(self): + """ Move the spaceship up and down with gravity. + There is a terminal velocity as well as stopping on the + screen boundaries. + """ + + # stop at top of screen + if self.rect.top < 0: + self.rect.top = 0 + + # stop at bottom of screen + elif self.rect.top > self.size[1] - self.rect.height: + self.rect.top = self.size[1] - self.rect.height + + else: + self.rect = self.rect.move(0, self.vy) + + # terminal velocity + self.vy = 10 / 3 if self.vy >= 10 / 3 else self.vy + self.g + + +class KeyboardController: + """ Control the ship with the keyboard. """ + + def __init__(self, model): + self.model = model + + def handle_event(self, event): + """ Space to flap or restart the game. """ + if event.type != pygame.locals.KEYDOWN: + return + if event.key == pygame.K_SPACE: + # Space either restarts the game or flaps the ship. + if self.model.in_progress: + self.model.spaceship.vy = -10 / 3 + else: + self.model.reset() + + +class Jupiter(pygame.sprite.Sprite): + """ Backdrop of jupiter""" + + def __init__(self, size): + super().__init__() + self.image = pygame.image.load('jupiter.png') + self.rect = self.image.get_rect() + self.rect.left = size[0] + self.vx = 0.05 + self.x = size[0] + + def update(self): + self.x -= self.vx + self.rect.left = self.x + + +class Model: + """ The model encompassing the state of the game. """ + + def __init__(self, size): + self.spaceship = SpaceShip(size) + self.astroidBelt = AstroidBelt(99, size) + self.in_progress = True + self.jupiter = Jupiter(size) + self.over_message = "Game Over, Hit Space!" + + def update(self): + """ Update the state of the game. """ + + # if collide with an unsafe astroid, stop the game. + if pygame.sprite.spritecollide(self.spaceship, self.astroidBelt.unsafeAstroids, False): + self.in_progress = False + + # remove the safe astroid once collided. + safe_collide = pygame.sprite.spritecollide( + self.spaceship, self.astroidBelt.safeAstroids, False) + for astroid in safe_collide: + self.astroidBelt.sound.play(loops=0) + self.astroidBelt.astroids.remove(astroid) + + #Crux of game, without this, game won't run + if self.in_progress: + self.spaceship.update() #Moves the spaceship + self.astroidBelt.update() #Calls the astroid belt and actions associated + self.jupiter.update() #Calls backdroup + + + def reset(self): + self.astroidBelt = AstroidBelt(99, size) + self.in_progress = True + + +class WindowView: + """ A class which renders the scene. """ + + def __init__(self, model, size): + self.model = model + self.size = size + self.font = pygame.font.SysFont('monospace', 45) + self.screen = pygame.display.set_mode(size) + + def draw(self): + self.screen.fill(pygame.Color(0, 0, 0)) + self.screen.blit(self.model.jupiter.image, self.model.jupiter.rect) + self.model.astroidBelt.astroids.draw(self.screen) + + # render the number options. + for astroid in self.model.astroidBelt.astroids: + self.screen.blit(self.font.render( + str(astroid.num), True, (255, 255, 255)), (astroid.rect.left + 40, astroid.rect.top + 40)) + + # render the probleem + self.screen.blit(self.font.render(str(self.model.astroidBelt.prob.a) + + " x " + str(self.model.astroidBelt.prob.b), True, (0, 0, 255)), (50, 20)) + + self.screen.blit(self.font.render( + str(int(self.model.astroidBelt.score)), True, (255, 255, 0)), (500, 20)) + + self.screen.blit(self.model.spaceship.image, self.model.spaceship.rect) + + if not self.model.in_progress: + self.screen.blit(self.font.render( + self.model.over_message, True, (0, 255, 255)), (100, 200)) + pygame.display.update() + + +class Astroid(pygame.sprite.Sprite): + """ A single astroid sprite. + It contains a number to be rendered on it as well as the window + dimensions as an iterable. + """ + + def __init__(self, num, size): + super().__init__() + self.num = num + self.size = size + self.image = pygame.transform.scale( + pygame.image.load('astroid.png'), (int(size[1] / 4), int(size[1] / 4))) + self.rect = self.image.get_rect() + + +class MultProb: + """ A randomly generated multiplication problem and answer + choices.. + Its parameter is the maximum number which can show up in a + problem. + """ + + def __init__(self, difficulty): + self.difficulty = difficulty + self.new_prob() + + def new_prob(self): + """ Create a new problem. """ + self.a = randint(0, self.difficulty) + self.b = randint(0, self.difficulty) + self.ans = self.a * self.b + self.generate_choices() + + def generate_choices(self): + """ Generate choices """ + choices = [] + self.safe_lvl = randint(0, 3) + for x in range(4): + choice = self.ans + # make choices all look viable by setting the last digit + # as well as constraining the range. + while choice == self.ans: + choice = randint(self.a // 10 * self.b // 10 * 100, (self.a // 10 + 1) + * (self.b // 10 + 1) * 100) // 10 * 10 + self.ans % 10 + choices.append(choice) + choices.insert(self.safe_lvl, self.ans) + self.choices = choices + + +class AstroidBelt: + """ A class containing groups of astroids. + difficulty is passed onto the multiplication problem. + For convenience, the score of the game is also kept here. + """ + + def __init__(self, difficulty, size): + self.difficulty = difficulty + self.size = size + self.prob = MultProb(self.difficulty) + self.gen_astroids() + self.score = 0 + self.x = size[0] + self.vx = 2/3 + self.sound = pygame.mixer.Sound('Ray1.wav') + + def gen_astroids(self): + """ Generate new astroids. """ + + astroids = pygame.sprite.Group( + *[Astroid(c, self.size) for c in self.prob.choices]) + + # group of wrong answers + self.unsafeAstroids = pygame.sprite.Group() + + # group of correct answers. + self.safeAstroids = pygame.sprite.Group() + for i, astroid in enumerate(astroids): + if i != self.prob.safe_lvl: + self.unsafeAstroids.add(astroid) + else: + self.safeAstroids.add(astroid) + for n, astroid in enumerate(astroids): + astroid.rect.top = self.size[1] / 4 * n + astroid.rect.left = self.size[0] + self.astroids = astroids + + def update(self): + """ move astroids over with constant speed, wrapping around + the screen. + """ + for astroid in self.astroids: + if self.x < -astroid.rect.width: + self.score += 1 #Score increases by 1 per astroid + self.difficulty += 1/4 #difficulty increases by a fraction + self.prob.new_prob() + self.gen_astroids() + self.vx += 0.01 + self.x = self.size[0] + else: + self.x -= self.vx / 4 + astroid.rect.left = self.x + + +if __name__ == '__main__': + pygame.init() + size = (640, 480) + model = Model(size) + view = WindowView(model, size) + running = True + controller = KeyboardController(model) + + while running: + for event in pygame.event.get(): + if event.type == pygame.locals.QUIT: + running = False + controller.handle_event(event) + model.update() + view.draw() + time.sleep(0.01) + pygame.quit() diff --git a/Flappy_Nerd_Reflection.pdf b/Flappy_Nerd_Reflection.pdf new file mode 100644 index 00000000..e2e91b56 Binary files /dev/null and b/Flappy_Nerd_Reflection.pdf differ diff --git a/IMG_1338.JPG b/IMG_1338.JPG new file mode 100644 index 00000000..c3412309 Binary files /dev/null and b/IMG_1338.JPG differ diff --git a/README.md b/README.md deleted file mode 100644 index 5f822327..00000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# InteractiveProgramming -This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. diff --git a/README.org b/README.org new file mode 100644 index 00000000..6b48232d --- /dev/null +++ b/README.org @@ -0,0 +1,79 @@ +[[file:sample.png]] + +* How to play +1. install [[https://www.python.org/][python]] and [[https://www.pygame.org/news][pygame]]. +2. =$ git clone https://github.com/concavegit/interactiveprogramming= +3. =$ cd InteractiveProgramming= +4. =$ python SpaceShip.py= +5. Bang your head on your desk wondering why you are playing such an evil game. + +* FlappyNerd +Educational Flappy Bird + +* UML Diagram +#+BEGIN_SRC plantuml :file class_diagram.png :exports results :results file + SpaceShip <|-- Sprite + Astroid <|-- Sprite + AstroidBelt <|-- Astroid + AstroidBelt <|-- MultProb + + Model *-- AstroidBelt + Model *-- SpaceShip + Model *-- Jupiter + KeyboardController *--Model + + WindowView *-- Model + + class WindowView { + size + model + font + screen + draw() + } + + class Astroid { + num + size + rect + update() + } + + class Model { + inProgress + spaceShip + astroidBelt + jupiter + update() + } + + class SpaceShip { + size + vx + x + rect + update() + } + + class AstroidBelt { + size + prob + vx + } + + class KeyboardController { + model + handleEvent() + } + + class MultProb { + a + b + choices + new_prob() + gen_choices() + } +#+END_SRC + +#+RESULTS: +[[file:class_diagram.png]] diff --git a/Ray1.wav b/Ray1.wav new file mode 100644 index 00000000..f67c7344 Binary files /dev/null and b/Ray1.wav differ diff --git a/SpaceShip.py b/SpaceShip.py new file mode 100644 index 00000000..d53a8925 --- /dev/null +++ b/SpaceShip.py @@ -0,0 +1,264 @@ +import pygame +import time +import pygame.locals +from random import randint + + +class SpaceShip(pygame.sprite.Sprite): + """ A spaceship sprite. + params: size, which is a tuple of the width an height of the + window in pixels. + """ + + def __init__(self, size): + super().__init__() + self.image = pygame.transform.scale( + pygame.image.load('spaceship.png'), (60, 40)) + self.rect = self.image.get_rect() + self.g = 1 / 9 + self.size = size + self.vy = 0 + + def update(self): + """ Move the spaceship up and down with gravity. + There is a terminal velocity as well as stopping on the + screen boundaries. + """ + + # stop at top of screen + if self.rect.top < 0: + self.rect.top = 0 + + # stop at bottom of screen + elif self.rect.top > self.size[1] - self.rect.height: + self.rect.top = self.size[1] - self.rect.height + + else: + self.rect = self.rect.move(0, self.vy) + + # terminal velocity + self.vy = 10 / 3 if self.vy >= 10 / 3 else self.vy + self.g + + +class KeyboardController: + """ Control the ship with the keyboard. """ + + def __init__(self, model): + self.model = model + + def handle_event(self, event): + """ Space to flap or restart the game. """ + if event.type != pygame.locals.KEYDOWN: + return + if event.key == pygame.K_SPACE: + # Space either restarts the game or flaps the ship. + if self.model.in_progress: + self.model.spaceship.vy = -10 / 3 + else: + self.model.reset() + + +class Jupiter(pygame.sprite.Sprite): + """ Backdrop of jupiter""" + + def __init__(self, size): + super().__init__() + self.image = pygame.image.load('jupiter.png') + self.rect = self.image.get_rect() + self.rect.left = size[0] + self.vx = 0.05 + self.x = size[0] + + def update(self): + self.x -= self.vx + self.rect.left = self.x + + +class Model: + """ The model encompassing the state of the game. """ + + def __init__(self, size): + self.spaceship = SpaceShip(size) + self.astroidBelt = AstroidBelt(99, size) + self.in_progress = True + self.jupiter = Jupiter(size) + self.over_message = "Game Over, Hit Space!" + + def update(self): + """ Update the state of the game. """ + + # if collide with an unsafe astroid, stop the game. + if pygame.sprite.spritecollide(self.spaceship, self.astroidBelt.unsafeAstroids, False): + self.in_progress = False + + # remove the safe astroid once collided. + safe_collide = pygame.sprite.spritecollide( + self.spaceship, self.astroidBelt.safeAstroids, False) + for astroid in safe_collide: + self.astroidBelt.astroids.remove(astroid) + + if self.in_progress: + self.spaceship.update() + self.astroidBelt.update() + self.jupiter.update() + + def reset(self): + self.astroidBelt = AstroidBelt(99, size) + self.in_progress = True + + +class WindowView: + """ A class which renders the scene. """ + + def __init__(self, model, size): + self.model = model + self.size = size + self.font = pygame.font.SysFont('monospace', 45) + self.screen = pygame.display.set_mode(size) + + def draw(self): + self.screen.fill(pygame.Color(0, 0, 0)) + self.screen.blit(self.model.jupiter.image, self.model.jupiter.rect) + self.model.astroidBelt.astroids.draw(self.screen) + + # render the number options. + for astroid in self.model.astroidBelt.astroids: + self.screen.blit(self.font.render( + str(astroid.num), True, (255, 255, 255)), (astroid.rect.left + 40, astroid.rect.top + 40)) + + # render the probleem + self.screen.blit(self.font.render(str(self.model.astroidBelt.prob.a) + + " x " + str(self.model.astroidBelt.prob.b), True, (0, 0, 255)), (50, 20)) + + self.screen.blit(self.font.render( + str(int(self.model.astroidBelt.score)), True, (255, 255, 0)), (500, 20)) + + self.screen.blit(self.model.spaceship.image, self.model.spaceship.rect) + + if not self.model.in_progress: + self.screen.blit(self.font.render( + self.model.over_message, True, (0, 255, 255)), (100, 200)) + pygame.display.update() + + +class Astroid(pygame.sprite.Sprite): + """ A single astroid sprite. + It contains a number to be rendered on it as well as the window + dimensions as an iterable. + """ + + def __init__(self, num, size): + super().__init__() + self.num = num + self.size = size + self.image = pygame.transform.scale( + pygame.image.load('astroid.png'), (int(size[1] / 4), int(size[1] / 4))) + self.rect = self.image.get_rect() + + +class MultProb: + """ A randomly generated multiplication problem and answer + choices.. + Its parameter is the maximum number which can show up in a + problem. + """ + + def __init__(self, difficulty): + self.difficulty = difficulty + self.new_prob() + + def new_prob(self): + """ Create a new problem. """ + self.a = randint(0, self.difficulty) + self.b = randint(0, self.difficulty) + self.ans = self.a * self.b + self.generate_choices() + + def generate_choices(self): + """ Generate choices """ + choices = [] + self.safe_lvl = randint(0, 3) + for x in range(4): + choice = self.ans + # make choices all look viable by setting the last digit + # as well as constraining the range. + while choice == self.ans: + choice = randint(self.a // 10 * self.b // 10 * 100, (self.a // 10 + 1) + * (self.b // 10 + 1) * 100) // 10 * 10 + self.ans % 10 + choices.append(choice) + choices.insert(self.safe_lvl, self.ans) + self.choices = choices + + +class AstroidBelt: + """ A class containing groups of astroids. + difficulty is passed onto the multiplication problem. + For convenience, the score of the game is also kept here. + """ + + def __init__(self, difficulty, size): + self.difficulty = difficulty + self.size = size + self.prob = MultProb(self.difficulty) + self.gen_astroids() + self.score = 0 + self.x = size[0] + self.vx = 2/3 + + def gen_astroids(self): + """ Generate new astroids. """ + + astroids = pygame.sprite.Group( + *[Astroid(c, self.size) for c in self.prob.choices]) + + # group of wrong answers + self.unsafeAstroids = pygame.sprite.Group() + + # group of correct answers. + self.safeAstroids = pygame.sprite.Group() + for i, astroid in enumerate(astroids): + if i != self.prob.safe_lvl: + self.unsafeAstroids.add(astroid) + else: + self.safeAstroids.add(astroid) + for n, astroid in enumerate(astroids): + astroid.rect.top = self.size[1] / 4 * n + astroid.rect.left = self.size[0] + self.astroids = astroids + + def update(self): + """ move astroids over with constant speed, wrapping around + the screen. + """ + point = False + for astroid in self.astroids: + if self.x < -astroid.rect.width: + self.score = point or True + self.difficulty += 1/4 + self.prob.new_prob() + self.gen_astroids() + self.vx += 0.01 + self.x = self.size[0] + else: + self.x -= self.vx / 4 + astroid.rect.left = self.x + self.score += point + + +if __name__ == '__main__': + pygame.init() + size = (640, 480) + model = Model(size) + view = WindowView(model, size) + running = True + controller = KeyboardController(model) + + while running: + for event in pygame.event.get(): + if event.type == pygame.locals.QUIT: + running = False + controller.handle_event(event) + model.update() + view.draw() + time.sleep(0.01) + pygame.quit() diff --git a/astroid.png b/astroid.png new file mode 100644 index 00000000..916344d3 Binary files /dev/null and b/astroid.png differ diff --git a/class_diagram.png b/class_diagram.png new file mode 100644 index 00000000..ad579d8a Binary files /dev/null and b/class_diagram.png differ diff --git a/jupiter.png b/jupiter.png new file mode 100644 index 00000000..2cf72834 Binary files /dev/null and b/jupiter.png differ diff --git a/proposal.org b/proposal.org new file mode 100644 index 00000000..abba9a12 --- /dev/null +++ b/proposal.org @@ -0,0 +1,18 @@ +* Main idea +The main idea is to create flappy bird, but more educational by having the target be the answer to a multiplication problem rather than a gap in the pipes. +Topics explored will be modeling a game using OOP and MVC, as well as overall structure on handling gravity, distance from viewport (if we add, say, the moon in the distance), etc. +The minimum viable product is a rectangle flying through a stack of boxed numbers with the score and problem displayed. +The stretch goal is moving scenery, complete with a flapping bird and a moon in the distance. + +* Learning Goals +- Kawin :: Get comfortable with creating games, or anything which requires animation and user input. +- Daena :: Learn how to break down problems in a programmer-esque way. + +* Libraries +- pygame :: render graphics scene, work with rects, etc. + +* Mid-project check-in +We'd like to have an MVP ready which is structured well enough that future addition is trivial. + +* Risks +- Making the game visually appealing is the hardest part. diff --git a/sample.png b/sample.png new file mode 100644 index 00000000..2b422080 Binary files /dev/null and b/sample.png differ diff --git a/spaceship.png b/spaceship.png new file mode 100644 index 00000000..9dc840a1 Binary files /dev/null and b/spaceship.png differ