diff --git a/Avacado-Sliced.png b/Avacado-Sliced.png new file mode 100644 index 0000000..eb1a3ac Binary files /dev/null and b/Avacado-Sliced.png differ diff --git a/Guac.png b/Guac.png new file mode 100644 index 0000000..8892cf2 Binary files /dev/null and b/Guac.png differ diff --git a/GuacSong.mp3 b/GuacSong.mp3 new file mode 100644 index 0000000..3461634 Binary files /dev/null and b/GuacSong.mp3 differ diff --git a/ProjectWrite-UpInteractiveGame.pdf b/ProjectWrite-UpInteractiveGame.pdf new file mode 100644 index 0000000..8b82aa3 Binary files /dev/null and b/ProjectWrite-UpInteractiveGame.pdf differ diff --git a/README.md b/README.md index 61ec120..ee03338 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # InteractiveProgramming -This is the base repo for the interactive programming project for Software Design, Spring 2016 at Olin College. +This is the base repo for our interactive programming project for Software Design, Spring 2016 at Olin College. + +There are three main python files in this project; two in PyGame and one in OpenCV. The PyGame file titled "whack_a_mole_no_cv.py" is the code for our mvp that runs the Whack-a-Mole game with a mouse controller. The PyGame file titled "whack_a_mole.py" is the code for the PyGame portion of the final Gual-a_Mole game that is paired with the OpenCV file "WhackAMoleOpenCV.py". Also in the folder are the image files necessary to display the visuals correctly and the sound files necessary to play the sound effects correctly. + +Open the whack_a_mole.py and WhackAMoleOpenCV.py files in Sublime Text and run from the whack_a_mole.py file to play Guac-a-Mole! Make sure you have something blue in your hand for the OpenCV to detect and no other blue in the space around you! + +Open the whack_a_mole_no_cv.py file and run in Sublime Text to play the mouse controlled version of Whack-a-Mole! + +https://www.youtube.com/watch?v=JNsKvZo6MDs Thank you for the inspiration, Dr. Jean! diff --git a/WhackAMole.py b/WhackAMole.py new file mode 100644 index 0000000..03eeb99 --- /dev/null +++ b/WhackAMole.py @@ -0,0 +1,113 @@ +import pygame +from pygame.locals import QUIT, KEYDOWN, MOUSEMOTION +import time +from random import choice + + +class MoleView(object): + """ Visualizes a brick breaker game in a pygame window """ + def __init__(self, model, screen): + """ Initialize the view with the specified model + and screen. """ + self.model = model + self.screen = screen + + + def draw(self): + """ Draw the game state to the screen """ + self.screen.fill(pygame.Color('black')) + # draw the bricks to the screen + for brick in self.model.bricks: + r = pygame.Rect(brick.left, brick.top, brick.width, brick.height) + pygame.draw.rect(self.screen, pygame.Color(brick.color), r) + pygame.display.update() + +class MoleHole(object): + """ Represents a brick in our brick breaker game """ + def __init__(self, left, top, width, height): + self.left = left + self.top = top + self.width = width + self.height = height + self.color = "red" # choice(["red", "green", "orange", "blue", "purple"]) + +class MoleModel(object): + """ Stores the game state for our brick breaker game """ + def __init__(self): + self.bricks = [] + self.MARGIN = 5 + self.BRICK_WIDTH = 40 + self.BRICK_HEIGHT = 40 + + + new_hole = MoleHole(5, 5, 40, 20) + self.bricks.append(new_hole) + for left in range(self.MARGIN, + 640 - self.BRICK_WIDTH - self.MARGIN, + self.BRICK_WIDTH + self.MARGIN): + for top in range(self.MARGIN, + 980/2, + self.BRICK_HEIGHT + self.MARGIN): + mole_hole = MoleHole(left, top, self.BRICK_WIDTH, self.BRICK_HEIGHT) + self.bricks.append(mole_hole) + + self.last_modified = choice(self.bricks) + self.last_modified.color = "blue" + + def update(self): + print "updating" + change_color = choice(self.bricks) + change_color.color = "blue" + if self.last_modified: + self.last_modified.color = "red" + self.last_modified = change_color + # print self.last_modified.top + print self.last_modified.left + +class MoleMouseController(object): + def __init__(self, model): + self.model = model + + def get_cursor(self): + get_pressed = pygame.mouse.get_pressed() + print self.model.last_modified.left + if get_pressed[0] == 1 or get_pressed[1] == 1 or get_pressed[2] == 1: + mouse_position = pygame.mouse.get_pos() + print mouse_position + if mouse_position[0] > self.model.last_modified.left and mouse_position[0] < (self.model.last_modified.left+40): + if mouse_position[1] > self.model.last_modified.top and mouse_position[1] < (self.model.last_modified.top+40): + print "IN BOUNDS" + # self.model.update() + self.model.last_modified.color = 'red' + + + # def handle_event(self, event): + # """ Look for mouse movements and respond appropriately """ + # if event.type != MOUSEMOTION: + # return + # self.model.paddle.left = event.pos[0] + +if __name__ == '__main__': + pygame.init() + size = (640, 480) + screen = pygame.display.set_mode(size) + + model = MoleModel() + view = MoleView(model, screen) + # controller = PyGameKeyboardController(model) + controller = MoleMouseController(model) + + running = True + while running: + ticks = pygame.time.get_ticks() + if ticks%1000 == 0: + model.update() + for event in pygame.event.get(): + if event.type == QUIT: + running = False + # controller.handle_event(event) + controller.get_cursor() + view.draw() + # time.sleep(1) + # pygame.time.wait(1000) + # print pygame.time.get_ticks() \ No newline at end of file diff --git a/WhackAMole1.jpg b/WhackAMole1.jpg new file mode 100644 index 0000000..62d7825 Binary files /dev/null and b/WhackAMole1.jpg differ diff --git a/WhackAMoleOpenCV.py b/WhackAMoleOpenCV.py new file mode 100644 index 0000000..eddf15d --- /dev/null +++ b/WhackAMoleOpenCV.py @@ -0,0 +1,53 @@ +#Import OpenCV +import cv2 +#Import Numpy +import numpy as np + +"""OpenCV portion of Gual-a-Mole! code""" + +camera_feed = cv2.VideoCapture(0) + +def color_tracking(child): + while(1): + _,frame = camera_feed.read() + #Convert the current frame to HSV + hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + + #Define the threshold for finding a blue object with hsv + lower_blue = np.array([100,100,100]) + upper_blue = np.array([140,255,255]) + + #Create a binary image, where anything blue appears white and everything else is black + mask = cv2.inRange(hsv, lower_blue, upper_blue) + + #Get rid of background noise using erosion and fill in the holes using dilation and erode the final image on last time + element = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3)) + mask = cv2.erode(mask,element, iterations=2) + mask = cv2.dilate(mask,element,iterations=2) + mask = cv2.erode(mask,element) + + #Create Contours for all blue objects + contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + maximumArea = 0 + bestContour = None + for contour in contours: + currentArea = cv2.contourArea(contour) + if currentArea > maximumArea: + bestContour = contour + maximumArea = currentArea + #Create a bounding box around the biggest blue object + if bestContour is not None: + x,y,w,h = cv2.boundingRect(bestContour) + cv2.rectangle(frame, (x,y),(x+w,y+h), (0,0,255), 3) + # sends the center of the rectangle to the parent + child.send([x+w/2,y+h/2]) + + #Show the original camera feed with a bounding box overlayed + cv2.imshow('frame',frame) + #Show the contours in a seperate window + cv2.imshow('mask',mask) + #Use this command to prevent freezes in the feed + k = cv2.waitKey(5) & 0xFF + #If escape is pressed close all windows + if k == 27: + break \ No newline at end of file diff --git a/avocado.jpeg b/avocado.jpeg new file mode 100644 index 0000000..dc55716 Binary files /dev/null and b/avocado.jpeg differ diff --git a/knife.png b/knife.png new file mode 100644 index 0000000..bf0a2e1 Binary files /dev/null and b/knife.png differ diff --git a/punch.wav b/punch.wav new file mode 100644 index 0000000..aae9ee0 Binary files /dev/null and b/punch.wav differ diff --git a/whack_a_mole.py b/whack_a_mole.py new file mode 100644 index 0000000..28323aa --- /dev/null +++ b/whack_a_mole.py @@ -0,0 +1,187 @@ +import pygame +from pygame.locals import QUIT, KEYDOWN, MOUSEMOTION +from pygame.transform import scale +import time +from random import choice +from multiprocessing import Process, Pipe +from WhackAMoleOpenCV import color_tracking +"""Guac-a-Mole code that connects with WhackAMoleOpenCV""" +class MoleView(object): + """ Visualizes a guac_a_mole game in a pygame window """ + def __init__(self, model, screen): + """ Initialize the view with the specified model + and screen. """ + self.model = model + self.screen = screen + + def draw(self): + """ Draw the game state to the screen """ + # draw the black screen + self.screen.fill(pygame.Color('black')) + # load images + img = pygame.image.load('Avacado-Sliced.png') + img_whacked = pygame.image.load('Guac.png') + img_knife = pygame.image.load('knife.png') + # make and display title + white = (250, 250, 250) + message = 'GUAC-A-MOLE!' + font = pygame.font.Font(None, 40) + text = font.render(message, 1, white) + screen.blit(text, (202,40)) + # make and display a countdown timer + white = (250, 250, 250) + message = str(model.time_left) + font = pygame.font.Font(None, 40) + text = font.render(message, 1, white) + screen.blit(text, (600,10)) + # make and display scoreboard + white = (250, 250, 250) + message = 'Bowls of Guac: ' + str(model.score) + font = pygame.font.Font(None, 40) + text = font.render(message, 1, white) + screen.blit(text, (400,450)) + # draw the knife cursor + r = pygame.Rect(self.model.hand[0], self.model.hand[1], 10, 10) + pygame.draw.rect(self.screen, pygame.Color('black'), r) + self.screen.blit(pygame.transform.scale(img_knife, (40,40)), (self.model.hand[0],self.model.hand[1])) + # if avocado is sliced, display guacamole image + if self.model.last_modified.whacked: + self.screen.blit(pygame.transform.scale(img_whacked, (40,40)), (self.model.last_modified.left,self.model.last_modified.top)) + # display moving avocado + else: + self.screen.blit(pygame.transform.scale(img, (40,40)), (self.model.last_modified.left,self.model.last_modified.top)) + pygame.display.update() + +class MoleHole(object): + """ Represents a mole hole in our whack-a-mole game """ + def __init__(self, left, top, width, height): + self.left = left + self.top = top + self.width = width + self.height = height + self.color = "green" + self.draw = True + self.whacked = False + +class MoleModel(object): + """ Stores the game state for our whack-a-mole game """ + def __init__(self): + self.holes = [] + self.MARGIN = 5 + self.BRICK_WIDTH = 40 + self.BRICK_HEIGHT = 40 + self.score = 0 + self.missed = 0 + self.click = False + self.hand = [0,0] + self.time_left = 60 + + # instantiates a new_hole and appends it to the self.holes list + new_hole = MoleHole(5, 5, 40, 20) + self.holes.append(new_hole) + for left in range(self.MARGIN, + 640 - self.BRICK_WIDTH - self.MARGIN, + self.BRICK_WIDTH + self.MARGIN): + for top in range(self.MARGIN, + 980/2, + self.BRICK_HEIGHT + self.MARGIN): + mole_hole = MoleHole(left, top, self.BRICK_WIDTH, self.BRICK_HEIGHT) + self.holes.append(mole_hole) + # randomly chooses a new hole and sets it equal to self_last_modified + self.last_modified = choice(self.holes) + + def update(self): + """Updates the status of the chosen mole hole and the timer, and ends the game if the countdown + has run out of time""" + change_color = choice(self.holes) + change_color.draw = False + if self.last_modified: + self.last_modified.draw = True + self.last_modified = change_color + self.last_modified.whacked = False + self.time_left -= 1 + if self.time_left == -1: + font = pygame.font.Font(None, 40) + white = (250, 250, 250) + text2 = font.render("Game Over! Enjoy the guac!",1, white) + screen.blit(text2, (140,200)) + pygame.display.update() + pygame.time.wait(2000) + pygame.quit() + sys.exit() + + def whack(self): + """If the click attribute is true, sets the click attribute to false and + sets last_modified.whacked to true to indicate that the avocado has been + sliced and the guacamole image should appear. + """ + if self.click: + self.click = False + self.last_modified.whacked = True + +class MoleMouseController(object): + """Represents the controller of the mouse position""" + def __init__(self, model): + self.model = model + + def get_cursor(self): + """Sets model.hand (the output of WhackAMoleOpenCV) equal to mouse_position, + horizontally flips the mouse_position, and checks if the mouse_position is + in the location of the avocado.""" + mouse_position = self.model.hand + mouse_position[0] = 640 - self.model.hand[0] + if mouse_position[0] > self.model.last_modified.left and mouse_position[0] < (self.model.last_modified.left+40): + if mouse_position[1] > self.model.last_modified.top and mouse_position[1] < (self.model.last_modified.top+40): + self.model.click = True + self.model.last_modified.color = 'red' + self.model.score += 5 + +def whack_mole(parent): + """The function that contains the while loop of the parent""" + running = True + # default value of 0 for time of the last update + previous_update = 0 + # while True statement + while running: + # sets ticks equal to current timing + ticks = pygame.time.get_ticks() + model.whack() + # sets model.hand equal to the output of WhackAMoleOpenCV.py + model.hand = parent.recv() + # calls the method get_cursor of the object controller + controller.get_cursor() + # checks if the difference between the past update and current time is + # greater than 1 second and updates if it is + if ticks - previous_update > 1000: + previous_update = ticks + model.update() + # calls the method draw of the object view + view.draw() + +if __name__ == '__main__': + pygame.init() + size = (640, 480) + screen = pygame.display.set_mode(size) + + # instantiates an object of MoleModel + model = MoleModel() + # instantiates an object MoleView + view = MoleView(model, screen) + # instantiates an object of MoleMouseController + controller = MoleMouseController(model) + + # plays the background music + pygame.mixer.music.load ("GuacSong.mp3") + pygame.mixer.music.play(-1,0.0) + + # creates a connection between the parent and child + parent, child = Pipe() + + # allows for multiprocessing of both the while loops of whack_mole + # and color_tracking from WhackAMoleOpenCV + p1 = Process(target=whack_mole, args=(parent,)) + p2 = Process(target=color_tracking, args=(child,)) + p1.start() + p2.start() + p1.join() + p2.join() \ No newline at end of file diff --git a/whack_a_mole_no_cv.py b/whack_a_mole_no_cv.py new file mode 100644 index 0000000..0e75ad0 --- /dev/null +++ b/whack_a_mole_no_cv.py @@ -0,0 +1,155 @@ +import pygame +from pygame.locals import QUIT, KEYDOWN, MOUSEMOTION +from pygame.transform import scale +import time +from random import choice +"""Whack-a-Mole code for mouse control, no OpenCV""" + +class MoleView(object): + """ Visualizes a whack_a_mole game in a pygame window """ + def __init__(self, model, screen): + """ Initialize the view with the specified model + and screen. """ + self.model = model + self.screen = screen + + def draw(self): + """ Draw the game state to the screen """ + # draw the black screen + self.screen.fill(pygame.Color('black')) + # load images + img = pygame.image.load('WhackAMole1.jpg') + img_whacked = pygame.image.load('whacked.jpg') + # make and display title + white = (250, 250, 250) + message = 'WHACK-A-MOLE!' + font = pygame.font.Font(None, 40) + text = font.render(message, 1, white) + screen.blit(text, (192,40)) + # make and display scoreboard + white = (250, 250, 250) + message = 'Score: ' + str(model.score) + ' Failed Whacks: ' + str(model.missed) + font = pygame.font.Font(None, 40) + text = font.render(message, 1, white) + screen.blit(text, (50,450)) + # if there are 3 failed whacks, end game + if self.model.missed == 3: + text2 = font.render("Game Over!",1, white) + screen.blit(text2, (230,200)) + pygame.display.update() + pygame.time.wait(2000) + pygame.quit() + sys.exit() + # if mole is whacked, display Pow! image and punch! sound effect + if self.model.last_modified.whacked: + self.screen.blit(pygame.transform.scale(img_whacked, (40,40)), (self.model.last_modified.left,self.model.last_modified.top)) + pygame.mixer.music.load ("punch.wav") + pygame.mixer.music.play(1,0.0) + # display moving mole + else: + self.screen.blit(pygame.transform.scale(img, (40,40)), (self.model.last_modified.left,self.model.last_modified.top)) + pygame.display.update() + +class MoleHole(object): + """ Represents a mole hole in our whack-a-mole game """ + def __init__(self, left, top, width, height): + self.left = left + self.top = top + self.width = width + self.height = height + self.color = "green" + self.draw = True + self.whacked = False + +class MoleModel(object): + """ Stores the game state for our whack-a-mole game """ + def __init__(self): + self.holes = [] + self.MARGIN = 5 + self.BRICK_WIDTH = 40 + self.BRICK_HEIGHT = 40 + self.score = 0 + self.missed = 0 + self.click = False + + # instantiates a new_hole and appends it to the self.holes list + new_hole = MoleHole(5, 5, 40, 20) + self.holes.append(new_hole) + for left in range(self.MARGIN, + 640 - self.BRICK_WIDTH - self.MARGIN, + self.BRICK_WIDTH + self.MARGIN): + for top in range(self.MARGIN, + 980/2, + self.BRICK_HEIGHT + self.MARGIN): + mole_hole = MoleHole(left, top, self.BRICK_WIDTH, self.BRICK_HEIGHT) + self.holes.append(mole_hole) + # randomly chooses a new hole and sets it equal to self_last_modified + self.last_modified = choice(self.holes) + + def update(self): + """Updates the status of the chosen mole hole""" + change_color = choice(self.holes) + change_color.draw = False + change_color.color = "white" + if self.last_modified: + self.last_modified.draw = True + self.last_modified = change_color + self.last_modified.whacked = False + + def whack(self): + """If the click attribute is true, sets the click attribute to false and + sets last_modified.whacked to true to indicate tha the mole has been + whacked and the pow! image should appear. + """ + if self.click: + self.click = False + self.last_modified.whacked = True + +class MoleMouseController(object): + """Represents the controller of the mouse position""" + def __init__(self, model): + self.model = model + + def get_cursor(self): + """Determines the position of the mouse cursor when it is being pressed + and checks if the mouse_position is in the location of the mole. If it is, + the score is incremeneted, otherwise the number of failed whacks is + incremented""" + get_pressed = pygame.mouse.get_pressed() + if get_pressed[0] == 1 or get_pressed[1] == 1 or get_pressed[2] == 1: + mouse_position = pygame.mouse.get_pos() + if mouse_position[0] > self.model.last_modified.left and mouse_position[0] < (self.model.last_modified.left+40): + if mouse_position[1] > self.model.last_modified.top and mouse_position[1] < (self.model.last_modified.top+40): + self.model.click = True + self.model.last_modified.color = 'red' + self.model.score += 5 + else: + self.model.missed += 1 + +if __name__ == '__main__': + pygame.init() + size = (640, 480) + screen = pygame.display.set_mode(size) + # instantiates an object of MoleModel + model = MoleModel() + # instantiates an object MoleView + view = MoleView(model, screen) + # instantiates an object of MoleMouseController + controller = MoleMouseController(model) + # sets while True loop + running = True + while running: + # gets the current time and sets it equal to the variable ticks + ticks = pygame.time.get_ticks() + model.whack() + # if the remainder of ticks/1000 is less than or equal to 5 + # (so if around one second has passed), updates + if ticks%1000 <= 5: + model.update() + for event in pygame.event.get(): + if event.type == QUIT: + running = False + # calls the method get_cursor of the object controller + controller.get_cursor() + # calls the method draw of the object view + view.draw() \ No newline at end of file diff --git a/whacked.jpg b/whacked.jpg new file mode 100644 index 0000000..ef15dae Binary files /dev/null and b/whacked.jpg differ