Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2de033a
Create main game class
vivienyuwenchen Oct 18, 2017
7d4d56a
Jumping Dot That Moves Side To Side
hthomas60 Oct 19, 2017
7881c09
Add player and keyboard control
vivienyuwenchen Oct 19, 2017
a72aed8
Merge branch 'master' of https://github.com/vivienyuwenchen/Interacti…
vivienyuwenchen Oct 19, 2017
ae5ded3
Add player class, collision detection, and game over
vivienyuwenchen Oct 21, 2017
035bf5d
Added player summuned obsticals
Oct 22, 2017
860cf7c
Add stamina bar
vivienyuwenchen Oct 23, 2017
dd67c71
player two spawns obsticals at different hights
Oct 23, 2017
9f2156e
merged
Oct 23, 2017
a98883f
Ad project writeup and reflection
vivienyuwenchen Oct 24, 2017
41fc448
added player 2 stamia bar
Oct 24, 2017
757dba6
Merge branch 'master' of https://github.com/vivienyuwenchen/Interacti…
Oct 24, 2017
7bab5b1
Fix isCollide to work at different heights
vivienyuwenchen Oct 25, 2017
0ede561
Change restart game key to space bar
vivienyuwenchen Oct 26, 2017
3d5ef9d
Add docstrings and comments
vivienyuwenchen Oct 26, 2017
6b22ee8
Fixed stamina bar regeneration
Oct 28, 2017
d55ede1
Added score and changed endgame scene
Oct 29, 2017
a4057a2
Update project writeup and reflection
vivienyuwenchen Oct 29, 2017
c6a297c
Add screenshots of game
vivienyuwenchen Oct 30, 2017
522df62
Add screenshots of game
vivienyuwenchen Oct 30, 2017
ea85407
Update README.md
vivienyuwenchen Oct 30, 2017
734db7c
Update project writeup and reflection
vivienyuwenchen Oct 30, 2017
4edcbf7
Merge branch 'master' of https://github.com/vivienyuwenchen/Interacti…
vivienyuwenchen Oct 30, 2017
58d7c1b
Added Captions To figures
hthomas60 Oct 30, 2017
af2ed11
Fixed Caption Spacing
hthomas60 Oct 30, 2017
0f26809
Update inline comments
vivienyuwenchen Oct 30, 2017
c66f558
Merge branch 'master' of https://github.com/vivienyuwenchen/Interacti…
vivienyuwenchen Oct 30, 2017
dd5ea39
Replace Barleft with Bar
vivienyuwenchen Oct 30, 2017
f877988
Added Player reviews
hthomas60 Oct 30, 2017
a896506
Added Player reviews
hthomas60 Oct 30, 2017
20ba136
Added Player reviews
hthomas60 Oct 30, 2017
d33d9c2
Added Player reviews
hthomas60 Oct 30, 2017
4441f05
Added Player reviews
hthomas60 Oct 30, 2017
6a55531
Added Player reviews
hthomas60 Oct 30, 2017
5fb8cc9
Added Player reviews
hthomas60 Oct 30, 2017
ce5b8eb
Same
hthomas60 Oct 30, 2017
886ec96
Add UML diagram
vivienyuwenchen Oct 30, 2017
a8bfe17
Merge branch 'master' of https://github.com/vivienyuwenchen/Interacti…
vivienyuwenchen Oct 30, 2017
eea96f3
Add UML diagram
vivienyuwenchen Oct 30, 2017
b97a0c0
Update project writeup and reflection
vivienyuwenchen Oct 30, 2017
cea81cc
Revised for MP5 with suggestions from feedback and start menu
vivienyuwenchen Dec 10, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions Game4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import os, sys

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a few lines of comments explaining what this cool game is about!

import pygame
from random import randint

from misc import *


class Game4Main:
"""The Main Game4 Class - This class handles the main
initialization and creating of the Game."""

def __init__(self, width=480,height=360):
# initialize pygame
pygame.init()
# set window size
self.width = width
self.height = height
# create screen
self.screen = pygame.display.set_mode((self.width, self.height))
# create clock
self.clock = pygame.time.Clock()
self.score = 0


def mainLoop(self):
"""Main screen for game."""
# initialize player
count = 0
play_len = 25

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be easier to follow the code if all the constants (or config variables) are all in one file. You can make a config.py class and put all the constants there, and load all the config variables at once when initializing the game. Also, it's a convention to use capital letters for constants e.g) PLAY_LEN = 25

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This convention (and others) come from https://www.python.org/dev/peps/pep-0008/#constants

play_x = 100
play_y = self.height - play_len
player = Player(play_x, play_y, play_len, self.screen)

# initialize stamina bars
P1_stamina_bar = StaminaBar(self.screen,25,"WHITE")
P2_stamina_bar = StaminaBar(self.screen,350,"RED")

# initialize obstacle length, x and y coordinates
obs_len = 25
obs_x = self.width
obs_y = self.height - obs_len
obstical_height = self.height - obs_len

# create list of obstacles
obstacles = []
new_obstical = False
# uncomment and place line below in [] of obstacles = [] to generate random obstacles:
# Obstacle(obs_x, obs_y, obs_len, self.screen,'BLUE')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you let user change these settings without interacting with the code? (uncommenting and stuff)



# initialize time variables
prev_time = 0
obs_dt = 500

# main event loop
while 1:
Copy link

@SeunginLyu SeunginLyu Dec 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while 1 does not provide any direct information about when the loop is supposed to end. Using a boolean variable named running = true provides better documentation in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, prefer True or False for a boolean.

Going beyond: you can also use itertools to produce a continuously increasing set of integers, like range(1, 100) except it keeps going forever instead of stopping at 100:

for count in itertools.count(1):

count+=1
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# check keyboard for obstacle player
if event.type == pygame.KEYDOWN:
# z to create obstacle at ground level
if event.key == pygame.K_z:
new_obstical = True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sp. new_obstacle

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using constants to document the levels:

GROUND_LEVEL_X_OFFSET = 0
FIRST_LEVEL_X_OFFSET = 25
SECOND_LEVEL_X_OFFSET = 50obstacle_height = obs_y - FIRST_LEVEL_X_OFFSET

The general principle is that you can name things instead of commenting them. The names are more likely to stay in sync with the code that the comments are, and are also evident everywhere the named thing is used.

You could also use e.g.

LEVEL_OFFSETS = {
   'ground': 0,
  'first': 25,
  'second': 50,
}
…
    obstacle_height = obs_y - LEVEL_OFFSETS['first']

or:

LEVEL_OFFSETS = {
  0: 0,
  1: 25,
  2: 50,
}
…
    obstacle_height = obs_y - LEVEL_OFFSETS[1]
LEVEL_OFFSETS = [0, 25, 50]
…
    obstacle_height = obs_y - LEVEL_OFFSETS[1]

This makes it easier to add levels. It also allows for downstream simplifications, for example a map from event.key to level name or index.

obstical_height = obs_y
# x to create obstacle at second level
if event.key == pygame.K_x:
new_obstical = True
obstical_height = obs_y-25
# c to create obstacle at third level
if event.key == pygame.K_c:
new_obstical = True
obstical_height = obs_y-50

# check keyboard for main player
pressed = pygame.key.get_pressed()
# up to jump
if pressed[pygame.K_UP] and P1_stamina_bar.bars >= player.jumpcost:
if player.play_y == (360 - player.play_len):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

360 is a “magic number”. Replace it by a constant, or an attribute that is initialized next to wherever this value is coming from.

P1_stamina_bar.decreaseBar(player.jumpcost)
player.jump()
P1_stamina_bar.draw()
# left to move left
if pressed[pygame.K_LEFT]:
player.moveLeft()
# right to move right
if pressed[pygame.K_RIGHT]:
player.moveRight()

# refresh screen
self.screen.fill(colors['BLACK'])

# update and draw player
player.update()
player.draw()

# update current time
current_time = pygame.time.get_ticks()

# generate obstacle at random time
if (current_time - prev_time > obs_dt):
# uncomment below to generate random obstacles:
# obstacles.append(Obstacle(obs_x, obs_y, obs_len, self.screen,'BLUE'))
new_obstical = False
prev_time = current_time
obs_dt = randint(1000, 3000)
# generate obstacle player's obstacle at appropriate height
if (new_obstical == True and P2_stamina_bar.bars >= 33):
new_obstical = False
P2_stamina_bar.decreaseBar(33)
obstacles.append(Obstacle(obs_x, obstical_height, obs_len, self.screen,'RED'))

# move each obstacle forward
for obstacle in obstacles:
obstacle.moveForward()
# check for collision between player and obstacles
if player.isCollide(obstacle.obs_x, obstacle.obs_y, obstacle.obs_len):
self.gameOver(str(count))
# remove obstacle from list if off screen
obstacles = [obstacle for obstacle in obstacles if not obstacle.isGone()]

# update stamina bars
P1_stamina_bar.increaseBar()
P2_stamina_bar.increaseBar(1.5)
P1_stamina_bar.draw()
P2_stamina_bar.draw()

# display score
font = pygame.font.SysFont("comicsansms", 72)
text = font.render(str(count), True, (255,255,255))
self.screen.blit(text,[200,10])

# update screen
pygame.display.flip()
self.clock.tick(30)


def gameOver(self,score):
"""Game over screen."""
# main event loop
while 1:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, while True is clearer.

for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going beyond: you can also use either of the following. Experiment with these styles, and see if they're to your liking.

if any(event.type == pygame.QUIT for event in pygame.event.get()):
    sys.exit()
if pygame.QUIT  in {event.type event in pygame.event.get()}:
    sys.exit()

({…} makes a set; same as set(…).)


# check keyboard for space key to restart game
pressed = pygame.key.get_pressed()
if pressed[pygame.K_SPACE]:
self.mainLoop()

# display game over screen
self.screen.fill(colors['BLACK'])
font = pygame.font.SysFont("comicsansms", 28)
text = font.render("Player 1 recived a score of " + str(score), True, (255,255,255))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(255,255,255) is a good candidate for a constant, especially as it's used more than once.

Think fragility and maintainability: it's easy to type (255,25,255) and not notice; and WHITE leaps out to the reader in a way that (255,255,255) does not.

self.screen.blit(text,[50,140])
text = font.render("Press Space Bar to play again", True, (255,255,255))
self.screen.blit(text,[50,178])

# update screen
pygame.display.flip()


if __name__ == "__main__":
Game4Main().mainLoop()
58 changes: 58 additions & 0 deletions Project_Writeup_and_Reflection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Project Writeup and Reflection

### Project Overview [Maximum 100 words]

*Write a short abstract describing your project.*

Blockade is competitive arcade game. It has two players. Player 1 is a white block that is continually moving forward. Player 2’s job is to stop Player 1 by throwing obstacles in its way. Player 1 can avoid obstacles by jumping over or ducking under them. Both players are limited by a stamina bar. Actions such jumping or throwing obstacles depletes player’s stamina and if the stamina gets too low players cannot perform actions. The game uses keyboard inputs as commands.

### Results [~2-3 paragraphs + figures/examples]

*Present what you accomplished. This will be different for each project, but screenshots are likely to be helpful.*

The game starts with Player 1 on the bottom left corner of the screen. Player 1’s objective is simple, avoid obstacles and get as far as you can. Player 1 can avoid obstacles by moving left, right or jumping. These actions are performed by pressing the Left, Right, and Up arrow keys respectively. Player 2 does not show up on screen but can make is presence known by throwing obstacles at Player 1. These obstacles can be thrown at three heights: ground level, just over Player 1 and High in the air. Player 2 can control the height of the obstacles thrown by pressing the Z, X, and C keys. The game ends when Player 1 collides with an obstacle and dies, Player 1’s score is determined by how long they survived. The competitive nature of the game come when the human players swap roles. With both trying to ensure the other gets a lower score.

Player actions cost stamina. If a player’s stamina gets too low, they cannot perform any actions until it regenerates. This adds another level of strategy to the game with each player trying to force the other to deplete their stamina bar. In the game, Player 2’s strategy heavily relies on forcing Player 1 to run out of stamina.

We playtested the game with around 9 individuals. Each person played at least one game as both players. Reviews were generally positive with testers describing it as "fascinating" and "a few minutes of mindless fun".


![screenshot1](https://github.com/vivienyuwenchen/InteractiveProgramming/blob/master/screenshot1.png)

Figure 1: This is how the game starts. Player 1 is the white square in the bottom left corner. PLayer 1’s distance is in the top center of the screen. The stamina bars for Player 1 and 2 are in the top left and right screen respectively.

![screenshot2](https://github.com/vivienyuwenchen/InteractiveProgramming/blob/master/screenshot2.png)

Figure 2: During the game Player 1 avoids the red obstacles by jumping. Player 2 can spawn red obstacles at multiple heights. Player 2’s staminal bar (red) has almost been depleted.

![screenshot3](https://github.com/vivienyuwenchen/InteractiveProgramming/blob/master/screenshot3.png)

Figure 3: Game over screen reports the distance Player 1 traveled and gives instructions for how to play again.

### Implementation [~2-3 paragraphs + UML diagram]

*Describe your implementation at a system architecture level. Include a UML class diagram, and talk about the major components, algorithms, data structures and how they fit together. You should also discuss at least one design decision where you had to choose between multiple alternatives, and explain why you made the choice you did.*

Our minimum viable product was a one player game similar to Google Chrome's dino run, in which the player jumps over randomly generated obstacles. The first class we created was for the main game. Its init handled the main initialization of the game. The mainLoop was a method that ran the main loop of the game, which got keyboard inputs and used them to control the player (and later the obstacles). We created two other classes. Unsurprisingly, they were the player and the obstacle. The obstacle only needed to move forward, so it had a moveForward method aside from init and repr, as well as isGone, used to delete obstacles off screen to prevent the programming from crashing from having to keep track of too many obstacles. The player had more requirements, such as moving left, moving right, and jumping, so we created the methods accordingly. We also added a method for collision detection in order to determine when game over should happen. We then created a gameOver method inside the main class game. We included a conditional statement in the mainLoop to direct the program to gameOver when the player collides with any obstacle and a conditional in gameOver to direct the program back to mainLoop when the space key is pressed.

We got our MVP done by the mid-project check-in, so we added additional features to allow a second player to control the obstacles. We then created another class for the stamina bar, with one method to decrease the bar with every action (i.e. when the player jumps for the player's stamina bar and when an obstacle is generated for the second player's stamina bar) and increase the bar over time up to a certain length.

![UML](https://github.com/vivienyuwenchen/InteractiveProgramming/blob/master/UML.png)

Figure 4: UML diagram of game

At one point, we considered implementing Model View Control. However, given the simplicity of our game, it wasn't worth rewriting whole sections just to split up the view and control into different classes. It would have cut down on the length of our mainLoop, but the pros just didn't outweigh the cons. Another decision that we made was to not create a subclass for each of the stamina bars since we only needed two and their only differences were their color and location, so we took those parameters as arguments instead. We also kept remnants of the code that randomized obstacles from our MVP in case we wanted to revise the code to allow the player choose their mode: one-player or two-player.

### Reflection [~2 paragraphs]

*From a process point of view, what went well? What could you improve? Other possible reflection topics: Was your project appropriately scoped? Did you have a good plan for unit testing? How will you use what you learned going forward? What do you wish you knew before you started that would have helped you succeed?*

*Also discuss your team process in your reflection. How did you plan to divide the work (e.g. split by class, always pair program together, etc.) and how did it actually happen? Were there any issues that arose while working together, and how did you address them? What would you do differently next time?*

Overall, this project went pretty well. We were able to deliver our MVP as well as a few of our stretch goals, like allowing a second player to generate obstacles and adding stamina bars, a scorekeeper, and a display of the score on the game over screen. For the most part, we planned on and were able to divide up the work based on what we wanted to complete within a period of time. We didn't look that far ahead, but planned at the beginning of each of our meetings what we wanted accomplished and divided the work as evenly as possible. Harry developed the main player and Vivien developed the obstacles. Once we made the two players, we both fixed bugs as they came up.

One problem we ran into was keeping a consistent class format. We developed the two players independently and as a result each player was done a little differently. We refactored the main player into an object in order to keep them consistent with each other. For the most part, we were able to effectively work independently and met up to either discuss future features for the project or how to fix bugs.

For a project of this scope our teamwork strategy was appropriate. However, it is not scalable. For larger projects we should spend more time planning out the classes so they can be easily integrated. We also tried to implement a CMV structure into the game; however, by that point most of the game had been written and changing the structure was deemed not worth the effort. If we were to do this project again we would have implement CMV from the beginning.

Unfortunately, Vivien's family came to visit the weekend before the project was due, so she could not spend as much time adding more features to the game as Harry really wanted to do. However, since we got quite a lot done early on, Vivien was able to spend time with her family and Harry just added some of the things he wanted to add.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# InteractiveProgramming

This is the base repo for the interactive programming project for Software Design at Olin College.

[Project Writeup and Reflection](https://github.com/vivienyuwenchen/InteractiveProgramming/blob/master/Project_Writeup_and_Reflection.md)

Required packages:
- pip install pygame

To run the game:
- python Game4.py
Binary file added UML.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added gameover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
128 changes: 128 additions & 0 deletions misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import os, sys

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file can be named models.py. The current file name misc.py does not really provide helpful documentation.

import pygame


colors = {'BLACK': (0, 0, 0),
'WHITE' : (255, 255, 255),
'GREEN' : (0, 255, 0),
'RED' : (255, 0, 0),
'BLUE' : (0, 0, 255),
}


class Obstacle:
"""A square obstacle, defined by its top left hand coordinate, length, and color.
Also takes in screen as an argument to draw the obstacle."""
def __init__(self, obs_x, obs_y, obs_len, screen, color):
"""Initialize the instance."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't necessary.

self.obs_x = obs_x # top left hand x coordinate
self.obs_y = obs_y # top left hand y coordinate
self.obs_len = obs_len # side length
self.screen = screen # game screen
self.color = color # color of obstacle

def __repr__(self):
return 'Obstacle({}, {}, {}, {})'.format(self.obs_x, self.obs_y, self.obs_len, self.screen)

def moveForward(self):
"""Update horizontal location and draw obstacle."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python convention is move_forward, is_gone.

move_forward_and_draw is a clearer description of this methods behavior. Separate move_forward and draw might be more naturally units, though.

self.obs_x -= 20
pygame.draw.rect(self.screen, colors[self.color], [self.obs_x, self.obs_y, self.obs_len, self.obs_len])

def isGone(self):
"""Check if obstacle is completely off screen."""
return self.obs_x < -self.obs_len


class Player:
"""A square player, defined by its top left hand coordinate and length.
Also takes in screen as an argument to draw the player."""
def __init__(self, play_x, play_y, play_len, screen):
"""Initialize the instance."""
self.play_x = play_x # top left hand x coordinate
self.play_y = play_y # top left hand y coordinate
self.play_len = play_len # side length
self.screen = screen # game screen
self.speed = 10 # right/left speed
self.jumpInProgress = False # initialize to False
self.v = 7.5 # "velocity" for jump
self.m = 2.5 # "mass" for jump
self.floor = play_y # location of player before jump, used for comparison
self.jumpcost = 30

def draw(self):
"""Draw player based on top left hand coordinate and length."""
pygame.draw.rect(self.screen, colors['WHITE'], [self.play_x, self.play_y, self.play_len, self.play_len])

def moveRight(self):
"""Update horizontal location of player after moving right."""
if self.play_x < 300:
self.play_x += self.speed

def moveLeft(self):
"""Update horizontal location of player after moving left."""
if self.play_x > 0:
self.play_x -= self.speed

def jump(self):
"""Set jumping status."""
self.jumpInProgress = True

def update(self):
"""Update height of player during jump."""
if self.jumpInProgress:
# change in height = "mass" times "velocity"
dy = self.m * self.v

# subtract height by change in height
self.play_y -= dy
# decrease velocity
self.v -= .75

# stop jumping if player has landed
if self.play_y >= self.floor:
# prevent player from falling through the floor
self.play_y = self.floor
# no longer jumping
self.jumpInProgress = False
# reset velocity
self.v = 7.5

def isCollide(self, obs_x, obs_y, obs_len):
"""Check collision of player with obstacle."""
# set coordinates for top left hand corner (0) and bottom right hand corner (1) of obstacle
obs_x0 = obs_x
obs_x1 = obs_x + obs_len
obs_y0 = obs_y
obs_y1 = obs_y + obs_len
# and of player
play_x0 = self.play_x
play_x1 = self.play_x + self.play_len
play_y0 = self.play_y
play_y1 = self.play_y + self.play_len
# check if player coordinates within obstacle coordinates
if (play_x0 in range(obs_x0, obs_x1) or play_x1 in range(obs_x0, obs_x1)) and (int(play_y0) in range(obs_y0, obs_y1) or int(play_y1) in range(obs_y0, obs_y1)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortcut: if (obs_x0 <= play_x0 < obs_x1 or play_x1 <= play_x0 < obs_x1) …

return True


class StaminaBar:
"""A stamina bar, defined by its starting location and color.
Also takes in screen as an argument to draw the stamina bar."""
def __init__(self, screen, start, color):
self.screen = screen # game screen
self.start = start # starting location of stamina bar
self.color = color # color of stamina bar
self.bars = 100 # initialize number of health bars

def draw(self):
"""Draw stamina bar based on color, starting location, and number of health bars."""
pygame.draw.rect(self.screen, colors[self.color], [self.start, 20, self.bars, 10])

def decreaseBar(self, num_bars):
"""Decrease health bar by num_bars."""
self.bars -= num_bars

def increaseBar(self, speed = 1):
"""Increase health bar continuously if number of bars is lower than 100."""
if self.bars < 100:
self.bars += 1 * speed
Loading