Browse Source

Added documentation, cleaned some code up.

master
Noëlle Anthony 7 years ago
parent
commit
14b211ca51
1 changed files with 121 additions and 27 deletions
  1. 121
    27
      CellMap.py

+ 121
- 27
CellMap.py View File

import random, sys, os
""" A tool for randomly generating maps.
It starts by populating a grid with randomized True/False values and
then uses a "cellular automata"-based smoothing algorithm to build a
map.

Maps are rectangles, but can be of any size. Naturally, larger maps
take longer to generate.

By default, the mapper will print to the screen as a grid of "I"s (walls)
and spaces (paths). You can tell the mapper to print to an image instead.
If you do, the following apply:

You can tell the mapper to make a map "chunky", which keeps the T/F
grid the same size but uses four pixels instead of one for each point
on the grid, doubling the size of the final generated image.

Maps are two-color: black and white by default, but it will use random
contrasting colors if told to.

You can tell the mapper to insert treasure, which appears as a third
color on the map.
"""

__author__ = "Noëlle Anthony"
__version__ = "1.0.0"

import random
import sys
import os

from PIL import Image from PIL import Image


class CellMap: class CellMap:
self.__treasure = bool(treasure) self.__treasure = bool(treasure)


def generateFullMap(self): def generateFullMap(self):
""" Puts everything together.
""" Puts everything together. Runs the smoothing routine a number
of times equal to self.reps, generates treasure (if self.treasure
is set), and creates and saves an image of the map if self.out is
set or prints the map to stdout if it isn't.
""" """
self.createMap() self.createMap()
for _ in range(self.reps): for _ in range(self.reps):
self.smoothMap() self.smoothMap()
if self.treasure:
self.generateTreasure()
if self.out: if self.out:
if self.treasure:
self.generateTreasure()
self.createImage() self.createImage()
else: else:
self.printScreen() self.printScreen()
def createMap(self): def createMap(self):
""" Initializes an x by y grid. """ Initializes an x by y grid.
x is width, y is height x is width, y is height
seed is the chance that a given cell will be "live" and should be an integer between 1-99.
seed is the chance that a given cell will be "live" and should be
an integer between 1-99.
If True is equivalent to "wall", then higher seeds make more walls. If True is equivalent to "wall", then higher seeds make more walls.
""" """
if self.__height == 0 or self.__width == 0 or self.__seed == 0: if self.__height == 0 or self.__width == 0 or self.__seed == 0:
self.genmap = new_map self.genmap = new_map


def smoothMap(self): def smoothMap(self):
""" Refines the grid.
""" Refines the grid using cellular-automaton rules.
If a wall doesn't have enough wall neighbors, it "dies" and
becomes a path. If a path has too many wall neighbors, it turns
into a wall. This is controlled by the values in self.death and
self.birth, respectively.
""" """
if self.death == 0 or self.birth == 0: if self.death == 0 or self.birth == 0:
print("The 'death' limit is currently {} and the 'birth' limit is {}.".format(self.death,self.birth)) print("The 'death' limit is currently {} and the 'birth' limit is {}.".format(self.death,self.birth))
self.genmap = new_map self.genmap = new_map


def countWalls(self, x, y): def countWalls(self, x, y):
""" Counts the number of wall neighbors a cell has and returns that count.
"""
count = 0 count = 0
for j in range(-1,2): for j in range(-1,2):
for i in range(-1,2): for i in range(-1,2):
# So we make this neighbor count as a wall. # So we make this neighbor count as a wall.
count += 1 count += 1
#pass #pass
elif self.genmap[n_y][n_x] and self.genmap[n_y][n_x] != "Gold":
elif self.genmap[n_y][n_x] and self.genmap[n_y][n_x] not in ("Gold","Diam"):
# This neighbor is on the map and is a wall. # This neighbor is on the map and is a wall.
count += 1 count += 1
return count return count


def generateTreasure(self): def generateTreasure(self):
self.treasurelist = []
walledin = False
""" If a path cell has 5 wall neighbors, put a treasure there.
If a path cell has at least 6 wall neighbors, put a rare treasure.
"""
for j in range(len(self.genmap)): for j in range(len(self.genmap)):
for i in range(len(self.genmap[j])): for i in range(len(self.genmap[j])):
if not self.genmap[j][i]: if not self.genmap[j][i]:
walledin = True if self.countWalls(i,j) >= 5 else False
if walledin:
self.genmap[j][i] = "Gold"
walledin = False

self.genmap[j][i] = "Gold" if self.countWalls(i,j) == 5 else self.genmap[j][i]
self.genmap[j][i] = "Diam" if self.countWalls(i,j) >= 6 else self.genmap[j][i]
def printScreen(self): def printScreen(self):
""" Prints the map to standard out, using "II" for a wall
and " " for a path.

The "color", "chunky", and "treasure" options don't affect
this mode.
"""
wall = "II" wall = "II"
path = " " path = " "
gold = "GG" gold = "GG"
diam = "DD"
for line in self.genmap: for line in self.genmap:
print("".join([path if not x else (gold if x == "Gold" else wall) for x in line]))
print("".join([path if not x
else (gold if x == "Gold"
else (diam if x == "Diam"
else wall)) for x in line]))
print() print()


def createImage(self): def createImage(self):
""" Creates and saves an image of the map.

If self.color is True, the map uses randomized complementary
colors; otherwise, it uses black for walls, white for paths, and
light grey for treasures.

If self.chunky is True, the map uses 4 pixels for each cell
instead of one. This results in an image that's twice as large,
and is useful for enlarging smaller maps without the added runtime
of actually generating a larger map.

If an image with the current map's name already exists, the script
will add digits after the filename but before the extension, to
avoid a collision. While the possibility of a name collision is
low, this allows you to make several copies of a given map (for
example, with different settings) without fear of overwriting
your previous maps.
"""
x, y = len(self.genmap[0]), len(self.genmap) x, y = len(self.genmap[0]), len(self.genmap)
if self.chunky: if self.chunky:
true_x, true_y = x*2, y*2 true_x, true_y = x*2, y*2
# Paths are white by default # Paths are white by default
c_space = [255-x for x in c_wall] c_space = [255-x for x in c_wall]
c_gold = [(x+64)%255 for x in c_space] c_gold = [(x+64)%255 for x in c_space]
c_diam = [(x+64)%255 for x in c_gold]
if self.chunky: if self.chunky:
for line in self.genmap: for line in self.genmap:
for _ in range(2): for _ in range(2):
for val in line: for val in line:
for _ in range(2): for _ in range(2):
lst.append(tuple(c_space) if not val else (tuple(c_gold) if val == "Gold" else tuple(c_wall)))
if not val:
lst.append(tuple(c_space))
elif val == "Gold":
lst.append(tuple(c_gold))
elif val == "Diam":
lst.append(tuple(c_diam))
else:
lst.append(tuple(c_wall))
else: else:
for line in self.genmap: for line in self.genmap:
for val in line: for val in line:
lst.append(tuple(c_space) if not val else (tuple(c_gold) if val == "Gold" else tuple(c_wall)))
if not val:
lst.append(tuple(c_space))
elif val == "Gold":
lst.append(tuple(c_gold))
elif val == "Diam":
lst.append(tuple(c_diam))
else:
lst.append(tuple(c_wall))
img.putdata(lst) img.putdata(lst)
if not os.path.exists("maps"): if not os.path.exists("maps"):
os.makedirs("maps") os.makedirs("maps")
print("Saved maps/{}.png".format(fn)) print("Saved maps/{}.png".format(fn))


def printArray(self): def printArray(self):
""" This prints the map as a list of lists of True/False values,
possibly useful for importing into other scripts or for uses
other than generating maps.
"""
print("[",end="\n") print("[",end="\n")
for line in self.genmap: for line in self.genmap:
print("\t{},".format(line)) print("\t{},".format(line))




def filename(): def filename():
""" Creates a 16-character hexadecimal ID.
Since the number of results is so large (16^16), the chance of
a collision is very small.
"""
hexes = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"] hexes = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
fn = [] fn = []
for _ in range(16): for _ in range(16):
return "".join(fn) return "".join(fn)


def parseArgs(args): def parseArgs(args):
""" Parses the command-line arguments sent to the script.
Discards anything that isn't a recognized as a valid flag.
"""
flags = { flags = {
"--height" : 20,
"--width" : 20,
"--seed" : 45,
"--death" : 4,
"--birth" : 4,
"--reps" : 2,
"--out" : False,
"--color" : False,
"--chunky" : False,
"--treas" : False,
"--height" : 20,
"--width" : 20,
"--seed" : 45,
"--death" : 4,
"--birth" : 4,
"--reps" : 2,
"--out" : False,
"--color" : False,
"--chunky" : False,
"--treasure": False,
} }
for flag, default in flags.items(): for flag, default in flags.items():
if flag in args: if flag in args:
flags["--color"] = True flags["--color"] = True
elif flag == "--chunky": elif flag == "--chunky":
flags["--chunky"] = True flags["--chunky"] = True
elif flag == "--treasure":
flags["--treasure"] = True
else: else:
flags[flag] = args[args.index(flag) + 1] flags[flag] = args[args.index(flag) + 1]
return flags return flags

Loading…
Cancel
Save