一、前言

1.1 需求来源

  我们通常能在电影中看到这样的场景:一个黑客坐在幽暗的房间中央,脸上打着来自屏幕的绿光,而屏幕则在匀速地向上滚动,一个个英文字符划过,宛如天空上的流星……
hacker_screen
  这个场景很酷,本文决定对该场景做一个小小的模拟。

1.2 模拟思路

  这个场景其实本质上可以拆解为黑底绿字、屏幕滚动并引入随机三个功能。

1.2.1 黑底绿字

  要实现黑底绿字,可以通过终端的输出格式来控制。通过转义方 “\033[显示方式;前景色;背景色m” 来控制其后面字符的显示格式、显示颜色和背景色.
  ①“\033[1;32;40m” 表示高亮显示 、绿色、黑色背景。
  ②打印“\033[0;0;40m” 能将后续输出的背景全部设置为黑色。
  ③通过不同的显示方式和绿色的组合,能够打印不同程度绿色的字符。
  ④在运行结束时,通过打印 “\033[0m” 可以将打印格式恢复到默认形式。

1.2.2 屏幕滚动

 确定每行能打印的字符数 num 后,在打印完 num 个字符后换行打印,结合 time.sleep即可实现屏幕匀速滚动。
 本文中确定了两个函数 print_lineprint_random_char。前者用于实现按行打印,后者则辅助前者用于确认每一行中每一个位置应该以什么颜色打印什么字符。

1.2.3 随机引入

  在一行中,有的位置会打印空格。首先初始化 status_list 来确定每个位置是否打印空格以及打印的格式。打印字符的比例为 p1
  在下一行中:
  ①不打印空格的位置,以 p2 的概率仍不打印空格,以 1-p2 的概率打印空格。
  ②打印空格的位置,,以 p3 的概率不打印空格。
  要维持下一行与上一行中,字符与空格的占比动态稳定,需满足:

# p1 * (p2) + (1 - p1) * p3 = p1
p3 = p1 * (1 - p2) / (1 - p1)

  

二、代码解析

2.1 颜色类 Color

  
  本文将颜色封装在 Color 类中。包含背景色、不同的绿色以及原色。

# 要打印的字符颜色
class Color:
    LIGHT_GREEN = '\033[28;92m'
    LIGHT_BOLD_GREEN = '\033[1;92m'
    DARK_GREEN = '\033[28;32m'
    DARK_BOLD_GREEN = '\033[1;32m'
    BACK_GROUND = '\033[40m'  # 默认黑色背景
    END = '\033[0m'  # 清除格式

  背景色应放在打印最初,用于设置后续背景;原色则放在最后,用于消除格式、恢复初始打印状态。

print(Color.BACK_GROUND + "BACK_GROUND")
print(Color.LIGHT_GREEN + "LIGHT_GREEN")
print(Color.LIGHT_BOLD_GREEN + "LIGHT_BOLD_GREEN")
print(Color.DARK_GREEN + "DARK_GREEN")
print(Color.DARK_BOLD_GREEN + "DARK_BOLD_GREEN")
print(Color.END + "END")

  打印结果如下图。可知,Color.BACK_GROUND 可以将后续背景设置为黑色;Color.END 能将打印恢复为图原先的样式;不同的颜色和显示方式的组合,打印的字符样式不同(不同设备上会略有差异)。
Colors

2.2 屏幕滚动类 HackerScreen

2.2.1 初始化

  设置屏幕能显示的字符数 num、每行中不打印空格的比率 p1、下一行中仍打印字符的概率 p2、下一行中不打印空格了的概率 p3、每行显示的时间间隔 time_span 秒。

def __init__(self, num=150, p1=0.19, p2=0.93, p3=0.01641975308641974, time_span=0.05):
    # 要打印的字符集
    self.chars = [chr(ord('a') + i) for i in range(26)] + [chr(ord('A') + i) for i in range(26)] + ["."]
    # 字体颜色集合
    self.colors = [Color.LIGHT_GREEN, Color.LIGHT_BOLD_GREEN, Color.DARK_GREEN, Color.DARK_BOLD_GREEN]
    self.num = num  # 屏幕每行能打印的字符数
    self.p1 = p1  # 不打印空格处的位置占比
    self.p2 = p2  # 不打印空格的位置,下一行继续不打印空格的概率
    self.p3 = p3  # 打印空格的位置,下一行不打印空格的概率
    self.time_span = time_span  # 换行的时间间隔,决定屏幕的滚动速度
    self.status_list = [[1, random.choice(self.colors)] if random.random() < p1
                        else [0, random.choice(self.colors)]
                        for _ in range(num)]  # 初始状态下,每个位置是否打印、打印颜色的状态

  self.colors 为要打印的全部字符集合;self.colors 为字符的打印格式集合。
  self.status_list 元素的数量为 num,每个元素包含两个值,一个是是否打印非空字符,一个是打印字符的颜色。通过 random 和列表推导式生成。self.status_list 用于实时维护每个位置的状态。

2.2.2 打印字符

  print_random_char 结合字符颜色,从字符集中随机取出一个字符进行打印。
  

# 随机从字符集中抽取一个字符打印
def print_random_char(self, color):
    print(color + random.choice(self.chars), end='')

2.2.3 打印行

  print_line 函数通过每一行的实时状态 self.status_list 决定打印空格或者非空字符。

# 传入每行字符的状态,决定每个位置是否打印,打印一行字符
def print_line(self):
    for index in range(self.num):
        # 以一定概率更改每个位置的状态
        # 打印空格的位置,下一行以 p3 的概率打印其他字符
        if self.status_list[index][0] == 0:
            print(" ", end='')
            if random.random() < self.p3:
                self.status_list[index][0] = 1
                self.status_list[index][1] = random.choice(self.colors)

        # 不打印空格的位置,下一行以 p2 的概率保持不打印空格
        else:
            self.print_random_char(self.status_list[index][1])
            if random.random() > p2:
                self.status_list[index][0] = 0
    print()  # 换行
    time.sleep(self.time_span)

  打印空格处,下一行以 p3 的概率转变为打印非空字符;打印非空字符处,下一行以 p2 的概率仍打印非空字符。
  每次换行后,等待 time_span 秒。

2.3 主函数 main

  设置每次随机结果不一致:

# 确保每次随机结果不一样
random.seed(time.time())

  初始化 HackScreen 实例 hg

num = 150  # 屏幕每行能打印的字符数
p1 = 0.19  # 不打印空格处的位置占比
p2 = 0.93  # 不打印空格的位置,下一行继续不打印空格的概率
p3 = p1 * (1 - p2) / (1 - p1)  # 打印空格的位置,下一行不打印空格的概率
time_span = 0.05  # 换行的时间间隔,决定屏幕的滚动速度

hg = HackerScreen(num, p1, p2, p3, time_span)

  设置起始颜色和终止颜色,通过 while 循环打印行,异常时(如 Ctrl+C、任务管理器终止进程等)退出循环:

try:
    print(Color.BACK_GROUND)  # 默认黑色背景
    while True:
        hg.print_line()
except:
    print(Color.END + "Finished!")  # 恢复原先的打印设置

三、成果演示

3.1 截图

效果图

3.2 动图

hacker_screen

四、完整代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @FileName  : hacker_screen.py
# @Time      : 2024/4/22 0:08
# @Author    : Carl.Zhang
# Function   : hacker_screen

import time
import random


# 要打印的字符颜色
class Color:
    LIGHT_GREEN = '\033[28;92m'
    LIGHT_BOLD_GREEN = '\033[1;92m'
    DARK_GREEN = '\033[28;32m'
    DARK_BOLD_GREEN = '\033[1;32m'
    BACK_GROUND = '\033[40m'  # 默认黑色背景
    END = '\033[0m'  # 清除格式


class HackerScreen:
    def __init__(self, num=150, p1=0.19, p2=0.93, p3=0.01641975308641974, time_span=0.05):
        # 要打印的字符集
        self.chars = [chr(ord('a') + i) for i in range(26)] + [chr(ord('A') + i) for i in range(26)] + ["."]
        # 字体颜色集合
        self.colors = [Color.LIGHT_GREEN, Color.LIGHT_BOLD_GREEN, Color.DARK_GREEN, Color.DARK_BOLD_GREEN]
        self.num = num  # 屏幕每行能打印的字符数
        self.p1 = p1  # 不打印空格处的位置占比
        self.p2 = p2  # 不打印空格的位置,下一行继续不打印空格的概率
        self.p3 = p3  # 打印空格的位置,下一行不打印空格的概率
        self.time_span = time_span  # 换行的时间间隔,决定屏幕的滚动速度
        self.status_list = [[1, random.choice(self.colors)] if random.random() < p1
                            else [0, random.choice(self.colors)]
                            for _ in range(num)]  # 初始状态下,每个位置是否打印、打印颜色的状态

    # 随机从字符集中抽取一个字符打印
    def print_random_char(self, color):
        print(color + random.choice(self.chars), end='')

    # 传入每行字符的状态,决定每个位置是否打印,打印一行字符
    def print_line(self):
        for index in range(self.num):
            # 以一定概率更改每个位置的状态
            # 打印空格的位置,下一行以 p3 的概率打印其他字符
            if self.status_list[index][0] == 0:
                print(" ", end='')
                if random.random() < self.p3:
                    self.status_list[index][0] = 1
                    self.status_list[index][1] = random.choice(self.colors)

            # 不打印空格的位置,下一行以 p2 的概率保持不打印空格
            else:
                self.print_random_char(self.status_list[index][1])
                if random.random() > p2:
                    self.status_list[index][0] = 0
        print()  # 换行
        time.sleep(self.time_span)


if __name__ == '__main__':
    # 确保每次随机结果不一样
    random.seed(time.time())

    num = 150  # 屏幕每行能打印的字符数
    p1 = 0.19  # 不打印空格处的位置占比
    p2 = 0.93  # 不打印空格的位置,下一行继续不打印空格的概率
    p3 = p1 * (1 - p2) / (1 - p1)  # 打印空格的位置,下一行不打印空格的概率
    time_span = 0.05  # 换行的时间间隔,决定屏幕的滚动速度

    hg = HackerScreen(num, p1, p2, p3, time_span)
    try:
        print(Color.BACK_GROUND)  # 默认黑色背景
        while True:
            hg.print_line()
    except:
        print(Color.END + "Finished!")  # 恢复原先的打印设置


# 1         p1           (1-p1)
# p1 * (p2) + (1 - p1) * p3 = p1
#       (1-0.17)*p3=0.17*0.05

更多 python 的使用方法和应用,敬请关注后续更新~

PS:文章原创,转载请注明出处!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐