With this code and your example data, both a/c/0.png and b/0.png get renamed to 1_0.png. This happens because changes to $n inside ( ) get lost when you leave the subshell.
Use bash's "globstar" feature to recurse for you, and assign numbers as normal. **/ matches all subdirectories. You want the current directory too, so add . to the list.
This doesn't ...
Overall your implementation is fine, I would just suggest a few things:
Naming - move_rings and helper are not very descriptive. Give them better names, and give them good docstrings (I like NumpyDoc, but personal preference is fine)
move_rings shouldn't both do calculations and print to stdout. Instead, move_rings should return the information, and the ...
I'll focus this review around this statement:
I hate it, it is ugly as hell and I feel like there should be more simpler and elegant ..
You then suggest some object-oriented (visitor, composite) and functional (generator) patterns as ways to improve readability. I would indeed make the code adhere more to OO principles.
Separation of ...