teufelsknoten.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #!/usr/bin/env python3
  2. from __future__ import print_function
  3. import numpy as np
  4. import itertools
  5. import sys
  6. class Stick(object):
  7. XPAR = 0
  8. YPAR = 1
  9. ZPAR = 2
  10. def __init__(self, name, array):
  11. self.name = name
  12. self.array = np.asarray(array)
  13. @property
  14. def dir(self):
  15. if len(self.array) > 2:
  16. return self.ZPAR
  17. elif len(self.array[0]) > 2:
  18. return self.YPAR
  19. return self.XPAR
  20. def rot(self, axis, n=1):
  21. if n == 0:
  22. return self
  23. if axis == 0: # X
  24. newArray = np.rot90(self.array, n, (0, 1))
  25. elif axis == 1: # Y
  26. newArray = np.rot90(self.array, n, (0, 2))
  27. elif axis == 2: # Z
  28. newArray = np.rot90(self.array, n, (1, 2))
  29. return self.__class__(self.name, newArray)
  30. def __genXZRotations(self):
  31. self.rotX = self.rot(0)
  32. self.rotZ = self.rot(2)
  33. def __genBasicRotations(self):
  34. self.basicRotations = []
  35. for rotY in (0, 1, 2, 3):
  36. for rotX in (0, 2):
  37. r = self.rot(0, rotX).rot(1, rotY)
  38. for other in self.basicRotations:
  39. if np.array_equal(other.array, r.array):
  40. break
  41. else:
  42. r.__genXZRotations()
  43. self.basicRotations.append(r)
  44. def genRotations(self):
  45. self.__genXZRotations()
  46. self.rotX.__genBasicRotations()
  47. self.rotZ.__genBasicRotations()
  48. self.__genBasicRotations()
  49. def __str__(self):
  50. if self.dir == self.XPAR:
  51. ret = [ self.name + " " ]
  52. for y in range(2):
  53. if y > 0:
  54. ret.append(" " * (len(self.name) + 1))
  55. for x in range(16):
  56. if x <= 5 or x >= 10:
  57. ret.append("=")
  58. else:
  59. xx = x - 6
  60. if self.array[0][y][xx] and\
  61. self.array[1][y][xx]:
  62. ret.append("#")
  63. elif self.array[0][y][xx]:
  64. ret.append("@")
  65. elif self.array[1][y][xx]:
  66. ret.append("*")
  67. else:
  68. ret.append(" ")
  69. ret.append("\n")
  70. elif self.dir == self.YPAR:
  71. ret = [ self.name + "\n" ]
  72. for y in range(16):
  73. for x in range(2):
  74. if y <= 5 or y >= 10:
  75. ret.append("=")
  76. else:
  77. yy = y - 6
  78. if self.array[0][yy][x] and\
  79. self.array[1][yy][x]:
  80. ret.append("#")
  81. elif self.array[0][yy][x]:
  82. ret.append("@")
  83. elif self.array[1][yy][x]:
  84. ret.append("*")
  85. else:
  86. ret.append(" ")
  87. ret.append("\n")
  88. else:
  89. ret = [ self.name + " " ]
  90. for y in range(2):
  91. if y > 0:
  92. ret.append(" " * (len(self.name) + 1))
  93. ret.append("|")
  94. for x in range(2):
  95. s = np.sum(self.array, 0)
  96. ret.append(str(s[y][x]))
  97. ret.append("|\n")
  98. return "".join(ret)
  99. def __repr__(self):
  100. return repr(self.array).replace("array", "Stick")
  101. def __hash__(self):
  102. return hash(self.name)
  103. def __eq__(self, other):
  104. return self.name == other.name
  105. def __ne__(self, other):
  106. return self.name != other.name
  107. class Solution(object):
  108. def __init__(self, a, b, c, d, e, f):
  109. self.a = a
  110. self.b = b
  111. self.c = c
  112. self.d = d
  113. self.e = e
  114. self.f = f
  115. def rot(self, axis):
  116. new = self.__class__(self.a, self.b, self.c, self.d, self.e, self.f)
  117. new.a = new.a.rot(axis, 2)
  118. new.b = new.b.rot(axis, 2)
  119. new.c = new.c.rot(axis, 2)
  120. new.d = new.d.rot(axis, 2)
  121. new.e = new.e.rot(axis, 2)
  122. new.f = new.f.rot(axis, 2)
  123. if axis == 0:
  124. new.c, new.d = new.d, new.c
  125. new.e, new.f = new.f, new.e
  126. elif axis == 1:
  127. new.a, new.b = new.b, new.a
  128. new.c, new.d = new.d, new.c
  129. elif axis == 2:
  130. new.a, new.b = new.b, new.a
  131. new.e, new.f = new.f, new.e
  132. return new
  133. def __eq_exact(self, other):
  134. return np.array_equal(self.a.array, other.a.array) and\
  135. np.array_equal(self.b.array, other.b.array) and\
  136. np.array_equal(self.c.array, other.c.array) and\
  137. np.array_equal(self.d.array, other.d.array) and\
  138. np.array_equal(self.e.array, other.e.array) and\
  139. np.array_equal(self.f.array, other.f.array)
  140. def __eq__(self, other):
  141. sx = self
  142. #FIXME this is wrong
  143. for x in range(2):
  144. if x > 0:
  145. sx = sx.rot(0)
  146. sy = sx
  147. for y in range(2):
  148. if y > 0:
  149. sy = sy.rot(1)
  150. sz = sy
  151. for z in range(2):
  152. if z > 0:
  153. sz = sz.rot(2)
  154. if sz.__eq_exact(other):
  155. return True
  156. return False
  157. def __ne__(self, other):
  158. return not self.__eq__(other)
  159. def __str__(self):
  160. a, b, c, d, e, f = self.a, self.b, self.c, self.d, self.e, self.f
  161. ret = "a=%s, b=%s, c=%s, d=%s e=%s f=%s\n" % (
  162. a.name, b.name, c.name, d.name, e.name, f.name)
  163. lines = []
  164. for lineA, lineB in zip(str(a).splitlines(), str(b).splitlines()):
  165. lines.append(lineA + " " + lineB)
  166. ret += "\n".join(lines) + "\n\n"
  167. ret += "\n".join((str(c), str(d), str(e), str(f)))
  168. return ret
  169. s1 = Stick("s1", (
  170. ( (1, 1),
  171. (1, 1),
  172. (1, 1),
  173. (1, 1), ),
  174. ( (1, 1),
  175. (1, 1),
  176. (1, 1),
  177. (1, 1), ), ))
  178. s2 = Stick("s2", (
  179. ( (0, 0),
  180. (0, 0),
  181. (0, 0),
  182. (0, 0), ),
  183. ( (1, 1),
  184. (1, 1),
  185. (1, 1),
  186. (1, 1), ), ))
  187. s3 = Stick("s3", (
  188. ( (1, 0),
  189. (0, 0),
  190. (0, 0),
  191. (1, 1), ),
  192. ( (1, 0),
  193. (1, 0),
  194. (1, 1),
  195. (1, 1), ), ))
  196. s4 = Stick("s4", (
  197. ( (0, 1),
  198. (0, 0),
  199. (0, 0),
  200. (1, 1), ),
  201. ( (0, 1),
  202. (0, 1),
  203. (1, 1),
  204. (1, 1), ), ))
  205. s5 = Stick("s5", (
  206. ( (0, 0),
  207. (0, 0),
  208. (0, 0),
  209. (0, 0), ),
  210. ( (1, 1),
  211. (0, 1),
  212. (0, 1),
  213. (1, 1), ), ))
  214. s6 = Stick("s6", (
  215. ( (0, 1),
  216. (0, 0),
  217. (0, 0),
  218. (0, 1), ),
  219. ( (0, 1),
  220. (1, 1),
  221. (1, 1),
  222. (0, 1), ), ))
  223. sticks = {s1, s2, s3, s4, s5, s6}
  224. for s in sticks:
  225. s.genRotations()
  226. emptyPlaneXY = np.asarray(
  227. (
  228. ( (0, 0, 0, 0),
  229. (0, 0, 0, 0),
  230. (0, 0, 0, 0),
  231. (0, 0, 0, 0), ),
  232. )
  233. )
  234. emptyPlaneYZ = np.rot90(emptyPlaneXY, 1, (0, 2))
  235. emptyPlaneXZ = np.rot90(emptyPlaneXY, 1, (0, 1))
  236. cornersXY = np.asarray(
  237. (
  238. ( (1, 0, 0, 1),
  239. (0, 0, 0, 0),
  240. (0, 0, 0, 0),
  241. (1, 0, 0, 1), ),
  242. ( (1, 0, 0, 1),
  243. (0, 0, 0, 0),
  244. (0, 0, 0, 0),
  245. (1, 0, 0, 1), ),
  246. )
  247. )
  248. cornersYZ = np.rot90(cornersXY, 1, (0, 2))
  249. cornersXZ = np.rot90(cornersXY, 1, (0, 1))
  250. def processZPlane(a, b, c, d, cRot, dRot, abcd):
  251. remainingSticks = sticks - {a, b, c, d}
  252. for _e, _f in itertools.permutations(remainingSticks, 2):
  253. for e in _e.basicRotations:
  254. eRot = e.rotX
  255. for f in _f.basicRotations:
  256. fRot = f.rotX
  257. ef = np.hstack((eRot.array, fRot.array))
  258. if np.any(ef - cornersYZ < 0):
  259. continue # At least one corner is not filled.
  260. efFilled = np.dstack((emptyPlaneYZ, ef, emptyPlaneYZ))
  261. abcdef = abcd + efFilled
  262. if not np.any(abcdef > 1):
  263. yield Solution(a, b, cRot, dRot, eRot, fRot)
  264. def processXYPlane():
  265. for _a, _b, _c, _d in itertools.permutations(sticks, 4):
  266. for a in _a.basicRotations:
  267. for b in _b.basicRotations:
  268. ab = np.dstack((a.array, b.array))
  269. if np.any(ab - cornersXY < 0):
  270. continue # At least one corner is not filled.
  271. for c in _c.basicRotations:
  272. cRot = c.rotZ
  273. for d in _d.basicRotations:
  274. dRot = d.rotZ
  275. cd = np.vstack((cRot.array, dRot.array))
  276. if np.any(cd - cornersXZ < 0):
  277. continue # At least one corner is not filled.
  278. abFilled = np.vstack((emptyPlaneXY, ab, emptyPlaneXY))
  279. cdFilled = np.hstack((emptyPlaneXZ, cd, emptyPlaneXZ))
  280. abcd = abFilled + cdFilled
  281. if np.any(abcd > 1):
  282. continue # This one collides in the XY plane already.
  283. yield from processZPlane(a, b, c, d, cRot, dRot, abcd)
  284. solutions = []
  285. for solution in processXYPlane():
  286. for otherSolution in solutions:
  287. if solution == otherSolution:
  288. break # We have that one already
  289. else:
  290. solutions.append(solution)
  291. print("Found %s solution(s).\n" % len(solutions))
  292. for i, solution in enumerate(solutions):
  293. print("Solution #%d: %s" % (i + 1, solution))