""" 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)