diff options
Diffstat (limited to 'bubbob/levels/RandomLevels.py')
-rw-r--r-- | bubbob/levels/RandomLevels.py | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/bubbob/levels/RandomLevels.py b/bubbob/levels/RandomLevels.py new file mode 100644 index 0000000..5c0f77d --- /dev/null +++ b/bubbob/levels/RandomLevels.py @@ -0,0 +1,362 @@ +# +# Second try at automatically generating levels that are +# a bit more related to each other instead of being completely independent. +# + +from __future__ import generators +import sys, random, math +from random import uniform, choice, randrange + + +class Parameter(object): + def __init__(self, name, rng): + self.name = name + self.rng = rng + def __get__(self, instance, cls): + assert self.name not in instance.__dict__ + value = self.rng() + setattr(instance, self.name, value) + return value + +class ChoiceParameter(Parameter): + def __init__(self, name, list): + Parameter.__init__(self, name, lambda : choice(list)) + +class BoolParameter(Parameter): + def __init__(self, name, prob=0.5): + Parameter.__init__(self, name, lambda : random.random() < prob) + +def flat(mean,var): + return randrange(mean-var,mean+var+1) + +def dice(n,sides,orig=1): + result = 0 + for i in range(n): + result += orig+randrange(sides) + return result + +def fork(choice1, prob, choice2): + if random.random() < prob: + return choice1() + else: + return choice2() + +MnstrCategory = { + "Nasty": 0, + "Monky": 0, + "Ghosty": 1, + "Flappy": 1, + "Springy": 2, + "Orcy": 0, + "Gramy": 0, + "Blitzy": 2} +MnstrNames = MnstrCategory.keys() +Bonuses = ['letter', 'fire', 'lightning', 'water', 'top'] + +def mnstrclslist(name): + import boarddef + classname1 = 'L' + name + classname2 = 'R' + name + return [getattr(boarddef, classname1), getattr(boarddef, classname2)] + +class Shape: + basemnstr = ChoiceParameter('basemnstr', MnstrNames) + extramnstr = ChoiceParameter('extramnstr', range(4)) + samemnstr = BoolParameter('samemnstr') + baseshape = ChoiceParameter('baseshape', ' ODBGMPRWZS') + rooms = BoolParameter('rooms') + holes = BoolParameter('holes') + lines = ChoiceParameter('lines', ' -/|') + platforms = BoolParameter('platforms') + platholes = BoolParameter('platholes') + platfull = BoolParameter('platfull') + mess = ChoiceParameter('mess', ' ....!') + closed = BoolParameter('closed', 0.95) + bonuses = ChoiceParameter('bonuses', xrange(3**len(Bonuses))) + smooth = ChoiceParameter('smooth', range(4)) + startplats = BoolParameter('startplats', 0.98) + makespace = BoolParameter('makespace', 0.8) + straightfall = BoolParameter('straightfall', 0.8) + mirrored = BoolParameter('mirrored', 0.4) + enlargeholes = BoolParameter('enlargeholes', 0.9) + + all_parameters = [name for name in locals().keys() + if not name.startswith('_')] + + def __init__(self, shape=None): + if shape: + self.__dict__.update(shape.__dict__) + self.modified = 0 + + def set_gens(self, rooms=0, platforms=0, holes=0, smooth=0, mess=' ', lines=' '): + self.rooms = rooms + self.platforms = platforms + self.holes = holes + self.smooth = smooth + self.mess = mess + self.lines = lines + + def reset(self, attrname=None): + if attrname: + try: + del self.__dict__[attrname] + except KeyError: + pass + else: + self.__dict__.clear() + self.modified = 1 + + def __eq__(self, other): + return (self.__class__ is other.__class__ and + self.__dict__ == other.__dict__) + + def test_similar_parameters(self, prevlist): + similarity = 0 + rprevlist = prevlist[:] + rprevlist.reverse() + for param in Shape.all_parameters: + accum = 0 + for prev in rprevlist: + if getattr(self, param) != getattr(prev, param): + break + else: + accum += 1 + similarity += accum + minimum = min(4*len(prevlist), 7) + if not (minimum <= similarity <= 17): + self.reset() + + def test_not_too_often(self, prevlist): + for param, bad_value, delay in [ + ('mess', '.', 2), + ('mess', '!', 11), + ('holes', 1, 1), + ]: + if getattr(self, param) == bad_value: + for prev in prevlist[-delay:]: + if getattr(prev, param) == bad_value: + self.reset(param) + + def test_mess_hole(self, prevlist): + if self.mess == '!': + self.holes = 1 + + all_tests = [value for (name, value) in locals().items() + if name.startswith('test_')] + + def accept(self, lvl): + f = lambda d=self.difficulty : randrange(3, 4+int(9*d)) + lvl.mlist = [(mnstrclslist(self.basemnstr), f)] + repeat = choice([2,2,3]) - self.extramnstr + if repeat > 1: + lvl.mlist *= repeat + if self.extramnstr: + othermnstr = [name for name in MnstrNames if name!=self.basemnstr] + if self.samemnstr: + othermnstr = [name for name in othermnstr + if MnstrCategory[name]==MnstrCategory[self.basemnstr]] + random.shuffle(othermnstr) + for name in othermnstr[:self.extramnstr]: + lvl.mlist.append((mnstrclslist(name), f)) + + lvl.genwalls = [] + + if self.baseshape == 'G': + lvl.genwalls.append((RandomLevel.grids, + uniform(0.7,0.8), + uniform(0.7,0.8))) + self.set_gens() + if self.baseshape == 'P': + lvl.genwalls.append((RandomLevel.pegs, + uniform(0.1,0.2), + uniform(0.45,0.7), + choice([0,1,1,1]))) + self.set_gens(smooth=3) + self.closed = random.random() < 0.80 + if self.baseshape == 'B': + nr = choice([0,0,1]) + lvl.genwalls.append((RandomLevel.bouncers, + dice(1, 100) + 250 - nr*200, # length + uniform(0.7, 1.7), + nr)) + self.set_gens(smooth=3) + if self.baseshape == 'W': + nr = dice(1, 3) + 2 + lvl.genwalls.append((RandomLevel.walkers, + dice(2, 100) + 100, # length + nr, nr + dice(2, 3), + choice([0,1]))) + self.set_gens() + if self.baseshape == 'R': + lvl.genwalls.append((RandomLevel.rivers, + randrange(3,(lvl.WIDTH-4)/4), # the number of rivers + uniform(0.3, 1.4), # the side stepping threshold + 10)) # the max side stepping size + self.set_gens() + if self.baseshape == 'Z': + lvl.genwalls.append((RandomLevel.zigzag,)) + self.set_gens() + if self.baseshape == 'M': + lvl.genwalls.append((RandomLevel.mondrian,)) + self.set_gens() + if self.baseshape == 'O': + lvl.genwalls.append((RandomLevel.remove_joined_blocks,)) + self.set_gens() + if self.baseshape == 'S': + lvl.genwalls.append((RandomLevel.platforms_reg,)) + self.set_gens() + self.makespace = random.random() < 0.1 + if self.closed: + self.startplats = 0 + if self.baseshape == 'D': + lvl.genwalls.append((RandomLevel.discrete_blocks,)) + self.set_gens() + self.makespace = random.random() < 0.1 + if self.closed: + self.startplats = 0 + + if self.rooms: + nr = dice(2, 6) + lvl.genwalls.append((RandomLevel.rooms, + lambda : flat(9-nr,2), # the half size of the room + lambda : uniform(0.8,1.2), # the excentricity of the room + nr)) # the number of rooms + if self.lines != ' ': + rng_angle = { + '-': lambda : 0, + '/': None, # default + '|': lambda : math.pi/2, + } + lvl.genwalls.append((RandomLevel.lines, + lambda : dice(8,3), # line length + dice(2,4), # number of lines + rng_angle[self.lines])) + if self.platforms: + nplat = dice(2,4,0) + if nplat: space = flat((lvl.HEIGHT-1)/nplat/2,(lvl.HEIGHT-1)/nplat/2-1) + else: space = 1 + if self.platholes: + nholes = lambda : dice(1,3) + else: + nholes = lambda : 0 + wholes = lambda : dice(2,3) + full = self.platfull + lvl.genwalls.append((RandomLevel.platforms, + (nplat,space), # number of platform and spacing + (nholes,wholes), # number of holes and width + full)) # full width platform + if self.mess != ' ': + threshold = { + '.': 0.02 + 0.08*random.random(), # normal + '!': 0.25 + 0.2 *random.random(), # super-filled + } + lvl.genwalls.append((RandomLevel.mess, threshold[self.mess])) + if self.holes: + nh = choice([1,1,2,2,2,3,3,3,4,5]) + lvl.genwalls.append((RandomLevel.holes, + lambda : flat(9-nh,2), # radius of the holes + lambda : uniform(0.9,1.1), # excentricity + nh, # number of holes + lambda : choice([0,0,0,1]))) # circle or rectangle + if self.closed: + lvl.genwalls.append((RandomLevel.close,)) + if self.smooth > 0: + # smooth away all lone empty spaces + lvl.genwalls.append((RandomLevel.smooth, 1.0, 1)) + # possibly smooth away some lone bricks + if self.smooth == 2: + lvl.genwalls.append((RandomLevel.smooth, 0.25, 0)) + elif self.smooth == 3: + lvl.genwalls.append((RandomLevel.smooth, 0.75, 0)) + if self.startplats: + lvl.genwalls.append((RandomLevel.startplatform, )) + lvl.genwalls.append((RandomLevel.openstartway, )) + + if self.makespace: + lvl.genwalls.append((RandomLevel.make_space, )) + + if self.straightfall: + lvl.genwalls.append((RandomLevel.prevent_straight_fall, )) + + if self.mirrored: + lvl.genwalls.append((RandomLevel.mirror, )) + + if self.enlargeholes: + lvl.genwalls.append((RandomLevel.enlarge_tiny_holes, )) + + lvl.genwalls.append((RandomLevel.generate_wind, )) + b = self.bonuses + for name in Bonuses: + setattr(lvl, name, (b % 3) == 1) + b = b // 3 + lvl.autogen_shape = self + + +def generate_shape(prevlist): + tests = Shape.all_tests + s = Shape() + for i in range(100): + s1 = Shape(s) + random.shuffle(tests) + for test in tests: + test(s1, prevlist) + if not s1.modified and s1 == s: + break + s = s1 +# else: +# sys.stdout.write('*') + del s.modified + return s + +def makeshapes(nblevels=25): + shapelist = [] + for i in range(nblevels): + s = generate_shape(shapelist) + s.difficulty = float(i+1)/nblevels + yield s + shapelist.append(s) + if len(shapelist) == 10: + del shapelist[:] + +def GenerateLevels(): +# print 'generating levels', + Levels = [] + for s in makeshapes(): + class level(RandomLevel): + WIDTH = 28 + HEIGHT = 23 + def enter(self, *args, **kw): + result = RandomLevel.enter(self, *args, **kw) + params = self.autogen_shape.__dict__.items() + params.sort() +# for keyvalue in params: +# print '%20s: %s' % keyvalue + return result + s.accept(level) + Levels.append(level) +# sys.stdout.write('.') +# sys.stdout.flush() +# print + class levelfinal(RandomLevel): + WIDTH = level.WIDTH + HEIGHT = level.HEIGHT + genwalls = [(RandomLevel.platforms,(5,3),(lambda:flat(2,1),lambda:flat(6,2)),1), + (RandomLevel.close,)] + Levels.append(levelfinal) + return Levels + +def GenerateSingleLevel(width, height): + [s] = makeshapes(1) + class level(RandomLevel): + WIDTH = width + HEIGHT = height + s.accept(level) + return level + +if __name__ == '__main__': + for s in makeshapes(): + print s.__dict__ +else: + rnglevel = {} + execfile('levels/rnglevel', rnglevel) + RandomLevel = rnglevel['RandomLevel'] |