mirror of
https://github.com/abakh/nbsdgames.git
synced 2025-02-02 15:07:27 -05:00
363 lines
12 KiB
Python
363 lines
12 KiB
Python
#
|
|
# 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']
|