diff --git a/Important Stuff/MiniProject4.pdf b/Important Stuff/MiniProject4.pdf new file mode 100644 index 00000000..9a0db7a9 Binary files /dev/null and b/Important Stuff/MiniProject4.pdf differ diff --git a/Important Stuff/Proposal.md b/Important Stuff/Proposal.md new file mode 100644 index 00000000..49446bb4 --- /dev/null +++ b/Important Stuff/Proposal.md @@ -0,0 +1,38 @@ +Project Proposal +Jonah Spicher & Ashley Swanson + +Our project will be a video game, focusing on alternative inputs to the traditional +mouse and keyboard style. Our game will consist of avoiding oncoming obstacles using +pitch to control the height of the little player guy. This nontraditional input will +allow us to explore interactive programming while learning about the tendencies of +foreign libraries such as pygame and OpenCV. As a stretch goal, we would also like +to explore electrical inputs, which would let us do really silly things like a +potentiometer controlled by the strength of your breath blowing on some apparatus. + +As For Learning Goals: +Jonah: + I am excited to learn a little bit about pygame, I would definitely do more with + that in the future. Making video games sounds cool. +Ashley: + I am excited to learn about different inputs as I have very little concept of how + to work with inputs outside of typing something into a box when prompted. I am + also looking forwards to tackling the documentation involved in figuring out the + libraries as this is something I generally find intimidating and I would like to + become more comfortable with it. + +And Now For Libraries: + We are going to use pygame for collisions and other video game features and OpenCV + to process our audio input. We may need some other ones but thats what we know at + the moment. Maybe like, math. + +By our mid-project check-in: + We will definitely have installed OpenCV and pygame and made sure specifically + that OpenCV will work with audio. We will probably also have planned out mostly + how our code will be structured, and started working on some of that. + +Potential Risks: + We might get so distracted by how hilarious it is that people are screaming at + our laptop that we never do any work. If you were not aware we are dating so + its pretty hilarious that we are partners. We are both totally fine with this and + work together well (we have worked on projects together before), but if we + break up over this project we will sue. diff --git a/Important Stuff/README.md b/Important Stuff/README.md new file mode 100644 index 00000000..0e11c881 --- /dev/null +++ b/Important Stuff/README.md @@ -0,0 +1,41 @@ +# InteractiveProgramming +This is the base repo for the interactive programming project for Software Design, Spring 2018 at Olin College. + +To run: + +In terminal, type: + +$python record.py & python main.py + +Necessary packages: + +PyAudio + +To install: + +In terminal, type: + +$ sudo git clone http://people.csail.mit.edu/hubert/git/pyaudio.git + +$ sudo apt-get install libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev + +$ sudo apt-get install python3-dev + +$ pip install pyaudio + +$ cd pyaudio + +$ sudo python setup.py install + +pygame + +To install: + +In terminal, type: + +$sudo apt-get install python3-pygame + + +Also, sorry for the awkwardly organized files, that is the result of git confusion. + +Test for merge conflict Ashley style diff --git a/Important Stuff/midproject_checkin.txt b/Important Stuff/midproject_checkin.txt new file mode 100644 index 00000000..7bcbcbfb --- /dev/null +++ b/Important Stuff/midproject_checkin.txt @@ -0,0 +1,11 @@ + So, just to explain where we are as of right now, we have implemented a very +basic version of our game, which was how we learned pygame. It makes +obstacles move to the left and you dodge them by moving up and down. It's very +bare bones right now, the plan is to make it nicer and then change the control +scheme to audio, as we discussed in the proposal. + + Speaking of audio, OpenCV will definitely not work. However, we learned that +we can use Pyaudio to record and then Librosa to analyze, and that should do +exactly what we want to do. We will record very small intervals, use Librosa +to find the pitch of the previous interval, and then map the location of the +player sprite to the pitch. diff --git a/Important Stuff/przemoli-pygametutorial-ba1a82994f56/.hg_archival.txt b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/.hg_archival.txt new file mode 100644 index 00000000..fae93112 --- /dev/null +++ b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/.hg_archival.txt @@ -0,0 +1,4 @@ +repo: ff5f763093769902dac5f101b33a9704a790606a +node: ba1a82994f56e80237171f5e48864562ad789434 +branch: default +tag: drawing1.0 diff --git a/Important Stuff/przemoli-pygametutorial-ba1a82994f56/.hgtags b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/.hgtags new file mode 100644 index 00000000..6bacdc51 --- /dev/null +++ b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/.hgtags @@ -0,0 +1,2 @@ +cc13abeb84a8d72cfe0225cf3cc772e92a25beeb basic0.5 +540433c50ffc681822bf3d82afb35cb5ca91c900 basic1.0 diff --git a/Important Stuff/przemoli-pygametutorial-ba1a82994f56/README.md b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/README.md new file mode 100644 index 00000000..51ff90f4 --- /dev/null +++ b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/README.md @@ -0,0 +1,8 @@ +# PyGame Tutorials +This repo contain PyGame Tutorials source code! + +Tutorials can be found at: +pygametutorials.wikidot.com + +# License +This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. diff --git a/Important Stuff/przemoli-pygametutorial-ba1a82994f56/main.py b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/main.py new file mode 100644 index 00000000..f3af9a33 --- /dev/null +++ b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/main.py @@ -0,0 +1,41 @@ +import pygame +from pygame.locals import * + +class App: + def __init__(self): + self._running = True + self._display_surf = None + self._image_surf = None + + def on_init(self): + pygame.init() + self._display_surf = pygame.display.set_mode((350,350), pygame.HWSURFACE) + self._running = True + self._image_surf = pygame.image.load("myimage.jpg").convert() + + def on_event(self, event): + if event.type == QUIT: + self._running = False + def on_loop(self): + pass + def on_render(self): + self._display_surf.blit(self._image_surf,(0,0)) + pygame.display.flip() + + def on_cleanup(self): + pygame.quit() + + def on_execute(self): + if self.on_init() == False: + self._running = False + + while( self._running ): + for event in pygame.event.get(): + self.on_event(event) + self.on_loop() + self.on_render() + self.on_cleanup() + +if __name__ == "__main__" : + theApp = App() + theApp.on_execute() diff --git a/Important Stuff/przemoli-pygametutorial-ba1a82994f56/myimage.jpg b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/myimage.jpg new file mode 100644 index 00000000..860d2366 Binary files /dev/null and b/Important Stuff/przemoli-pygametutorial-ba1a82994f56/myimage.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/classes.py b/classes.py new file mode 100644 index 00000000..5cebca0c --- /dev/null +++ b/classes.py @@ -0,0 +1,216 @@ +"""A collection of all of the classes and methods used to run the game, main +imports from here. Initially this was because we thought recording was going to +work differently than it had to. The way things ended up, main looks kind of silly. +""" +import pygame, sys, random +from pygame.locals import * + +import pyaudio +from pyaudio import * +import wave +import time + +import matplotlib.pyplot as plt +from scipy.fftpack import fft +from scipy.io import wavfile +from numpy import arange + +import pickle +from pickle import dump, load + +SCREENWIDTH = 1500 +SCREENHEIGHT = 1000 + +class Block(pygame.sprite.Sprite): + """These are the obstacles that move towards you. They inherit from pygame's + sprite class. + """ + def __init__(self, screen, color, width, height): + """Gives a size, color, and speed. + """ + super().__init__() + self.image = pygame.Surface((width, height)) + self.image.fill(color) + self.screen = screen + self.rect = self.image.get_rect() + self.speedx = 10 + + def update(self): + """Moves the block according to its speed. + """ + self.rect.left -= self.speedx + if self.rect.right < 0: + x_displacement = random.randint(0, SCREENWIDTH*2) + self.rect.x = (SCREENWIDTH + x_displacement) + self.rect.y = random.randrange(SCREENHEIGHT - 150) + + +class Player(pygame.sprite.Sprite): + """This is also a sprite, but it is the block you control. + """ + def __init__(self, screen, color, size, increment): + """Gives the player a color, size, and sets how quickly it moves. Also + initializes expected frequencies. + """ + super().__init__() + self.image = pygame.Surface((size, size)) + self.size = size + self.image.fill(color) + self.screen = screen + self.rect = self.image.get_rect() + self.increment = increment + self.is_recording = False + + #Initially, we wanted to calibrate the game to the user. This could be + #implemented somewhat easily, but as it is, we ran out of time. Also, + #we found these values worked for most people. + self.calibrated_high = 100 + self.calibrated_low = 450 + + def collide(self, obstacles, game): + """Checks to see if the player has hit an obstacle. + """ + if pygame.sprite.spritecollide(self, obstacles, False): + #First, collide edits stop.txt to tell record to stop recording. + f = open('stop.txt', 'wb') + insert = pickle.dumps('stop') + f.write(insert) + f.close() + + #Then, it displays the score for a few seconds + pygame.font.init() + myfont = pygame.font.SysFont('Comic Sans MS', 300) + textsurface = myfont.render("Score=%d"%(int(game.score)), False, (255, 255, 255)) + game.mainSurface.blit(textsurface,(275,400)) + pygame.display.update() + time.sleep(2) + + #Then it quits. + pygame.quit() + sys.exit() + + def analyze_freq(self): + """Analyze frequency uses a fast Fourier transform to get the frequency + of file.wav, which is consistently being updated by record. It returns + the frequency in Hz. FFT is somewhat imprecise with such short increments, + and gets better the longer we record for, but also the game feels laggy. + 0.2 seconds is somewhat of a balance. + """ + #A try clause was necessary, because occasionally, it will try to read + #file.wav while record is trying to write to it, resulting in an error. + #Our except clause keeps the previous values stored in fie.wav + try: + fs, data = wavfile.read('file.wav') + except: + data = self.data + fs = self.fs + + self.data = data + self.fs = fs + + a = data.T[0] + b=[(ele/2**8.)*2-1 for ele in a] + c = fft(b) + + d = len(c)//2 + + k = arange(len(data)) + T = len(data)/fs + frqLabel = k/T + + #Eccentricities in this algorithm make low frequencies appear very strongly, + #So we look after the first few entries in the frequency list to get a more + #accurate idea of what's going on. + val = max(abs(c[2:(d-1)])) + i = list(abs(c[2:(d-1)])).index(val) + return frqLabel[2:(d-1)][i] + + def freq_movement(self, frequency): + """Moves the player based on the detected frequency. + """ + + #First sets the middle frequency based on the high and low established in + #init + mid_freq = (self.calibrated_low + self.calibrated_high)/2 + if frequency < 70: + pass + + elif frequency > mid_freq: + #Then moves the correct direction, assuming that moving would not + #move the player offscreen. + if self.rect.y < self.increment: + self.rect.y = 0 + elif self.rect.y > 0: + self.rect.y -= self.increment + + elif frequency < mid_freq: + if self.rect.y > SCREENHEIGHT-(self.size + self.increment): + self.rect.y = SCREENHEIGHT - self.size + elif self.rect.y < SCREENHEIGHT- self.size: + self.rect.y += self.increment + + + + + +class Game: + """This class organizes the player and several obstacles, then runs and renders + the game. + """ + def __init__(self, num_blocks, player_color=(255, 0, 0)): + """Makes a given number of obstacle blocks, then a player. Also prepares + necessary pygame assets. + """ + pygame.init() + + + self.mainSurface = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT), 0, 32) + pygame.display.set_caption("Collision Game") + self.blocksGroup = pygame.sprite.Group() + self.player_group = pygame.sprite.Group() + self.score = 0 + + for i in range(num_blocks): + height = random.randint(30, 150) + weight = random.randint(30, 150) + myBlock = Block(self.mainSurface, (255, 255, 255), weight, height) + x_displacement = random.randint(0, SCREENWIDTH) + myBlock.rect.x = (SCREENWIDTH + x_displacement) + myBlock.rect.y = random.randrange(SCREENHEIGHT - 150) + self.blocksGroup.add(myBlock) + + self.player = Player(self.mainSurface, player_color, 50, 10) + self.player.rect.x = 200 + self.player.rect.y = 0.5*SCREENHEIGHT + #The player has to be added to a group all by itself in order to be + #drawn later. + self.player_group.add(self.player) + #Ha, comic sans was in the example we got, it doesn't actually do comic + #sans, it just does some default font. This is fine. + self.myfont = pygame.font.SysFont('Comic Sans MS', 150) + + def run(self): + """Checks for the quit event, then moves everything as much as it needs + to be moved and checkes for a collision, then draws everything. Then + updates the score. + """ + while True: + for event in pygame.event.get(): + if event.type == QUIT: + print(self.score) + pygame.quit() + sys.exit() + + freq = self.player.analyze_freq() + self.player.freq_movement(freq) + self.player.collide(self.blocksGroup, self) + + self.mainSurface.fill((0, 0, 0)) + textsurface = self.myfont.render(str(int(self.score)), False, (255, 255, 255)) + print(textsurface) + self.player_group.draw(self.mainSurface) + self.blocksGroup.update() + self.blocksGroup.draw(self.mainSurface) + self.mainSurface.blit(textsurface,(10,10)) + pygame.display.update() + self.score += .1 diff --git a/file.wav b/file.wav new file mode 100644 index 00000000..6f2a28fd Binary files /dev/null and b/file.wav differ diff --git a/main.py b/main.py new file mode 100644 index 00000000..b347e708 --- /dev/null +++ b/main.py @@ -0,0 +1,9 @@ +""" +@author Ashley Swanson & Jonah Spicher +""" + +from classes import Player, Block, Game + +if __name__ == "__main__": + game = Game(num_blocks=10) + game.run() diff --git a/przemoli-pygametutorial-ba1a82994f56/.hg_archival.txt b/przemoli-pygametutorial-ba1a82994f56/.hg_archival.txt new file mode 100644 index 00000000..fae93112 --- /dev/null +++ b/przemoli-pygametutorial-ba1a82994f56/.hg_archival.txt @@ -0,0 +1,4 @@ +repo: ff5f763093769902dac5f101b33a9704a790606a +node: ba1a82994f56e80237171f5e48864562ad789434 +branch: default +tag: drawing1.0 diff --git a/przemoli-pygametutorial-ba1a82994f56/.hgtags b/przemoli-pygametutorial-ba1a82994f56/.hgtags new file mode 100644 index 00000000..6bacdc51 --- /dev/null +++ b/przemoli-pygametutorial-ba1a82994f56/.hgtags @@ -0,0 +1,2 @@ +cc13abeb84a8d72cfe0225cf3cc772e92a25beeb basic0.5 +540433c50ffc681822bf3d82afb35cb5ca91c900 basic1.0 diff --git a/przemoli-pygametutorial-ba1a82994f56/README.md b/przemoli-pygametutorial-ba1a82994f56/README.md new file mode 100644 index 00000000..51ff90f4 --- /dev/null +++ b/przemoli-pygametutorial-ba1a82994f56/README.md @@ -0,0 +1,8 @@ +# PyGame Tutorials +This repo contain PyGame Tutorials source code! + +Tutorials can be found at: +pygametutorials.wikidot.com + +# License +This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. diff --git a/przemoli-pygametutorial-ba1a82994f56/main.py b/przemoli-pygametutorial-ba1a82994f56/main.py new file mode 100644 index 00000000..f3af9a33 --- /dev/null +++ b/przemoli-pygametutorial-ba1a82994f56/main.py @@ -0,0 +1,41 @@ +import pygame +from pygame.locals import * + +class App: + def __init__(self): + self._running = True + self._display_surf = None + self._image_surf = None + + def on_init(self): + pygame.init() + self._display_surf = pygame.display.set_mode((350,350), pygame.HWSURFACE) + self._running = True + self._image_surf = pygame.image.load("myimage.jpg").convert() + + def on_event(self, event): + if event.type == QUIT: + self._running = False + def on_loop(self): + pass + def on_render(self): + self._display_surf.blit(self._image_surf,(0,0)) + pygame.display.flip() + + def on_cleanup(self): + pygame.quit() + + def on_execute(self): + if self.on_init() == False: + self._running = False + + while( self._running ): + for event in pygame.event.get(): + self.on_event(event) + self.on_loop() + self.on_render() + self.on_cleanup() + +if __name__ == "__main__" : + theApp = App() + theApp.on_execute() diff --git a/przemoli-pygametutorial-ba1a82994f56/myimage.jpg b/przemoli-pygametutorial-ba1a82994f56/myimage.jpg new file mode 100644 index 00000000..860d2366 Binary files /dev/null and b/przemoli-pygametutorial-ba1a82994f56/myimage.jpg differ diff --git a/pyaudio b/pyaudio new file mode 160000 index 00000000..7090e25b --- /dev/null +++ b/pyaudio @@ -0,0 +1 @@ +Subproject commit 7090e25bcba41413bd7ce89aa73bc0efb1ae1ca1 diff --git a/record.py b/record.py new file mode 100644 index 00000000..43c77129 --- /dev/null +++ b/record.py @@ -0,0 +1,61 @@ +"""Running this in a separate file was necessary because that is the only way +to keep it from interrupting the game. While the main file was recording, it +would record every frame, which obviously caused problems. In order to communicate +with main, it writes to two files. file.wav is the data that record is getting, +stop.txt tells record when to stop. +""" +import pyaudio +from pyaudio import * +import wave +import os +from os.path import exists +import sys +import pickle +from pickle import dump, load + + +def record(record_time, a_format=pyaudio.paInt16, channels=2, rate=44100, chunk=1024, file_name='file.wav'): + """Records for a given amount of time, and stores the recording in file.wav + by default. Don't change that, though, because classes reads file.wav. + """ + audio = pyaudio.PyAudio() + stream = audio.open(format=a_format, channels=channels, + rate=rate, input=True, + frames_per_buffer=chunk) + + frames = [] + for i in range(0, int(rate / chunk * record_time)): + data = stream.read(chunk) + frames.append(data) + + stream.stop_stream() + stream.close() + audio.terminate() + + waveFile = wave.open(file_name, 'wb') + waveFile.setnchannels(channels) + waveFile.setsampwidth(audio.get_sample_size(a_format)) + waveFile.setframerate(rate) + waveFile.writeframes(b''.join(frames)) + waveFile.close() + + + +#First, stop.txt has to say something other that stop. Otherwise, record stops +#immediately. +f = open('stop.txt', 'wb') +insert = pickle.dumps('go') +f.write(insert) +f.close() + +while True: + #Then it repeatedly checks to see if stop.txt says stop, and if not, it records + f = open('stop.txt', 'rb+') + curr = f.read() + f.seek(0, 0) + update = pickle.loads(curr) + + if update=='stop': + break + + record(0.2) diff --git a/stop.txt b/stop.txt new file mode 100644 index 00000000..c1566f91 Binary files /dev/null and b/stop.txt differ