HeadlineZapper
HeadlineZapper
A sample PyGame application based on the model of a simple Finite State Machine to produce an "RSS-feed reader meets 1980's shoot 'em up" (in 160 lines of code).
see also: OneLoopDrumMachine
from: Tech Day 3.01 Tech Day 3.02
Game Concept
Headlines are read from an RSS feed and presented word for word to the player. Every now and then a random "noise" word is displayed.
The goal of the game is to correctly "zap" the noise words.
So actually there are three score "events" in the game:
- If a player presses the "zap" button while a noise word is displayed, their score goes up.
- If a player presses the "zap" button while a normal word is displayed, their score goes down.
- If a player does not zap a noise word, the score should go down.
Screenshots
attachment:basque_thumb.png attachment:green_thumb.png attachment:tomato_thumb.png
Code
attachment:headlinezapper_start.zip
attachment:headlinezapper_final.zip
Starting
Step 1
To start, we might want to read headlines from a feed, and display the words one by one.
You may want to refer to: PythonFeedparser
In the simplest case we want to translate from the feedparser feed object ultimately to a flat list of word images that PyGame can use to draw.
words = []
...
words.append(...)
words = []
for e in feed.entries:
for w in e.title.split():
words.append(w)
print words
Now you can prepare each word to be drawn to the screen, so we create a "parallel list" of "wordimages".
wordimages = []
for w in words:
wordimages.append(font.render(w, True, (255, 255, 255)))
wordindex = 0
AND INSIDE THE LOOP:
# DRAW THE SCREEN
screen.fill((0, 0, 0))
screen.blit(wordimages[wordindex], (0, 0))
pygame.display.flip()
# ADJUST THE WORDINDEX
wordindex += 1
if wordindex >= len(words):
wordindex = 0
FPS = 30
SECONDS_PER_WORD = 1.0
frames_per_word = SECONDS_PER_WORD * FPS
framecount += 1
if (framecount >= frames_per_word):
framecount = 0
# ADJUST THE WORDINDEX
wordindex += 1
if wordindex >= len(words):
wordindex = 0
State Machine
attachment:headlinezapper_fsm.png
Final Code
import pygame
from pygame.locals import *
import sys
import feedparser
import random
pygame.init()
pygame.font.init() # we need fonts
pygame.mixer.init() # we need sound
fontpath = "/var/lib/defoma/fontconfig.d/D/[[DejaVu]]-Sans-[[ExtraLight]].ttf"
url = "http://www.eitb24.com/rss/rss-eitb24-home-en.xml"
if len(sys.argv) >= 2:
url = sys.argv[1]
size = width, height = 640, 480
FPS = 30
SECONDS_PER_WORD = 1.0
frames_per_word = SECONDS_PER_WORD * FPS
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
feed = feedparser.parse(url)
sweepdown = pygame.mixer.Sound("sweep_down_long.wav")
mleep = pygame.mixer.Sound("mleep3.wav")
beat1 = pygame.mixer.Sound("low1.wav")
beat2 = pygame.mixer.Sound("low2.wav")
font = pygame.font.Font(fontpath, 36)
score_font = pygame.font.Font(fontpath, 272)
words = []
entries = feed.entries[:]
random.shuffle(entries)
for e in entries:
for w in e.title.split():
words.append(w)
wordimages = []
for w in words:
wordimages.append(font.render(w, True, (255, 255, 255)))
wordindex = 0
framecount = 0
score = 0
noisewords = "tomato pumpkin cherry Apple Kiwi \"kumquat Banana watermelon Canteloupe CHERRY".split()
noisewordimages = []
for w in noisewords:
noisewordimages.append(font.render(w, True, (255, 255, 255)))
noisewordindex = None
REGULAR = 0
NOISE = 1
LOSE = 2
WIN = 3
state = 0
(beatframes, beatcount, framesperbeat) = (0, 0, 40)
while 1:
# HANDLE EVENTS
for event in pygame.event.get():
if event.type == pygame.QUIT or \
(event.type == KEYDOWN and event.key == K_ESCAPE):
sys.exit()
if event.type == KEYDOWN:
if event.key == K_f:
pygame.display.toggle_fullscreen()
if event.key == K_SPACE:
if (state == LOSE or state == WIN):
pass
elif (state == REGULAR):
# LOSE, you zapped a normal word!
sweepdown.play()
state = LOSE
else:
# WIN, you zapped a noise word
mleep.play()
state = WIN
########
### DRAW THE SCREEN
if (state == LOSE):
screen.fill((255, 0, 0))
elif (state == WIN):
screen.fill((0, 255, 0))
else:
screen.fill((0, 0, 0))
# UPDATE / DRAW SCORE
score_color = (128, 128, 128)
if state == WIN:
score_color = (128, 255, 128)
score += 1
elif state == LOSE:
score_color = (255, 128, 128)
score -= 1
score_surf = score_font.render(str(score), True, score_color)
screen.blit(score_surf, (50, 200))
if (noisewordindex == None):
screen.blit(wordimages[wordindex], (100, 100))
else:
screen.blit(noisewordimages[noisewordindex], (100, 100))
pygame.display.flip()
beatframes -= 1
if beatframes <= 0:
beatcount += 1
beatframes = framesperbeat
if (beatcount % 2):
beat1.play()
else:
beat2.play()
framesperbeat -= 0.5 # 0.15
frames_per_word -= 0.25 # 0.15
# ADJUST THE STATE
framecount += 1
# set the "idle" time depending on state (longer for win and lose)
if (state == WIN or state == LOSE):
fc = 90
else:
fc = frames_per_word
if (framecount >= fc):
framecount = 0
# NEXT WORD
if (state == NOISE):
# GO TO LOSE!
state = LOSE
sweepdown.play()
elif (state == LOSE or state == WIN):
noisewordindex = None
state = REGULAR
else:
if random.randint(0, 8) == 0:
# GO TO NOISE STATE
state = NOISE
noisewordindex = random.randint(0, len(noisewords)-1)
else:
# STAY IN REGULAR STATE
wordindex += 1
if wordindex >= len(words):
wordindex = 0
# MANAGE TIMING (ensures we don't go too fast)
clock.tick(FPS)