Skip to content

Instantly share code, notes, and snippets.

@RoadrunnerWMC
Created January 3, 2017 06:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RoadrunnerWMC/9ec9f8a2c893acb20ea623c1b7c3421f to your computer and use it in GitHub Desktop.
Save RoadrunnerWMC/9ec9f8a2c893acb20ea623c1b7c3421f to your computer and use it in GitHub Desktop.
An early version of a script to generate random object fills with the NSMBW bonus-room tileset (Pa2_sora)
# 1/1/17 (woo)
# By RoadrunnerWMC
# Early hacky version, but works well enough. You'll need to modify
# hardcoded file paths to use it; sorry.
#
# "But in all honesty I doubt someone will ever bother making such a tool."
# Sounds like a challenge.
import random
import struct
class Object:
x = y = None
color = None
POINTS = []
def __init__(self, x=0, y=0):
self.x, self.y = x, y
def contains(self, x, y):
return (x, y) in self.coords()
def coords(self):
return [(self.x + x, self.y + y) for x, y in self.POINTS]
def touching(self):
for y in range(-1, self.height() + 2):
for x in range(-1, self.width() + 2):
if (x, y) in self.POINTS: continue
touching = False
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if (x + dx, y + dy) in self.POINTS:
touching = True
break
if touching:
yield (self.x + x, self.y + y)
@classmethod
def area(cls):
return len(cls.POINTS)
@classmethod
def width(cls):
return max(x for x, y in cls.POINTS) + 1
@classmethod
def height(cls):
return max(y for x, y in cls.POINTS) + 1
def id(self):
return self.BASE_ID + self.color * 9
class Object1x1(Object):
BASE_ID = 1
POINTS = [(0, 0)]
class Object2x1(Object):
BASE_ID = 2
POINTS = [(0, 0), (1, 0)]
class Object1x2(Object):
BASE_ID = 3
POINTS = [(0, 0),
(0, 1)]
class Object2x2(Object):
BASE_ID = 4
POINTS = [(0, 0), (1, 0),
(0, 1), (1, 1)]
class Object3x3(Object):
BASE_ID = 5
POINTS = [(0, 0), (1, 0), (2, 0),
(0, 1), (1, 1), (2, 1),
(0, 2), (1, 2), (2, 2)]
class ObjectL1(Object):
BASE_ID = 6
POINTS = [(0, 0), (1, 0),
(0, 1)]
class ObjectL2(Object):
def __init__(self, x, y):
# We want this one to be able to hug an existing corner if there
# is one. Therefore, we will place it one tile higher than
# requested. Kind of hacky? Yes. Works? Yes.
super().__init__(x, y - 1)
BASE_ID = 7
POINTS = [ (1, 0),
(0, 1), (1, 1)]
class ObjectL3(Object):
BASE_ID = 8
POINTS = [(0, 0), (1, 0),
(1, 1)]
class ObjectL4(Object):
BASE_ID = 9
POINTS = [(0, 0),
(0, 1), (1, 1)]
OBJECTS = [Object1x1, Object1x2, Object2x1, Object2x2, Object3x3, ObjectL1, ObjectL2, ObjectL3, ObjectL4]
def doThing():
objectList = []
def nextPosition():
for x in range(minX, maxX + 1):
for y in range(minY, maxY + 2):
# While, not if, because we might need to fill this up again momentarily
while not positionOccupied(x, y):
yield x, y
filledPoss = set()
with open('/home/user/course1_bgdatL1-pattern.bin', 'rb') as f:
inpd = f.read()
minX, maxX, minY, maxY = 9999, -1, 9999, -1
validPoss = set(); i = 0
while i < len(inpd) - 2:
type, x, y, w, h = struct.unpack_from('>5H', inpd, i)
if x < minX: minX = x
if y < minY: minY = y
if x + w > maxX: maxX = x
if y + h > maxY: maxY = y
for x1 in range(w):
for y1 in range(h):
validPoss.add((x + x1, y + y1))
i += 10
def positionOccupied(x, y):
if (x, y) not in validPoss: return True
if (x, y) in filledPoss: return True
for o in objectList:
if o.contains(x, y):
filledPoss.add((x, y))
return True
return False
OBJECTS.sort(reverse=True, key=lambda objCls: objCls.area())
temp = 0
for x, y in nextPosition():
fits = False
while not fits:
obj = random.choice(OBJECTS)(x, y)
fits = True
for x1, y1 in obj.coords():
if positionOccupied(x1, y1):
fits = False
break
# Choose a color randomly, but with colors the object is
# touching being less likely to be picked
colorCounts = {a: 0 for a in range(4)}
for obj1 in objectList:
for x1, y1 in obj.touching():
if (x1, y1) in obj1.coords():
colorCounts[obj1.color] += 1
break
maxColorsTouching = max(colorCounts.values())
weights = []
for i in range(4):
weight = 1 / (colorCounts[i] + 1)
weights.append(weight)
obj.color = random.choices(range(4), weights)[0]
objectList.insert(0, obj)
if x > temp:
temp = x
print(f'{temp} {len(objectList)}')
outp = bytearray(10 * len(objectList) + 2)
outp[-2] = 255; outp[-1] = 255
for i, o in enumerate(objectList):
idStuff = (2 << 12) | o.id()
outp[i * 10: i * 10 + 10] = struct.pack('>5H', idStuff, o.x, o.y, o.width(), o.height())
with open('course1_bgdatL1.bin', 'wb') as f:
f.write(outp)
doThing()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment