import array, wave, cStringIO, random, base64, math
import pygame
from pygame.locals import *
MIX_FREQ = 22050
FPS = 40
SW,SH = 640,432
TW,TH = 16,16
WIDTH,HEIGHT = 40,24
TMP,BKGR,WALL,BLOCK,PLAYER,B,SUPER_B,E1,E2,CRAZY_B = -1,0,1,2,3,4,5,6,7,8
POINTS = {B:25,SUPER_B:75,CRAZY_B:125,E1:50,E2:50}
LEVELS = [[3,0,0],[6,0,0],[6,1,0],[6,2,0]]
LEVELS.extend([[6+(v+2)/3,3+(v+1)/3,v/3] for v in xrange(0,95)])
def ft(f,l): return [float(f*v)/MIX_FREQ for v in xrange(0,int(l*MIX_FREQ))]
def t_sin(t): return [math.sin(v*math.pi*2.0) for v in t]
def t_square(t): return [float(1-int((v%1.0)/0.5)*2) for v in t]
def t_rand(t): return [float(random.randrange(-1000,1000))/1000.0 for v in t]
def t_saw(t): return [((v+0.5)%1.0)*2.0-1.0 for v in t]
def t_tri(t): return [abs(((v-0.25)%1.0)-0.5)*4.0-1.0 for v in t]
def sa(si,a,d,s,r):
a,d,r = int(a*MIX_FREQ),int(d*MIX_FREQ),int(r*MIX_FREQ)
so,l,n = [],len(si),0.0
ms = si; so.extend([ms[n]*n/a for n in xrange(0,a)])
ds = 1.0-s
ms = ms[a:]; so.extend([ms[n]*(1.0-ds*n/d) for n in xrange(0,d)])
ms = ms[d:]; so.extend([v*s for v in ms[:len(ms)-r]])
ms = ms[-r:]; so.extend([ms[n]*s*(r-n)/r for n in xrange(0,r)])
return so
def ts_fm(t,s,m): return [t[n]+s[n]*m for n in xrange(0,len(t))]
class _sound:
def set_volume(u,v): pass
def play(u): pass
def sound_gen(i,n):
print str(i),n
if not pygame.mixer.get_init(): return _sound()
freqs = [440.00,493.92,261.64,293.68,329.64,349.24,392.00]
let,axe,okt = n.upper()
freq = freqs[ord(let)-ord('A')]
if axe == "\x23": freq **= (2**(1.0/12.0))
freq *= 2**(int(okt)-4)
samples = [int(v*32767) for v in i(freq)]
stream = cStringIO.StringIO()
f = wave.open(stream,'w')
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(MIX_FREQ)
f.writeframes(array.array('h',samples).tostring())
f.close()
stream.seek(0)
return pygame.mixer.Sound(stream)
TILES_IMG = """iVBORw0KGgoAAAANSUhEUgAAAJAAAAAQCAMAAADOKAZSAAAAkFBMVEUAAQAGCBwdCCAaFgMzCwwWGFAiLwoBOzFqExRbEluSERFYMQe0BwYAXko+ObAVdxeUIJa2CbrcGBcAe2JIRNKBUAKaSAPkAOWqTwAAknZaUP+HbAJUV/8HrA0wki63ZirhS0sE2Q54cv/kQuC9gQfhT+GzmAXlenrhbeBF3ULig+Owsq7Zxyjk1QCU5ZL7/fmXFvauAAACvUlEQVRIx83Wa3OqQAwG4BWwChW5VZFWVESsiHb//787b5JdLy1nysdm2qDjdnjcZEOV+harldput6uVvRb3yIoiVEPDfRm4sPvvGwNaKubYaxEKJgzJY0Hjt/HjHwUpYubZty9lVVVlGQ3xjDrl+wmF7/iqG/1YsFwq8ch1KZjQeCbGs94/iKazlGMWyPuoar9axDBR19V5ntd1DVPX/b7ecEJxCWj8vt+vbyJvpvUOobWIokprAmldloNAjdbn8wm57gPdu0euTx4BYX8Agkj2N9VK6c9PylS1l4pewYNcuvg8xld3lOKi0PrXw/HwajLCz2ll01BOnF7QUxdlxiNVC60HILNHUwZhgwiELSofQShanJ/OTaI4+9Ttl8v1enCPlEmUMKiuGRT3gZ67KLMe7u3QeggkojTd3UsWuKpsv7RuuWRoI9zw1FCDSIbnQKCL5CODzigWAjlJfi+xOV7c20UxUc77/gZar0cM+txJBEHgEai1QU2Un3GzJuGMDeCtuV5Nxg3y5kzWmlf1gL6NoVUmW1PIPqGHnPkbxZyCumiW7nZPoOrm2WxohxqSxJx93iGSLDh/MKixoDjuAX0bQ6uMB2Jm6jbhGWRB3NV3UDANqIfaFt3APxGB/LqpY99JGniohzyq1cI7Im+p55MaIKW1InYv6HEM8RySjuYsp8yA5JRhKBrQdBpMAWIRxSbicx8D4iifM8XicFzglH18yCmLIZL1cS/oxxwSjjlnZg4xaGzHUDqT/QEIx96lYvE8iqLW/f0GTlLX8goTu//Y2/7hXGQ/5hCL5uP7oEYQhz2AYI/KzQaeYaPaJxFKhv3pOfXq3j+SnzwWNBrPH76LJ6KACsalIlCEBtoMe7zS5IypXr7qB0n/mC4KJwiiIOPXPhKfHoJewKLbw9WNQCojd+h/Bk6c3Ob4n4t/ZNtuJJTTDocAAAAASUVORK5CYII="""
FONTIMG = """iVBORw0KGgoAAAANSUhEUgAAAMAAAAAwAQMAAAB9gOddAAAABlBMVEUAAAD///+l2Z/dAAAB5klEQVQ4y53TsW7bMBAA0DNchB0O4ppBcH/BQBYNAtxPSf5AQPdQRYYsRvxLZ2jwQpi/QKNrB3bjQJi9k2JZajwEJQhb5oOOvuMRNsqbXTSHZLaIsO4Q5GcL/BWySzLtO8gzATxt12SREOgC1mI3A/wIeAuyi7x01r7bJYa8Fdj3kHwh8MulAqp8BHtEYjg5DDw18KfGOjj47f4b+uwcb4Zap+ASgyR4OkSpgeMSoFZxgL4kr/FZRa6Ht5aO/dIwJI5OI0iQYXA9OpQxAM0BRoCb8PRWLkfg6HkXB9io+O26+bbMh1QUeLLWvITnyzoErD3VWuOPN/uHKHyEh8KGf8FOYNw86ORd4iptuFBE1wSDiv4QuUorLhTRtSSG1uY1dCqYXWIbt4Ds13zsnZbu4mhXoEdun3tOnggfuE+mANQDfB7MPhkXc4tGTffwTf6ZuNszlfllAnsfAtQa6o5kfhLsLcheUuNaZarzFAwF7iCt+XrVPGE6lg5Q/ijSdJVTUq5FfQYoPXz9PsIKoFjw1bkDqBq4fxz7ZyUv8U1jaCqxy5FgD4WAL8XGs1KRQcMZFsy+vJ7VOyQBniMMofhCSahlq8ZQw+b9Y1MpWLYT4Aj9Y9XcwaKlWYIcSnOCXwDaeUl6mJXkL1K+ideEVtW4AAAAAElFTkSuQmCC"""
def IEXPLODE(f):
l = 0.3
op = sa(t_square(ft(f*1.5,l)),0.1,0.1,0.5,0.1)
return sa(t_square(ts_fm(ft(f,l),op,1.7)), 0.0,0.1,0.4,0.2)
def IHATCH(f):
l = 0.3
op = sa(t_sin(ft(f*0.25,l)),0.25,0.0,1.0,0.05)
return sa(t_tri(ts_fm(ft(f*3.5,l),op,1.5)), 0.05,0.05,0.5,0.1)
def ISMASH(f):
l = 0.1
op = sa(t_square(ft(f*0.25,l)),0.0,0.0,1.0,0.0)
return sa(t_sin(ts_fm(ft(f,l),op,38.7)), 0.02,0.06,0.8,0.02)
def ISCREAM(f):
l = 0.7
op = sa(t_saw(ft(f*2.0,l)),0.0,0.0,1.0,0.6)
return sa(t_sin(ts_fm(ft(f,l),op,12.2)), 0.02,0.1,0.8,0.4)
SFX = [
('explode',IEXPLODE,'C-2',0.5),
('hatch',IHATCH,'G-6',0.25),
('smash',ISMASH,'G-2',0.15),
('scream',ISCREAM,'G-8',0.5),
]
def IB(f):
l = 1.0
op = sa(t_saw(ft(f*0.25,l)),0.0,0.0,1.0,0.0)
return sa(t_sin(ts_fm(ft(f/2.0,l),op,0.05)), 0.1,0.1,0.5,0.2)
def IB2(f):
l = 1.0
op = sa(t_sin(ft(f*0.25,l)),0.0,0.0,1.0,0.0)
return sa(t_square(ts_fm(ft(f/2.0,l),op,0.012)), 0.01,0.1,0.1,0.1)
def IKICK(f):
l = 0.25
op = sa(t_rand(ft(f,l)),0.0,0.0,1.0,0.1)
return sa(t_saw(ts_fm(ft(f,l),op,0.5)), 0.0,0.05,0.12,0.2)
N = None
TB = [
"C-4",N,N,N,"G-3",N,N,N,"F-3",N,N,N,"G-3",N,N,N,
"C-4",N,N,N,"G-3",N,N,N,"F-3",N,N,N,"C-3",N,N,N,
]
TB.extend(TB)
TKICK = [
N,N,"C-3",N,N,N,"C-3",N,N,N,"C-3",N,N,N,"C-3","C-3",
N,N,"C-3",N,N,N,"C-3",N,N,N,"C-3",N,N,"C-3",N,"C-3",
N,N,"C-3",N,N,N,"C-3",N,N,N,"C-3",N,N,N,"C-3","C-3",
N,N,"C-3",N,N,N,"C-3",N,N,N,"C-3",N,N,"C-3","C-3","C-3",
]
PATTS = [
((TB,IB),),
((TB,IB),(TKICK,IKICK),),
((TB,IB),(TKICK,IKICK),(TB,IB2),),
((TB,IB2),(TKICK,IKICK),),
]
def rr(a,b,step=1): return random.randrange(a,b,step)
def rand(): return rr(0,65536)
def sign(v):
if v < 0: return -1
if v > 0: return 1
return 0
class Player:
def __init__(u,x,y,live):
u.x,u.y,u.live = x,y,live
u.frame = 0
class L:
def __init__(u,game):
u.g = game
def init(u):
layer = u.layer = [[0 for x in xrange(0,WIDTH)] for y in range(0,HEIGHT)]
beasts = u.beasts = []
for y in range(0,HEIGHT):
for x in range(0,WIDTH):
layer[y][x] = BKGR
if rand()%40 < 12:
layer[y][x] = BLOCK
if rand()%1000 < 8:
layer[y][x] = WALL
for x in range(0,WIDTH):
layer[0][x] = WALL
layer[HEIGHT-1][x] = WALL
for y in range(0,HEIGHT):
layer[y][0] = WALL
layer[y][WIDTH-1] = WALL
x,y = WIDTH/2,HEIGHT/2
for yy in (-1,0,1):
for xx in (-1,0,1):
layer[y+yy][x+xx] = TMP
u.player = Player(x,y,1)
l = LEVELS[u.g.level_n]
for i in range(0,l[0]):
beasts.append(u.add(B))
for i in range(0,l[1]):
beasts.append(u.add(SUPER_B))
for i in range(0,l[2]):
beasts.append(u.add(E1))
for y in range(0,HEIGHT):
for x in range(0,WIDTH):
if layer[y][x] == TMP:
layer[y][x] = BKGR
img = u.g.image
tiles = u.tiles = []
for x in xrange(0,img.get_width(),TW):
tiles.append(img.subsurface(x,0,TW,TH))
u.frame = 0
u.first = 1
def paint(u,z):
z.fill((0,0,0xaa))
s = z.subsurface(0,16,TW*WIDTH,TH*HEIGHT)
layer = u.layer
tiles = u.tiles
for y in xrange(0,HEIGHT):
for x in xrange(0,WIDTH):
s.blit(tiles[layer[y][x]],(x*TW,y*TH))
px,py = u.player.x,u.player.y
for x,y in u.beasts:
v = layer[y][x]
if v not in (B,SUPER_B,CRAZY_B): continue
dx,dy = px-x,py-y
if abs(dx) > 2*abs(dy): dy = 0
if abs(dy) > 2*abs(dx): dx = 0
dx,dy = sign(dx),sign(dy)
s.fill((255,255,255),(x*TW+4,y*TH+4,2,2))
s.fill((255,255,255),(x*TW+10,y*TH+4,2,2))
s.fill((0,0,0),(x*TW+4+dx,y*TH+4+dy,2,2))
s.fill((0,0,0),(x*TW+10+dx,y*TH+4+dy,2,2))
if u.player.live:
s.blit(tiles[PLAYER],(u.player.x*TW,u.player.y*TH))
fnt = u.g.font
for y,text in [(0,'score: %05d --= beast =-- level: %02d'%(max(0,u.g.score),u.g.level_n+1)),(SH-32,"crush: B25 E50 F125 cage: C75 wait: C200"),(SH-16,"pyweek 64k compo - phil hassey - june 06")]:
z.blit(u.g.font.render(text),(0,y))
pygame.display.flip()
if u.first:
u.first = 0
return Pause(u.g,"get ready!",u)
def add(u,i):
c = TMP
while c != BKGR:
x,y = rr(1,WIDTH),rr(1,HEIGHT)
c = u.layer[y][x]
u.layer[y][x] = i
return x,y
def event(u,e):
if e.type is KEYDOWN:
if e.key in (K_UP,K_DOWN,K_LEFT,K_RIGHT):
u.player.frame = 0
if e.type is KEYDOWN and e.key == K_ESCAPE:
return Prompt(u.g,"quit? y/n",Quit(u.g),u)
def repaint(u):
u._paint = 1
def loop(u):
if u.player.live == 0:
u.g.sfx['scream'].play()
return Prompt(u.g,"try again? y/n",Reset(u.g,u.g.level_n,L(u.g)),Quit(u.g))
if not len(u.beasts):
u.g.level_n = min(len(LEVELS)-1,u.g.level_n+1)
return Pause(u.g,'good job!',L(u.g))
if u.player.frame%4 == 0:
u.loop_player()
u.repaint()
u.beasts_clean()
if u.frame%32 == 0:
u.loop_beasts()
u.repaint()
u.beasts_clean()
u.frame += 1
u.player.frame += 1
if (u.frame%120) == 0:
u.g.score -= 1
def loop_player(u):
p = u.player
x,y = p.x,p.y
if u.layer[y][x] != BKGR: p.live = 0
if not p.live: return
keys = pygame.key.get_pressed()
dx = -keys[K_LEFT] + keys[K_RIGHT]
dy = -keys[K_UP] + keys[K_DOWN]
if (dx,dy) == (0,0): return
x,y = x+dx,y+dy
v = u.layer[y][x]
if v in (WALL,E1,E2):
return
elif v in (B,SUPER_B,CRAZY_B):
p.live = 0
return
elif v in (BKGR,):
p.x,p.y = x,y
return
elif v in (BLOCK,):
if u.push(x,y,dx,dy,1):
p.x,p.y = x,y
return
def move(u,ox,oy,nx,ny):
v = u.layer[oy][ox]
u.layer[oy][ox] = BKGR
u.layer[ny][nx] = v
def beasts_clean(u):
while u.beasts.count(None): u.beasts.remove(None)
def loop_beasts(u):
layer = u.layer
beasts = u.beasts
px,py = u.player.x,u.player.y
n = 0
for x,y in beasts[:]:
ox,oy = x,y
v = layer[y][x]
if (x,y) == (px,py): u.player.live = 0
if v == B:
if rr(0,2) == 0: dx,dy = rr(-1,2),rr(-1,2)
else: dx,dy = sign(px-x),sign(py-y)
x,y = x+dx,y+dy
if layer[y][x] == BKGR:
u.move(ox,oy,x,y)
beasts[n] = x,y
elif v == SUPER_B:
if u.is_surrounded(x,y) and (rand()%10) == 0:
u.g.sfx['explode'].play()
beasts[n] = None
layer[y][x] = BKGR
for dy in (-1,0,1):
for dx in (-1,0,1):
if (dx,dy) != (0,0) and layer[y+dy][x+dx] == BLOCK:
layer[y+dy][x+dx] = B
beasts.append((x+dx,y+dy))
else:
if rr(0,2) == 0: dx,dy = rr(-1,2),rr(-1,2)
else: dx,dy = sign(px-x),sign(py-y)
x,y = x+dx,y+dy
if layer[y][x] == BKGR:
u.move(ox,oy,x,y)
beasts[n] = x,y
elif v == E1:
if rand()%15 == 0:
u.g.sfx['hatch'].play()
layer[y][x] = E2
elif v == E2:
if rand()%15 == 0:
u.g.sfx['hatch'].play()
layer[y][x] = CRAZY_B
elif v == CRAZY_B:
if rr(0,2) == 0: dx,dy = rr(-1,2),rr(-1,2)
else: dx,dy = sign(px-x),sign(py-y)
x,y = x+dx,y+dy
if u.push(x,y,dx,dy):
u.move(ox,oy,x,y)
beasts[n] = x,y
n += 1
def is_surrounded(u,x,y):
for dy in (-1,0,1):
for dx in (-1,0,1):
if (dx,dy) != (0,0):
if u.layer[y+dy][x+dx] == BKGR: return 0
return 1
def push(u,x,y,dx,dy,m=0):
if (dx,dy) == (0,0): return 0
ox,oy = x,y
layer = u.layer
beasts = u.beasts
while layer[y][x] == BLOCK:
x,y = x+dx,y+dy
n = layer[y][x]
if n == WALL: return 0
elif n in (B,E1,E2,CRAZY_B):
if layer[y+dy][x+dx] == BKGR: return 0
elif n == SUPER_B:
if not u.is_surrounded(x,y): return 0
layer[oy][ox] = BKGR
layer[y][x] = BLOCK
if n in (B,SUPER_B,E1,E2,CRAZY_B):
u.g.sfx['smash'].play()
layer[y][x] = BLOCK
u.beasts[u.beasts.index((x,y))] = None
u.g.score += POINTS[n]*m
return 1
class ImageFont:
def __init__(u,image,size,hints):
x,y = 0,0
tw,th = u.scale = size
chars = u.chars = {}
for c in hints:
img = image.subsurface(x,y,tw,th)
chars[c] = img
x += tw
if x >= image.get_width(): x,y = 0,y+th
def render(u,text):
tw,th = u.scale
size = len(text)*tw,th
s = pygame.Surface(size).convert_alpha()
s.fill((0,0,0,0))
x = 0
for c in text:
if c in u.chars:
s.blit(u.chars[c],(x,0))
x += tw
return s
class Quit:
def __init__(u,game):
u.g = game
def init(u):
u.g.quit = 1
class Reset:
def __init__(u,game,level_n,next):
u.g,u.level_n,u.next = game,level_n,next
def init(u):
u.g.level_n = u.level_n
u.g.score = 0
return u.next
class Prompt:
def __init__(u,game,text,yes,no):
u.g,u.text,u.yes,u.no = game,text,yes,no
def paint(u,z):
u.bkgr = z.convert()
img = u.g.font.render(u.text)
x,y = (SW-img.get_width())/2,(SH-img.get_height())/2
z.blit(img,(x,y))
pygame.display.flip()
def event(u,e):
z = pygame.display.get_surface()
if e.type is KEYDOWN and e.key == K_y:
z.blit(u.bkgr,(0,0))
return u.yes
if e.type is KEYDOWN and e.key == K_n:
z.blit(u.bkgr,(0,0))
return u.no
class Pause:
def __init__(u,game,text,next):
u.g,u.text,u.next = game,text,next
def paint(u,z):
u.bkgr = z.convert()
img = u.g.font.render(u.text)
x,y = (SW-img.get_width())/2,(SH-img.get_height())/2
z.blit(img,(x,y))
pygame.display.flip()
def event(u,e):
if e.type is KEYDOWN and e.key == K_RETURN:
pygame.display.get_surface().blit(u.bkgr,(0,0))
return u.next
class Game:
def fnc(u,f,v=None):
s = u.state
if not hasattr(s,f): return 0
f = getattr(s,f)
if v != None: r = f(v)
else: r = f()
if r != None:
u.state = r
u.state._paint = 1
return 1
return 0
def run(u,state,z=None):
u.quit = 0
u.state = state
if z != None: u.z = z
u.init()
while not u.quit:
u.loop()
def loop(u):
s = u.state
if not hasattr(s,'_init') or s._init:
s._init = 0
if u.fnc('init'): return
else:
if u.fnc('loop'): return
if not hasattr(s,'_paint') or s._paint:
s._paint = 0
if u.fnc('paint',u.z): return
else:
if u.fnc('update',u.z): return
for e in pygame.event.get():
if not u.event(e):
if u.fnc('event',e): return
u.tick()
return
def init(u):
u.full = 0
u.level_n = 0
u.score = 0
u.quit = 0
u.mute = 0
u.z = pygame.display.set_mode((SW,SH))
pygame.display.set_caption("beast - alt-enter: full s"+"creen; f2: toggle music; page up/down: select level")
pygame.mouse.set_visible(0)
try:
pygame.mixer.init(MIX_FREQ, -16, 0, 1024)
except: pass
hints = """abcdefghijklmno_pqrstuvwxyz_____0123456789ABCDEF:-()!?.<>/\+=__ """
img = pygame.image.load(cStringIO.StringIO(base64.b64decode(FONTIMG))).convert()
img2 = pygame.Surface((img.get_width()*16/12,img.get_height()*16/12)).convert()
for y in xrange(0,img.get_height(),12):
for x in xrange(0,img.get_width(),12):
img2.blit(img.subsurface(x,y,12,12),(x*16/12+2,y*16/12+2))
img = img2
c = [ (min(255,0xaa+v*16),min(255,0x55+v*12),v*16) for v in xrange(0,16)]
for y in xrange(1,img.get_height()-1):
for x in xrange(1,img.get_width()-1):
ok = 0
for dx,dy in [(-1,-1),(0,-1),(1,-1),(-1,0),(0,0),(1,0),(-1,1),(0,1),(1,1)]:
if img.get_at((x+dx,y+dy))[0]: ok = 1
if ok and img.get_at((x,y))[0]:
img.set_at((x,y),c[y%16])
elif ok:
img.set_at((x,y),(0,0,8))
img.set_colorkey((0,0,0))
u.image = pygame.image.load(cStringIO.StringIO(base64.b64decode(TILES_IMG))).convert()
img.blit(u.image.subsurface(48,0,96,16),(160,32))
u.font = ImageFont(img,(16,16),hints)
u.sfx = {}
for name,ins,note,vol in SFX:
snd = sound_gen(ins,note)
snd.set_volume(vol)
u.sfx[name] = snd
pygame.time.wait(0)
u.nt = pygame.time.get_ticks()
u.notes = {}
u.mute = 1
for n in xrange(0,64*len(PATTS)): u.music(n)
u.mute = 0
u.frame = 0
def music(u,t):
_p = PATTS[(t/64) % len(PATTS)]
_r = (t%64)
for _t,_i in _p:
_n = _t[_r]
if _n != None:
key = str(_i),_n
if key not in u.notes:
u.notes[key] = sound_gen(_i,_n)
snd = u.notes[key]
snd.set_volume(0.125)
if not u.mute: snd.play()
def tick(u):
ct = pygame.time.get_ticks()
if ct < u.nt:
pygame.time.wait(u.nt-ct)
u.nt+=1000/FPS
else: u.nt = ct+1000/FPS
tk = 10
if u.frame%tk == 0:
u.music(u.frame/tk)
u.frame += 1
def event(u,e):
if e.type is QUIT:
u.quit = 1
return 1
if e.type is KEYDOWN and e.key == K_RETURN and (pygame.key.get_mods()&(KMOD_LALT|KMOD_RALT))!=0:
u.full ^= 1
tmp = u.z.convert()
u.z = pygame.display.set_mode((SW,SH),u.full*FULLSCREEN)
u.z.blit(tmp,(0,0))
pygame.display.flip()
return 1
if e.type is KEYDOWN and e.key == K_F2:
u.mute ^= 1
return 1
if e.type is KEYDOWN and e.key == K_PAGEUP:
u.state = Reset(u,min(len(LEVELS)-1,u.level_n + 1),L(u))
return 1
if e.type is KEYDOWN and e.key == K_PAGEDOWN:
u.state = Reset(u,max(0,u.level_n - 1),L(u))
return 1
g = Game()
g.run(L(g))