typy/ty.py

234 lines
7.3 KiB
Python

#!/usr/bin/env python
from __future__ import with_statement
import logging
import optparse
import os
import sys
import random
import pygame
from pygame.locals import *
import typy.words
import typy.events
if os.environ.get('DEBUG'):
logging.basicConfig(level=logging.DEBUG)
STORIES_DIR = 'stories'
class LetterSpool(object):
spooled = None
parent = None
font = None
clear_keys = (K_SPACE,)
color = (255, 255, 255,)
parent_width = None
parent_height = None
def __init__(self, parent, **kwargs):
self.__dict__.update(kwargs)
self.parent = parent
self.parent_width, self.parent_height = parent.get_size()
self.font = pygame.font.SysFont('Courier', 32)
self.spooled = []
def clear(self):
logging.debug('LetterSpool.clear()')
event = pygame.event.Event(typy.events.WORD_COMPLETED,
word=''.join(self.spooled))
pygame.event.post(event)
self.spooled = []
def handle_key(self, event):
if event.key in self.clear_keys:
self.clear()
return
if event.key == K_BACKSPACE:
self.spooled = self.spooled[:-1]
elif event.key != K_RETURN:
self.spooled.append(event.unicode)
buffer = ''.join(self.spooled)
spool_width, spool_height = self.font.size(buffer)
offset_y = self.parent_height - (5 + spool_height)
offset_x = (self.parent_width / 2) - (spool_width / 2)
self.parent.blit(self.font.render(buffer, 0, self.color),
(offset_x, offset_y,))
class AnimatingObject(object):
pass
class AnimatingWord(AnimatingObject):
def __init__(self, parent, word, **kwargs):
self.__dict__.update(kwargs)
self.color = (255, 255, 255,)
self.parent = parent
self.parent_width, self.parent_height = parent.get_size()
self.word = word.strip()
self.step = 0.05
self.font = pygame.font.SysFont('Courier', 42)
self.width, self.height = self.font.size(word)
self.offset_x = self.parent_width
self.offset_y = (self.parent_height / 2) - (self.height / 2)
self.surface = self.render_word()
def render_word(self):
return self.font.render(self.word, 0, self.color)
def offscreen(self):
if self.offset_x + self.width <= 0:
return True
return False
def next_ready(self):
''' Returns true if we're far enough left to start the next word '''
space_w, space_h = self.font.size(' ')
if self.width + self.offset_x + space_w <= self.parent_width:
return True
return False
def update(self):
''' Update the word's position, returning True if it's offscreen '''
self.parent.fill( (0, 0, 0),
rect=pygame.Rect(self.offset_x, self.offset_y,
self.width, self.height))
if self.offset_x <= 0:
self.color = (255, 0, 0)
self.surface = self.render_word()
self.parent.blit(self.surface, (self.offset_x, self.offset_y))
self.offset_x = self.offset_x - self.step
return False
class GameRunner(object):
surface = None
size = None
clock = None
tick = 30
background_music = True
font = None
spool = None
sounds = None
def __init__(self, width, height, story, **kwargs):
self.size = width, height
self.surface = pygame.display.set_mode(self.size,
pygame.HWSURFACE | pygame.DOUBLEBUF)
self.story = story
self.clock = pygame.time.Clock()
self.clock.tick(self.tick)
pygame.display.set_caption('Typy!')
pygame.mouse.set_visible(False)
pygame.event.set_allowed(typy.events.WORD_COMPLETED)
self.font = pygame.font.SysFont('Courier', 48)
self.spool = LetterSpool(self.surface)
self.sounds = {}
def should_exit(self, event):
if event.type == QUIT:
return True
if event.type == KEYDOWN and event.key == K_ESCAPE:
return True
return False
def handle_key_event(self, event):
## Return a boolean whether we should continue the runloop
if self.should_exit(event):
return False
if event.type == KEYDOWN:
self.surface.fill((0, 0, 0))
self.spool.handle_key(event)
return True
def runloop(self):
run = True
if self.background_music:
self.sounds['chaching'] = pygame.mixer.Sound(os.path.join('sound', 'effects', 'cha_ching.wav'))
music_dir = os.path.join('sound', 'background')
song = random.choice(os.listdir(music_dir))
logging.debug('Using %s for background music' % song)
pygame.mixer.music.load(os.path.join('sound', 'background', song))
pygame.mixer.music.set_volume(0.5)
pygame.mixer.music.play(-1, 0.0)
story_title = None
scrollwords = []
surface = self.surface
with open(self.story, 'r') as fd:
lines = fd.readlines()
story_title = lines[0]
for line in lines[1:]:
if line.startswith('----'):
continue
scrollwords.extend(
(AnimatingWord(surface, w) for w in line.split(' ') if w))
last_word_index = 0
while run:
for event in pygame.event.get():
if event.type == KEYDOWN:
run = self.handle_key_event(event)
continue
if event.type == typy.events.WORD_COMPLETED:
first_word = scrollwords[0]
if first_word.word == event.word:
logging.debug('Player completed word "%s"' % event.word)
scrollwords = scrollwords[1:]
self.sounds['chaching'].play()
pygame.event.pump()
last_word_index -= 1
if last_word_index < 0:
last_word_index = 0
self.surface.fill((0, 0, 0))
for index, word in enumerate(scrollwords):
if index <= last_word_index:
word.update()
if last_word_index <= (len(scrollwords) - 1):
if scrollwords[last_word_index].next_ready():
last_word_index += 1
if scrollwords and scrollwords[0].offscreen():
scrollwords = scrollwords[1:]
last_word_index -= 1
pygame.display.update()
if self.background_music:
pygame.mixer.music.fadeout(500)
def main():
options = optparse.OptionParser()
options.add_option('-s', '--story', dest='story',
help='Select a story from the `stories` folder')
opts, args = options.parse_args()
if not os.path.exists(STORIES_DIR):
print '>>> The `stories` folder is missing'
return -1
if not opts.story:
print '>>> You need a story!'
return -1
if not os.path.exists(opts.story):
print '>>> Looks like the story "%s" doesn\'t exist!' % opts.story
return -1
pygame.init()
game = GameRunner(640, 480, opts.story)
game.runloop()
pygame.quit()
return 0
if __name__ == "__main__" :
sys.exit(main())