nbsdgames/bubbob/boards.py
2022-02-03 13:46:22 +03:30

1471 lines
46 KiB
Python

import random, os, sys, math
import gamesrv
import images
CELL = 16 # this constant is inlined at some places, don't change
HALFCELL = CELL//2
FRAME_TIME = 0.025
#DEFAULT_LEVEL_FILE = 'levels/scratch.py'
BOARD_BKGND = 1 # 0 = black, 1 = darker larger wall tiles
class Copyable:
pass # see bonuses.py, class Clock
class Board(Copyable):
letter = 0
fire = 0
lightning = 0
water = 0
top = 0
WIND_DELTA = HALFCELL
def __init__(self, num):
# the subclasses should define 'walls', 'winds', 'monsters'
self.walls = walls = [line for line in self.walls.split('\n') if line]
self.winds = winds = [line for line in self.winds.split('\n') if line]
self.num = num
self.width = len(walls[0])
self.height = len(walls)
for line in walls:
assert len(line) == self.width, "some wall lines are longer than others"
for line in winds:
assert len(line) == self.width, "some wind lines are longer than others"
#assert walls[0] == walls[-1], "first and last lines must be identical"
assert len(winds) == self.height, "wall and wind heights differ"
self.walls_by_pos = {}
self.sprites = {}
if self.top:
testline = self.walls[0]
else:
testline = self.walls[-1]
self.holes = testline.find(' ') >= 0
self.playingboard = 0
self.bonuslevel = not self.monsters or (gamesrv.game.finalboard is not None and self.num >= gamesrv.game.finalboard)
self.cleaning_gen_state = 0
def set_musics(self, prefix=[]):
if (self.num+1) % 20 < 10:
gamesrv.set_musics(prefix + [images.music_intro], [images.music_game],
reset=0)
else:
gamesrv.set_musics(prefix + [], [images.music_game2], reset=0)
def writesprites(self, name, xyicolist):
sprlist = self.sprites.setdefault(name, [])
xyicolist = xyicolist[:]
xyicolist.reverse()
for s in sprlist[:]:
if xyicolist:
s.move(*xyicolist.pop())
else:
s.kill()
sprlist.remove(s)
while xyicolist:
x, y, ico = xyicolist.pop()
sprlist.append(gamesrv.Sprite(ico, x, y))
def enter(self, complete=1, inplace=0, fastreenter=False):
global curboard
if inplace:
print("Re -", end=' ')
print("Entering board", self.num+1)
self.set_musics()
# add board walls
l = self.sprites.setdefault('walls', [])
bl = self.sprites.setdefault('borderwalls', [])
if inplace:
deltay = 0
else:
deltay = bheight
wnx = wny = 1
while haspat((self.num, wnx, 0)):
wnx += 1
while haspat((self.num, 0, wny)):
wny += 1
self.wnx = wnx
self.wny = wny
if haspat((self.num, 'l')):
lefticon = patget((self.num, 'l'))
if haspat((self.num, 'r')):
righticon = patget((self.num, 'r'))
else:
righticon = lefticon
xrange = list(range(2, self.width-2))
else:
xrange = list(range(self.width))
lefticon = righticon = None
if BOARD_BKGND == 1:
gl = self.sprites.setdefault('background', [])
xmax = (self.width-2)*CELL
ymax = self.height*CELL
y = -HALFCELL
ystep = 0
firstextra = 1
while y < ymax:
x = 2*CELL+HALFCELL
xstep = 0
while x < xmax:
bitmap, rect = loadpattern((self.num, xstep, ystep),
images.KEYCOL)
bitmap, rect = images.makebkgndpattern(bitmap, rect)
if firstextra:
# special position where a bit of black might show up
x -= rect[2]
xstep = (xstep-1) % wnx
firstextra = 0
continue
bkgndicon = bitmap.geticon(*rect)
w = gamesrv.Sprite(bkgndicon, x, y + deltay)
gl.append(w)
x += rect[2]
xstep = (xstep+1) % wnx
y += rect[3]
ystep = (ystep+1) % wny
else:
gl = []
if lefticon is not None:
for y in range(0, self.height, lefticon.h // CELL):
bl.append(gamesrv.Sprite(lefticon, 0, y*CELL + deltay))
for y in range(self.height):
for x in xrange:
c = self.walls[y][x]
if c == '#':
wallicon = patget((self.num, x%wnx, y%wny), images.KEYCOL)
w = gamesrv.Sprite(wallicon, x*CELL, y*CELL + deltay)
l.append(w)
self.walls_by_pos[y,x] = w
if not l:
# self.sprites['walls'] must not be empty, for putwall
wallicon = patget((self.num, 0, 0), images.KEYCOL)
w = gamesrv.Sprite(wallicon, 0, -wallicon.h)
l.append(w)
if righticon is not None:
for y in range(0, self.height, lefticon.h // CELL):
bl.append(gamesrv.Sprite(righticon, (self.width-2)*CELL, y*CELL + deltay))
while deltay:
dy = -min(deltay, 8)
for w in gl:
w.step(0, dy)
for w in l:
w.step(0, dy)
for w in bl:
w.step(0, dy)
deltay += dy
yield 1
if inplace:
for w in images.ActiveSprites:
w.to_front()
curboard = self
if gamesrv.game:
gamesrv.game.updateboard()
if not complete:
return
# add players
from player import BubPlayer, scoreboard
if not inplace:
random.shuffle(BubPlayer.PlayerList)
scoreboard(1, inplace=inplace)
if not fastreenter:
random.shuffle(BubPlayer.PlayerList)
playing = []
for p in BubPlayer.PlayerList:
if p.isplaying():
p.enterboard(playing)
p.zarkon()
playing.append(p)
for d in BubPlayer.DragonList:
d.enter_new_board()
else:
# kill stuff left over from leave(inplace=1) (Big Clock bonus only)
import bonuses
keepme = bonuses.Points
dragons = {}
playing = []
for p in BubPlayer.PlayerList:
if p.isplaying():
for d in p.dragons:
if hasattr(d, 'dcap'):
d.dcap['shield'] = 90
dragons[d] = True
playing.append(p)
for s in images.ActiveSprites[:]:
if isinstance(s, keepme) or s in dragons:
pass
else:
s.kill()
# add monsters
if not self.bonuslevel:
import monsters
f_monsters = gamesrv.game.f_monsters
if f_monsters < 0.1:
f_monsters = max(1.0, min(2.0, (len(playing)-2)/2.2+1.0))
for mdef in self.monsters:
if not fastreenter:
yield 2
cls = getattr(monsters, mdef.__class__.__name__)
dir = mdef.dir
i = random.random()
while i < f_monsters:
cls(mdef, dir=dir)
dir = -dir
i += 1.0
self.playingboard = 1
def putwall(self, x, y, w=None):
wallicon = patget((self.num, x%self.wnx, y%self.wny), images.KEYCOL)
if w is None:
w = gamesrv.Sprite(wallicon, 0, bheight)
l = self.sprites['walls']
w.to_back(l[-1])
l.append(w)
self.walls_by_pos[y,x] = w
if y >= 0:
line = self.walls[y]
self.walls[y] = line[:x] + '#' + line[x+1:]
def killwall(self, x, y, kill=1):
w = self.walls_by_pos[y,x]
if kill:
l = self.sprites['walls']
if len(l) > 1:
l.remove(w)
w.kill()
else:
# self.sprites['walls'] must never be empty
# or putwall will crash!
w.move(0, -bheight)
del self.walls_by_pos[y,x]
line = self.walls[y]
self.walls[y] = line[:x] + ' ' + line[x+1:]
return w
def reorder_walls(self):
walls_by_pos = self.walls_by_pos
items = [(yx, w1.ico) for yx, w1 in list(walls_by_pos.items())]
if not items:
return # otherwise self.sprites['walls'] would be emptied
items.sort()
l = self.sprites['walls']
while len(l) > len(items):
l.pop().kill()
assert len(items) == len(l)
for ((y,x), ico), w2 in zip(items, l):
w2.move(x*CELL, y*CELL, ico)
walls_by_pos[y,x] = w2
def leave(self, inplace=0):
global curboard
if not gamesrv.has_loop_music():
gamesrv.fadeout(1.5)
from player import BubPlayer
for p in BubPlayer.PlayerList:
if p.isplaying():
p.savecaps()
if BubPlayer.LeaveBonus:
for t in BubPlayer.LeaveBonus:
yield t
BubPlayer.LeaveBonus = None
curboard = None
if inplace:
i = -1
else:
while images.ActiveSprites:
s = random.choice(images.ActiveSprites)
s.kill()
yield 0.9
i = 0
sprites = []
for l in list(self.sprites.values()):
sprites += l
self.sprites.clear()
self.walls_by_pos.clear()
random.shuffle(sprites)
for s in sprites:
s.kill()
if i:
i -= 1
else:
yield 0.32
i = 3
if not inplace:
for p in BubPlayer.PlayerList:
if p.isplaying():
p.zarkoff()
yield 4
def clean_gen_state(self):
self.cleaning_gen_state = 1
while len(BoardGen) > 1:
#yield force_singlegen()
#if 'flood' in self.sprites:
# for s in self.sprites['flood']:
# s.kill()
# del self.sprites['flood']
yield normal_frame()
self.cleaning_gen_state = 0
def bget(x, y):
if 0 <= x < curboard.width:
if y < 0 or y >= curboard.height:
y = 0
return curboard.walls[y][x]
else:
return '#'
def wget(x, y):
delta = curboard.WIND_DELTA
x = (x + delta) // 16
y = (y + delta) // 16
if 0 <= x < curboard.width:
if y < 0:
y = 0
elif y >= curboard.height:
y = -1
return curboard.winds[y][x]
elif x < 0:
return '>'
else:
return '<'
def onground(x, y):
if y & 15:
return 0
x0 = (x+5) // 16
x1 = (x+16) // 16
x2 = (x+27) // 16
y0 = y // 16 + 2
if x0 < 0 or x2 >= curboard.width:
return 0
y1 = y0 - 1
if not (0 < y0 < curboard.height):
if y0 != curboard.height:
y1 = 0
y0 = 0
y0 = curboard.walls[y0]
y1 = curboard.walls[y1]
return (' ' == y1[x0] == y1[x1] == y1[x2] and
not (' ' == y0[x0] == y0[x1] == y0[x2]))
#return (' ' == bget(x0,y0-1) == bget(x1,y0-1) == bget(x2,y0-1) and
# not (' ' == bget(x0,y0) == bget(x1,y0) == bget(x2,y0)))
#return (bget(x1,y0-1)==' ' and
# ((bget(x1,y0)=='#') or
# (bget(x0,y0)=='#' and bget(x0,y0-1)==' ') or
# (bget(x2,y0)=='#' and bget(x2,y0-1)==' ')))
def onground_nobottom(x, y):
return onground(x, y) and y+32 < bheight
def underground(x, y):
if y % CELL:
return 0
x0 = (x+5) // CELL
x1 = (x+CELL) // CELL
x2 = (x+2*CELL-5) // CELL
y0 = y // CELL
if x0 < 0 or x2 >= curboard.width:
return 0
y1 = y0 - 1
if not (0 < y0 < curboard.height):
if y0 != curboard.height:
y1 = 0
y0 = 0
y0 = curboard.walls[y0]
y1 = curboard.walls[y1]
return (' ' == y0[x0] == y0[x1] == y0[x2] and
not (' ' == y1[x0] == y1[x1] == y1[x2]))
def x2bounds(x):
if x < 32:
return 32
elif x > bwidth - 64:
return bwidth - 64
else:
return x
def vertical_warp(nx, ny):
if ny >= bheight:
ny -= bheightmod
elif ny < -32:
ny += bheightmod
return nx, ny
def vertical_warp_sprite(spr):
if spr.y >= bheight:
spr.step(0, -bheightmod)
elif spr.y < -32:
spr.step(0, bheightmod)
##def vertical_warp(nx, ny):
## if ny >= bheight:
## ny -= bheightmod
## elif ny < -32:
## ny += bheightmod
## else:
## return (nx, ny), 0
## from player import BubPlayer
## if BubPlayer.Moebius:
## nx = bwidth - 2*CELL - nx
## return (nx, ny), 1
## else:
## return (nx, ny), 0
MODULES = ['boards', 'bonuses', 'bubbles', 'images',
'mnstrmap', 'monsters', 'player', 'ranking',
'binboards', 'macbinary', 'boarddef',
'ext1', 'ext2', 'ext3', 'ext4', 'ext5', 'ext6', 'ext7']
def loadmodules(force=0):
levelfilename = gamesrv.game.levelfile
modulefiles = {None: levelfilename}
for m in MODULES:
if os.path.isfile(m+'.py'):
modulefiles[m] = m+'.py'
elif os.path.isfile(os.path.join(m, '__init__.py')):
modulefiles[m] = os.path.join(m, '__init__.py')
mtimes = {}
for m, mfile in list(modulefiles.items()):
mtimes[m] = os.stat(mfile).st_mtime
reload = force or (mtimes != getattr(sys, 'ST_MTIMES', None))
import player
playerlist = player.BubPlayer.PlayerList
if reload:
delete = hasattr(sys, 'ST_MTIMES')
sys.ST_MTIMES = mtimes
if delete:
print("Reloading modules.")
for m, mfile in list(modulefiles.items()):
if m is not None and m in sys.modules:
del sys.modules[m]
# Clear
clearallsprites()
import player
for p in playerlist:
player.upgrade(p)
for n in range(len(playerlist), images.MAX):
playerlist.append(player.BubPlayer(n))
player.BubPlayer.PlayerList = playerlist
if reload:
import boards
from images import haspat, loadpattern
boards.haspat = haspat
boards.loadpattern = loadpattern
del boards.BoardList[:]
if levelfilename.lower().endswith('.py'):
levels = {}
print('Source level file:', levelfilename)
exec(compile(open(levelfilename, "rb").read(), levelfilename, 'exec'), levels)
if 'GenerateLevels' in levels:
levels = levels['GenerateLevels']()
if isinstance(levels, list):
levels = dict(list(zip(list(range(len(levels))), levels)))
else:
import binboards
levels = binboards.load(levelfilename)
boards.register(levels)
return reload
def clearallsprites():
gamesrv.clearsprites()
import images
del images.ActiveSprites[:]
images.SpritesByLoc.clear()
def wait_for_one_player():
from player import BubPlayer
clearallsprites()
nimages = None
while not [p for p in BubPlayer.PlayerList if p.isplaying()]:
yield 3
if not nimages:
desc = getattr(gamesrv.game, 'FnDesc', '?')
host, port = getattr(gamesrv.game, 'address', ('?', '?'))
images.writestrlines([
"Welcome to",
desc.upper(),
"at %s:%s" % (host.lower(), port),
None,
"Click on your Favorite Color's Dragon",
"Choose four keys: Right, Left, Jump, Shoot",
"and Let's Go!",
None,
"Click again for more than one player",
"on the same machine.",
])
from mnstrmap import PlayerBubbles
nimages = [PlayerBubbles.bubble[0],
PlayerBubbles.bubble[1],
PlayerBubbles.bubble[1],
PlayerBubbles.bubble[0],
PlayerBubbles.bubble[2],
PlayerBubbles.bubble[2]]
screenwidth = bwidth + 9*CELL
screenheight = bheight
def welcomebubbling(self):
fx = self.x
dx = (random.random() - 0.5) * 1.9
for y in range(self.y-3, -self.ico.h, -3):
fx += dx
self.move(int(fx), y)
yield None
if y == self.ypop:
from mnstrmap import PlayerBubbles
self.setimages(None)
self.gen.append(self.die(PlayerBubbles.explosion))
self.kill()
yield 10
gamesrv.set_musics([], [images.music_game2], reset=0)
if ((not images.ActiveSprites or random.random() < 0.05678) and
gamesrv.clients):
# make sure the extension's images are loaded too
# NB. this is also needed for import auto-detection
import ext1; import ext2; import ext3
import ext4; import ext5; import ext6
import ext7
ico = images.sprget(nimages[0])
s = images.ActiveSprite(ico,
random.randrange(0, screenwidth-ico.w),
screenheight)
s.ypop = random.randrange(-ico.h, screenheight)
s.gen = [welcomebubbling(s)]
s.setimages(s.cyclic(nimages, speed=1))
if random.random() > 0.4321:
try:
key, (filename, (x, y, w, h)) = random.choice(
list(images.sprmap.items()))
except:
w = h = 0
if w == h == 32:
s2 = images.ActiveSprite(images.sprget(key), -32, 0)
s2.gen = [s2.following(s, (s.ico.w-32)//2, (s.ico.h-32)//2)]
s.ypop = None
images.action(images.ActiveSprites[:])
def patget(n, keycol=None):
bitmap, rect = loadpattern(n, keycol)
return bitmap.geticon(*rect)
def get_lives():
return gamesrv.game.limitlives
def do_nothing():
while True:
yield 5
BoardList = []
curboard = None
BoardGen = [do_nothing()]
def next_board(num=0, complete=1, fastreenter=False):
yield force_singlegen()
set_frametime(1.0)
brd = curboard
inplace = 0
if brd:
inplace = brd.bonuslevel or fastreenter
num = brd.num
if not inplace:
num += gamesrv.game.stepboard
if num >= len(BoardList):
num = len(BoardList)-1
if (gamesrv.game.finalboard is not None and num > gamesrv.game.finalboard):
num = gamesrv.game.finalboard
for t in brd.leave(inplace=inplace):
yield t
# reset global board state
from player import BubPlayer, reset_global_board_state
reset_global_board_state()
if not inplace:
del BubPlayer.MonsterList[:]
# wait for at least one player
for t in wait_for_one_player():
yield t
# reload modules if changed
if loadmodules():
import boards
boards.BoardGen = [boards.next_board(num)]
return
if num < 0:
num = 0
elif num >= len(BoardList):
num = len(BoardList)-1
brd = BoardList[num](num)
for t in brd.enter(complete, inplace=inplace, fastreenter=fastreenter):
yield t
if brd.bonuslevel:
gen = bonus_play
else:
gen = normal_play
BoardGen[0] = gen()
def set_frametime(ft, privtime=100):
from player import BubPlayer
BubPlayer.BaseFrametime = ft
BubPlayer.PlayersPrivateTime = privtime
images.loadsounds(1.0 / ft)
def extra_boardgen(gen, at_end=0):
if curboard.playingboard:
if at_end or not BoardGen:
BoardGen.append(gen)
else:
BoardGen.insert(1, gen)
def replace_boardgen(gen, force=0):
if curboard.playingboard or force:
curboard.playingboard = 0
BoardGen[0] = gen
def force_singlegen():
del BoardGen[1:]
return 0
def has_singlegen():
return len(BoardGen) <= 1
def display_hat(p, d):
if p.team == -1 or getattr(d,'isdying',0) or hasattr(d,'no_hat'):
return
try:
bottom_up = d.bottom_up()
except AttributeError:
bottom_up = 0
try:
image = ('hat', p.team, d.dir, d.hatangle)
except AttributeError:
image = ('hat', p.team)
if bottom_up:
image = 'vflip', image
y = d.y
else:
y = d.y - 16
ico = images.sprget(image)
if (getattr(d,'hatsprite',None) is None or
not d.hatsprite.alive):
d.hatsprite = images.ActiveSprite(ico, d.x, y)
else:
d.hatsprite.to_front()
d.hatsprite.move(d.x, y, ico)
d.hatsprite.gen = [d.hatsprite.die([None])]
def normal_frame():
from player import BubPlayer
BubPlayer.FrameCounter += 1
# main generator dispatch loop
images.action(images.ActiveSprites[:])
frametime = 10
for p in BubPlayer.PlayerList:
if p.isplaying():
frametime = BubPlayer.BaseFrametime
p.zarkon()
for d in p.dragons:
d.to_front()
display_hat(p, d)
d.prefix(p.pn)
if not (BubPlayer.FrameCounter & 31):
gamesrv.compactsprites()
reset = getattr(BubPlayer, 'MultiplyerReset', 0)
if reset and BubPlayer.FrameCounter >= reset:
BubPlayer.MultiplyerReset = 0
set_frametime(1.0)
return frametime
def normal_play():
from player import BubPlayer
import bonuses
framecounter = 0
bonus_callback = bonuses.start_normal_play()
while BubPlayer.MonsterList:
bonus_callback()
yield normal_frame()
if not BubPlayer.DragonList:
continue
framecounter += 1
BASE = 500
if not (framecounter % BASE):
if framecounter == 4*BASE:
from monsters import Monster
from mnstrmap import BigImages
ico = images.sprget(BigImages.hurryup[1])
s = images.ActiveSprite(ico, (bwidth-ico.w)//2, (bheight-ico.h)//2)
s.setimages(s.die(BigImages.hurryup * 12, 2))
images.Snd.Hurry.play()
mlist = [s for s in images.ActiveSprites
if (isinstance(s, Monster) and s.regular() and
not s.angry)]
if mlist:
s = random.choice(mlist)
s.angry = [s.genangry()]
s.resetimages()
if framecounter >= 6*BASE:
mlist = [s for s in images.ActiveSprites
if isinstance(s, Monster) and s.regular() and s.angry]
if mlist:
images.Snd.Hell.play()
gamesrv.set_musics([], [])
s = random.choice(mlist)
s.become_ghost()
framecounter = -200
else:
framecounter = 2*BASE
if framecounter == 0:
curboard.set_musics()
replace_boardgen(last_monster_killed(), 1)
##def normal_play():
## # TESTING!!
## from player import BubPlayer
## for p in BubPlayer.PlayerList:
## if not p.icons:
## p.loadicons(p.icons, images.sprget)
## results = {BubPlayer.PlayerList[0]: 100,
## BubPlayer.PlayerList[1]: 200,
## BubPlayer.PlayerList[2]: 300,
## BubPlayer.PlayerList[3]: 400,
## BubPlayer.PlayerList[4]: 100,
## BubPlayer.PlayerList[5]: 200,
## BubPlayer.PlayerList[6]: 300,
## BubPlayer.PlayerList[7]: 400,
## BubPlayer.PlayerList[8]:1000,
## BubPlayer.PlayerList[9]:1000,
## }
## maximum = None
## for t in result_ranking(results, maximum):
## yield t
def last_monster_killed(end_delay=390, music=None):
from player import BubPlayer
for t in exit_board(music=music):
yield t
if curboard.bonuslevel:
curboard.playingboard = 1
for t in bonus_play():
yield t
end_delay -= 1
if end_delay <= 0:
replace_boardgen(next_board(), 1)
break
else:
for i in range(end_delay):
yield normal_frame()
replace_boardgen(next_board(), 1)
##def bonus_play():
## from player import BubPlayer
## import bubbles
## while BubPlayer.LimitScoreColor is None:
## yield normal_frame()
## players = [(p.points, p.pn) for p in BubPlayer.PlayerList
## if p.isplaying()]
## if players:
## players.sort()
## points, BubPlayer.LimitScoreColor = players[-1]
## BubPlayer.LimitScore = ((points + limit) // 100000) * 100000
## for p in BubPlayer.PlayerList:
## if p.isplaying():
## p.givepoints(0) # check LimitScore and update scoreboard()
## while not (BubPlayer.BubblesBecome or BubPlayer.MegaBonus):
## if random.random() < 0.06:
## bubbles.newbonusbubble()
## yield normal_frame()
## # special board end
## import monsters
## monsters.argh_em_all()
## replace_boardgen(last_monster_killed(), 1)
class TimeCounter(Copyable):
def __init__(self, limittime, blink=0):
from player import BubPlayer
self.saved_time = BubPlayer.LimitTime
self.time = limittime / FRAME_TIME
self.prev = None
self.blink = blink
def update(self, t):
from player import BubPlayer, scoreboard
self.time -= t
if self.time < 0.0:
self.time = 0.0
BubPlayer.LimitTime = self.time * FRAME_TIME
next = int(BubPlayer.LimitTime)
if self.blink and BubPlayer.LimitTime - next >= 0.5:
BubPlayer.LimitTime = next = None
if self.prev != next:
scoreboard(compresslimittime=1)
self.prev = next
def restore(self):
from player import BubPlayer
BubPlayer.LimitTime = self.saved_time
def bonus_play():
from player import BubPlayer
import bubbles
BubPlayer.MegaBonus = None
BubPlayer.BubblesBecome = None
Time0 = 5.0 / FRAME_TIME # when to slow down time
tc = TimeCounter(BubPlayer.LimitTime or 180.9) # 3:00
prev = None
while not (BubPlayer.BubblesBecome or BubPlayer.MegaBonus):
if random.random() < 0.099:
bubbles.newbonusbubble()
t = normal_frame()
tc.update(t)
if tc.time < Time0:
if tc.time <= 0.5:
tc.time = 0.5
BubPlayer.LimitTime = 0.0
t *= math.sqrt(Time0 / tc.time)
yield t
if tc.time == 0.5:
gamesrv.game.End = 'gameover'
gamesrv.game.updateboard()
replace_boardgen(game_over(), 1)
return
# special board end
import monsters
monsters.argh_em_all()
replace_boardgen(last_monster_killed(), 1)
def game_over():
yield force_singlegen()
from player import scoreboard
import ranking
images.Snd.Extralife.play()
gamesrv.set_musics([], [images.music_potion])
scoreboard()
for t in ranking.game_over():
yield t
def game_reset():
import time
from player import BubPlayer
t1 = time.time()
while 1:
yield 0
if BubPlayer.LimitTime and BubPlayer.LimitTime >= 1.0:
# someone else ticking the clock, try again later
return
if abs(time.time() - t1) > 2.0:
break
# anyone playing ?
if not gamesrv.game.End:
return # yes -> cancel game_reset()
# let's tick the clock !
tc = TimeCounter(60.9, blink=1) # 1:00
t1 = time.time()
while tc.time:
yield 0
# anyone playing now ?
if not gamesrv.game.End:
tc.restore()
return # yes -> cancel game_reset()
t = time.time() # use real time
deltat = (t-t1)/FRAME_TIME
if deltat < 1.0:
deltat = 1.0
elif deltat > 100.0:
deltat = 100.0
tc.update(deltat)
t1 = t
gamesrv.game.reset()
##def wasting_play():
## from player import BubPlayer, scoreboard
## import bubbles
## curboard.wastingplay = {}
## for p in BubPlayer.PlayerList:
## if p.isplaying():
## p.letters = {}
## p.bonbons = p.points // 50000
## scoreboard()
## while len(BubPlayer.DragonList) > 1:
## if random.random() < 0.03:
## bubbles.newbubble(1)
## yield normal_frame()
## for d in BubPlayer.DragonList:
## curboard.wastingplay[d.bubber] = len(curboard.wastingplay)
## for i in range(50):
## yield normal_frame()
## total = len(curboard.wastingplay)
## results = [(total-n, p) for p, n in curboard.wastingplay.items()]
## results.sort()
## results = [(p, str(n)) for n, p in results]
## for t in display_ranking(results):
## yield t
## # never ending
def skiplevels(blink, skip):
# (not used any more)
saved = BoardGen[:]
while skip:
skip -= 1
BoardGen[:] = saved
for i in range(10): # frozen pause
yield 3
if blink:
blink.step(-bwidth, 0)
yield 3.33
blink.step(bwidth, 0)
blink = None
for t in next_board(complete=(skip==0)):
yield t
def exit_board(delay=8, music=None, repeatmusic=[]):
from bubbles import Bubble
from bonuses import RandomBonus, end_normal_play
from player import BubPlayer
from monsters import Monster
end_normal_play()
curboard.playingboard = 0
actives = images.ActiveSprites[:]
for s in actives:
if ((isinstance(s, Monster) and s.still_playing())
or isinstance(s, RandomBonus)):
s.kill()
music = music or []
if BubPlayer.MegaBonus:
music[:1] = [images.music_modern]
if music or repeatmusic:
gamesrv.set_musics(music, repeatmusic)
for i in range(delay):
yield normal_frame()
bubble_outcome = BubPlayer.BubblesBecome or Bubble.pop
for s in actives:
if isinstance(s, Bubble):
bubble_outcome(s)
yield normal_frame()
if BubPlayer.MegaBonus:
BubPlayer.MegaBonus()
def potion_fill(blist, big=0):
from player import BubPlayer
from bonuses import Bonus
#timeleft = 1680.0
for t in exit_board(0, music=[images.music_potion]):
#timeleft -= t
yield t
notes = all_notes = []
y = 1
while y < 11 or (y < height-2 and (len(all_notes) < 10 or big)):
for x in range(2, width-3, 2):
if ' ' == bget(x,y) == bget(x+1,y) == bget(x,y+1) == bget(x+1,y+1):
b = Bonus(x*CELL, y*CELL, falling=0, *blist[((x+y)//2)%len(blist)])
b.timeout = (444,666)[big]
all_notes.append(b)
for i in range(2):
t = normal_frame()
#timeleft -= t
yield t
y += 2
while notes: #and timeleft > 0.0:
notes = [b for b in notes if b.alive]
t = normal_frame()
#timeleft -= t
yield t
for i in range(10):
t = normal_frame()
#timeleft -= t
yield t
results = {}
for b in all_notes:
for d in b.taken_by:
bubber = d.bubber
results[bubber] = results.get(bubber, 0) + 1
for t in result_ranking(results, len(all_notes)):
yield t
#fadeouttime = 3.33
#fullsoundframes = bonusframes - 10 - int(fadeouttime / FRAME_TIME)
#for i in range(fullsoundframes):
# yield normal_frame()
#gamesrv.fadeout(fadeouttime)
#for i in range(fullsoundframes, 490):
# yield normal_frame()
def result_ranking(results, maximum=None, timeleft=200):
import ranking
results = ranking.ranking_picture(results, maximum, timeleft is not None)
if curboard.bonuslevel and timeleft is not None:
play_again = bonus_play()
else:
play_again = None
for t in ranking.display(results, timeleft, play_again):
yield t
if gamesrv.game.End != 'gameover':
gamesrv.set_musics([], [])
replace_boardgen(next_board(), 1)
def extra_water_flood():
from mnstrmap import Flood
from monsters import Monster
waves_icons = [images.sprget(n) for n in Flood.waves]
fill_icon = images.sprget(Flood.fill)
bspr = []
if 'flood' in curboard.sprites:
return # only one flooding at a time
curboard.sprites['flood'] = bspr
waves_sprites = [gamesrv.Sprite(waves_icons[0], x, bheight-CELL)
for x in range(0, bwidth, CELL)]
bspr += waves_sprites
fill_by_line = []
poplist = [None]
while waves_sprites[0].y > 0:
yield 0
waves_icons.insert(0, waves_icons.pop())
for s in waves_sprites:
s.seticon(waves_icons[0])
yield 0
sprites = [gamesrv.Sprite(fill_icon, s.x, s.y) for s in waves_sprites]
bspr += sprites
fill_by_line.append(sprites)
for s in waves_sprites:
s.step(0, -16)
for s in images.touching(0, waves_sprites[0].y, bwidth, bheight):
if isinstance(s, Monster):
s.argh(poplist)
while 1:
for i in range(2):
yield 0
waves_icons.insert(0, waves_icons.pop())
for s in waves_sprites:
s.seticon(waves_icons[0])
if not fill_by_line:
break
for s in fill_by_line.pop():
s.kill()
for s in waves_sprites:
s.step(0, 16)
for s in waves_sprites:
s.kill()
del curboard.sprites['flood']
def extra_aquarium():
from mnstrmap import Flood
from player import BubPlayer
for i in range(200):
if 'flood' not in curboard.sprites: # only one flooding at a time
break
yield 0
if curboard.cleaning_gen_state:
return
else:
return
curboard.sprites['flood'] = []
gl = curboard.sprites.setdefault('background', [])
curboard.holes = True # so that random PlainBubbles show up anyway
walls = curboard.sprites['walls']
seen = {}
def newsprite(ico, x, y):
s = gamesrv.Sprite(ico, x, y)
s.to_back(walls[0])
gl.append(s)
return s
def fishplayers(ymin):
for d in BubPlayer.DragonList:
if d not in seen and d.y >= ymin:
seen[d] = True
d.become_fish()
d.bubber.emotic(d, 4)
waves_icons = [images.sprget(n) for n in Flood.waves]
fill_icon = images.sprget(Flood.fill)
waves_sprites = [newsprite(waves_icons[0], x, bheight-CELL)
for x in range(2*CELL + HALFCELL, bwidth - 2*CELL, CELL)]
while waves_sprites[0].y > -fill_icon.h:
fishplayers(waves_sprites[0].y)
yield 0
waves_icons.append(waves_icons.pop(0))
for s in waves_sprites:
s.seticon(waves_icons[0])
fishplayers(waves_sprites[0].y)
yield 0
for s in waves_sprites:
newsprite(fill_icon, s.x, s.y)
for s in waves_sprites:
s.step(0, -16)
for s in waves_sprites:
s.kill()
BubPlayer.SuperFish = True
fishplayers(-sys.maxsize)
def extra_walls_falling():
walls_by_pos = curboard.walls_by_pos
moves = 1
while moves and not curboard.cleaning_gen_state:
moves = 0
for y in range(height-3, -1, -1):
for x in range(2, width-2):
if ((y,x) in walls_by_pos and
(y+1,x) not in walls_by_pos and
(y+2,x) not in walls_by_pos):
y0 = y
while (y0-1,x) in walls_by_pos:
y0 -= 1
w = curboard.killwall(x, y0, 0)
curboard.putwall(x, y+1, w)
moves = 1
curboard.reorder_walls()
for y in range(6):
yield 0
def single_blocks_falling(xylist):
walls_by_pos = curboard.walls_by_pos
while xylist:
newlist = []
for x, y in xylist:
if ((y,x) in walls_by_pos and (y+1,x) not in walls_by_pos and
y < curboard.height-1):
newlist.append((x, y+1))
for x, y in newlist:
w = curboard.killwall(x, y-1, 0)
curboard.putwall(x, y, w)
xylist = newlist
curboard.reorder_walls()
for i in range(7):
yield 0
def extra_display_repulse(cx, cy, dlimit=5000, dfactor=1000):
offsets = {}
for s in list(gamesrv.sprites_by_n.values()):
x, y = s.getdisplaypos()
if x is not None:
dx = x - cx
dy = y - cy
d = dx*dx + dy*dy + 100
if d <= dlimit:
dx = (dx*dfactor)//d
dy = (dy*dfactor)//d
offsets[s] = dx, dy
s.setdisplaypos(int(x+dx), int(y+dy))
yield 0
yield 0
while offsets:
prevoffsets = offsets
offsets = {}
for s, (dx, dy) in list(prevoffsets.items()):
if s.alive:
if dx < 0:
dx += max(1, (-dx)//5)
elif dx:
dx -= max(1, dx//5)
if dy < 0:
dy += max(1, (-dy)//5)
elif dy:
dy -= max(1, dy//5)
if dx or dy:
offsets[s] = dx, dy
s.setdisplaypos(int(s.x+dx), int(s.y+dy))
yield 0
def extra_bkgnd_black(cx, cy):
gl = curboard.sprites.get('background')
dist = 0
while gl:
dist += 17
dist2 = dist * dist
gl2 = []
for s in gl:
if (s.x-cx)*(s.x-cx) + (s.y-cy)*(s.y-cy) < dist2:
s.kill()
else:
gl2.append(s)
gl[:] = gl2
yield 0
def extra_light_off(timeout, icocache={}):
for i in range(timeout):
if curboard.cleaning_gen_state:
break
dragons = {}
import player
playerlist = player.BubPlayer.PlayerList
for bubber in playerlist:
for dragon in bubber.dragons:
dragons[dragon] = True
for s in list(gamesrv.sprites_by_n.values()):
try:
ico = icocache[s.ico, s in dragons]
except KeyError:
ico = images.make_darker(s.ico, s in dragons)
icocache[s.ico, s in dragons] = ico
s.setdisplayicon(ico)
yield 0
for s in list(gamesrv.sprites_by_n.values()):
s.setdisplayicon(s.ico)
def extra_swap_up_down(N=27):
# unregister all walls
walls = list(curboard.walls_by_pos.items())
walls.sort()
if not walls:
return
curboard.walls_by_pos.clear()
emptyline = '##' + ' '*(width-4) + '##'
curboard.walls = [emptyline] * height
l = curboard.sprites['walls']
wallicon = l[0].ico
wallpool = l[:]
l[:] = [gamesrv.Sprite(wallicon, 0, -wallicon.h)]
# force the top half of the walls on front
#for (y,x), w in walls:
# if y*2 < height:
# show the walls swapping up/down
ycenter = ((height-1)*CELL) // 2
for i in range(N):
alpha = math.cos((math.pi*(i+1))/N)
ymap = {}
for y in range(height):
ymap[y] = int(alpha*(y*CELL-ycenter)) + ycenter
for (y,x), w in walls:
if y in ymap:
w.move(x*CELL, ymap[y])
yield 0
if i == (N+1)//2:
# reorder the wall sprites in the middle of the swap
walls = [((-y,x), w) for (y,x), w in walls]
walls.sort()
for i in range(len(walls)):
(y,x), w = walls[i]
walls[i] = (y,x), wallpool[i]
walls = [((-y,x), w) for (y,x), w in walls]
walls.sort()
# reverse all dragons!
from player import BubPlayer
for dragon in BubPlayer.DragonList:
dragon.dcap['gravity'] *= -1.0
# freeze the walls in their new position
i = 0
for (y,x), w in walls:
y = height-1 - y
if 0 <= y < height and (y,x) not in curboard.walls_by_pos:
w = wallpool[i]
i += 1
curboard.putwall(x, y, w)
l[:0] = wallpool[:i]
for w in wallpool[i:]:
w.kill()
curboard.reorder_walls()
def extra_catch_all_monsters(dragons=[], everything=False):
from monsters import Monster
from bubbles import BigBubbleCatcher
from bonuses import Bonus, BonusMaker
if not dragons:
from player import BubPlayer
dragons = BubPlayer.DragonList
i = 0
give_up = 33
for s in images.ActiveSprites[:]:
if curboard.cleaning_gen_state:
break
while not dragons:
give_up -= 1
if give_up == 0:
return
yield 0
if not s.alive or not s.touchable:
continue
if isinstance(s, Bonus):
ok = s.bubblable
elif isinstance(s, BonusMaker):
ok = everything
else:
ok = isinstance(s, Monster)
if ok:
dragon = dragons[i%len(dragons)]
BigBubbleCatcher(dragon, s, 542 + 17*i)
i += 1
yield 0
yield 0
def extra_make_random_level(cx=None, cy=None, repeat_delay=200):
from bonuses import DustStar
# generate any random level
localdir = os.path.dirname(__file__)
filename = os.path.join(localdir, 'levels', 'RandomLevels.py')
d = {}
exec(compile(open(filename, "rb").read(), filename, 'exec'), d)
Level = d['GenerateSingleLevel'](curboard.width, curboard.height)
lvl = Level(curboard.num)
walllist = []
if cx is None: cx = bwidth // 2
if cy is None: cy = bheight // 2
for y in range(curboard.height):
dy = cy - (y*16+8)
dy2 = dy*dy*0.75
for x in range(2, curboard.width-2):
dx = cx - (x*16+8)
d2 = dx*dx + dy2
walllist.append((d2, x, y))
walllist.sort()
# dynamically replace the current level's walls with the new level's
dist = 0
speedf = 15.0
added = 0
for d2, x, y in walllist:
while d2 > dist*dist:
if added:
curboard.reorder_walls()
added = 0
yield 0
dist += 4.1
speedf *= 0.99
if curboard.walls[y][x] == ' ':
if lvl.walls[y][x] == ' ':
continue
else:
curboard.putwall(x, y)
added = 1
big = 1
else:
if lvl.walls[y][x] == ' ':
curboard.killwall(x, y)
big = 1
else:
big = 0
sx = x*16
sy = y*16
DustStar(sx - 8*big,
sy - 8*big,
speedf * (sx-cx) / dist,
speedf * (sy-cy) / dist,
big=big)
# patch the winds too
curboard.winds = lvl.winds
curboard.reorder_walls()
yield 0
# wait a bit and restart
if repeat_delay < 1000:
for i in range(repeat_delay):
yield 0
if curboard.cleaning_gen_state:
return
extra_boardgen(extra_make_random_level(
repeat_delay = repeat_delay * 3 // 2))
def extra_bubbles(timeout):
from bubbles import newforcedbubble
falloff = 0.25
L = math.log(0.965) # same speed falloff rate as in throwing_bubble()
cx = (bwidth - CELL) // 2
cy = (bheight - CELL) // 2
for i in range(timeout):
if curboard.cleaning_gen_state:
return
if random.random() < falloff:
bubble = newforcedbubble()
if bubble:
tx = random.randrange(CELL, bwidth - 2*CELL) - cx
ty = random.randrange(CELL, bheight - 2*CELL) - cy
if ty == 0:
ty = 1
dist = math.sqrt(tx * tx + ty * ty)
acos = tx / dist
asin = ty / dist
hspeed = 4 - dist * L
bubble.thrown_bubble(cx, cy, hspeed, (acos, asin))
falloff *= 0.998
yield 0
def initsubgame(music, displaypoints):
from player import BubPlayer, scoreboard
for t in exit_board(0, repeatmusic=[music]):
yield t
BubPlayer.DisplayPoints = displaypoints
scoreboard()
for t in curboard.clean_gen_state():
yield t
def register(dict):
global width, height, bwidth, bheight, bheightmod
items = list(dict.items())
items.sort()
for name, board in items:
try:
if not issubclass(board, Board) or board is Board:
continue
except TypeError:
continue
BoardList.append(board)
# check sizes
assert BoardList, "board file does not define any board"
B = BoardList[0]
try:
test = B(-1)
width = test.width
height = test.height
for B in BoardList[1:]:
test = B(-1)
assert test.width == width, "some boards have a different width"
assert test.height == height, "some boards have a different height"
except Exception as e:
print('Caught "%s" in level "%s":' % (e, B.__name__))
raise e
bwidth = width*CELL
bheight = height*CELL
bheightmod = (height+2)*CELL
##def define_boards(filename):
## global curboard, boards, width, height, bwidth, bheight, bheightmod
## curboard = None
## boards = []
## def board((wallfile, wallrect), shape):
## lines = shape.strip().split('\n')
## bmp = gamesrv.getbitmap(wallfile)
## wallicon = bmp.geticon(*wallrect)
## boards.append(Board(lines, wallicon))
## d = {'board': board}
## execfile(filename, d)
## assert boards, "board file does not define any board"
## width = boards[0].width
## height = boards[0].height
## for b in boards[1:]:
## assert b.width == width, "some boards have a different width"
## assert b.height == height, "some boards have a different height"
## bwidth = width*CELL
## bheight = height*CELL
## bheightmod = len(boards[0].lines)*CELL
#try:
# import psyco
#except ImportError:
# pass
#else:
# psyco.bind(normal_frame)