
NO = ( 0, 0, 0, 2, 0)
Pi = (+1, 0, 0, 0, 0)
PI = (+1, 0, 0, 0, 1)
Pj = ( 0,+1, 0, 0, 0)
PJ = ( 0,+1, 0, 0, 1)
PK = ( 0, 0,+1, 0, 2)
Mi = (-1, 0, 0, 1, 0)
MI = (-1, 0, 0, 1, 1)
Mj = ( 0,-1, 0, 1, 0)
MJ = ( 0,-1, 0, 1, 1)
MK = ( 0, 0,-1, 1, 2)
#      i  j  k  ^  ^
#               |  Id for comparison
#               Lifetime index

PRODUCE = {
    PI: [ Mj ], # +I -> -j
    MJ: [ Mi ], # -J -> -i
    MI: [ Pj ], # -I -> +j
    PJ: [ Pi ], # +J -> +i
    NO: [ NO ],
    Pi: [ NO ],
    Pj: [ NO ],
    Mi: [ NO ],
    Mj: [ NO ],
    PK: [ PI, MI, PJ, MJ ], # +K -> +I, -J, -I, +J
    MK: [ PI, MI, PJ, MJ ], # -K -> +I, -J, -I, +J
}


class Index:
    LastDistance = 0
    NumberOfVisits = 0
    MinIndex = 0
    MaxIndex = 0
    def __init__(self, i, j, k, lifetime, direction):
        self.globalLifetime = lifetime
        self.direction = direction

        # Assign parent's position
        self.i = i
        self.j = j
        self.k = k

        # Step away from parent
        self.lifetime = lifetime[direction[3]]
        self.step()
 
    def isLive(self):
        return self.lifetime > 0

    def visit(self):
        Index.NumberOfVisits += 1
        distance = self.distance()
        if distance < Index.LastDistance:
     	   raise NameError("Order is not preserved")
     	Index.LastDistance = distance
        Index.MinIndex = min(self.i, Index.MinIndex)
        Index.MinIndex = min(self.j, Index.MinIndex)
        Index.MinIndex = min(self.k, Index.MinIndex)
        Index.MaxIndex = max(self.i, Index.MaxIndex)
        Index.MaxIndex = max(self.j, Index.MaxIndex)
        Index.MaxIndex = max(self.k, Index.MaxIndex)
        print("[{}, {}, {}]".format(self.i, self.j, self.k))

    def step(self):
        # Move in your direction
        self.i += self.direction[0]
        self.j += self.direction[1]
        self.k += self.direction[2]

    def iterate(self):
        self.lifetime -= 1
         
    def produce(self, result):
        for direction in PRODUCE[self.direction]:
            self.create(direction, result)

    def create(self, direction, result):
        index = Index(self.i, self.j, self.k, self.globalLifetime, direction)
        if index.isLive():
            result.append(index)

    def distance(self):
        # Manhattan Distance
        return abs(self.i) + abs(self.j) + abs(self.k)

def Traverse(N):
    TotalNumber = N*N*N
    halfA = halfB = (N-1)/2
    if N % 2 == 0:
        halfA = N/2
        halfB = N/2-1

    MinIndex = -min(halfB, halfA)
    MaxIndex = max(halfB, halfA)

    lifetime = (halfA, halfB, 0)

    SecondaryNodes = []
    MainNodes = []
    KNodes = []

    # visit center
    center = Index(0, 0, 0, lifetime, NO)
    center.visit()

    center.create(PI, MainNodes)
    center.create(MI, MainNodes)
    center.create(PJ, MainNodes)
    center.create(MJ, MainNodes)
    center.create(PK, KNodes)
    center.create(MK, KNodes)

    while len(SecondaryNodes) + len(MainNodes) + len(KNodes) > 0:

        # First - visit all side nodes
        temp = []
        for m in SecondaryNodes:
            m.visit()
            m.step()
            m.iterate()
            # Save node only if it is alive
            if m.isLive():
                temp.append(m)

        SecondaryNodes = temp

        # Second - visit all main nodes as they may produce secondary nodes
        temp = []
        for m in MainNodes:
            m.visit() # 1 - Visit
            m.produce(SecondaryNodes) # 2 - Produce second
            m.step() # 3 - Step 
            m.iterate() # 4 - Lose a life
            if m.isLive():
                temp.append(m)

        MainNodes = temp

        # Third - visit all K nodes as they may produce main nodes
        temp = []
        for m in KNodes:
            m.visit()
            m.produce(MainNodes)
            m.step()
            m.iterate()
            if m.isLive():
                temp.append(m)

        KNodes = temp
    if TotalNumber != Index.NumberOfVisits:
        raise NameError("Number of visited elements does not match {}/{}".format(Index.NumberOfVisits, TotalNumber))
    if MinIndex != Index.MinIndex:
        raise NameError("Minimal index is out of bounds {}/{}".format(Index.MinIndex, MinIndex))
    if MaxIndex != Index.MaxIndex:
        raise NameError("Maximal index is out of bounds {}/{}".format(Index.MaxIndex, MaxIndex))

Traverse(6)