Browse Source

Added documentation, cleaned some code up.

master
Noëlle Anthony 6 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

@@ -1,4 +1,33 @@
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

class CellMap:
@@ -102,14 +131,17 @@ class CellMap:
self.__treasure = bool(treasure)

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()
for _ in range(self.reps):
self.smoothMap()
if self.treasure:
self.generateTreasure()
if self.out:
if self.treasure:
self.generateTreasure()
self.createImage()
else:
self.printScreen()
@@ -123,7 +155,8 @@ class CellMap:
def createMap(self):
""" Initializes an x by y grid.
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 self.__height == 0 or self.__width == 0 or self.__seed == 0:
@@ -143,7 +176,11 @@ class CellMap:
self.genmap = new_map

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:
print("The 'death' limit is currently {} and the 'birth' limit is {}.".format(self.death,self.birth))
@@ -181,6 +218,8 @@ class CellMap:
self.genmap = new_map

def countWalls(self, x, y):
""" Counts the number of wall neighbors a cell has and returns that count.
"""
count = 0
for j in range(-1,2):
for i in range(-1,2):
@@ -192,31 +231,58 @@ class CellMap:
# So we make this neighbor count as a wall.
count += 1
#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.
count += 1
return count

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 i in range(len(self.genmap[j])):
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):
""" 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"
path = " "
gold = "GG"
diam = "DD"
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()

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)
if self.chunky:
true_x, true_y = x*2, y*2
@@ -229,16 +295,31 @@ class CellMap:
# Paths are white by default
c_space = [255-x for x in c_wall]
c_gold = [(x+64)%255 for x in c_space]
c_diam = [(x+64)%255 for x in c_gold]
if self.chunky:
for line in self.genmap:
for _ in range(2):
for val in line:
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:
for line in self.genmap:
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)
if not os.path.exists("maps"):
os.makedirs("maps")
@@ -251,6 +332,10 @@ class CellMap:
print("Saved maps/{}.png".format(fn))

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")
for line in self.genmap:
print("\t{},".format(line))
@@ -258,6 +343,10 @@ class CellMap:


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"]
fn = []
for _ in range(16):
@@ -265,17 +354,20 @@ def filename():
return "".join(fn)

def parseArgs(args):
""" Parses the command-line arguments sent to the script.
Discards anything that isn't a recognized as a valid flag.
"""
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():
if flag in args:
@@ -285,6 +377,8 @@ def parseArgs(args):
flags["--color"] = True
elif flag == "--chunky":
flags["--chunky"] = True
elif flag == "--treasure":
flags["--treasure"] = True
else:
flags[flag] = args[args.index(flag) + 1]
return flags

Loading…
Cancel
Save