Created
January 3, 2017 06:37
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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