151 lines
5.4 KiB
Python
151 lines
5.4 KiB
Python
"""
|
|
Dominion
|
|
|
|
Tools for creating 2-dimensional dominions
|
|
"""
|
|
import random, pathlib
|
|
from matplotlib import pyplot as plt
|
|
import output
|
|
|
|
|
|
class Dominion:
|
|
"""
|
|
A dominion, which is a square array of entries with the property that every
|
|
2 by 2 subarray has at most two distinct entries. Higher-dimensional
|
|
analogues may be implemented in the future.
|
|
|
|
Attributes:
|
|
labels (frozenset): The labels which may appear as entries in the
|
|
dominion.
|
|
array (tuple of tuple): The array of entries belonging to the dominion.
|
|
"""
|
|
|
|
def __init__(self, labels, array):
|
|
"""
|
|
Create a dominion with a given array of labels.
|
|
|
|
Argument:
|
|
labels (iterable): The labels which may appear as entries in the
|
|
dominion.
|
|
array (iterable of iterable): The array of entries belonging to the
|
|
dominion.
|
|
"""
|
|
|
|
self.labels = frozenset(labels)
|
|
self.array = tuple(tuple(row) for row in array)
|
|
|
|
def show(self):
|
|
"""
|
|
Display a textual representation of the dominion in question.
|
|
"""
|
|
|
|
for row in self.array:
|
|
print(row)
|
|
|
|
def __repr__(self):
|
|
return "A Dominion of size {} with {} possible labels.".format(
|
|
len(self.array), len(self.labels))
|
|
|
|
def __str__(self):
|
|
labels = '{' + ', '.join(map(str, self.labels)) + '}'
|
|
return "A Dominion of size {} with labels from {}.".format(
|
|
len(self.array), labels)
|
|
|
|
def draw(self, color_map, filename):
|
|
"""
|
|
Render an image from a given dominion and color map.
|
|
|
|
Arguments:
|
|
color_map (string): The name of a color map.
|
|
filename (string): The name of the resulting file.
|
|
"""
|
|
|
|
plt.imsave(output.path + '//{}.png'.format(filename), \
|
|
self.array, cmap=color_map)
|
|
|
|
|
|
def new_row(row, labels, constraint_graph=None):
|
|
"""
|
|
Construct a new row for a dominion with a given collection of labels and a
|
|
graph constraining which labels can appear together.
|
|
|
|
Arguments:
|
|
row (tuple): A tuple of labels representing a row of a dominion.
|
|
labels (iterable): The pixel labels used in the dominion. The entries
|
|
of `row` should come from this.
|
|
constraint_graph (Graph): The graph determining which labels can appear
|
|
next to each other. The vertices of `constraint_graph` should be
|
|
the entries of `labels`. The default value `None` behaves as though
|
|
the graph is the complete graph on the vertex set whose members are
|
|
the entries of `labels'.
|
|
Returns:
|
|
tuple: A new row which is permitted to follow `row` in a dominion with
|
|
the given labels and constraints.
|
|
"""
|
|
|
|
partial_row = []
|
|
n = len(row)
|
|
for i in range(n):
|
|
if i == 0:
|
|
left_candidates = frozenset((row[0],))
|
|
right_candidates = frozenset((row[0], row[1]))
|
|
elif i == n - 1:
|
|
left_candidates = frozenset(
|
|
(row[n - 2], row[n - 1], partial_row[n - 2]))
|
|
right_candidates = frozenset((row[n - 1],))
|
|
else:
|
|
left_candidates = frozenset(
|
|
(row[i - 1], row[i], partial_row[i - 1]))
|
|
right_candidates = frozenset((row[i], row[i + 1]))
|
|
# If either side already has two candidates, we must choose from the
|
|
# intersection of the two sides.
|
|
candidates = left_candidates.intersection(right_candidates)
|
|
# Otherwise, it must be that both the left and right sides have only a
|
|
# single member. In this case, we may also choose an adjacent vertex on
|
|
# the constraint graph.
|
|
if len(left_candidates) == 1 and len(right_candidates) == 1:
|
|
if constraint_graph is None:
|
|
candidates = labels
|
|
else:
|
|
candidates = candidates.union(constraint_graph.neighbors(
|
|
tuple(candidates)[0]))
|
|
# Add a random candidate.
|
|
random_candidate = random.sample(list(candidates), 1)
|
|
partial_row += random_candidate
|
|
return tuple(partial_row)
|
|
|
|
|
|
def random_dominion(size, labels, constraint_graph=None):
|
|
"""
|
|
Create a random dominion given a size, collection of labels, and constraint
|
|
graph.
|
|
|
|
Arguments:
|
|
size (int): The number of rows (and columns) of the dominion.
|
|
labels (iterable): The pixel labels used in the dominion. The entries
|
|
of `row` should come from this.
|
|
constraint_graph (Graph): The graph determining which labels can appear
|
|
next to each other. The vertices of `constraint_graph` should be
|
|
the entries of `labels`. The default value `None` behaves as though
|
|
the graph is the complete graph on the vertex set whose members are
|
|
the entries of `labels'.
|
|
Returns:
|
|
Dominion: The randomly-generated dominion.
|
|
"""
|
|
|
|
partial_dominion = [[random.choice(labels)]]
|
|
for _ in range(size - 1):
|
|
if constraint_graph is None:
|
|
new_label = random.choice(labels)
|
|
else:
|
|
new_label = random.choice(
|
|
tuple(constraint_graph.neighbors(
|
|
partial_dominion[0][-1])) + (partial_dominion[0][-1],))
|
|
partial_dominion[0].append(new_label)
|
|
|
|
for _ in range(size - 1):
|
|
next_row = new_row(partial_dominion[-1], labels, constraint_graph)
|
|
partial_dominion.append(next_row)
|
|
|
|
return Dominion(labels, partial_dominion)
|