7

I'm writing an arcanoid game in python on windows console and what bothers me is annoying blinking after every iteration of "displaying" loop. Does anyone have any idea how to reduce it? This is part of the code:

#-*- coding: UTF-8 -*-
import time
import os

clear = lambda: os.system('cls')
def gra():
    koniec='0'
    while koniec!='1':
        for i in range(4):
            for j in range(10):
                print '[___]',
            print '\n',
        for i in range(10):
            print '\n'
        for i in range(10):
            print ' ',
        print '~~~~~~~~~~'
        time.sleep(0.1)
        clear()
gra()
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Jarshah
  • 91
  • 1
  • 4

4 Answers4

4

There is a limit to what you can do, but gathering everything into 1 big string and then printing once between screen clears is better than a number of small prints in a loop. Run the following code and look how much better the second half of the program runs than the first half:

import time, os, random

def display1(chars):
    os.system('cls')
    for row in chars:
        print(''.join(row))

def display2(chars):
    os.system('cls')
    print('\n'.join(''.join(row) for row in chars))


chars = []
for i in range(40):
    chars.append(["-"]*40)

for i in range(100):
    r = random.randint(0,39)
    c = random.randint(0,39)
    chars[r][c] = "X"
    time.sleep(0.1)
    display1(chars)

os.system('cls')
time.sleep(1)

chars = []
for i in range(40):
    chars.append(["-"]*40)

for i in range(100):
    r = random.randint(0,39)
    c = random.randint(0,39)
    chars[r][c] = "X"
    time.sleep(0.1)
    display2(chars)

On Edit: You can combine these ideas with the excellent idea of @GingerPlusPlus to avoid cls. The trick is to print a large number of backspaces.

First -- write your own version of cls:

def cls(n = 0):
    if n == 0:
        os.system('cls')
    else:
        print('\b'*n)

The first time it is called -- pass it zero and it just clear the screen.

The following function pushes a character array to the command window in one big print and returns the number of characters printed (since this is the number of backspaces needed to reposition the cursor):

def display(chars):
    s = '\n'.join(''.join(row) for row in chars)
    print(s)
    return len(s)

Used like thus:

chars = []
for i in range(40):
    chars.append(["-"]*40)

for i in range(100):
    n = 0
    r = random.randint(0,39)
    c = random.randint(0,39)
    chars[r][c] = "X"
    time.sleep(0.1)
    cls(n)
    n = display(chars)

when the above code is run, the display changes smoothly with virtually no flicker.

John Coleman
  • 51,337
  • 7
  • 54
  • 119
  • I used your first suggestion and it is much better now. But there is something wrong with the second one. `def display(chars):`, `s = '\n'.join(''.join(row) for row in chars)`, `print(s)`, `return len(s)` len(s) has always the same value here, so this version of cls clears everything after every iteration. – Jarshah Jan 24 '16 at 16:05
  • @Jarshah That was the point -- to simulate clearing the screen by backspacing since it flickers a bit less than the widows `cls` command. You need to know how big the screen is by keeping track of how many characters you have just written to it. If only part of the screen needs repainting and you can keep track of how much to backspace -- just pass then back-space based `cls()` a different value. If you can figure out an optimal `n` to pass you could probably reduce flicker even more. – John Coleman Jan 24 '16 at 16:13
  • O.K. I thought that you wanted to find out where the last change was made and then count how many backspace you need to clear it. Maybe I'll try to apply something like this. – Jarshah Jan 24 '16 at 16:52
  • @Jarshah The code was mostly proof-of-concept. In my mental model I thought of the char array as being like an image file which is pushed to the screen at each step. I wrote the code that way since if you have an image which is some sense growing then you might push larger arrays to the screen, hence I didn't want to hard-wire the number of backspaces. If you have something like a background with moving objects then maybe you can keep track of how much to backspace to redraw the moving objects. – John Coleman Jan 24 '16 at 16:58
3

You could do:

rows_full=row_count+2
print("\033[F"*rows_full)

#print the screen again

The disadvantages are that you can't overwrite the very top bit of the console, and have to know how many rows have been printed. But knowing the row amount should be trivial when working with a fixed-rowed ASCII graphics game. It also isn't a CLS, it just moves you to the top.

Oh, the explanation! It uses the ANSI escape sequence F, which moves to the row above. It prints this many times, which puts you to the top of the screen. Also, you have to add 2 to the rowcount, otherwise the top rows will repeat.

PS: If anyone knows how to make it so that that method can reach the top part of the console, I'll give you some cookies.

ender_scythe
  • 470
  • 7
  • 16
  • one hack is to print an empty line on top on first run only. This could completely solve the issue. – Z Li Jan 11 '21 at 21:21
  • necro comment, appologies: `print("\033[2J\033[1;1H",end="")` will clear the screen and place you at the top left corner, `print("033[1;1H",end="")` just puts you at the top left. Also `os.get_terminal_size().lines` will get the terminal height in lines, `os.get_terminal_size().columns` for columns – Venya Oct 18 '22 at 14:36
1

I figured it out... You can use ANSI codes to move the cursor then clear the lines without any BLINK!

print('\033[4A\033[2K', end='')

\033[4A Moves the cursor 4 lines up (\033[{lines}A you can replace lines with however many you need) \033[2K Clears all those lines without the screen blinking. You can use it in a simple typewrite function that needs a constant message or a box around it like this:

from time import sleep   

def typewrite(text: str):
    lines = text.split('\n')
    for line in lines:
        display = ''
        for char in line:
            display += char
            print(f'╭─ SOME MESSAGE OR SOMEONES NAME ────────────────────────────────────────────╮')
            print(f'│ {display:74} │') # :74 is the same as ' ' * 74
            print(f'╰────────────────────────────────────────────────────────────────────────────╯')
            
            sleep(0.05)
            
            print('\033[3A\033[2K', end='')

The only problem with this is that the top line is blinking. To fix this all we need to do is to add a empty line that is blinking so the user cant see it. We also move the cursor up from 3 to 4 lines.

def typewrite(text: str):
    lines = text.split('\n')
    for line in lines:
        display = ''
        for char in line:
            display += char
            print('')
            print(f'╭─ SOME MESSAGE OR SOMEONES NAME ────────────────────────────────────────────╮')
            print(f'│ {display:74} │') # :74 is the same as ' ' * 74
            print(f'╰────────────────────────────────────────────────────────────────────────────╯')
            
            sleep(0.05)
            
            print('\033[4A\033[2K', end='')

To make this into your code just print your text and add a print('') at the start. Then use this print('\033[4A\033[2K', end='') but change the 4 to however many lines that you printed including the print(''). Then it should work without blinking. You can put print('\033[4B', end='') at the end which just moves the cursor back up.

If you want to hide the cursor you can use this gibberish or make the cursor the same color as the background:

import ctypes

if os.name == 'nt':
    class _CursorInfo(ctypes.Structure):
        _fields_ = [("size", ctypes.c_int),
                    ("visible", ctypes.c_byte)]
def hide_cursor() -> None:
    if os.name == 'nt':
        ci = _CursorInfo()
        handle = ctypes.windll.kernel32.GetStdHandle(-11)
        ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
        ci.visible = False
        ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
def show_cursor() -> None:
    if os.name == 'nt':
        ci = _CursorInfo()
        handle = ctypes.windll.kernel32.GetStdHandle(-11)
        ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
        ci.visible = True
        ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))

Note: All of this is still new to me so I am still testing this out to fully understand it.

  • 1
    Welcome to Stack Overflow. [Please don't post](https://meta.stackexchange.com/q/104227/248627) the [same](https://stackoverflow.com/a/73396379/354577) [answer](https://stackoverflow.com/a/73396206/354577) [multiple](https://stackoverflow.com/a/73396032/354577) times. – ChrisGPT was on strike Aug 21 '22 at 18:51
0

anything really done with the windows console will blink if your clearing and rewriting the screen, unless you find something quite advanced I don't think you can remove the blinking

my only suggestion is to limit the clearing to when something actually changes.

or to use a tkinter text box instead of the cmd. If you want to use that idea I can help with it, although I mostly use tkinter for python 3.4

hopefully that helps!

Venya
  • 190
  • 7