HeadlineZapper

From Media Design: Networked & Lens-Based wiki
Jump to navigation Jump to search

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

File: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)

Resources