mirror of
https://git-qiuchenly.yltfspace.com/QiuChenly/corepatch
synced 2025-11-25 20:27:33 +08:00
DLC更新: 中国风
This commit is contained in:
42
config.json
42
config.json
@@ -3,27 +3,27 @@
|
|||||||
"Author": "QiuChenly",
|
"Author": "QiuChenly",
|
||||||
"Version": 3.0,
|
"Version": 3.0,
|
||||||
"Description": {
|
"Description": {
|
||||||
"desc": "这是一个用于解释说明json对象用途的说明文档。basePublicConfig里面的同名配置如果在AppList里面配置了相同的Key,则优先使用AppList里面的值。",
|
"desc": "\u8fd9\u662f\u4e00\u4e2a\u7528\u4e8e\u89e3\u91ca\u8bf4\u660ejson\u5bf9\u8c61\u7528\u9014\u7684\u8bf4\u660e\u6587\u6863\u3002basePublicConfig\u91cc\u9762\u7684\u540c\u540d\u914d\u7f6e\u5982\u679c\u5728AppList\u91cc\u9762\u914d\u7f6e\u4e86\u76f8\u540c\u7684Key\uff0c\u5219\u4f18\u5148\u4f7f\u7528AppList\u91cc\u9762\u7684\u503c\u3002",
|
||||||
"bridgeFile": "app路径和注入文件路径之间的中间路径,有些app只能注入到/Contents,所以加了这个。",
|
"bridgeFile": "app\u8def\u5f84\u548c\u6ce8\u5165\u6587\u4ef6\u8def\u5f84\u4e4b\u95f4\u7684\u4e2d\u95f4\u8def\u5f84\uff0c\u6709\u4e9bapp\u53ea\u80fd\u6ce8\u5165\u5230/Contents\uff0c\u6240\u4ee5\u52a0\u4e86\u8fd9\u4e2a\u3002",
|
||||||
"packageName": "app包名,用来确认App是否存在。",
|
"packageName": "app\u5305\u540d\uff0c\u7528\u6765\u786e\u8ba4App\u662f\u5426\u5b58\u5728\u3002",
|
||||||
"injectFile": "注入文件的相对app路径的路径",
|
"injectFile": "\u6ce8\u5165\u6587\u4ef6\u7684\u76f8\u5bf9app\u8def\u5f84\u7684\u8def\u5f84",
|
||||||
"supportVersion": "支持的版本号,用于大版本号 如果不提供任意版本号则直接无条件注入",
|
"supportVersion": "\u652f\u6301\u7684\u7248\u672c\u53f7\uff0c\u7528\u4e8e\u5927\u7248\u672c\u53f7 \u5982\u679c\u4e0d\u63d0\u4f9b\u4efb\u610f\u7248\u672c\u53f7\u5219\u76f4\u63a5\u65e0\u6761\u4ef6\u6ce8\u5165",
|
||||||
"supportSubVersion": "更精确的子版本号,用于某些app大版本号不变但是经常变小版本号的陋习",
|
"supportSubVersion": "\u66f4\u7cbe\u786e\u7684\u5b50\u7248\u672c\u53f7\uff0c\u7528\u4e8e\u67d0\u4e9bapp\u5927\u7248\u672c\u53f7\u4e0d\u53d8\u4f46\u662f\u7ecf\u5e38\u53d8\u5c0f\u7248\u672c\u53f7\u7684\u964b\u4e60",
|
||||||
"extraShell": "额外的执行shell,比如PD18需要执行一些额外的shell,支持自定义。",
|
"extraShell": "\u989d\u5916\u7684\u6267\u884cshell\uff0c\u6bd4\u5982PD18\u9700\u8981\u6267\u884c\u4e00\u4e9b\u989d\u5916\u7684shell\uff0c\u652f\u6301\u81ea\u5b9a\u4e49\u3002",
|
||||||
"needCopyToAppDir": "有些app需要复制到app目录中才能在SIP打开的时候运行 这个属性可以控制库文件被复制到Framework",
|
"needCopyToAppDir": "\u6709\u4e9bapp\u9700\u8981\u590d\u5236\u5230app\u76ee\u5f55\u4e2d\u624d\u80fd\u5728SIP\u6253\u5f00\u7684\u65f6\u5019\u8fd0\u884c \u8fd9\u4e2a\u5c5e\u6027\u53ef\u4ee5\u63a7\u5236\u5e93\u6587\u4ef6\u88ab\u590d\u5236\u5230Framework",
|
||||||
"deepSignApp": "自动签名整个App",
|
"deepSignApp": "\u81ea\u52a8\u7b7e\u540d\u6574\u4e2aApp",
|
||||||
"disableLibraryValidate": "关闭库验证 暂时只为pd启用",
|
"disableLibraryValidate": "\u5173\u95ed\u5e93\u9a8c\u8bc1 \u6682\u65f6\u53ea\u4e3apd\u542f\u7528",
|
||||||
"entitlements": "用于跳过部分权限 在启用SIP的macOS上 如ARM64",
|
"entitlements": "\u7528\u4e8e\u8df3\u8fc7\u90e8\u5206\u6743\u9650 \u5728\u542f\u7528SIP\u7684macOS\u4e0a \u5982ARM64",
|
||||||
"noSignTarget": "默认注入后会对目标文件进行签名,我们有时候不需要这个行为。",
|
"noSignTarget": "\u9ed8\u8ba4\u6ce8\u5165\u540e\u4f1a\u5bf9\u76ee\u6807\u6587\u4ef6\u8fdb\u884c\u7b7e\u540d,\u6211\u4eec\u6709\u65f6\u5019\u4e0d\u9700\u8981\u8fd9\u4e2a\u884c\u4e3a\u3002",
|
||||||
"noDeep": "--deep 控制",
|
"noDeep": "--deep \u63a7\u5236",
|
||||||
"tccutil": "自动执行tccutil reset xxx CFBundleIdentifier,true为All,详细可用数组。目前会将所有的值在componentApp操作一遍默认行为。",
|
"tccutil": "\u81ea\u52a8\u6267\u884ctccutil reset xxx CFBundleIdentifier\uff0ctrue\u4e3aAll\uff0c\u8be6\u7ec6\u53ef\u7528\u6570\u7ec4\u3002\u76ee\u524d\u4f1a\u5c06\u6240\u6709\u7684\u503c\u5728componentApp\u64cd\u4f5c\u4e00\u904d\u9ed8\u8ba4\u884c\u4e3a\u3002",
|
||||||
"autoHandleSetapp": "自动根据 CFBundleIdentifier 找出App的appBaseLocate,并拼接/Contents/MacOS/为bridgeFile,且默认找出bridgeFile下的对应二进制程序,不太完善,仅setapp测试通过",
|
"autoHandleSetapp": "\u81ea\u52a8\u6839\u636e CFBundleIdentifier \u627e\u51faApp\u7684appBaseLocate\uff0c\u5e76\u62fc\u63a5/Contents/MacOS/\u4e3abridgeFile\uff0c\u4e14\u9ed8\u8ba4\u627e\u51fabridgeFile\u4e0b\u7684\u5bf9\u5e94\u4e8c\u8fdb\u5236\u7a0b\u5e8f\uff0c\u4e0d\u592a\u5b8c\u5584\uff0c\u4ec5setapp\u6d4b\u8bd5\u901a\u8fc7",
|
||||||
"autoHandleHelper": "是否自动处理helper文件",
|
"autoHandleHelper": "\u662f\u5426\u81ea\u52a8\u5904\u7406helper\u6587\u4ef6",
|
||||||
"helperFile": "helper文件的路径,例如/Contents/Library/LaunchServices/com.nssurge.surge-mac.helper,可以传数组",
|
"helperFile": "helper\u6587\u4ef6\u7684\u8def\u5f84\uff0c\u4f8b\u5982/Contents/Library/LaunchServices/com.nssurge.surge-mac.helper\uff0c\u53ef\u4ee5\u4f20\u6570\u7ec4",
|
||||||
"componentApp": "组合app,有些大型app是由很多子app组成的,单独处理意义也不大,就这样吧.",
|
"componentApp": "\u7ec4\u5408app,\u6709\u4e9b\u5927\u578bapp\u662f\u7531\u5f88\u591a\u5b50app\u7ec4\u6210\u7684,\u5355\u72ec\u5904\u7406\u610f\u4e49\u4e5f\u4e0d\u5927,\u5c31\u8fd9\u6837\u5427.",
|
||||||
"forQiuChenly": "专门为QiuChenly特供的App,看到这条的你硬改也没用。",
|
"forQiuChenly": "\u4e13\u95e8\u4e3aQiuChenly\u7279\u4f9b\u7684App\uff0c\u770b\u5230\u8fd9\u6761\u7684\u4f60\u786c\u6539\u4e5f\u6ca1\u7528\u3002",
|
||||||
"keygen": "不注入app,生成key破解,适用于重签名后丢失某特性的app,为true时,其他配置默认失效",
|
"keygen": "\u4e0d\u6ce8\u5165app\uff0c\u751f\u6210key\u7834\u89e3\uff0c\u9002\u7528\u4e8e\u91cd\u7b7e\u540d\u540e\u4e22\u5931\u67d0\u7279\u6027\u7684app\uff0c\u4e3atrue\u65f6\uff0c\u5176\u4ed6\u914d\u7f6e\u9ed8\u8ba4\u5931\u6548",
|
||||||
"dylibSelect": "选择哪一个注入库, 如果是CoreInject则表示这是新版重构代码后的注入库,没有就默认91."
|
"dylibSelect": "\u9009\u62e9\u54ea\u4e00\u4e2a\u6ce8\u5165\u5e93, \u5982\u679c\u662fCoreInject\u5219\u8868\u793a\u8fd9\u662f\u65b0\u7248\u91cd\u6784\u4ee3\u7801\u540e\u7684\u6ce8\u5165\u5e93,\u6ca1\u6709\u5c31\u9ed8\u8ba491."
|
||||||
},
|
},
|
||||||
"basePublicConfig": {
|
"basePublicConfig": {
|
||||||
"bridgeFile": "/Contents/Frameworks/"
|
"bridgeFile": "/Contents/Frameworks/"
|
||||||
|
|||||||
25
main.py
25
main.py
@@ -8,6 +8,8 @@ from src.app.app_manager import AppManager
|
|||||||
from src.ui.menu_manager import MenuManager
|
from src.ui.menu_manager import MenuManager
|
||||||
from src.utils.i18n import I18n, _
|
from src.utils.i18n import I18n, _
|
||||||
from src.ui.language_selector import change_language_with_menu, auto_set_language
|
from src.ui.language_selector import change_language_with_menu, auto_set_language
|
||||||
|
from src.ui.sakura_animation import SakuraAnimation
|
||||||
|
from src.ui.panda_animation import PandaAnimation
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -28,6 +30,16 @@ def main():
|
|||||||
# 使用自动语言检测设置语言
|
# 使用自动语言检测设置语言
|
||||||
auto_set_language(config)
|
auto_set_language(config)
|
||||||
|
|
||||||
|
# 根据语言显示不同的欢迎动画
|
||||||
|
if I18n._current_language == I18n.JAPANESE:
|
||||||
|
# 日语:显示樱花花瓣雨动画
|
||||||
|
sakura_animation = SakuraAnimation(duration=5, num_petals=150, static_petals=500)
|
||||||
|
sakura_animation.play()
|
||||||
|
elif I18n._current_language == I18n.CHINESE:
|
||||||
|
# 中文:显示熊猫动画
|
||||||
|
panda_animation = PandaAnimation(duration=5)
|
||||||
|
panda_animation.play()
|
||||||
|
|
||||||
# 扫描安装的应用(不再显示重复的扫描提示)
|
# 扫描安装的应用(不再显示重复的扫描提示)
|
||||||
installed_apps = scan_apps()
|
installed_apps = scan_apps()
|
||||||
|
|
||||||
@@ -81,8 +93,21 @@ def main():
|
|||||||
|
|
||||||
elif choice == '4':
|
elif choice == '4':
|
||||||
# 使用新的语言选择菜单
|
# 使用新的语言选择菜单
|
||||||
|
previous_language = config.get("Language", "en_US")
|
||||||
change_language_with_menu(config)
|
change_language_with_menu(config)
|
||||||
|
|
||||||
|
# 根据新选择的语言显示不同的欢迎动画
|
||||||
|
current_language = config.get("Language", "en_US")
|
||||||
|
if current_language != previous_language:
|
||||||
|
if current_language == I18n.JAPANESE:
|
||||||
|
# 日语:显示樱花花瓣雨动画
|
||||||
|
sakura_animation = SakuraAnimation(duration=5, num_petals=150, static_petals=500)
|
||||||
|
sakura_animation.play()
|
||||||
|
elif current_language == I18n.CHINESE:
|
||||||
|
# 中文:显示熊猫动画
|
||||||
|
panda_animation = PandaAnimation(duration=5)
|
||||||
|
panda_animation.play()
|
||||||
|
|
||||||
elif choice == '5':
|
elif choice == '5':
|
||||||
# 退出程序
|
# 退出程序
|
||||||
print("\n" + _("thank_you_message", "感谢使用,再见!"))
|
print("\n" + _("thank_you_message", "感谢使用,再见!"))
|
||||||
|
|||||||
192
src/ui/panda_animation.py
Normal file
192
src/ui/panda_animation.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
from src.utils.ui_helper import clear_screen, ensure_black_background
|
||||||
|
|
||||||
|
# 背景和颜色重置代码
|
||||||
|
BLACK_BG = "\033[40m" # 黑色背景
|
||||||
|
RED_BG = "\033[41m" # 红色背景
|
||||||
|
BRIGHT_RED_FG = "\033[1;31m" # 亮红色前景
|
||||||
|
RESET_COLOR = "\033[0m"
|
||||||
|
|
||||||
|
# 中国元素emoji列表
|
||||||
|
CHINA_EMOJIS = [
|
||||||
|
"🇨🇳", # 中国国旗
|
||||||
|
"🐼", # 熊猫
|
||||||
|
"🏮", # 灯笼
|
||||||
|
"🧧", # 红包/中国结
|
||||||
|
"🐉", # 龙
|
||||||
|
"🏯", # 中国建筑
|
||||||
|
"🍜", # 面条
|
||||||
|
"🥢", # 筷子
|
||||||
|
"🍵", # 茶
|
||||||
|
"🥮", # 月饼
|
||||||
|
"🀄", # 麻将
|
||||||
|
"🎏" # 鲤鱼旗
|
||||||
|
]
|
||||||
|
|
||||||
|
# 不同语言/文字的"中国"表示
|
||||||
|
CHINA_VARIANTS = [
|
||||||
|
"【 简体模式 - 中国 】",
|
||||||
|
"【 繁體模式 - 中國 】",
|
||||||
|
"【 粤语模式 - 中國(zung1 gwok3) 】"
|
||||||
|
]
|
||||||
|
|
||||||
|
class PandaAnimation:
|
||||||
|
"""中国元素emoji动画效果类"""
|
||||||
|
|
||||||
|
def __init__(self, duration=8):
|
||||||
|
"""
|
||||||
|
初始化中国元素emoji动画
|
||||||
|
|
||||||
|
Args:
|
||||||
|
duration (int): 动画持续时间(秒)
|
||||||
|
"""
|
||||||
|
self.duration = duration
|
||||||
|
self.emojis = [] # 存储所有飘落的emoji位置和速度
|
||||||
|
self.max_emojis = 60 # 屏幕上最多显示的emoji数量
|
||||||
|
self.text_switch_interval = 1.5 # 文字切换间隔(秒)
|
||||||
|
self.current_text_index = 0 # 当前显示的文字索引
|
||||||
|
|
||||||
|
def _get_terminal_size(self):
|
||||||
|
"""获取终端大小"""
|
||||||
|
try:
|
||||||
|
columns, lines = os.get_terminal_size()
|
||||||
|
return lines, columns
|
||||||
|
except:
|
||||||
|
return 24, 80
|
||||||
|
|
||||||
|
def _fill_background(self, color=BLACK_BG):
|
||||||
|
"""填充整个屏幕为指定背景色"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 确保ANSI背景色
|
||||||
|
sys.stdout.write(color)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
# 清屏
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
# 使用指定颜色空格填充整个屏幕
|
||||||
|
for _ in range(height):
|
||||||
|
sys.stdout.write(color + " " * width + "\n")
|
||||||
|
|
||||||
|
# 将光标移回屏幕左上角
|
||||||
|
sys.stdout.write("\033[H")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def _create_new_emoji(self):
|
||||||
|
"""创建一个新的飘落emoji"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 随机位置(全屏范围内)
|
||||||
|
row = random.randint(0, height - 2)
|
||||||
|
col = random.randint(0, width - 4) # emoji宽度约为2个字符
|
||||||
|
|
||||||
|
# 随机移动速度和方向
|
||||||
|
speed_y = random.uniform(0.1, 0.6) # 垂直速度
|
||||||
|
speed_x = random.uniform(-0.3, 0.3) # 水平速度,可以是负值(向左)
|
||||||
|
|
||||||
|
# 随机大小(使用ANSI转义序列调整字体大小)
|
||||||
|
size = random.choice([1, 2, 3])
|
||||||
|
|
||||||
|
# 随机选择一个中国元素emoji
|
||||||
|
emoji = random.choice(CHINA_EMOJIS)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"row": row,
|
||||||
|
"col": col,
|
||||||
|
"speed_y": speed_y,
|
||||||
|
"speed_x": speed_x,
|
||||||
|
"size": size,
|
||||||
|
"emoji": emoji
|
||||||
|
}
|
||||||
|
|
||||||
|
def _update_emojis(self):
|
||||||
|
"""更新所有飘落emoji的位置"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 移除已经飘出屏幕的emoji
|
||||||
|
self.emojis = [emoji for emoji in self.emojis
|
||||||
|
if 0 <= emoji["row"] < height and 0 <= emoji["col"] < width - 4]
|
||||||
|
|
||||||
|
# 如果emoji数量少于最大值,随机添加新的emoji
|
||||||
|
if len(self.emojis) < self.max_emojis and random.random() < 0.2:
|
||||||
|
self.emojis.append(self._create_new_emoji())
|
||||||
|
|
||||||
|
# 更新每个emoji的位置
|
||||||
|
for emoji in self.emojis:
|
||||||
|
emoji["row"] += emoji["speed_y"]
|
||||||
|
emoji["col"] += emoji["speed_x"]
|
||||||
|
|
||||||
|
# 如果到了屏幕边缘,反弹或重新设置方向
|
||||||
|
if emoji["col"] <= 0 or emoji["col"] >= width - 4:
|
||||||
|
emoji["speed_x"] *= -0.8 # 反弹,减速
|
||||||
|
|
||||||
|
def _draw_emojis(self, elapsed_time):
|
||||||
|
"""绘制所有飘落的emoji和底部文字"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 先清屏
|
||||||
|
self._fill_background(BLACK_BG)
|
||||||
|
|
||||||
|
# 绘制每个emoji
|
||||||
|
for emoji in self.emojis:
|
||||||
|
row = int(emoji["row"])
|
||||||
|
col = int(emoji["col"])
|
||||||
|
size = emoji["size"]
|
||||||
|
emoji_char = emoji["emoji"]
|
||||||
|
|
||||||
|
if 0 <= row < height and 0 <= col < width - 4:
|
||||||
|
size_code = f"\033[{size}m" if size > 1 else ""
|
||||||
|
sys.stdout.write(f"\033[{row};{col}H{size_code}{emoji_char}{RESET_COLOR}")
|
||||||
|
|
||||||
|
# 计算当前应该显示哪个文字变体(根据经过的时间)
|
||||||
|
text_index = int((elapsed_time / self.text_switch_interval) % len(CHINA_VARIANTS))
|
||||||
|
|
||||||
|
# 底部显示文字(交替显示不同变体)
|
||||||
|
msg = f"{BRIGHT_RED_FG}{CHINA_VARIANTS[text_index]}{RESET_COLOR}"
|
||||||
|
msg_pos = (width - len(msg) + 20) // 2 # +20是为了补偿ANSI颜色代码和宽字符带来的长度计算问题
|
||||||
|
sys.stdout.write(f"\033[{height-1};{msg_pos}H{msg}")
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
"""播放中国元素emoji动画"""
|
||||||
|
try:
|
||||||
|
# 确保黑色背景
|
||||||
|
ensure_black_background()
|
||||||
|
|
||||||
|
# 清屏并填充黑色背景
|
||||||
|
self._fill_background(BLACK_BG)
|
||||||
|
|
||||||
|
# 记录开始时间
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 初始化更多emoji(全屏分布)
|
||||||
|
for _ in range(25):
|
||||||
|
self.emojis.append(self._create_new_emoji())
|
||||||
|
|
||||||
|
# 播放动画直到达到指定时间
|
||||||
|
while time.time() - start_time < self.duration:
|
||||||
|
# 计算已经过的时间
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 更新emoji位置
|
||||||
|
self._update_emojis()
|
||||||
|
|
||||||
|
# 绘制emoji和文字
|
||||||
|
self._draw_emojis(elapsed_time)
|
||||||
|
|
||||||
|
# 控制帧率
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# 清屏并恢复黑色背景
|
||||||
|
clear_screen()
|
||||||
|
ensure_black_background()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# 用户中断
|
||||||
|
clear_screen()
|
||||||
|
ensure_black_background()
|
||||||
274
src/ui/sakura_animation.py
Normal file
274
src/ui/sakura_animation.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import threading
|
||||||
|
from src.utils.ui_helper import clear_screen, ensure_black_background
|
||||||
|
from src.utils.color import Color
|
||||||
|
|
||||||
|
# 樱花符号
|
||||||
|
SAKURA_SYMBOLS = ["🌸", "💮", "🌹", "❀", "✿", "❁", "❃", "❊", "✽"]
|
||||||
|
# 颜色变种(粉色系列)
|
||||||
|
SAKURA_COLORS = [
|
||||||
|
"\033[38;5;218m", # 浅粉色
|
||||||
|
"\033[38;5;217m", # 粉红色
|
||||||
|
"\033[38;5;211m", # 亮粉色
|
||||||
|
"\033[38;5;219m", # 淡紫粉色
|
||||||
|
"\033[38;5;225m", # 极浅粉色
|
||||||
|
]
|
||||||
|
# 黑色背景和颜色重置代码
|
||||||
|
BLACK_BG = "\033[40m"
|
||||||
|
RESET_COLOR = "\033[0m"
|
||||||
|
|
||||||
|
class Petal:
|
||||||
|
"""表示单个花瓣的类"""
|
||||||
|
def __init__(self, x, y, terminal_height, terminal_width, is_static=False):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.symbol = random.choice(SAKURA_SYMBOLS)
|
||||||
|
self.color = random.choice(SAKURA_COLORS)
|
||||||
|
self.speed = random.uniform(0.2, 1.0) if not is_static else 0
|
||||||
|
self.drift = random.uniform(-0.3, 0.3) if not is_static else 0 # 左右漂移的速度
|
||||||
|
self.terminal_height = terminal_height
|
||||||
|
self.terminal_width = terminal_width
|
||||||
|
self.is_static = is_static # 是否是静态花瓣(不移动)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""更新花瓣位置"""
|
||||||
|
if self.is_static:
|
||||||
|
return # 静态花瓣不移动
|
||||||
|
|
||||||
|
self.y += self.speed
|
||||||
|
self.x += self.drift
|
||||||
|
|
||||||
|
# 如果花瓣飘出屏幕底部,重新从顶部开始
|
||||||
|
if self.y >= self.terminal_height:
|
||||||
|
self.y = 0
|
||||||
|
self.x = random.uniform(0, self.terminal_width)
|
||||||
|
|
||||||
|
# 如果花瓣飘出屏幕左右,则拉回到屏幕内
|
||||||
|
if self.x < 0:
|
||||||
|
self.x = 0
|
||||||
|
self.drift = abs(self.drift) # 反向漂移
|
||||||
|
elif self.x >= self.terminal_width:
|
||||||
|
self.x = self.terminal_width - 1
|
||||||
|
self.drift = -abs(self.drift) # 反向漂移
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""花瓣的字符表示"""
|
||||||
|
return f"{self.color}{self.symbol}{RESET_COLOR}{BLACK_BG}"
|
||||||
|
|
||||||
|
class SakuraAnimation:
|
||||||
|
"""樱花花瓣雨动画效果类"""
|
||||||
|
|
||||||
|
def __init__(self, duration=3, num_petals=50, static_petals=400):
|
||||||
|
"""
|
||||||
|
初始化樱花动画
|
||||||
|
|
||||||
|
Args:
|
||||||
|
duration (int): 动画持续时间(秒)
|
||||||
|
num_petals (int): 动态花瓣数量
|
||||||
|
static_petals (int): 静态背景花瓣数量
|
||||||
|
"""
|
||||||
|
self.duration = duration
|
||||||
|
self.num_petals = num_petals
|
||||||
|
self.static_petals = static_petals
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
|
||||||
|
def _get_terminal_size(self):
|
||||||
|
"""获取终端大小"""
|
||||||
|
try:
|
||||||
|
columns, lines = os.get_terminal_size()
|
||||||
|
return lines, columns
|
||||||
|
except:
|
||||||
|
return 24, 80
|
||||||
|
|
||||||
|
def _fill_background(self):
|
||||||
|
"""填充整个屏幕为黑色背景"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 确保ANSI背景色为黑色
|
||||||
|
sys.stdout.write(BLACK_BG)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
# 清屏
|
||||||
|
clear_screen()
|
||||||
|
|
||||||
|
# 使用黑色空格填充整个屏幕
|
||||||
|
for _ in range(height):
|
||||||
|
sys.stdout.write(BLACK_BG + " " * width + "\n")
|
||||||
|
|
||||||
|
# 将光标移回屏幕左上角
|
||||||
|
sys.stdout.write("\033[H")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def _center_text(self, text, width):
|
||||||
|
"""居中显示文本"""
|
||||||
|
padding = (width - len(text)) // 2
|
||||||
|
return BLACK_BG + " " * padding + text
|
||||||
|
|
||||||
|
def _draw_welcome_message(self, frame_count):
|
||||||
|
"""绘制欢迎消息,带有淡入效果"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
progress = min(1.0, frame_count / 10.0) # 10帧内逐渐淡入
|
||||||
|
|
||||||
|
# 欢迎消息
|
||||||
|
welcome_text = "ようこそ!" # "欢迎!"的日语
|
||||||
|
|
||||||
|
# 计算中心位置
|
||||||
|
center_y = height // 2 - 3
|
||||||
|
|
||||||
|
# 渐变色(从暗到亮)
|
||||||
|
gradient = [
|
||||||
|
"\033[38;5;52m", "\033[38;5;88m", "\033[38;5;124m",
|
||||||
|
"\033[38;5;160m", "\033[38;5;196m", "\033[38;5;202m",
|
||||||
|
"\033[38;5;208m", "\033[38;5;214m", "\033[38;5;220m"
|
||||||
|
]
|
||||||
|
|
||||||
|
# 根据进度选择颜色
|
||||||
|
color_idx = min(int(progress * len(gradient)), len(gradient) - 1)
|
||||||
|
color = gradient[color_idx]
|
||||||
|
|
||||||
|
# 生成带颜色的文本
|
||||||
|
colored_text = f"{color}{welcome_text}{RESET_COLOR}{BLACK_BG}"
|
||||||
|
|
||||||
|
# 居中显示
|
||||||
|
centered_text = self._center_text(colored_text, width)
|
||||||
|
sys.stdout.write(f"\033[{center_y};0H{centered_text}")
|
||||||
|
|
||||||
|
# 日语说明
|
||||||
|
jp_text = "日本語モードへようこそ" # "欢迎使用日语模式"
|
||||||
|
jp_colored = f"\033[38;5;219m{jp_text}{RESET_COLOR}{BLACK_BG}"
|
||||||
|
centered_jp = self._center_text(jp_colored, width)
|
||||||
|
sys.stdout.write(f"\033[{center_y+2};0H{centered_jp}")
|
||||||
|
|
||||||
|
def _create_background_canvas(self, static_petals):
|
||||||
|
"""创建包含静态花瓣的背景画布"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 创建空白画布 (使用黑色背景)
|
||||||
|
canvas = [[BLACK_BG + ' ' for _ in range(width)] for _ in range(height)]
|
||||||
|
|
||||||
|
# 将静态花瓣均匀分布在画布上
|
||||||
|
for petal in static_petals:
|
||||||
|
x, y = int(petal.x), int(petal.y)
|
||||||
|
if 0 <= x < height and 0 <= y < width:
|
||||||
|
canvas[x][y] = str(petal)
|
||||||
|
|
||||||
|
return canvas
|
||||||
|
|
||||||
|
def _draw_frame(self, dynamic_petals, static_canvas, frame_count):
|
||||||
|
"""绘制单帧动画"""
|
||||||
|
# 复制静态背景画布
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
canvas = [row[:] for row in static_canvas] # 深复制静态画布
|
||||||
|
|
||||||
|
# 更新并绘制动态花瓣
|
||||||
|
for petal in dynamic_petals:
|
||||||
|
# 更新花瓣位置(先更新再绘制,这样显示的是更新后的位置)
|
||||||
|
petal.update()
|
||||||
|
|
||||||
|
# 将动态花瓣放置在画布上
|
||||||
|
x, y = int(petal.x), int(petal.y)
|
||||||
|
if 0 <= x < height and 0 <= y < width:
|
||||||
|
canvas[x][y] = str(petal)
|
||||||
|
|
||||||
|
# 清屏后绘制整个画布
|
||||||
|
clear_screen()
|
||||||
|
sys.stdout.write("\033[H") # 移动光标到左上角
|
||||||
|
for row in canvas:
|
||||||
|
print(''.join(row))
|
||||||
|
|
||||||
|
# 绘制欢迎信息(带淡入效果)
|
||||||
|
self._draw_welcome_message(frame_count)
|
||||||
|
|
||||||
|
# 在底部中央显示"日本語"文字
|
||||||
|
if height > 2:
|
||||||
|
msg = f"\033[38;5;213m✿ 日本語モード ✿{RESET_COLOR}{BLACK_BG}"
|
||||||
|
padding = " " * ((width - len(msg) + 24) // 2) # 调整补偿ANSI颜色代码
|
||||||
|
sys.stdout.write(f"\033[{height-1};0H{BLACK_BG}{padding}{msg}") # 将光标移动到底部中央
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def _create_initial_petals(self, count, is_static=False):
|
||||||
|
"""创建初始的花瓣,均匀分布在整个屏幕上"""
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
petals = []
|
||||||
|
|
||||||
|
# 计算每个区域的花瓣数量
|
||||||
|
sections_h = 10 # 水平分区数
|
||||||
|
sections_v = 10 # 垂直分区数
|
||||||
|
|
||||||
|
# 为了让花瓣看起来更自然,我们在每个区域内随机分布花瓣
|
||||||
|
section_width = width / sections_h
|
||||||
|
section_height = height / sections_v
|
||||||
|
|
||||||
|
# 每个区域至少有一定数量的花瓣
|
||||||
|
petals_per_section = max(1, count // (sections_h * sections_v))
|
||||||
|
|
||||||
|
# 为每个区域分配花瓣
|
||||||
|
for section_x in range(sections_h):
|
||||||
|
for section_y in range(sections_v):
|
||||||
|
# 在当前区域内随机生成花瓣
|
||||||
|
for _ in range(petals_per_section):
|
||||||
|
# 随机位置在当前区域内
|
||||||
|
x = random.uniform(section_y * section_height, (section_y + 1) * section_height)
|
||||||
|
y = random.uniform(section_x * section_width, (section_x + 1) * section_width)
|
||||||
|
petals.append(Petal(x, y, height, width, is_static=is_static))
|
||||||
|
|
||||||
|
# 如果花瓣总数不足,再随机添加一些
|
||||||
|
remaining = count - len(petals)
|
||||||
|
for _ in range(remaining):
|
||||||
|
x = random.uniform(0, height)
|
||||||
|
y = random.uniform(0, width)
|
||||||
|
petals.append(Petal(x, y, height, width, is_static=is_static))
|
||||||
|
|
||||||
|
return petals
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
"""播放樱花花瓣雨动画"""
|
||||||
|
try:
|
||||||
|
# 确保黑色背景
|
||||||
|
ensure_black_background()
|
||||||
|
|
||||||
|
# 额外填充整个屏幕为黑色
|
||||||
|
self._fill_background()
|
||||||
|
|
||||||
|
# 获取屏幕尺寸
|
||||||
|
height, width = self._get_terminal_size()
|
||||||
|
|
||||||
|
# 创建静态背景花瓣(铺满屏幕的花瓣)
|
||||||
|
static_petal_count = max(self.static_petals, int((height * width) / 25)) # 根据屏幕大小动态调整
|
||||||
|
static_petals = self._create_initial_petals(static_petal_count, is_static=True)
|
||||||
|
|
||||||
|
# 创建背景画布(含静态花瓣)
|
||||||
|
static_canvas = self._create_background_canvas(static_petals)
|
||||||
|
|
||||||
|
# 创建动态花瓣(会飘动的花瓣)
|
||||||
|
dynamic_petal_count = max(self.num_petals, int((height * width) / 100))
|
||||||
|
dynamic_petals = self._create_initial_petals(dynamic_petal_count, is_static=False)
|
||||||
|
|
||||||
|
# 记录开始时间
|
||||||
|
start_time = time.time()
|
||||||
|
frame_count = 0
|
||||||
|
|
||||||
|
# 播放动画直到达到指定时间
|
||||||
|
while time.time() - start_time < self.duration:
|
||||||
|
if self.stop_event.is_set():
|
||||||
|
break
|
||||||
|
self._draw_frame(dynamic_petals, static_canvas, frame_count)
|
||||||
|
time.sleep(0.1) # 控制帧率
|
||||||
|
frame_count += 1
|
||||||
|
|
||||||
|
# 清屏并恢复黑色背景
|
||||||
|
clear_screen()
|
||||||
|
ensure_black_background()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""停止动画"""
|
||||||
|
self.stop_event.set()
|
||||||
|
clear_screen()
|
||||||
|
ensure_black_background()
|
||||||
Reference in New Issue
Block a user