Replace multi-line string in terminal

I am trying to write some code which will overwrite its previous output e.g. the original output is "1" but the "1" is replaced with a "2". This makes it look as though the "1" had never been outputted in the first place. I have a list of lists called board, I convert this list into a multi-line string using this code:

rendered_board = ""
for y in range(board_height):
    for x in range(board_width):
        rendered_board += board[y][x]

    rendered_board += "\n"

I get the string I want, However, I want to be able to update this string with a new one and have it replace the old.

Example output :

+-------+                      +-------+
|       |  *gets replaced by*  |       |
|       | -------------------> |   *   |
|       |                      |       |
+-------+                      +-------+

Currently I have been trying to do this by using end="\r" e.g.

print(rendered_board, end="\r")
update_board()
print(rendered_board, end="\r")

This has resulted in this output:

+-------+
|       |
|       |
|       |
+-------+
+-------+
|       |
|   *   |
|       |
+-------+

Solution 1:

That's not as trivial as you might think. You can use \r (carriage return) to return back to the current line, or \b to move a character back - both reminiscent from the early electronic typewriter days - but to go around a screen you need more control over the terminal. Sure, you can call os.system('clear') on *nix / os.system('cls') on Windows to clear the whole screen and draw it again, but that's just not elegant.

Instead, Python provides the curses module designed just for that purpose. Here's a simple demonstration on how to use it:

import curses
import time

console = curses.initscr()  # initialize is our playground

# this can be more streamlined but it's enough for demonstration purposes...
def draw_board(width, height, charset="+-| "):
    h_line = charset[0] + charset[1] * (width - 2) + charset[0]
    v_line = charset[2] + charset[3] * (width - 2) + charset[2]
    console.clear()
    console.addstr(0, 0, h_line)
    for line in range(1, height):
        console.addstr(line, 0, v_line)
    console.addstr(height-1, 0, h_line)
    console.refresh()

def draw_star(x, y, char="*"):
    console.addstr(x, y, char)
    console.refresh()

draw_board(10, 10)  # draw a board
time.sleep(1)  # wait a second
draw_star(6, 6)  # draw our star
time.sleep(1)  # wait a second
draw_star(6, 6, " ")  # clear the star
draw_star(3, 3)  # place the star on another position
time.sleep(3)  # wait a few seconds

curses.endwin()  # return control back to the console

Of course, you can get far fancier than this, but it should give you the idea on how to utilize it.

Solution 2:

You haven't specified if you need solution working on Linux or Windows.

For Linux you can use zwer answer. However curses module is not available on Windows.

Windows cmd is very simple and doesn't understand advanced formatting. You have clear and redraw the whole window to get animation.

Here is an example of a star moving randomly on board for 20 frames of animation:

import os
import time
import random
import copy


board = [['+', '-', '-', '-', '-', '-', '-', '-', '-', '+'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'],
 ['+', '-', '-', '-', '-', '-', '-', '-', '-', '+']]


for i in range(20):
    os.system('cls')
    frame = copy.deepcopy(board)
    frame[random.randint(1, 8)][random.randint(1, 8)] = '*'
    print('\n'.join([''.join(i) for i in frame]))
    time.sleep(1)