add files
Browse files
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()
|