├── tilings ├── wtm_beaver_nst_41.png ├── wtm_binary_adder.png ├── wtm_binary_increment.png ├── wtm_beaver_2_states_2_symbols_s=6.png ├── wtm_beaver_4_states_2_symbols_s=107.png └── wtm_busy_beaver_3_states_2_symbols_s=14.png ├── wtm_sandbox.py ├── LICENSE ├── wtm_tests.py ├── wtm_machines.py ├── README.md ├── wtm_model.py └── wtm_draw.py /tilings/wtm_beaver_nst_41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/WangTilesTuringMachines/main/tilings/wtm_beaver_nst_41.png -------------------------------------------------------------------------------- /tilings/wtm_binary_adder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/WangTilesTuringMachines/main/tilings/wtm_binary_adder.png -------------------------------------------------------------------------------- /tilings/wtm_binary_increment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/WangTilesTuringMachines/main/tilings/wtm_binary_increment.png -------------------------------------------------------------------------------- /tilings/wtm_beaver_2_states_2_symbols_s=6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/WangTilesTuringMachines/main/tilings/wtm_beaver_2_states_2_symbols_s=6.png -------------------------------------------------------------------------------- /tilings/wtm_beaver_4_states_2_symbols_s=107.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/WangTilesTuringMachines/main/tilings/wtm_beaver_4_states_2_symbols_s=107.png -------------------------------------------------------------------------------- /tilings/wtm_busy_beaver_3_states_2_symbols_s=14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/WangTilesTuringMachines/main/tilings/wtm_busy_beaver_3_states_2_symbols_s=14.png -------------------------------------------------------------------------------- /wtm_sandbox.py: -------------------------------------------------------------------------------- 1 | import wtm_draw as draw 2 | from wtm_machines import machines 3 | 4 | #tm = machines["Beaver 5 2"] 5 | #step, left_shifts, tape, output = tm.run("0") 6 | #print(step, left_shifts, output) 7 | #ones = sum(["0" not in t.s != " " for t in tape]) 8 | #print("ones:", ones) 9 | 10 | #filename = draw.draw_tm(machines["Binary increment"], input="111") 11 | filename = draw.draw_tm(machines["Beaver 2 2"], input="0") 12 | 13 | #filename = draw.draw_tm(machines["Beaver 3 2"], input="0", tileset=True, tiling=True) 14 | 15 | #filename = draw.draw_tm(machines["Beaver 4 2"], input="0") 16 | #filename = draw.draw_tm(machines["Beaver 5 2"], input="0") 17 | #filename = draw.draw_tm(machines["Binary adder"], input="101#11", head_pos=5, tileset=True, tiling=True) 18 | 19 | import subprocess 20 | subprocess.call(['open', filename]) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nicolas Seriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /wtm_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from wtm_model import WangTM 4 | from wtm_machines import machines 5 | 6 | import unittest 7 | 8 | class TestWangTM(unittest.TestCase): 9 | 10 | def test_binary_increment(self): 11 | 12 | tm = machines["Binary increment"] 13 | step,left_shift,tape,output = tm.run("111") 14 | self.assertEqual(step, 8) 15 | self.assertEqual(output, "1000") 16 | self.assertEqual(len(tape), 6) 17 | self.assertEqual(left_shift, 2) 18 | 19 | x = tape[0].e 20 | for t in tape[1:]: 21 | self.assertEqual(t.w, x) 22 | x = t.e 23 | 24 | def test_binary_adder(self): 25 | 26 | tm = machines["Binary adder"] 27 | step,left_shift,tape,output = tm.run("101#11", head=5) 28 | self.assertEqual(step, 32) 29 | self.assertEqual(output, "1000#00") 30 | 31 | def test_beaver_2_2(self): 32 | 33 | tm = machines["Beaver 2 2"] 34 | step,left_shift,tape,output = tm.run("0") 35 | self.assertEqual(step, 6) 36 | self.assertEqual(output, "1111") 37 | self.assertEqual(len(tape), 4) 38 | 39 | def test_beaver_3_2(self): 40 | 41 | tm = machines["Beaver 3 2"] 42 | step,left_shift,tape,output = tm.run("0") 43 | self.assertEqual(step, 14) 44 | self.assertEqual(output, "111111") 45 | 46 | def test_beaver_4_2(self): 47 | 48 | tm = machines["Beaver 4 2"] 49 | step,left_shift,tape,output = tm.run("0") 50 | self.assertEqual(step, 107) 51 | self.assertEqual(output, "10111111111111") 52 | 53 | if __name__ == "__main__": 54 | unittest.main() 55 | -------------------------------------------------------------------------------- /wtm_machines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from wtm_model import WangTM 4 | 5 | machines = {} 6 | 7 | machines["Binary increment"] = WangTM(name="Binary increment", 8 | states=["A","B","H"], 9 | alphabet=["0","1"], 10 | blank_symbol="#", 11 | transitions_string= "#LB 0RA 1RA 1LH 1LH 0LB") 12 | 13 | # https://machinedeturing.com/ang_calculateur.php?page 14 | 15 | machines["Binary adder"] = WangTM(name="Binary adder", 16 | states=["A","B","C","D","E","F","H"], 17 | alphabet=["0","1"], 18 | blank_symbol="#", 19 | transitions_string=' '.join(["#LA 1LA 0LB", "#LC 0LB 1LB", "1RD 1RD 0LC", "#RE 0RD 1RD", "#RH 0RE 1RF", "#LA 0RF 1RF"])) 20 | 21 | machines["Beaver"] = WangTM(name="Beaver nst 41", 22 | states=["A","B","C","D","E","H"], 23 | alphabet=["1"], 24 | blank_symbol="0", 25 | transitions_string= "0RC 1LE 0LC 0LA 1RE 1RD 1LH 0RB 1LA 1RB") 26 | 27 | # https://webusers.imj-prg.fr/~pascal.michel/bbc.html 28 | 29 | machines["Beaver 2 2"] = WangTM(name="Beaver 2 states 2 symbols, S=6", 30 | states=["A","B","H"], 31 | alphabet=["1"], 32 | blank_symbol="0", 33 | transitions_string= "1RB 1LB 1LA 1RH") 34 | 35 | machines["Beaver 3 2"] = WangTM(name="Busy Beaver 3 states 2 symbols, S=14", 36 | states=["A","B","C","H"], 37 | alphabet=["1"], 38 | blank_symbol="0", 39 | transitions_string= "1RB 1RH 0RC 1RB 1LC 1LA") 40 | 41 | machines["Beaver 4 2"] = WangTM(name="Beaver 4 states 2 symbols, S=107", 42 | states=["A","B","C","D","H"], 43 | alphabet=["1"], 44 | blank_symbol="0", 45 | transitions_string= "1RB 1LB 1LA 0LC 1RH 1LD 1RD 0RA") 46 | 47 | machines["Beaver 5 2"] = WangTM(name="Beaver 5 states 2 symbols, S=47176870, Sigma=4098", 48 | states=["A","B","C","D","E","H"], 49 | alphabet=["1"], 50 | blank_symbol="0", 51 | transitions_string= "1LB 1RC 1LC 1LB 1LD 0RE 1RA 1RD 1LH 0RA") 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wang Tiles Turing Machines 2 | 3 | This is the companion source code for my page [Simulating Turing Machines with Wang Tiles](https://seriot.ch/projects/simulating_turing_machines_with_wang_tiles.html). 4 | 5 | __Simulating Turing Machines with Wang tiles__ 6 | 7 | `wtm` (Wang tiles Turing Machine) is a Python program that demonstrates how to perform computation by simulating a Turing machine (TM) with Wang tiles in practice. It is based on the procedure described by Robinson in 1971 in [Undecidability and nonperiodicity for tilings of the plane](https://lipn.fr/~fernique/qc/robinson.pdf). 8 | 9 | Here is the tileset for a Turing machine that will increment a number. The input `111` can be read at the top of the tiling, and the result `1000` appears at the bottom. 10 | 11 | 12 | 13 | How does it work? `wtm` generates a specific tileset for each machine. Each tileset encodes the machine alphabet, states and transitions. Each configuration of the machine is simulated with a row of Wang tiles, and each machine step grows the tiling downwards. Tilesets are such that procedurally tiling a new row simulates the execution of the machine. 14 | 15 | Some tiles have colors on their south edge that do not appear on any north edge. Consequently, whenever these tiles are used, the tiling cannot continue and the machine halts. The output of the calculation can then be read on the last row. 16 | 17 | In summary, `wtm` does: 18 | 19 | * encode a Turing machine into a set of Wang tiles 20 | * run the machine for a given input 21 | * draw the tiling that results from the computation 22 | 23 | __Source Code__ 24 | 25 | The source code consists in the following Python files: 26 | 27 | * `wtm_model.py` WangTM class, can generate Wang tiles set for any TM configuration, and run the maching for any input 28 | * `wtm_draw.py` methods to draw the tileset and the execution of a WangTM for some given input, using the [Pycairo](https://pycairo.readthedocs.io/) graphic library 29 | * `wtm_machines.py` sample Turing machines as WangTM instances 30 | * `wtm_tests.py` unit tests for the sample machines 31 | * `wtm.py` a sandbox for running and/or drawing WangTM instances 32 | 33 | __Sample usage__ 34 | 35 | # define a Turing machine 36 | tm = WangTM(name="Binary increment", 37 | states=["A","B","H"], 38 | alphabet=["0","1"], 39 | blank_symbol="#", 40 | transitions_string= "#LB 0RA 1RA 1LH 1LH 0LB") 41 | 42 | # raw run 43 | step, left_shifts, tape, output = tm.run("1010") 44 | 45 | # raw run with callback at each step 46 | for step, left_shifts, tape in self.run_gen("1010"): 47 | pass 48 | 49 | # run and draw the tiling 50 | filename = draw_tm(tm, input="1010”) 51 | -------------------------------------------------------------------------------- /wtm_model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # a Turing machine where states and transitions are represented with Wang tiles 4 | 5 | from collections import namedtuple 6 | 7 | Tile = namedtuple("Tile", "n e s w") 8 | 9 | class WangTM: 10 | 11 | def __init__(self, name, states, alphabet, blank_symbol, transitions_string): 12 | 13 | self.name = name 14 | self.states = states # first state first, halting state last 15 | self.alphabet = alphabet # without blank symbol 16 | self.blank_symbol = blank_symbol 17 | 18 | # TNF (Tree Normal Form) eg. "1RB 1LB 1LA 1RH 0RA" 19 | # eg. "1RB 1LB 1LA 1RH 0RA 1LA" for A#, A0, A1, B#, B0, B1 20 | self.transitions_string = transitions_string 21 | 22 | if not len(alphabet) > 0: 23 | raise Exception("Alphabet cannot be empty") 24 | 25 | if not len(states) > 0: 26 | raise Exception("States cannot empty") 27 | 28 | if states[-1] != "H": 29 | raise Exception("Last state must be the Halting symbol") 30 | 31 | if self.blank_symbol in self.alphabet: 32 | raise Exception("Blank symbol '%s' must not be part of the alphabet" % self.blank_symbol) 33 | 34 | self.alphabet.insert(0, blank_symbol) # TNF 35 | 36 | # filled before running 37 | self.tiles_alphabet = {} 38 | self.tiles_head = {} 39 | self.tiles_action = {} 40 | self.tiles_move = {} 41 | 42 | def run(self, input_string, head=0): 43 | 44 | tape = [] 45 | 46 | for step, left_shifts, tape in self.run_gen(input_string, head): 47 | pass 48 | 49 | output = "".join([t.s for t in tape]).replace(self.states[-1], "").strip(self.blank_symbol) 50 | 51 | return (step, left_shifts, tape, output) 52 | 53 | def run_gen(self, input_string, head=0): 54 | 55 | if len(input_string) == 0: 56 | raise Exception("Input cannot be empty") 57 | 58 | if len(self.tiles_alphabet) == 0: 59 | self.create_tileset() 60 | 61 | left_shifts = 0 62 | 63 | step = 0 64 | 65 | tape = [self.tiles_alphabet[c] for c in input_string] 66 | 67 | a = self.tiles_alphabet[input_string[head]].s 68 | tape[head] = self.tiles_head[a] 69 | 70 | yield(step, left_shifts, tape) 71 | 72 | while tape[head].s in self.tiles_action: 73 | 74 | step += 1 75 | 76 | # 1. previous action tile turns into alphabet tile 77 | 78 | for h in (head-1, head, head+1): 79 | if h in range(0, len(tape)) and tape[h].n in self.tiles_action: 80 | tape[h] = self.tiles_alphabet[tape[h].s] 81 | 82 | # 2. update head with action tile 83 | 84 | t = self.tiles_action[tape[head].s] 85 | 86 | tape[head] = t 87 | 88 | # 3. propagate state left or right with move tile 89 | 90 | move_left = t.w != " " 91 | move_right = t.e != " " 92 | 93 | expand_left = move_left and head == 0 94 | expand_right = move_right and head == len(tape)-1 95 | 96 | if expand_left: 97 | t = self.tiles_move[(" ", self.blank_symbol, t.w)] 98 | tape.insert(0, t) 99 | head = 0 100 | left_shifts += 1 101 | elif expand_right: 102 | t = self.tiles_move[(t.e, self.blank_symbol, " ")] 103 | tape.append(t) 104 | head += 1 105 | elif move_left: 106 | head -= 1 107 | tape[head] = self.tiles_move[(" ", tape[head].s, t.w)] 108 | elif move_right: 109 | head += 1 110 | tape[head] = self.tiles_move[(t.e, tape[head].s, " ")] 111 | 112 | yield(step, left_shifts, tape) 113 | 114 | def create_tileset(self): 115 | 116 | # alphabet tiles 117 | for a in self.alphabet: 118 | self.tiles_alphabet[a] = Tile(a, " ", a, " ") 119 | self.tiles_head[a] = Tile(a, " ", self.states[0]+a, " ") 120 | 121 | # action tiles 122 | for i,wds in enumerate(self.transitions_string.split(" ")): 123 | 124 | if not len(wds) == 3: 125 | raise Exception("Use transitions such as 1RB 1LB 1LA 1RH") 126 | 127 | b,d,p = wds # write, dir, state 128 | 129 | q = self.states[int(i/(len(self.alphabet)))] # A A A B B B C ... 130 | a = self.alphabet[i%(len(self.alphabet))] # 0 1 # 0 1 # 0 ... 131 | 132 | t = (q,a),(b,d,p) 133 | 134 | if d not in "LR": 135 | raise Exception("Unknown direction to move:", d) 136 | 137 | if d == "R": 138 | t = Tile(q+a, p, b, " ") 139 | elif d == "L": 140 | t = Tile(q+a, " ", b, p) 141 | 142 | #print(f"In state {q} read {a} -> write {b}, move {d}, state {p}") 143 | 144 | self.tiles_action[q+a] = t 145 | 146 | # moving tiles 147 | # we can come up with a minimal tileset by: 148 | # - not creating moving tiles for halting state 149 | # - setting specific strings/colors on south of halting action tiles 150 | # but it prevents the head from finishing in the expected position 151 | # as defined in the transtions string 152 | for s in self.states: 153 | for a in self.alphabet: 154 | self.tiles_move[(" ", a, s)] = Tile(a, s, s+a, " ") 155 | self.tiles_move[(s, a, " ")] = Tile(a, " ", s+a, s) 156 | -------------------------------------------------------------------------------- /wtm_draw.py: -------------------------------------------------------------------------------- 1 | import cairo 2 | import math 3 | import colorsys 4 | 5 | from wtm_model import WangTM 6 | 7 | SP = 8 # tiles legend spacer 8 | 9 | def draw_triangle(c, color, translate, rotate): 10 | 11 | c.save() 12 | 13 | c.translate(*translate) 14 | c.rotate(rotate) 15 | 16 | c.move_to(0,0) 17 | c.line_to(64,0) 18 | c.line_to(32,32) 19 | c.line_to(0,0) 20 | c.set_source_rgb(*color) 21 | c.fill_preserve() 22 | c.set_source_rgb(0,0,0) 23 | c.stroke() 24 | 25 | c.restore() 26 | 27 | def draw_tile(c, t, color_dict): 28 | 29 | draw_triangle(c, color_dict[t.n], translate=(0,0), rotate=0) 30 | c.move_to(30 - 5*len(t.n)/2., 15) 31 | c.show_text(t.n) 32 | 33 | draw_triangle(c, color_dict[t.e], translate=(64,0), rotate=math.pi/2) 34 | c.move_to(50, 35) 35 | c.show_text(t.e) 36 | 37 | draw_triangle(c, color_dict[t.s], translate=(64,64), rotate=math.pi) 38 | c.move_to(30 - 5*len(t.s)/2., 55) 39 | c.show_text(t.s) 40 | 41 | draw_triangle(c, color_dict[t.w], translate=(0,64), rotate=math.pi/-2) 42 | c.move_to(5, 35) 43 | c.show_text(t.w) 44 | 45 | c.save() 46 | c.set_line_width(1) 47 | c.set_source_rgb(0,0,0) 48 | c.rectangle(0,0,64,64) 49 | c.stroke() 50 | c.restore() 51 | 52 | def draw_tileset(c, color_dict, tm): 53 | 54 | if len(tm.tiles_alphabet) == 0: 55 | tm.build_tiles() 56 | assert(len(tm.tiles_alphabet) > 0) 57 | 58 | # copy tiles dicts to empty them while drawing 59 | # this was can make sure to have drawn them all once 60 | 61 | tiles_alphabet = tm.tiles_alphabet.copy() 62 | tiles_head = tm.tiles_head.copy() 63 | tiles_action = tm.tiles_action.copy() 64 | tiles_move = tm.tiles_move.copy() 65 | 66 | c.save() 67 | 68 | c.translate(0,32) 69 | 70 | lines = [] 71 | lines.append("Tileset for machine: " + tm.name) 72 | lines.append(" Alphabet: " + ','.join(tm.alphabet)) 73 | lines.append(" States: " + ','.join(tm.states)) 74 | lines.append(" Transitions: " + tm.transitions_string) 75 | 76 | for i,s in enumerate(lines): 77 | c.move_to(64+SP, 16*i) 78 | c.show_text(s) 79 | 80 | c.translate(0, 90) 81 | c.move_to(64+SP,-10) 82 | 83 | c.save() 84 | c.translate(64+SP, 0) 85 | c.show_text("Alphabet tiles") 86 | for a in tm.alphabet: 87 | draw_tile(c, tiles_alphabet.pop(a), color_dict) 88 | c.translate(64+SP, 0) 89 | 90 | c.translate(0,-10) 91 | c.move_to(SP, 0) 92 | c.show_text("Head tiles") 93 | c.translate(SP,10) 94 | 95 | c.save() 96 | for a in tm.alphabet: 97 | draw_tile(c, tiles_head.pop(a), color_dict) 98 | c.translate(2*(64+SP), 0) 99 | c.restore() 100 | 101 | c.restore() 102 | 103 | c.translate(0, 64+2*SP) 104 | 105 | c.save() 106 | c.translate(64+SP,0) 107 | c.show_text("Action tiles") 108 | 109 | c.move_to(len(tm.tiles_alphabet)*(64+SP)+SP,0) 110 | c.show_text("Moving tiles") 111 | c.restore() 112 | 113 | c.translate(0,10) 114 | 115 | c.save() 116 | for s in tm.states: 117 | c.save() 118 | c.move_to(64-16,32) 119 | c.show_text(s) 120 | c.translate(64+SP, 0) 121 | for a in tm.alphabet: 122 | if s+a in tiles_action: 123 | draw_tile(c, tiles_action.pop(s+a), color_dict) 124 | c.translate(64+SP, 0) 125 | 126 | c.translate(SP,0) 127 | 128 | for a in tm.alphabet: 129 | draw_tile(c, tiles_move.pop((" ", a, s)), color_dict) 130 | c.translate(64+SP, 0) 131 | draw_tile(c, tiles_move.pop((s, a, " ")), color_dict) 132 | c.translate(64+SP, 0) 133 | c.restore() 134 | c.translate(0,64+SP) 135 | 136 | c.restore() 137 | 138 | c.restore() 139 | 140 | # make sure that all tiles have been drawn 141 | 142 | assert(len(tiles_alphabet) == 0) 143 | assert(len(tiles_head) == 0) 144 | assert(len(tiles_action) == 0) 145 | assert(len(tiles_move) == 0) 146 | 147 | def run_and_draw_tiles(c, color_dict, tm, input, head_pos): 148 | 149 | c.save() 150 | 151 | for step,left_shifts,tape in tm.run_gen(input, head_pos): 152 | 153 | c.save() 154 | 155 | c.translate(-64*left_shifts,64-SP) 156 | 157 | c.move_to(-32,32) 158 | 159 | c.show_text("%d" % step) 160 | 161 | for t in tape: 162 | draw_tile(c, t, color_dict) 163 | c.translate(64,0) 164 | c.restore() 165 | 166 | c.translate(0,64) 167 | 168 | c.restore() 169 | 170 | output = ''.join([t.s for t in tape]).replace(tm.states[-1],"").strip(tm.blank_symbol) 171 | 172 | io_length = max(len(input), len(output)) 173 | 174 | c.save() 175 | c.translate(-64*left_shifts,0) 176 | c.move_to(0,-32) 177 | c.show_text("Machine : " + tm.name) 178 | c.move_to(0,-16) 179 | c.show_text("Transitions : " + tm.transitions_string) 180 | c.move_to(0,0) 181 | c.show_text("Input: : " + input.rjust(io_length)) 182 | c.move_to(0,16) 183 | c.show_text("Output: : " + output.rjust(io_length)) 184 | c.move_to(0,32) 185 | c.show_text(" Steps : " + str(step)) 186 | c.restore() 187 | 188 | def build_color_dict(tm): 189 | 190 | keys = set() 191 | keys.update(tm.alphabet) 192 | keys.update(tm.states) 193 | keys.update(set([s+a for s in tm.states for a in tm.alphabet])) 194 | 195 | d = {} 196 | d['#'] = (1,1,1) 197 | d[' '] = (0.9,0.9,0.9) 198 | d['0'] = (1,0.5,0.5) 199 | d['1'] = (0.5,1,0.5) 200 | d['H'] = (0.7,0.7,0.7) 201 | d['H1'] = (0,0.6,0) 202 | d['H0'] = (1,0.3,0) 203 | d['H#'] = (1,1,0) 204 | 205 | N = len(set(keys)-set(d.keys())) 206 | HSV_tuples = [(x/(N), 0.6, 1.0) for x in range(N)] 207 | RGB_tuples = [colorsys.hsv_to_rgb(*x) for x in HSV_tuples] 208 | 209 | for k in sorted(list(keys)): 210 | if not k in d: 211 | d[k] = RGB_tuples.pop() 212 | 213 | return d 214 | 215 | def canvas_size(tm, tape, steps, output, draw_tileset, draw_tiling): 216 | 217 | tileset_width = 64*2 + len(tm.tiles_alphabet)*(64+SP) + SP + 2*len(tm.tiles_head)*(64+SP) 218 | tiling_width = (len(tape))*64 219 | 220 | width = 0 221 | if draw_tileset: 222 | width += tileset_width 223 | if draw_tiling: 224 | width += tiling_width + 2*64 225 | 226 | tileset_height = 4*64+(64+SP)*len(tm.states) 227 | tiling_height = 64*(steps+3)+32 228 | 229 | height = 0 230 | if draw_tileset and draw_tiling: 231 | height = max(tileset_height, tiling_height) 232 | elif draw_tileset: 233 | height = tileset_height 234 | else: 235 | height = tiling_height 236 | 237 | return (width, height, tileset_width) 238 | 239 | def draw_tm(tm: WangTM, input, head_pos=0, tileset=True, tiling=True): 240 | 241 | # first run to get the size 242 | step, left_shifts, tape, output = tm.run(input, head_pos) 243 | 244 | w,h,tileset_width = canvas_size(tm, tape, step, output, tileset, tiling) 245 | 246 | surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w), h) 247 | c = cairo.Context(surface) 248 | 249 | c.set_antialias(cairo.ANTIALIAS_NONE) 250 | 251 | fo = cairo.FontOptions() 252 | fo.set_antialias(cairo.ANTIALIAS_NONE) 253 | c.set_font_options(fo) 254 | 255 | c.select_font_face("Courier New") 256 | c.set_font_size(14) 257 | 258 | c.set_line_width(1) 259 | 260 | c.set_source_rgb(1,1,1) 261 | c.paint() 262 | 263 | c.set_source_rgb(0,0,0) 264 | 265 | # 266 | 267 | color_dict = build_color_dict(tm) 268 | 269 | if tileset: 270 | draw_tileset(c, color_dict, tm) 271 | 272 | if tileset and tiling: 273 | c.translate(tileset_width, 0) 274 | 275 | if tiling: 276 | c.translate(left_shifts*64+64, 64) 277 | run_and_draw_tiles(c, color_dict, tm, input, head_pos) 278 | 279 | import os 280 | dir = "tilings" 281 | if not os.path.exists(dir): 282 | os.makedirs(dir) 283 | s = "wtm_" + tm.name.replace(" ", "_").lower() + ".png" 284 | filename = os.path.sep.join([dir, s]) 285 | surface.write_to_png(filename) 286 | 287 | return filename 288 | --------------------------------------------------------------------------------