It is not written with the intent to be of general purpose or a masterpiece of programming, but instead as a pragmatic tool that is more practical than generating the illustrations manually with GIMP.
Code: Select all
# Copyright (C) 2017 Mario Castelán Castro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Instructions:
#
# Run this program with Python 3 and no command line arguments. A set
# of PPM files will be generated, one for each cell environment that
# can be specified for non-totalistic rules in the Hensel notation.
#
# These files can then be converted to PNG. I used optipng
# <http://optipng.sourceforge.net/>.
nhds = {
"0": " | X | ",
"1c":"- | X | ",
"1e":" - | X | ",
"2c":"- -| X | ",
"2e":" - |-X | ",
"2k":" - | X |- ",
"2a":"-- | X | ",
"2i":" - | X | - ",
"2n":"- | X | -",
"3c":"- -| X | -",
"3e":" - |-X | - ",
"3k":" - |-X | -",
"3a":"-- |-X | ",
"3i":"- |-X |- ",
"3n":"- |-X | -",
"3y":"- -| X | - ",
"3q":"- | X | --",
"3j":" |-X | --",
"3r":" - | X | --",
"4c":"- -| X |- -",
"4e":" - |-X-| - ",
"4k":"- -|-X | - ",
"4a":"- |-X |-- ",
"4i":"- -|-X-| ",
"4n":"- |-X |- -",
"4y":"- -| X-|- ",
"4q":" --| X-|- ",
"4j":" - |-X-|- ",
"4r":" |-X-|-- ",
"4t":"---| X | - ",
"4w":"- |-X | --",
"4z":"-- | X | --"}
def parse(string):
result = 0
error_str = "Bad environment specifier."
if len(string) != 11:
raise Exception(error_str)
# Check fixed characters
for (pos, char) in [(3, "|"), (5, "X"), (7, "|")]:
if string[pos] != char:
raise Exception(error_str)
# Check for variable characters
for (i, pos) in zip(range(8), [0, 1, 2, 4, 6, 8, 9, 10]):
if string[pos] == " ":
pass
elif string[pos] == "-":
result = result | 1 << i
else:
raise Exception(error_str)
result = result & 0xf | (result & 0xf0) << 1
return result
def check_non_negative_integer(*l):
for x in l:
if not isinstance(x, int) or x < 0:
raise Exception("Not a non-negative integer.")
def get_bit(cell_env, x, y):
if x >= 3 or x < 0 or y >= 3 or y < 0:
return 0
else:
index = 3 * y + x
return 1 & cell_env >> index
def count_bits(x):
check_non_negative_integer(x)
bits = 0
while x != 0:
x = x & x - 1
bits += 1
return bits
def strides(initial, stride, count):
return range(initial, initial + count * stride, stride)
def set_strides(output, offset, input, repeats, stride_step, stride_count):
check_non_negative_integer(offset, repeats, stride_step, stride_count)
if offset + stride_step * (stride_count - 1) + len(input) * repeats > len(output):
raise Exception("Output bytearray too small.")
for stride_offset in strides(offset, stride_step, stride_count):
for i in strides(stride_offset, len(input), repeats):
output[i:i + len(input)] = input
def write_ppm(cell_env,
file_name,
cell_size = 16,
# Colors
color_live = ( 0, 0, 0),
color_dead = (255, 255, 255),
color_marked = (192, 239, 192),
color_grid = (192, 192, 192),
color_grid_marked = ( 82, 192, 82)):
file = open(file_name, "wb")
img_side_len = cell_size * 3 + 1
header = "P6 {0} {0} 255 ".format(img_side_len).encode()
header_size = len(header)
img_total_size = header_size + 3 * img_side_len ** 2
img_raw = bytearray(img_total_size)
img_raw[0:header_size] = header
color = None
# We divide the image in an irregular grid of size 7×7. There is
# one element of this grid for each interior of cell, horizontal
# border segment, vertical border segment, and intersection.
for j in range (7):
for i in range (7):
x = i // 2
y = j // 2
# i and j are coordinates of the irregular 7×7 grid just
# described. x and y are coordinates of a cell.
offset_x = cell_size * x + i % 2
offset_y = cell_size * y + j % 2
if (i % 2) == 1 and (j % 2) == 1:
# This element is the interior of a cell
width = cell_size - 1
height = cell_size - 1
if x == 1 and y == 1:
color = color_live
elif get_bit(cell_env, x, y) == 1:
color = color_marked
else:
color = color_dead
else:
# This element is part of the grid
width = 1 if i % 2 == 0 else cell_size - 1
height = 1 if j % 2 == 0 else cell_size - 1
is_marked = get_bit(cell_env, x, y)
if i % 2 == 0:
is_marked = is_marked | get_bit(cell_env, x - 1, y)
if j % 2 == 0:
is_marked = is_marked | get_bit(cell_env, x, y - 1)
if i % 2 == 0 and j % 2 == 0:
is_marked = is_marked | get_bit(cell_env, x - 1, y - 1)
if is_marked == 1:
color = color_grid_marked
else:
color = color_grid
offset = header_size + 3 * (img_side_len * offset_y + offset_x)
# print("({}, {}): ({}, {}) {}".format(i, j, width, height, color))
# Fill the rectangle
set_strides(img_raw, offset, color, width, 3 * img_side_len, height)
file.write(img_raw)
file.close()
def gen_files():
for string in nhds:
if not (string != "0" or string != "8") \
and (len(string) != 2 \
or not (ord("1") <= ord(string[0]) <= ord("7")) \
or "cekainyqjrtwz".find(string[1]) == -1):
print(string)
raise Exception("Bad cell environment name.")
n = int(string[0])
cell_env = parse (nhds[string])
if n != count_bits(cell_env):
raise Exception("Bad number of bits.")
write_ppm(cell_env, "{}.ppm".format(string))
if n < 4:
if n == 0:
cell_env_name = "8.ppm"
else:
cell_env_name = "{}{}.ppm".format(8 - n, string[1])
write_ppm(0b111101111 & ~cell_env, cell_env_name)
gen_files()