2018-02-09 00:29:16 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
""" Generates Marching Cubes' algorithm array of triangles
|
|
|
|
|
|
|
|
Generates the array of triangles for a given intersection pattern of a unit
|
|
|
|
cube.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from functools import reduce
|
2018-02-09 00:58:09 +01:00
|
|
|
import sys
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
|
2018-02-09 01:28:06 +01:00
|
|
|
PREAMBLE = """
|
2018-02-11 18:34:00 +01:00
|
|
|
#include "../MarchingCubes.hpp"
|
2018-02-09 01:28:06 +01:00
|
|
|
|
|
|
|
typedef MarchingCubes::CubeTri Tri;
|
|
|
|
typedef std::vector<Tri> TriVect;
|
|
|
|
typedef MarchingCubes::CubeEdge Edge;
|
2018-02-11 20:39:59 +01:00
|
|
|
|
|
|
|
const TriVect MarchingCubes::edges_of_intersection[] = {
|
2018-02-09 01:28:06 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
2018-02-09 00:29:16 +01:00
|
|
|
class Vert:
|
|
|
|
frnt_bot_l = (0, 0, 1)
|
|
|
|
frnt_bot_r = (1, 0, 1)
|
|
|
|
frnt_top_l = (0, 1, 1)
|
|
|
|
frnt_top_r = (1, 1, 1)
|
|
|
|
back_bot_l = (0, 0, 0)
|
|
|
|
back_bot_r = (1, 0, 0)
|
|
|
|
back_top_l = (0, 1, 0)
|
|
|
|
back_top_r = (1, 1, 0)
|
|
|
|
|
|
|
|
|
|
|
|
class Edge:
|
|
|
|
def __init__(self, v1, v2):
|
|
|
|
self.vert = [
|
|
|
|
v1 if v1 < v2 else v2,
|
|
|
|
v2 if v1 < v2 else v1,
|
|
|
|
]
|
|
|
|
if v1 == v2:
|
|
|
|
raise ValueError((v1, v2))
|
|
|
|
|
|
|
|
def same_as(self, oth):
|
|
|
|
return self.vert == oth.vert
|
|
|
|
|
|
|
|
def transform(self, transf):
|
|
|
|
return Edge(
|
|
|
|
transf(self.vert[0]),
|
|
|
|
transf(self.vert[1]))
|
|
|
|
|
|
|
|
def dump(self):
|
|
|
|
def cbool(val):
|
2018-02-09 01:28:06 +01:00
|
|
|
return "1" if val else "0"
|
2018-02-09 00:29:16 +01:00
|
|
|
|
2018-02-09 01:28:06 +01:00
|
|
|
return "Edge({}, {}, {}, {}, {}, {})".format(
|
2018-02-09 00:29:16 +01:00
|
|
|
cbool(self.vert[0][0]),
|
|
|
|
cbool(self.vert[0][1]),
|
|
|
|
cbool(self.vert[0][2]),
|
|
|
|
cbool(self.vert[1][0]),
|
|
|
|
cbool(self.vert[1][1]),
|
|
|
|
cbool(self.vert[1][2]))
|
|
|
|
|
|
|
|
|
|
|
|
class Edg:
|
|
|
|
frnt_l = Edge(Vert.frnt_bot_l, Vert.frnt_top_l)
|
|
|
|
frnt_r = Edge(Vert.frnt_bot_r, Vert.frnt_top_r)
|
|
|
|
frnt_top = Edge(Vert.frnt_top_l, Vert.frnt_top_r)
|
|
|
|
frnt_bot = Edge(Vert.frnt_bot_l, Vert.frnt_bot_r)
|
|
|
|
back_l = Edge(Vert.back_bot_l, Vert.back_top_l)
|
|
|
|
back_r = Edge(Vert.back_bot_r, Vert.back_top_r)
|
|
|
|
back_top = Edge(Vert.back_top_l, Vert.back_top_r)
|
|
|
|
back_bot = Edge(Vert.back_bot_l, Vert.back_bot_r)
|
|
|
|
top_l = Edge(Vert.frnt_top_l, Vert.back_top_l)
|
|
|
|
top_r = Edge(Vert.frnt_top_r, Vert.back_top_r)
|
|
|
|
bot_l = Edge(Vert.frnt_bot_l, Vert.back_bot_l)
|
|
|
|
bot_r = Edge(Vert.frnt_bot_r, Vert.back_bot_r)
|
|
|
|
|
|
|
|
|
|
|
|
class TriangulatedCube:
|
2018-02-09 20:07:38 +01:00
|
|
|
ALL_ELTS = {
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
Vert.frnt_top_l,
|
|
|
|
Vert.frnt_top_r,
|
|
|
|
Vert.back_bot_l,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.back_top_l,
|
|
|
|
Vert.back_top_r,
|
|
|
|
}
|
|
|
|
|
2018-02-09 00:29:16 +01:00
|
|
|
def __init__(self, activated, triangles=None):
|
|
|
|
self.triangles = [] if triangles is None else triangles
|
|
|
|
self.activated = activated
|
2018-02-09 20:07:38 +01:00
|
|
|
self.non_activated = TriangulatedCube.ALL_ELTS - activated
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
def transform(self, transf):
|
|
|
|
n_tri = []
|
|
|
|
for tri in self.triangles:
|
|
|
|
n_edge = []
|
|
|
|
for edge in tri:
|
|
|
|
n_edge.append(edge.transform(transf))
|
|
|
|
n_tri.append(n_edge)
|
|
|
|
|
|
|
|
n_act = set()
|
|
|
|
for act in self.activated:
|
|
|
|
n_act.add(transf(act))
|
|
|
|
return TriangulatedCube(n_act, n_tri)
|
|
|
|
|
2018-02-09 00:53:47 +01:00
|
|
|
def reverse_activated(self):
|
2018-02-09 20:07:38 +01:00
|
|
|
self.activated, self.non_activated = \
|
|
|
|
self.non_activated, self.activated
|
2018-02-09 00:53:47 +01:00
|
|
|
|
2018-02-09 00:29:16 +01:00
|
|
|
def activated_code(self):
|
|
|
|
out = 0
|
|
|
|
for act in self.activated:
|
|
|
|
val = (act[0]
|
|
|
|
+ (act[1] << 1)
|
|
|
|
+ (act[2] << 2))
|
|
|
|
out |= (1 << val)
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
def dump_tri(tri):
|
2018-02-09 01:28:06 +01:00
|
|
|
return ("Tri({}, {}, {})".format(
|
|
|
|
tri[0].dump(), tri[1].dump(), tri[2].dump()))
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
def rot_general(vert, fixed, ax1, ax2):
|
|
|
|
cases = {
|
|
|
|
(0, 0): (0, 1),
|
|
|
|
(0, 1): (1, 1),
|
|
|
|
(1, 1): (1, 0),
|
|
|
|
(1, 0): (0, 0),
|
|
|
|
}
|
|
|
|
moved = cases[(vert[ax1], vert[ax2])]
|
|
|
|
out = [0] * 3
|
|
|
|
out[fixed] = vert[fixed]
|
|
|
|
out[ax1] = moved[0]
|
|
|
|
out[ax2] = moved[1]
|
|
|
|
return (out[0], out[1], out[2])
|
|
|
|
|
|
|
|
|
|
|
|
def rot_x(vert):
|
|
|
|
return rot_general(vert, 0, 1, 2)
|
|
|
|
|
|
|
|
|
|
|
|
def rot_y(vert):
|
|
|
|
return rot_general(vert, 1, 2, 0)
|
|
|
|
|
|
|
|
|
|
|
|
def rot_z(vert):
|
|
|
|
return rot_general(vert, 2, 0, 1)
|
|
|
|
|
|
|
|
|
|
|
|
def all_transforms():
|
|
|
|
def compose2(fun1, fun2):
|
|
|
|
return lambda x: fun1(fun2(x))
|
|
|
|
|
|
|
|
def compose(*fs):
|
|
|
|
return reduce(compose2, fs, lambda x: x)
|
|
|
|
|
|
|
|
def funcpow(fun, exp):
|
|
|
|
return compose(*([fun] * exp))
|
|
|
|
|
|
|
|
output = []
|
|
|
|
|
|
|
|
for num_rx in range(4):
|
|
|
|
for num_ry in range(4):
|
|
|
|
for num_rz in range(4):
|
2018-02-09 01:39:49 +01:00
|
|
|
cur = compose(
|
|
|
|
funcpow(rot_x, num_rx),
|
|
|
|
funcpow(rot_y, num_ry),
|
|
|
|
funcpow(rot_z, num_rz))
|
|
|
|
output.append(cur)
|
2018-02-09 00:29:16 +01:00
|
|
|
return output
|
|
|
|
|
|
|
|
|
2018-02-09 01:52:00 +01:00
|
|
|
def gen_index(base_cases):
|
2018-02-09 00:29:16 +01:00
|
|
|
transforms = all_transforms()
|
|
|
|
index = [None for x in range(256)]
|
|
|
|
for case in base_cases:
|
|
|
|
code = case.activated_code()
|
|
|
|
assert index[code] is None
|
|
|
|
index[code] = case
|
|
|
|
|
|
|
|
for transf in transforms:
|
2018-02-09 00:53:47 +01:00
|
|
|
for rev_activated in [False, True]:
|
|
|
|
for case in base_cases:
|
|
|
|
tr_case = case.transform(transf)
|
|
|
|
if rev_activated:
|
|
|
|
tr_case.reverse_activated()
|
|
|
|
code = tr_case.activated_code()
|
|
|
|
if index[code] is None:
|
|
|
|
index[code] = tr_case
|
2018-02-09 00:29:16 +01:00
|
|
|
|
2018-02-09 00:58:09 +01:00
|
|
|
has_unbound = False
|
2018-02-09 00:29:16 +01:00
|
|
|
for (val, tri) in enumerate(index):
|
|
|
|
if tri is None:
|
2018-02-09 00:58:09 +01:00
|
|
|
print(">> UNBOUND {}".format(val), file=sys.stderr)
|
|
|
|
has_unbound = True
|
|
|
|
if has_unbound:
|
|
|
|
raise RuntimeError("Some cases were not generated.")
|
2018-02-09 00:29:16 +01:00
|
|
|
|
2018-02-09 01:52:00 +01:00
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
|
|
def pretty_print(index):
|
|
|
|
output = ""
|
|
|
|
output += PREAMBLE
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
for (case_id, case) in enumerate(index):
|
2018-02-12 00:30:06 +01:00
|
|
|
output += "\tTriVect({{ // == {}\n".format(case_id)
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
for (tri_id, tri) in enumerate(case.triangles):
|
2018-02-09 01:52:00 +01:00
|
|
|
output += '\t\t{}{}'.format(
|
|
|
|
dump_tri(tri),
|
|
|
|
",\n" if tri_id != len(case.triangles) - 1 else '\n')
|
|
|
|
output += '\t\t})' + (',\n' if case_id != len(index) - 1 else '\n')
|
|
|
|
output += "};\n"
|
|
|
|
|
|
|
|
return output
|
|
|
|
|
|
|
|
|
|
|
|
def do_main(base_cases):
|
|
|
|
print(pretty_print(gen_index(base_cases)))
|
|
|
|
|
|
|
|
|
|
|
|
BASE_CASES = [
|
|
|
|
# Source: <https://en.wikipedia.org/wiki/File:MarchingCubes.svg>
|
|
|
|
# -- 1 --
|
|
|
|
TriangulatedCube(set(), []),
|
|
|
|
# -- 2 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.frnt_l,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 3 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 4 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.frnt_top_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.frnt_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_top,
|
|
|
|
Edg.top_r,
|
|
|
|
Edg.frnt_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
|
|
|
|
# -- 5 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.back_bot_l,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.back_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.frnt_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.back_l,
|
|
|
|
Edg.bot_l,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 6 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.back_bot_l,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.back_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.back_l,
|
|
|
|
Edg.frnt_l,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 7 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_top_l,
|
|
|
|
Vert.back_bot_l,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.back_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.frnt_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.back_l,
|
|
|
|
Edg.bot_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.frnt_top,
|
|
|
|
Edg.top_l,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 8 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.frnt_top_r,
|
|
|
|
Vert.back_top_l,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.frnt_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_top,
|
|
|
|
Edg.top_r,
|
|
|
|
Edg.frnt_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_bot,
|
|
|
|
Edg.bot_r,
|
|
|
|
Edg.back_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.top_l,
|
|
|
|
Edg.back_l,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 9 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.back_bot_l,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.back_top_l,
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.top_l,
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.back_top,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.back_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 10 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.back_top_l,
|
|
|
|
Vert.back_bot_l,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.top_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.top_l,
|
|
|
|
Edg.back_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.top_l,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.back_top,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.frnt_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 11 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.back_top_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.bot_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.top_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 12 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
Vert.back_top_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.bot_l,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.top_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 13 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_top_l,
|
|
|
|
Vert.frnt_bot_r,
|
|
|
|
Vert.back_top_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.frnt_top,
|
|
|
|
Edg.top_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_r,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_r,
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.top_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 14 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.frnt_top_l,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.back_top_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_top,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.top_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.top_l,
|
|
|
|
Edg.bot_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_bot,
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_top,
|
|
|
|
Edg.bot_r,
|
|
|
|
Edg.top_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
# -- 15 --
|
|
|
|
TriangulatedCube(
|
|
|
|
{
|
|
|
|
Vert.frnt_bot_l,
|
|
|
|
Vert.back_bot_l,
|
|
|
|
Vert.back_bot_r,
|
|
|
|
Vert.back_top_r,
|
|
|
|
},
|
|
|
|
[
|
|
|
|
[
|
|
|
|
Edg.frnt_l,
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.back_l,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.back_l,
|
|
|
|
Edg.top_r,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.back_l,
|
|
|
|
Edg.top_r,
|
|
|
|
Edg.back_top,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
Edg.frnt_bot,
|
|
|
|
Edg.top_r,
|
|
|
|
Edg.bot_r,
|
|
|
|
],
|
|
|
|
]),
|
|
|
|
]
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2018-02-09 01:52:00 +01:00
|
|
|
do_main(BASE_CASES)
|
2018-02-09 00:29:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|