diff --git a/ARIALUNI.TTF b/ARIALUNI.TTF new file mode 100644 index 0000000..51a18bc Binary files /dev/null and b/ARIALUNI.TTF differ diff --git a/README.md b/README.md index 61ec120..d5c05d5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -# InteractiveProgramming -This is the base repo for the interactive programming project for Software Design, Spring 2016 at Olin College. +# TypingGame +A matrix styled typing game that is designed to help teach people how to type and have fun in the process. + +![example screenshot](Sample.png) diff --git a/Sample.png b/Sample.png new file mode 100644 index 0000000..6dff9d6 Binary files /dev/null and b/Sample.png differ diff --git a/UserInterface.py b/UserInterface.py new file mode 100644 index 0000000..db9b971 --- /dev/null +++ b/UserInterface.py @@ -0,0 +1,144 @@ +import sys, pygame, time, random, unicodeGen +from modely import Model, Letter + +speed = 3 +runTime = 60 # seconds + +pygame.init() + +size = width, height = 1200, 600 # window size +black = 0, 0, 0 +white = 255, 255, 255 +grey = 50, 50, 50 +red = 255, 0, 0 + +font = 'ARIALUNI.TTF' +textFont = pygame.font.Font(font, 40) +scoreFont = pygame.font.Font(font, 20) + +screen = pygame.display.set_mode(size) # make the window + +mod = Model(runTime) # make a game that will run for runTime seconds + +letters = [] +for i in range(10): # Generate our random list of Letters + letters.append(Letter()) + + +def randExclude(exclude, start, stop): + '''returns a random column in range of x-values excluding the values given + to exclude. This prevents letters dropping into the same column and + covering each other. + ''' + r = None + while r in exclude or r is None: + r = random.randrange(start, stop) + return r + + +def replaceLet(let, start, stop): + '''Ensures that there are no letters in the same column. If there are, + replace them. + ''' + xVals = [] + for i in range(len(let)): + if let[i].x in xVals: + a = randExclude(xVals, start, stop) + let[i].x = a + xVals.append(a) + else: + xVals.append(let[i].x) + return(let) + + +letters = replaceLet(letters, 0, 19) +# make sure that there are no overlapping letters in the original list + + +def xInLetters(l): + '''returns a list of all the occupied columns''' + xV = [] + for i in range(len(l)): + xV.append(l[i].x) + return xV + + +letterSize = textFont.size('X') +targetStart = 100 +# Distance from the bottom of the screen to the top of the target + +target = pygame.Surface((width, 70)) + +# curTime = time.clock() - startTime +clock = pygame.time.Clock() +startTime = pygame.time.get_ticks() +fps = 30 + +while not mod.gameover: # This is the main loop + clock.tick(fps) # This determines how often the loop runs + screen.fill(black) + target.fill(grey) + screen.blit(target, (0, height - targetStart)) + potentials = [] + + for i in range(len(letters)): + # Go through all the letters to update their positions and + # determine whether they are on the screen and whether they are in the + # target zone. + thisLetter = letters[i] + letters[i].y += speed + screen.blit(thisLetter.surf, ((width/20)*thisLetter.x, thisLetter.y)) + + if thisLetter.getEnd() >= 600: # If letter off the screen, replace it + xs = xInLetters(letters) + letters[i] = Letter() + letters[i].x = randExclude(xs, 0, 19) + print(' X') + timeRunning = pygame.time.get_ticks() - startTime + mod.updateScore('m', timeRunning) + + if thisLetter.getEnd() > height - targetStart: + # If bottom of letter has reached top of target box, add it to the + # list of correct letters + potentials.append(thisLetter.value) + + for event in pygame.event.get(): # When something happens + if event.type == pygame.QUIT: # Terminate the program if window closed + sys.exit() + if event.type == pygame.KEYDOWN: + keyPressed = event.key + if keyPressed in potentials: # Hit! + for i in range(len(letters)): + # scan through to find letter(s) that was pressed and is in + # target box, replace it, and update score + if (keyPressed == letters[i].value + and letters[i].getEnd() >= height - targetStart): + print('X') + timeRunning = pygame.time.get_ticks() - startTime + mod.updateScore('h', timeRunning) + xs = xInLetters(letters) + letters[i] = Letter() + letters[i].x = randExclude(xs, 0, 19) + else: # Pressed wrong key + print(' X') + timeRunning = pygame.time.get_ticks() - startTime + mod.updateScore('w', timeRunning) + + # Add the scoreboard and update the dispay: + scoreboard = scoreFont.render( + ('Score: ' + str(round(mod.score(), 1))), 1, red) + screen.blit(scoreboard, (width - 100, 10)) + pygame.display.flip() + +while mod.gameover: + clock.tick(fps) + screen.fill(black) + endFont = pygame.font.Font(font, 100) + endText = endFont.render(('Score: ' + str(round(mod.score(), 1))), 1, red) + endTextSize = endText.get_size() + screen.blit(endText, (width/2 - endTextSize[0]/2, + height/2 - endTextSize[1]/2)) + pygame.display.flip() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + sys.exit() diff --git a/modely.py b/modely.py new file mode 100644 index 0000000..a5f6673 --- /dev/null +++ b/modely.py @@ -0,0 +1,94 @@ +import pygame, random, unicodeGen, math + +allUnicodeChoices = unicodeGen.get_all_unicode() # Run this once to generate the list + +white = 255, 255, 255 +green = 0, 255, 0 + +class Model: + '''Model keeps track of the game state, including running time and + score-related info''' + + def __init__(self, runTime=60, hits=0, misses=0, wrongKey=0, gameOver=False): + '''keeps track of hits misses and game state and initializes the class. + When undefined, hits and misses equal zero and gameover is false. + ''' + self.hits = hits + self.misses = misses + self.wrongKey = wrongKey + self.gameover = gameOver + self.runTime = runTime + + def score(self): + '''Keeps track of score''' + total = self.hits + self.misses + self.wrongKey + if(total == 0): + return 0 + return (self.hits / total) * 100 + + def updateScore(self, event, timeRunning): + '''Updates score statistics by modifying values in the class model''' + if event == 'h': + self.hits += 1 + elif event == 'm': + self.misses += 1 + else: + self.wrongKey += 1 + if timeRunning/1000 >= self.runTime: # Game over after runTime seconds + self.gameover = True + + +class Letter: + '''Letter objects include the letter to be typed, its location on the + screen, size, and related information, and the random unicode string that + follows it''' + + def __init__(self, + font='ARIALUNI.TTF', value=None, x=None, y=None, surf=None): + # arguments not passed in are randomly generated + self.font = font + self.textFont = pygame.font.Font(self.font, 40) + self.tailFont = pygame.font.Font(self.font, 40) + + charWidth = 40 + # by experimentation, the widest characters appear to be about 40 + # pixels in this font and size + charHeight = self.textFont.size('X')[1] + # all characters should be the same height + + if(value == None): + self.value = random.randint(97, 122) + # this is the range a-z in ascii + else: + self.value = value + + self.tail = [] # tail is the random unicode string + # Add a random number of random characters to the tail: + self.tailLength = random.randint(3, 12) + for i in range(self.tailLength): + self.tail.append(random.choice(allUnicodeChoices)) + + self.surf = pygame.Surface((charWidth, charHeight*(self.tailLength+1))) + # Create a new object for the character and tail to be on + targetChar = self.textFont.render(chr(self.value), 1, white) + # This is the character at the bottom that you're supposed to type + self.surf.blit(targetChar, (0, (self.tailLength)*charHeight)) + for i in range(len(self.tail)): + uni = self.tailFont.render(self.tail[i], 1, green) + self.surf.blit(uni, (0, ((i)*charHeight))) + + self.height = self.surf.get_height() + + if y == None: + self.y = 0 - (random.randint(0, 600) + self.height) + # Place the surface off the screen + else: + self.y = y + if(x == None): + self.x = random.randint(0, 19) + else: + self.x = x + + def getEnd(self): + # return the coordinates of the bottom end of the surface + return self.y + self.height diff --git a/unicodeGen.py b/unicodeGen.py new file mode 100644 index 0000000..7ec0a42 --- /dev/null +++ b/unicodeGen.py @@ -0,0 +1,33 @@ +# import random +# import pickle + + +def get_all_unicode(): + '''Generates all unicode characters in the specified ranges and stores + it in a list which can randomly be selected from''' + + try: + get_char = unichr + except NameError: + get_char = chr + + # Update this to include code point ranges to be sampled + include_ranges = [ # these ranges selected for interesting non-alphanumeric characters with minimal gaps + + (0x2200, 0x22FF), # Mathematical Operators + (0x0400, 0x04FF), # Cyrillic + (0x0250, 0x02AF), # IPA Extensions + (0x30A0, 0x30FF), # Katakana, almost no missing characters + (0x0600, 0x06FF), # Arabic + (0x03A3, 0x03FF), # Greek + (0x05D0, 0x05EA), # Hebrew, no missing characters + (0x0904, 0x0939), # Devanagari + (0xF900, 0xFA6D), # CJK compatibility ideographs, almost no missing characters + ] + + alphabet = [ # generate all unicode in ranges + get_char(code_point) for current_range in include_ranges + for code_point in range(current_range[0], current_range[1] + 1) + ] + + return alphabet