sloast commited on
Commit
f3a39f8
1 Parent(s): 1760bc6
Files changed (3) hide show
  1. gen.py +147 -0
  2. visual.py +182 -0
  3. webui.py +52 -0
gen.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import reduce
2
+ import random
3
+ import visual
4
+
5
+
6
+ ######## Width and height #######
7
+ N = 3
8
+ #M = 3
9
+ #################################
10
+
11
+
12
+
13
+ EMPTY = -1
14
+ WIRE = 0
15
+ SOURCE = 1
16
+ SINK = 2
17
+ types = ["Wire","Source","Sink"]
18
+
19
+ NORTH,EAST,SOUTH,WEST = range(4)
20
+ edges = ["North","East","South","West"]
21
+
22
+ directions = [(0,1),(1,0),(0,-1),(-1,0)]
23
+
24
+ def isWireConnected(puzzle,x,y):
25
+ _,ds = puzzle[y][x]
26
+ drs= [directions[d] for d in ds]
27
+ for d in ds:
28
+ dx,dy = directions[d]
29
+ if 0 <= y+dy < len(puzzle) and 0 <= x+dx < len(puzzle[y+dy]):
30
+ t2,ds2 = puzzle[y+dy][x+dx]
31
+ if t2 == EMPTY:
32
+ continue
33
+ if (d+2)%4 not in ds2:
34
+ return False
35
+ else:
36
+ return False
37
+ return True
38
+
39
+ def gettile():
40
+ return (WIRE, [i for i in range(4) if random.random() < 0.4])
41
+
42
+ def consistent(puzzle,x,y):
43
+ if not isWireConnected(puzzle,x,y):
44
+ return False
45
+ if y > 0:
46
+ if not isWireConnected(puzzle,x,y-1):
47
+ return False
48
+ if x > 0:
49
+ if not isWireConnected(puzzle,x-1,y):
50
+ return False
51
+ return True
52
+
53
+ def joined(puzzle,x1,y1,x2,y2):
54
+ # is there a wire connecting the tiles at x1,y1 and x2,y2
55
+ _,ds = puzzle[y1][x1]
56
+ _,ds2 = puzzle[y2][x2]
57
+ diff = (x2-x1,y2-y1)
58
+ if diff not in [(0,1),(1,0),(0,-1),(-1,0)]: return False
59
+ d = directions.index(diff)
60
+ return (d+2)%4 in ds2 and d in ds
61
+
62
+ def neighbours(puzzle,x,y):
63
+ _,ds = puzzle[y][x]
64
+ for d in ds:
65
+ dx,dy = directions[d]
66
+ if 0 <= y+dy < len(puzzle) and 0 <= x+dx < len(puzzle[y+dy]):
67
+ yield (x+dx,y+dy)
68
+
69
+ def reachable(puzzle,x,y):
70
+ ttype,_ = puzzle[y][x]
71
+ found = [(x,y)]
72
+ ttype2 = SINK if ttype == SOURCE else SOURCE
73
+ assert ttype in [SOURCE,SINK]
74
+
75
+ flag = True
76
+ while flag:
77
+ flag = False
78
+ for (x1,y1) in found.copy():
79
+ for (x2,y2) in neighbours(puzzle,x1,y1):
80
+ if (x2,y2) in found:
81
+ continue
82
+ found.append((x2,y2))
83
+ flag = True
84
+ if puzzle[y2][x2][0] == ttype2:
85
+ return (x2,y2)
86
+ return random.choice(found[1:])
87
+
88
+ seed1=0
89
+ def genpuzzle(w,h=None,seed=0):
90
+ global seed1
91
+ if h is None:
92
+ h = w
93
+ if seed == 0:
94
+ seed = random.randint(0,9223372036854775807)
95
+ random.seed(seed)
96
+ seed1 = seed
97
+ puzzle = [[(EMPTY,[]) for _ in range(w)] for _ in range(h)]
98
+ for y in range(h):
99
+ for x in range(w):
100
+ puzzle[y][x] = gettile()
101
+ while not consistent(puzzle,x,y):
102
+ puzzle[y][x] = gettile()
103
+
104
+ numsinks = (w*h//10) + 1
105
+ for _ in range(numsinks):
106
+ x = random.randint(0,w-1)
107
+ y = random.randint(0,h-1)
108
+ if puzzle[y][x][1] != []:
109
+ t1,t2 = random.choice([(SOURCE,SINK),(SINK,SOURCE)])
110
+ puzzle[y][x] = (t1,puzzle[y][x][1])
111
+ x2,y2 = reachable(puzzle,x,y)
112
+ puzzle[y2][x2] = (t2,puzzle[y2][x2][1])
113
+
114
+ return puzzle
115
+
116
+ def h(puzzle):
117
+ output = "["
118
+ for row in reversed(puzzle):
119
+ ss = []
120
+ for t in row:
121
+ s = types[t[0]]# + " "
122
+ s += "[" + ",".join(map(lambda x: edges[x], t[1])) + "]"
123
+ ss.append(s)
124
+ output += "[" + ",".join(ss) + "],"
125
+ output = output[:-1] + "]"
126
+ return output
127
+
128
+ def shuf(puzzle):
129
+ def rot(rs,x):
130
+ return (x[0], list(map(lambda y: (y+rs)%4, x[1])))
131
+ return [[rot(random.randint(0,3),t) for t in ln] for ln in puzzle]
132
+
133
+
134
+ if __name__ == '__main__':
135
+ try:
136
+ p = genpuzzle(N,M)
137
+ except NameError:
138
+ p = genpuzzle(N)
139
+
140
+ p_ = shuf(p)
141
+
142
+ print(h(p))
143
+ print('=======================================')
144
+ print(h(p_))
145
+
146
+ p2 = visual.join(p,p_)
147
+ visual.show(p2)
visual.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from re import match
2
+ import matplotlib.pyplot as plt
3
+ from matplotlib.patches import Arc
4
+
5
+ EMPTY = -1
6
+ WIRE = 0
7
+ SOURCE = 1
8
+ SINK = 2
9
+
10
+ NORTH = 0
11
+ EAST = 1
12
+ SOUTH = 2
13
+ WEST = 3
14
+
15
+ ############
16
+ SCALE = 1
17
+ WIDTH = .25
18
+ CIRCRAD = .25
19
+ ############
20
+
21
+ HLF = WIDTH/2
22
+ LW = 50 * WIDTH * SCALE
23
+
24
+ def parseDirs(dirs):
25
+ if dirs == '': return []
26
+ return list(map(["North","East","South","West"].index,dirs.split(',')))
27
+
28
+ def parse(s:str):
29
+ s = ''.join(s.split())
30
+ s = match(r'\[(.*)\]',s).group(1)
31
+ lines = s.split(']],')
32
+ #objs = [l.split('],') for l in lines]
33
+ tiles = []
34
+
35
+ for l in lines:
36
+ currline= []
37
+ ts = l.split('],')
38
+ for t in ts:
39
+ t = t.strip('[]')
40
+ if r:=match(r'Wire\[?(.*)',t):
41
+ currline.append((WIRE,parseDirs(r.group(1))))
42
+ elif r:=match(r'Source\[?(.*)',t):
43
+ currline.append((SOURCE,parseDirs(r.group(1))))
44
+ elif r:=match(r'Sink\[?(.*)',t):
45
+ currline.append((SINK,parseDirs(r.group(1))))
46
+ else:
47
+ raise Exception("didnt work :/")
48
+ tiles.append(currline)
49
+ tiles.reverse()
50
+ print(tiles)
51
+ return tiles
52
+
53
+ def join(xs,ys):
54
+ return [x + [(EMPTY,[])] + y for x,y in zip(xs,ys)]
55
+
56
+
57
+ def rectpos(x, y, d):
58
+ return [((x - HLF, y), WIDTH, .5), ((x, y - HLF), .5, WIDTH), ((x - HLF, y - .5), WIDTH, .5), ((x - .5, y - HLF), .5, WIDTH)][d]
59
+
60
+ def mktile(tile, pos, wirevalid=True, circlevalid=True,
61
+ wirevalidcolor='green', wireinvalidcolor='red',
62
+ circlevalidcolor='green', circleinvalidcolor='red'
63
+ ):
64
+
65
+ wirecolor = wirevalidcolor if wirevalid else wireinvalidcolor
66
+ circlecolor = circlevalidcolor if circlevalid else circleinvalidcolor
67
+ tiletype,ds = tile
68
+ x,y = pos
69
+
70
+ if tiletype == EMPTY: return
71
+
72
+ box = plt.Rectangle((x-.5,y-.5),1,1,color='grey',fill=False, zorder=2)
73
+ plt.gca().add_patch(box)
74
+
75
+ if not (wirevalid and circlevalid):
76
+ plt.gca().add_patch(plt.Rectangle((x-.5,y-.5),1,1,color='red',fill=False, zorder=3, lw=1))
77
+
78
+ # if line is a right angle draw an arc
79
+ if tiletype == WIRE and len(ds) == 2 and (ds[0] - ds[1]) % 2 == 1:
80
+ ds = sorted(ds)
81
+ if ds == [0,3]:
82
+ plt.gca().add_patch(Arc((x-.5,y+.5),1,1,theta1=270,theta2=360,color=wirecolor, lw=LW))
83
+ elif ds[0] == 0:
84
+ plt.gca().add_patch(Arc((x+.5,y+.5),1,1,theta1=180,theta2=270,color=wirecolor, lw=LW))
85
+ elif ds[0] == 1:
86
+ plt.gca().add_patch(Arc((x+.5,y-.5),1,1,theta1=90,theta2=180,color=wirecolor, lw=LW))
87
+ elif ds[0] == 2:
88
+ plt.gca().add_patch(Arc((x-.5,y-.5),1,1,theta1=0,theta2=90,color=wirecolor, lw=LW))
89
+
90
+ else:
91
+ if len(ds) == 1:
92
+ circ = plt.Circle((x,y),HLF,color=wirecolor)
93
+ plt.gca().add_patch(circ)
94
+
95
+ for d in ds:
96
+ rect = plt.Rectangle(*rectpos(x,y,d),color=wirecolor)
97
+ plt.gca().add_patch(rect)
98
+
99
+ if tiletype > 0:
100
+ circ = plt.Circle((x,y),CIRCRAD,color=circlecolor)
101
+ plt.gca().add_patch(circ)
102
+ if tiletype == 2:
103
+ circ = plt.Circle((x,y),CIRCRAD/2,color='white')
104
+ plt.gca().add_patch(circ)
105
+
106
+ def mkgrid(grid):
107
+ for y,l in enumerate(grid):
108
+ for x,t in enumerate(l):
109
+ if t[0] > WIRE:
110
+ mktile(t,(x,y), isWireConnected(grid,x,y), sinksource(grid,x,y))
111
+ else:
112
+ mktile(t,(x,y), isWireConnected(grid,x,y))
113
+
114
+ directions = [(0,1),(1,0),(0,-1),(-1,0)]
115
+
116
+ def isWireConnected(puzzle,x,y):
117
+ _,ds = puzzle[y][x]
118
+ drs= [directions[d] for d in ds]
119
+ return all(0 <= y+dy < len(puzzle) and 0 <= x+dx < len(puzzle[y+dy]) and (d+2)%4 in puzzle[y+dy][x+dx][1] for d,(dx,dy) in zip(ds, drs))
120
+
121
+ def neighbours(puzzle,x,y):
122
+ _,ds = puzzle[y][x]
123
+ for d in ds:
124
+ dx,dy = directions[d]
125
+ if 0 <= y+dy < len(puzzle) and 0 <= x+dx < len(puzzle[y+dy]):
126
+ yield (x+dx,y+dy)
127
+
128
+ def sinksource(puzzle,x,y):
129
+ ttype,_ = puzzle[y][x]
130
+ found = [(x,y)]
131
+ ttype2 = SINK if ttype == SOURCE else SOURCE
132
+ assert ttype in [SOURCE,SINK]
133
+
134
+ flag = True
135
+ while flag:
136
+ flag = False
137
+ for (x1,y1) in found.copy():
138
+ for (x2,y2) in neighbours(puzzle,x1,y1):
139
+ if (x2,y2) in found:
140
+ continue
141
+ found.append((x2,y2))
142
+ flag = True
143
+ if puzzle[y2][x2][0] == ttype2:
144
+ return True
145
+ return False
146
+
147
+ def joined(puzzle,x1,y1,x2,y2):
148
+ # is there a wire connecting the tiles at x1,y1 and x2,y2
149
+ _,ds = puzzle[y1][x1]
150
+ _,ds2 = puzzle[y2][x2]
151
+ diff = (x2-x1,y2-y1)
152
+ if diff not in [(0,1),(1,0),(0,-1),(-1,0)]: return False
153
+ d = directions.index(diff)
154
+ return (d+2)%4 in ds2 and d in ds
155
+
156
+ def setup(p, show_axes=False):
157
+ plt.figure(figsize=(len(p[0])*SCALE,len(p)*SCALE))
158
+ plt.axes().set_aspect('equal')
159
+ plt.gca().xaxis.set_major_locator(plt.MultipleLocator(1))
160
+ plt.gca().yaxis.set_major_locator(plt.MultipleLocator(1))
161
+ plt.gca().get_xaxis().set_visible(show_axes)
162
+ plt.gca().get_yaxis().set_visible(show_axes)
163
+ mkgrid(p)
164
+ plt.axis('scaled')
165
+
166
+ def fig(p):
167
+ setup(p)
168
+ return plt
169
+
170
+ def show(p, show_axes=False):
171
+ setup(p, show_axes)
172
+ plt.show()
173
+
174
+ def main():
175
+ inp = input(">>>")
176
+
177
+ while inp != "":
178
+ show(parse(inp))
179
+ inp = input(">>>")
180
+
181
+ if __name__ == '__main__':
182
+ main()
webui.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import visual
3
+ import gen
4
+ import subprocess
5
+ import sys
6
+
7
+ def showpuzzle(encoding):
8
+ p1 = visual.parse(encoding)
9
+ return visual.fig(p1)
10
+
11
+ showpuzzint = gr.Interface(fn=showpuzzle, inputs=gr.Text(label="Puzzle encoding"), outputs=gr.Plot())
12
+
13
+
14
+ def genpuzzle(width,height,is_square,shuffle,seed):
15
+ if is_square:
16
+ height=width
17
+ p = gen.genpuzzle(width,height,seed)
18
+ if seed==0:
19
+ seed=gen.seed1
20
+ if not shuffle:
21
+ return visual.fig(p),seed,gen.h(p),''
22
+ p_ = gen.shuf(p)
23
+ p2 = visual.join(p,p_)
24
+ return visual.fig(p2),seed,gen.h(p),gen.h(p_)
25
+
26
+ genint = gr.Interface(fn=genpuzzle, inputs=[gr.Slider(label="Width",step=1,minimum=1,maximum=40,value=3),gr.Slider(label="Height",step=1,minimum=1,maximum=40,value=3),gr.Checkbox(label="Square",value=True),gr.Checkbox(label="Shuffle"),gr.Number(label="Seed",value=0)], outputs=[gr.Plot(label="Puzzle"),gr.Number(label="Seed"),gr.Text(label="Puzzle encoding"),gr.Text(label="Shuffled encoding")])
27
+
28
+ def shuffle(encoding,show):
29
+ p = visual.parse(encoding)
30
+ p_ = gen.shuf(p)
31
+ if show:
32
+ return visual.fig(p_),gen.h(p_)
33
+ return None,gen.h(p_)
34
+
35
+ shufint = gr.Interface(fn=shuffle, inputs=[gr.Text(label="Puzzle encoding"),gr.Checkbox(label="Visualise")], outputs=[gr.Plot(label="Visualisation"),gr.Text(label="Shuffled encoding")])
36
+
37
+ def sendcommand(function,args):
38
+ process = subprocess.run(["ghc", "Challenges.hs", "-e", f'{function} {args}' ], check=True, capture_output=True)
39
+ return process.stdout.decode(sys.stdout.encoding).strip()
40
+
41
+ commandint = gr.Interface(fn=sendcommand, inputs=['text','text'], outputs='text', description="sends a function call to Challenges.hs")
42
+
43
+ def runtests():
44
+ output = subprocess.run(["runghc", "../Tests.hs"],check=True,capture_output=True).stderr.decode(sys.stdout.encoding).strip()
45
+ if output == "":
46
+ return "All tests passed"
47
+ return output
48
+
49
+ testint = gr.Interface(fn=runtests, inputs=None, outputs='text', description="runs automated tests from Tests.hs")
50
+
51
+ demo = gr.TabbedInterface([showpuzzint,genint,shufint,commandint,testint],["visualise","generate","shuffle","run","test"],title="comp2209 Tools")
52
+ demo.launch()