Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Is there a better way of doing this terminal animation without having a load of if ... 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)
share|improve this question
    
I guess that a progress bar, shouldn't you write \b 9 times? –  tokland Feb 8 '13 at 14:06
add comment

2 Answers

up vote 8 down vote accepted

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

share|improve this answer
3  
+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.) –  Gareth Rees Feb 8 '13 at 12:11
    
Alternatively, you could sys.stdout.write('\r') instead of the \b\b\b, since the animation steps have equal length. –  Attila O. Feb 9 '13 at 0:42
    
@AttilaO. thanks, I use IDLE and don't actually know how these terminal characters work but I'll take your word for it... –  Stuart Feb 9 '13 at 11:12
    
Wow, fantastic answer. Thank you –  user1051390 Feb 9 '13 at 12:59
add comment

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)
share|improve this answer
    
Does not replicate the original animation –  Stuart Feb 8 '13 at 13:53
    
@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. –  tokland Feb 8 '13 at 14:03
1  
If it is on a single line, you could use \r instead. –  Attila O. Feb 9 '13 at 0:43
    
indeed, updated –  tokland Feb 10 '13 at 19:33
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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