0% found this document useful (0 votes)
64 views

Elementary Cellular Automata

The document describes elementary cellular automata (ECA) and how they can be encoded as integers between 0 and 255. It provides details on: 1. How an ECA determines the next state of pixels based on sequences of 3 pixels. There are 256 possible ECA based on 28 combinations. 2. A coding scheme that maps each ECA to a unique integer where the binary representation of the integer encodes the pixel update rules. 3. How the code will draw pictures representing the behavior of different ECA based on this mapping, starting from an initial pattern and applying the pixel update rules repeatedly.

Uploaded by

Duan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
64 views

Elementary Cellular Automata

The document describes elementary cellular automata (ECA) and how they can be encoded as integers between 0 and 255. It provides details on: 1. How an ECA determines the next state of pixels based on sequences of 3 pixels. There are 256 possible ECA based on 28 combinations. 2. A coding scheme that maps each ECA to a unique integer where the binary representation of the integer encodes the pixel update rules. 3. How the code will draw pictures representing the behavior of different ECA based on this mapping, starting from an initial pattern and applying the pixel update rules repeatedly.

Uploaded by

Duan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

基本元胞自动机(ECA)的构成顺序取决于为3个连续像素(例如a、b和c)他们中的每一个都是黑色或者白色

(1或0)。b下面的像素是黑色或者白色的。使用这3个像素,有两种可能的表现形式对于2^3个排列顺序,
因此有2的2次方的3次方=256种基本细胞自动机。
基于以下编码,256个ECA可以与小于256的256个自然数一一对应方案,
让E是小于256的自然数。设E=e7e6e5e4e3e2e1e0是以基数2表示的8位数字。对于所有小于8的自然数P,
设P=p2p1p0为在基数2中表示为3位数的这个数。然后E对ECA进行编码,
使得对于所有P<8,P中间像素以下的像素应为ep。例如

Elementary cellular automata


Eric Martin, CSE, UNSW
COMP9021 Principles of Programming

An elementary cellular automaton (ECA) determines for each possible sequence of 3 consecutive
pixels, say a, b and c, each of which is either black or white (1 or 0), whether the pixel below b
should be black or white. That is 2 possible outcomes for each of the 23 possible sequences of 3
pixels, hence there are 22 = 256 elementary cellular automata. The 256 ECA’s can be put in
3

one-to-one correspondence with the 256 natural numbers smaller than 256 based on the following
coding scheme.
Let E be a natural number smaller than 256. Let E b = e7 e6 e5 e4 e3 e2 e1 e0 be this number represented
in base 2 as an 8 bit number. For all natural numbers P smaller than 8, let Pe = p2 p1 p0 be this
number represented in base 2 as a 3 bit number. Then E encodes the ECA such that for all P < 8,
the pixel below the middle pixel of Pe should be eP . For instance:
• b
0 = 00000000, so 0 encodes the following ECA:

111 110 101 100 011 010 001 000


0 0 0 0 0 0 0 0

• 90
b = 01011010, so 90 encodes the following ECA:

111 110 101 100 011 010 001 000


0 1 0 1 1 0 1 0

• 255
d = 11111111, so 255 encodes the following ECA:

111 110 101 100 011 010 001 000


1 1 1 1 1 1 1 1

We talk about “rule E” to refer to the ECA mapped to E by this correspondence.


For a better visualisation, let us represent Rule 90 using black and white squares instead of 1’s and
0’s:
       
       
There are two standard ways to consider the workings of an ECA:
• start with a random sequence of black and white pixels, infinite on both sides, or
• start with a unique black pixel and on both sides, an infinite sequence of white pixels.
The program elementary_cellular_automata.py creates a widget that has features for both
workings; here we consider the second workings only. In any case, the conditions imposed by an
ECA fully determine the infinite sequence of pixels l2 below an infinite sequence of pixels l1 , and
then fully determine the infinite sequence of pixels l3 below l2 , and then fully determine the infinite

1
有两种标准方法可以考虑ECA的工作:
-从黑白像素的随机序列开始,两边无限,或者-从一个独特的黑色像素开始,
两边是无限的白色序列像素程序基本单元_自动机.py创建一个具有两个加工特性的小部件;
这里我们只考虑第二个加工。在任何情况下,ECA施加的条件完全确定像素l1的无限序列下的像素l2,
然后完全确定l2下的像素l3的无限序列,然后完全确定l3下的像素l4的无限序列…例如,根据规则90,
sequence of pixels l4 below l3 … For instance, with Rule 90, the first 8 sequences are as follows (all
前8个序列如下(全部8行两侧未显示的像素为白色):
pixels that are not shown on both sides of all 8 lines are white):
很明显,这个过程产生的图像是一个圆锥体。更准确地说,使用规则E并按照上面的E=e7e6e4e3e2e1e0编写,
-如果e0=0,则圆锥体周围的所有像素均为白色; 
-如果e0=1和e7=1,则圆锥体周围的所有像素均为黑色(当然第一行除外); 
-如果e0=1和e7=0,则圆锥体周围的连续线在全白和全黑之间交替。 

我们的目标是编写代码来绘制与上面类似的图片,对于任何ECA,编码为0到255之间的整数

(小部件也接受8个连续的0和1)。为了捕获编码的ECA,我们首先定义一个函数decoded_rule(),

该函数的参数是一个自然数E小于256的整数,并返回一个字典,该字典的键是0和1的三倍,关联值是0或1,

由规则E决定。例如,使用规则90,字典将是

{(1,1,1):0,(1,1,0):1,(1,0,1):0,(1,0,0):1,(0,1,1):1,
(0,1,0):0,(0,0,1):1,(0,0,0):0}。
It is clear that the picture that results from this process is a cone. More precisely, working with
rule E and writing as above E
b = e7 e6 e5 e4 e3 e2 e1 e0 ,

• if e0 = 0 then all all pixels around the cone are white;


• if e0 = 1 and e7 = 1 then all all pixels around the cone are black (except for the first line of
course);
• if e0 = 1 and e7 = 0 then successive lines around the cone alternate between all white and all
black.
Our aim is to write code to draw a similar kind of picture as the one above, for any ECA, encoded
as an integer between 0 and 255 (the widget also accepts 8 consecutive 0’s and 1’s). To capture
the encoded ECA, we first define a function, decoded_rule(), meant to take an integer whose
value is a natural number E smaller than 256 as argument and return a dictionary whose keys are
triples of 0’s and 1’s with an associated value of 0 or 1 as determined by rule E. For instance,
with rule 90, the dictionary would be {(1, 1, 1): 0, (1, 1, 0): 1, (1, 0, 1): 0, (1, 0,
0): 1, (0, 1, 1): 1, (0, 1, 0): 0, (0, 0, 1): 1, (0, 0, 0): 0}.
An integer can be represented as a string in any of bases 2, 8, 10 (the default), or 16, with two
variants for base 16 to use either lowercase or uppercase letters for the “digits” 10 up to 15:
[1]: # b: binary
# o: octal
# x or X: hexadecimal
f'90', f'{90:b}', f'{90:o}', f'{90:x}', f'{90:X}'
b将数字转换为2进制,o转换为8进制,X或x转换为16进制
[1]: ('90', '1011010', '132', '5a', '5A')

Formatting allows one to possibly pad either spaces or 0s to the left of the integer representation
to make sure the field width has a minimal value:
[2]: # A field width of 6 at least, padding with spaces if needed
f'{90:6}', f'{90:6b}', f'{90:6o}', f'{90:6x}', f'{90:6X}' :后面是占位符宽度
# A field width of 6 at least, padding with 0s if needed
f'{90:06}', f'{90:06b}', f'{90:06o}', f'{90:06x}', f'{90:06X}'

[2]: (' 90', '1011010', ' 132', ' 5a', ' 5A')

[2]: ('000090', '1011010', '000132', '00005a', '00005A')

2
When formatting integers, padding is to the left, which corresponds to a right aligningment. This is
also the default when formatting floating point numbers, whereas strings are left aligned by default.
In any case, we can use <, ^ or > just after : in the format specification to left align, centre align or
right align the value, respectively. Moreover, 0 can be used in the format specifier in combination
with <, ^ or > when formatting integers, floating point numbers or strings, but without <, ^ or >
only when formatting integers or floating point numbers as it implies that the padding 0s, if any,
are inserted to the right of the + or - sign, if any, which makes sense for numbers but not for strings:
[3]: # A field width of 8 at least, padding with spaces if needed,
# with a left, centre, right or default alignment
f'{-90:<8}', f'{-90:^8}', f'{-90:>8}', f'{-90:8}' >右对齐
f'{-90.:<8}', f'{-90.:^8}', f'{-90.:>8}', f'{-90.:8}' <左对齐
f'{"-90":<8}', f'{"-90":^8}', f'{"-90":>8}', f'{"-90":8}' ^中间对其

# A field width of 8 at least, padding with 0s if needed,


# with a left, centre, right or default alignment, the latter
# being applicable only when formatting numbers as the 0 format
# specifier then requires that the 0s be padded to the right of
# the + or - sign, if any.
f'{-90:<08}', f'{-90:^08}', f'{-90:>08}', f'{-90:08}'
f'{-90.:<08}', f'{-90.:^08}', f'{-90.:>08}', f'{-90.:08}'
f'{"-90":<08}', f'{"-90":^08}', f'{"-90":>08}' # f'{"-90":08}' is illegal

[3]: ('-90 ', ' -90 ', ' -90', ' -90')

[3]: ('-90.0 ', ' -90.0 ', ' -90.0', ' -90.0')

[3]: ('-90 ', ' -90 ', ' -90', '-90 ')

[3]: ('-9000000', '00-90000', '00000-90', '-0000090')

[3]: ('-90.0000', '0-90.000', '000-90.0', '-00090.0')

[3]: ('-9000000', '00-90000', '00000-90')


因此,定义规则的8位列表很容易获得,方法是在列表理解中将规则编号格式化为二进制,字段宽度为8:
So the list of 8 bits that define a rule is easy to get by formatting the rule number in binary with
a field width of 8 within a list comprehension:
[4]: [int(d) for d in f'{90:08b}']

[4]: [0, 1, 0, 1, 1, 0, 1, 0]
要生成密钥,我们可以使用相同的技术,首先格式化所有小于8的二进制自然数,字段宽度为3:
To generate the keys, we could use the same technique, first formatting all natural numbers smaller
than 8 in binary with a field width of 3:
[5]: for i in range(8):
print(f'{i:03b}')

3
000
001
010
011
100
101
110
111
Getting a string of characters from a number, and then a list of digits from the string, is not the
best approach. Note that if n is a natural number, then integer division of n by 10 shifts all digits
in the decimal representation of n by one, “losing” the rightmost one in the process, equal to n
modulo 10.
A syntactic digression is necessary to properly read the code fragment that follows. An identifier
can start with an underscore, and it can even just consist of an underscore. It is good practice to
use _ in a statement of the form for _ in range(n): ... to indicate that the code loops n many
times, as opposed to a statement of the form for i in range(n): ... where all values between
0 and n minus 1 are generated and assigned to i, which is then used in one way or another in the
body of the loop. We make use of this convention to illustrate the previous observation:
[6]: n = 21078
print(n); print()
for _ in range(7):
n, d = divmod(n, 10)
print(n, d)

21078

2107 8
210 7
21 0
2 1
0 2
0 0
0 0
More generally, if n and k are natural numbers, then dividing n by 10k shifts all digits in the
decimal representation of n by k, “losing” the k rightmost ones in the process, which make up the
number n modulo 10k :
[7]: n = 16503421078003459
print(n); print()
for _ in range(7):
n, d = divmod(n, 1_000)
print(n, d)

16503421078003459

16503421078003 459

4
16503421078 3
16503421 78
16503 421
16 503
0 16
0 0
Similarly, if n is a natural number, then integer division of n by 2 shifts all digits in the binary
representation of n by one, “losing” the rightmost one in the process, equal to n modulo 2:
[8]: n = 214
print(f'{n:b}'); print()
for _ in range(9):
n, d = divmod(n, 2)
print(f'{n:b} {d:b}')

11010110

1101011 0
110101 1
11010 1
1101 0
110 1
11 0
1 1
0 1
0 0
More generally, if n and k are natural numbers, then dividing n by 2k shifts all digits in the binary
representation of n by k, “losing” the k rightmost ones in the process, which make up the number
n modulo 2k :
[9]: n = 2345678
print(f'{n:b}'); print()
for _ in range(9):
n, d = divmod(n, 8)
print(f'{n:b} {d:b}')

1000111100101011001110

1000111100101011001 110
1000111100101011 1
1000111100101 11
1000111100 101
1000111 100
1000 111
1 0
0 1
0 0

5
So the keys of the dictionary that decoded_rule() should return can be generated as follows:
[10]: for p in range(8):
p // 4, p // 2 % 2, p % 2

[10]: (0, 0, 0)

[10]: (0, 0, 1)

[10]: (0, 1, 0)

[10]: (0, 1, 1)

[10]: (1, 0, 0)

[10]: (1, 0, 1)

[10]: (1, 1, 0)

[10]: (1, 1, 1)

Putting it all together, with the help of a dictionary comprehension:


[11]: def record_rule(E):
values = [int(d) for d in f'{E:08b}'] 先把90转化为2进制
return {(p // 4, p // 2 % 2, p % 2): values[7 - p] for p in range(8)}

# As Rule 90 is symmetric, had we written values[p]


# instead of values[7 - p], we would not see the mistake.
record_rule(90)
record_rule(41)

[11]: {(0, 0, 0): 0,


(0, 0, 1): 1,
(0, 1, 0): 0,
(0, 1, 1): 1,
(1, 0, 0): 1,
(1, 0, 1): 0,
(1, 1, 0): 1,
(1, 1, 1): 0}

[11]: {(0, 0, 0): 1,


(0, 0, 1): 0,
(0, 1, 0): 0,
(0, 1, 1): 1,
(1, 0, 0): 0,
(1, 0, 1): 1,
(1, 1, 0): 0,

6
(1, 1, 1): 0}

Rather than displaying lines of 0’s and 1’s, it is preferable to take advantage of the Unicode character
set and instead, display lines of white and black squares. The Unicode character set considerably
extends the ASCII character set. A Unicode character has a code point, a natural number which
when it is smaller than 128, is the ASCII code of an ASCII character. The ord() function returns
the code point of the (string consisting of the unique) character provided as argument:

[12]: ord('+')
ord('⬛')
ord('😋') 返回字符的ASCI码

[12]: 43

[12]: 11035

[12]: 128523

Conversely, the chr() function takes an integer whose value is a natural number n as argument
and returns the character with n as code point:
[13]: chr(43)
chr(11035)
chr(128523) 返回值是当前整数对应的ASCII字符
[13]: '+'

[13]: '⬛'

[13]: '😋'

Code points are more often represented in base 16. More generally, integer literals can use either
binary, octal, decimal, or hexadecimal representations:
[14]: # 0b, 0o, and either 0x or 0X, are prefixes
# for base 2, 8, and 16, respectively.
# 43 in base 2, 8, 10, and 16
0b101011, 0o53, 43, 0X2b
# 11035 in base 2, 8, 19, and 16
0b10101100011011, 0o25433, 11035, 0x2b1b
# 128523 in base 2, 8, 19, and 16
0b11111011000001011, 0o373013, 128523, 0x1F60B

[14]: (43, 43, 43, 43)

[14]: (11035, 11035, 11035, 11035)

[14]: (128523, 128523, 128523, 128523)

7
When written in base 16, code points are at most 8 hexadecimal digits long. A character whose
code point has at least 5 hexadecimal digits has one Unicode string representation that starts with
\U, followed by 8 hexadecimal digits (leading 0’s are used when needed):

[15]: '\U0001f60b'

[15]: '😋'

A character whose code point has at most 4 hexadecimal digits has two Unicode string represen-
tations; one that starts with \u followed by 4 hexadecimal digits, one that starts with \U followed
by 8 hexadecimal digits (in both cases, leading 0’s are used when needed):

[16]: '\u002B', '\U0000002B'


'\u2b1b', '\U00002b1b'

[16]: ('+', '+')

[16]: ('⬛', '⬛')

String representations can also start with \N followed by the character name (all of whose letters
can be either lowercase or uppercase) surrounded by curly braces:

[17]: '\N{plus sign}'


'\N{Black Large Square}'
'\N{FACE SAVOURING DELICIOUS FOOD}'

[17]: '+'

[17]: '⬛'

[17]: '😋'

Recall that we want to draw a segment of a line l determined by the workings of an ECA, start-
ing with a line with a single black pixel and infinitely many white pixels on both sides. We
now define a function, display_line(), that can fill this purpose. There are three arguments to
display_line():
• The first argument is denoted by bit_sequence and meant to take as value a tuple that
represents the pixels on that part of l that intersects the cone determined by the workings
of the ECA. For instance, with Rule 90, for the first four lines, bit_sequence takes the
values (1,), (1, 0, 1), (1, 0, 0, 0, 1), (1, 0, 0, 0, 1) and (1, 0, 1, 0, 1, 0, 1),
respectively.
• The second argument is denoted by end_bit and meant to take the value 0 or 1 and represent
the pixel outside the cone on l. For instance, with Rule 90, it is 0.
• The third argument is denoted by nb_of_end_bits and meant to take as value a natural
number, possibly equal to 0, that represents the number of times we want to display the pixel
outside the cone on l, on each side.
display_line() makes use of an auxiliary function to display the pixel outside the cone; it calls it
twice, one for each side of the cone. It also makes use of the fact that the unicode strings '\u2b1c'

8
and '\u2b1b' depict white and black squares, respectively:
[18]: def display_end_squares(end_bit, nb_of_end_bits):
print(end_bit * nb_of_end_bits, end='')

def display_line(bit_sequence, end_bit, nb_of_end_bits):


squares = {0: '\u2b1c', 1: '\u2b1b'}
display_end_squares(squares[end_bit], nb_of_end_bits)
print(''.join(squares[b] for b in bit_sequence), end='')
display_end_squares(squares[end_bit], nb_of_end_bits)
print()

display_line((1, 0, 1, 0, 1, 0, 1), 0, 4)
display_line((1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1), 0, 0)

⬜⬜⬜⬜⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬜
⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛
With record_rule() and display_line() in hand, not much is left to complete our task of
drawing the segments of the first few lines of pixels as determined by the workings of an ECA
starting with a line consisting of nothing but white pixels, with the exception of a single black
pixel. The function display_ECA() takes two arguments. The first one is denoted by rule_nb and
meant to take as value the natural number smaller than 256 that encodes the ECA we want to work
with. The second one is denoted by size and meant to take as value the number of white pixels to
display on both sides of the black pixel in the middle of the first line segment; hence the first line
segment consists of 2 * size + 1 many pixels. The function display_ECA() will draw size + 1
many line segments: that way, the last line segment will span from left to right boundaries of the
cone, whereas the penultimate line segment will have one pixel outside the cone on both sides, the
second last line segment will have two pixels outside the cone on both sides, etc.
At any stage, new_line will denote a tuple representing the sequence of pixels that make up a given
line segment spanning from left to right boundaries of the cone, and end_bit will represent the
pixel outside the cone (always equal to 0 for Rule 90). In order to determine the next line segment
from the current one, we add two copies of end_bit at the beginning of new_line, and two copies
of end_bit at the end, making up current_line. So:
• current_line[1], current_line[2] and current_line[3] evaluate to the pixel outside the
cone for the first two, and the pixel on the left boundary of the cone for the third one, and
determine the pixel on the left boundary of the cone on the next line.
• current_line[-3], current_line[-2] and current_line[-1] evaluate to the pixel outside
the cone for the last two, and the pixel on the right boundary of the cone for the first one,
and determine the pixel on the right boundary of the cone on the next line.
• As end_bit evaluates to the pixel outside the cone, (end_bit, end_bit, end_bit) deter-
mines the pixel outside the cone on the next line.
[19]: def display_ECA(rule_nb, size):
bit_below = record_rule(rule_nb)
new_line = [1]
end_bit = 0
display_line(new_line, end_bit, size)

9
for n in range(size):
current_line = [end_bit] * 2 + new_line + [end_bit] * 2
new_line = [bit_below[current_line[i], current_line[i + 1],
current_line[i + 2]
] for i in range(len(current_line) - 2)
]
end_bit = bit_below[end_bit, end_bit, end_bit]
display_line(new_line, end_bit, size - n - 1)

Rule 90 is an example where the outside of the cone consists of nothing but white pixels:
[20]: display_ECA(90, 7)

⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬜
⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜
⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜
⬜⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜
⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛
Rule 107 is an example where outside the cone, black and white half-infinite lines alternate:
[21]: display_ECA(107, 12)

⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬜⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬜⬛⬛⬜⬛⬛⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬜⬜⬜⬜⬜⬛⬛⬛⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬛⬛⬜⬜⬜⬛⬛⬜⬛⬛⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬜⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬜⬜⬜⬜⬜⬛⬛⬛
Rule 149 is an example where the outside of the cone consists of nothing but black pixels, except
for the first line of course:
[22]: display_ECA(149, 18)

⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬜⬛⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬜⬜⬜⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬛⬛⬛⬜⬛⬛⬜⬜⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬜⬜⬛⬜⬜⬜⬜⬛⬜⬛⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬛⬜⬛⬛⬛⬛⬜⬛⬜⬜⬛⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬜⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬜⬜⬜⬛⬜⬜⬛⬛⬜⬜⬜⬛⬜⬜⬜⬛⬛⬜⬜⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬜⬛⬛⬜⬛⬛⬜⬜⬜⬛⬛⬜⬛⬛⬛⬜⬜⬜⬛⬜⬛⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛
⬛⬛⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬛⬜⬛⬛⬜⬛⬜⬜⬛⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛⬛⬛
⬛⬜⬛⬛⬛⬛⬛⬛⬜⬜⬜⬛⬛⬛⬛⬜⬛⬜⬜⬜⬜⬛⬛⬜⬜⬜⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬛
⬜⬜⬜⬛⬛⬛⬛⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬛⬛⬜⬜⬜⬛⬛⬜⬜⬛⬜⬜⬜⬜⬛⬜⬜⬛⬛
Though there are 256 ECA’s, only a quarter are really different due to symmetries. The mirrored
rule of a rule exhibits vertical symmetry: given three pixels p0 , p1 and p2 , the pixel imposed by a

10
rule E below the middle pixel of p0 p1 p2 is the pixel imposed by the mirrored rule of rule E below
the middle pixel of p2 p1 p0 . Rule 90 exhibits vertical symmetry, hence it is its own mirrored rule.
Let us define a function, mirrored_rule(), meant to get a rule as argument and return its mir-
rored rule. Given E < 256, and writing the representation of E in base 2 as the 8 bit number
e7 e6 e5 e4 e3 e2 e1 e0 , the mirrored rule of E is then e7 e3 e5 e1 e6 e2 e4 e0 , as reflected by the correspon-
dence between
111 110 101 100 011 010 001 000
and
111 011 101 001 110 010 100 000
mirrored_rule() could generate from its argument E the formatted string f'{E:08b}', say s,
then create the new string ''.join((s[0], s[4], s[2], s[6], s[1], s[5], s[3], s[7])),
and convert the latter into an integer. By default, int() converts a string that represents an
integer in base 10, but it can also accept representations for other bases:
[23]: # With 0 as second argument, interpret the base from the literal
int('0b101011', 0), int('43', 0), int('0o53', 0), int('0X2b', 0)
int('101011', 2), int('0b101011', 2)
int('1121', 3)
int('223', 4)
int('133', 5)
int('53', 8), int('0o53', 8),
int('2b', 16), int('0X2b', 16)
# 36 is the largest base
int('17', 36)
int('z', 36), int('Z', 36)

[23]: (43, 43, 43, 43)

[23]: (43, 43)

[23]: 43

[23]: 43

[23]: 43

[23]: (43, 43)

[23]: (43, 43)

[23]: 43

[23]: (35, 35)

Let us still not “hardcode” the sequence of bits as (s[0], s[4], s[2], s[6], s[1], s[5],
s[3], s[7]), but generate it. Let us first examine the sorted() function. By default, sorted()
returns the list of members of its arguments in their default order:

11
[24]: sorted([2, -2, 1, -1, 0])
# Lexicographic/lexical/dictionary/alphabetic order
sorted({'a', 'b', 'ab', 'bb', 'abc', 'C'})
sorted(((2, 1, 0), (0, 1, 2), (1, 2, 0), (1, 0, 2)))

[24]: [-2, -1, 0, 1, 2] sorted() 函数对所有可迭代的对象进行排序操作


[24]: ['C', 'a', 'ab', 'abc', 'b', 'bb'] key -- 主要是用来进行比较的元素,只有一个参数,
具体的函数的参数就是取自于可迭代对象中,
[24]: [(0, 1, 2), (1, 0, 2), (1, 2, 0), (2, 1, 0)]
指定可迭代对象中的一个元素来进行排序。
sorted() accepts the reverse keyword argument: reverse -- 排序规则,reverse = True 降序 ,
[25]: sorted([2, -2, 1, -1, 0], reverse=True)
sorted({'a', 'b', 'ab', 'bb', 'abc', 'C'}, reverse=True)
sorted(((2, 1, 0), (0, 1, 2), (1, 2, 0), (1, 0, 2)), reverse=True)

[25]: [2, 1, 0, -1, -2]

[25]: ['bb', 'b', 'abc', 'ab', 'a', 'C']

[25]: [(2, 1, 0), (1, 2, 0), (1, 0, 2), (0, 1, 2)]

sorted() also accepts the key argument, which should evaluate to a callable, e.g., a function. The
function is called on all elements of the sequence to sort, and elements are sorted in the natural
order of the values returned by the function:
[26]: sorted([2, -2, 1, -1, 0], key=abs)
sorted({'a', 'b', 'ab', 'bb', 'abc', 'C'}, key=str.lower)
sorted({'a', 'b', 'ab', 'bb', 'abc', 'C'}, key=len)

[26]: [0, 1, -1, 2, -2]

[26]: ['a', 'ab', 'abc', 'b', 'bb', 'C']

[26]: ['a', 'C', 'b', 'bb', 'ab', 'abc']

We can also set key to an own defined function:


[27]: def _2_0_1(s):
return s[2], s[0], s[1]

def _2_1_0(s):
return s[2], s[1], s[0]

sorted(((2, 1, 0), (0, 1, 2), (1, 2, 0), (1, 0, 2)), key=_2_0_1)


sorted(((2, 1, 0), (0, 1, 2), (1, 2, 0), (1, 0, 2)), key=_2_1_0)

12
[27]: [(1, 2, 0), (2, 1, 0), (0, 1, 2), (1, 0, 2)]

[27]: [(2, 1, 0), (1, 2, 0), (1, 0, 2), (0, 1, 2)]

So we could generate the sequence (0, 4, 2, 6, 1, 5, 3, 7) as follows:

[28]: def three_two_one(p):


return p % 2, p // 2 % 2, p % 4

for p in sorted(range(8), key=three_two_one):


p, f'{p:03b}'

[28]: (0, '000')

[28]: (4, '100')

[28]: (2, '010')

[28]: (6, '110')

[28]: (1, '001')

[28]: (5, '101')

[28]: (3, '011')

[28]: (7, '111')


Lambda表达式提供了一种简洁的方法来定义不需要命名的函数:
There is a better way, using a lambda expression. Lambda expressions offer a concise way to
define functions, that do not need to be named:
[29]: # Functions taking no argument, so returning a constant
f = lambda: 3; f()
(lambda: (1, 2, 3))()

[29]: 3

[29]: (1, 2, 3)

[30]: # Functions taking one argument, the first of which is identity


f = lambda x: x; f(3) 函数名字:lambda 函数参数:函数计算表达式;
(lambda x: 2 * x + 1)(3) 函数名字(参数) 以这样形式调用函数
[30]: 3

[30]: 7

13
[31]: # Functions taking two arguments
f = lambda x, y: 2 * (x + y); f(3, 7)
(lambda x, y: x + y)([1, 2, 3], [4, 5, 6])

[31]: 20

[31]: [1, 2, 3, 4, 5, 6]

Putting everything together, we can define mirrored_rule() as follows:


[32]: def mirrored_rule(E):
return int(''.join(f'{E:08b}'[i] for i in sorted(
range(8), key=lambda i: (i % 2, i // 2 % 2, i // 4)
)
), 2
)

mirrored_rule(90)
mirrored_rule(107)
mirrored_rule(149)

[32]: 90

[32]: 121

[32]: 135

Another symmetry between ECA’s emerges by exchanging all 0’s to 1’s and all 1’s to 0’s. This
maps rules to their complementaries. For instance, the complementary of rule 90, represented as

111 110 101 100 011 010 001 000


0 1 0 1 1 0 1 0

is represented as
000 001 010 011 100 101 110 111
1 0 1 0 0 1 0 1
hence is the rule whose binary representation is 10100101 (10100101 read from right to left), hence
is rule 165. Let us define a function, complementary_rule(), meant to get a rule as argument and
return its complementary rule:
[33]: def complementary_rule(E):
return int(''.join({'0': '1', '1': '0'}[c] for c in reversed(f'{E:08b}')
), 2
)

complementary_rule(90)
complementary_rule(107)
complementary_rule(149)

14
[33]: 165

[33]: 41

[33]: 86

A rule can be its own mirror, or its own complementary, but it cannot be both. For most rules, the
rule itself, and its mirror, and its complementary, are all different, exhibiting minimum symmetry:
[34]: display_ECA(60, 15)
print()
display_ECA(mirrored_rule(60), 15)
print()
display_ECA(complementary_rule(60), 15)
print()
display_ECA(complementary_rule(mirrored_rule(60)), 15)

⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛

15
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬛⬜⬛⬜⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬛⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛
⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛⬛

16

You might also like