10
\$\begingroup\$

Is there a better way of creating this terminal animation without having a series of conditional statements (i.e. if ... elif ... elif...)?

import sys, time
count = 100
i = 0
num = 0

def animation():
    global num
    if num == 0:
        num += 1
        return '[=      ]'
    elif num == 1:
        num += 1
        return '[ =     ]'
    elif num == 2:
        num += 1
        return '[  =    ]'
    elif num == 3:
        num += 1
        return '[   =   ]'
    elif num == 4:
        num += 1
        return '[    =  ]'
    elif num == 5:
        num += 1
        return '[     = ]'
    elif num == 6:
        num += 1
        return '[      =]'
    elif num == 7:
        num += 1
        return '[      =]'
    elif num == 8:
        num += 1
        return '[     = ]'
    elif num == 9:
        num += 1
        return '[    =  ]'
    elif num == 10:
        num += 1
        return '[   =   ]'
    elif num == 11:
        num += 1
        return '[  =    ]'
    elif num == 12:
        num += 1
        return '[ =     ]'
    elif num == 13:
        num = 0
        return '[=      ]'

while i < count:
    sys.stdout.write('\b\b\b')
    sys.stdout.write(animation())
    sys.stdout.flush()
    time.sleep(0.2)
\$\endgroup\$
1
  • \$\begingroup\$ I guess that a progress bar, shouldn't you write \b 9 times? \$\endgroup\$
    – tokland
    Feb 8 '13 at 14:06
16
\$\begingroup\$

You can build up the string like this:

def animation(counter, length):
    stage = counter % (length * 2 + 2)
    if stage < length + 1:
        left_spaces = stage
    else:
        left_spaces = length * 2 - 1 - stage
    return '[' + ' ' * left_spaces + '=' + ' ' * (length - left_spaces) + ']'

for i in range(100):
    sys.stdout.write('\b\b\b')
    sys.stdout.write(animation(i, 6))
    sys.stdout.flush()
    time.sleep(0.2)

Alternatively store the animation strings in a tuple or list:

animation_strings = ('[=      ]', '[ =     ]', '[  =    ]', '[   =   ]',
                     '[    =  ]', '[     = ]', '[      =]', '[      =]',
                     '[     = ]', '[    =  ]', '[   =   ]', '[  =    ]',
                     '[ =     ]', '[=      ]')
for i in range(100):
    sys.stdout.write('\b\b\b')
    sys.stdout.write(animation_strings[i % len(animation_strings)])
    sys.stdout.flush()
    time.sleep(0.2)

You can replace the animation function with cycle from itertools:

import sys, time
from itertools import cycle
animation = cycle('[=      ]', '[ =     ]', '[  =    ]', '[   =   ]',
                  '[    =  ]', '[     = ]', '[      =]', '[      =]',
                  '[     = ]', '[    =  ]', '[   =   ]', '[  =    ]',
                  '[ =     ]', '[=      ]')
# alternatively:
# animation = cycle('[' + ' ' * n + '=' + ' ' * (6 - n) + ']' 
#                   for n in range(7) + range(6, -1, -1))

for _ in range(100):
    sys.stdout.write('\b\b\b')
    sys.stdout.write(animation.next())
    sys.stdout.flush()
    time.sleep(0.2)

Finally, you could make your own generator function.

def animation_generator(length):
    while True:
        for n in range(length + 1):
            yield '[' + ' ' * n + '=' + ' ' * (length - n) + ']'
        for n in range(length + 1):
            yield '[' + ' ' * (length - n) + '=' + ' ' * n + ']'

animation = animation_generator(6)
for _ in range(100):
    sys.stdout.write('\b\b\b')
    sys.stdout.write(animation.next())
    sys.stdout.flush()
    time.sleep(0.2)

EDIT: made the above suggestions less reliant on global variables

\$\endgroup\$
3
  • 3
    \$\begingroup\$ +1 for itertools.cycle. (But I think you'd want to build up the animation programmatically so that it would be easy to change its length.) \$\endgroup\$ Feb 8 '13 at 12:11
  • \$\begingroup\$ Alternatively, you could sys.stdout.write('\r') instead of the \b\b\b, since the animation steps have equal length. \$\endgroup\$
    – Attila O.
    Feb 9 '13 at 0:42
  • \$\begingroup\$ @AttilaO. thanks, I use IDLE and don't actually know how these terminal characters work but I'll take your word for it... \$\endgroup\$
    – Stuart
    Feb 9 '13 at 11:12
4
\$\begingroup\$

Notes:

  • Don't use global variables without a good (an extremely good) justification. A function is (should be) a black box that gets values and returns values (unless you have unavoidable side-effects to perform, for example reading a file or printing to the screen).
  • It's very cumbersome and inflexible to write every possible string of the progressbar by hand, use code instead to build them.

I'd write:

import sys
import time

def render(size, position):
    return "[" + (" " * position) + "=" + (" " * (size - position - 1)) + "]" 

def draw(size, iterations, channel=sys.stdout, waittime=0.2): 
    for index in range(iterations):
        n = index % (size*2)
        position = (n if n < size else size*2 - n - 1)
        bar = render(size, position)
        channel.write(bar + '\r')
        channel.flush()
        time.sleep(waittime)  

if __name__ == '__main__':
    draw(6, 100, channel=sys.stdout)
\$\endgroup\$
2
  • \$\begingroup\$ Does not replicate the original animation \$\endgroup\$
    – Stuart
    Feb 8 '13 at 13:53
  • \$\begingroup\$ @Stuart. I know, I thought it looked more beautiful without stopping at the limits. I'll update it. But I guess \b is expected to match the string length, otherwise it makes no sense. \$\endgroup\$
    – tokland
    Feb 8 '13 at 14:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.