Python 脚本基础模块
Python 脚本基础模块
@firestaradmin 2020年12月15日15:14:34
如果死亡会带来平静,和安息。死去吧。
一、键盘鼠标操作模拟
autopy 模块
安装:pip3 install autopy
主要用来模拟鼠标和键盘的点击操作。
1. 键盘
这个模块包含控制键盘的函数.
函数
按下键或者释放键
autopy.key.toggle(autopy.key.Code.UP_ARROW, True, []) #按下方向键
autopy.key.toggle(autopy.key.Code.UP_ARROW, False, []) #释放方向键
autopy.key.toggle(autopy.key.Code.UP_ARROW, True, [autopy.key.Modifier.CONTROL], 0)
#参数1 键盘代码(看下表)或键名
#参数2 True表示按下,False松开
#参数3 同时按下的修饰键
#参数4 延迟n秒后再往下执行
#注意释放,要不然会电脑会一直按着那个键,直到你终止程序
autopy.key.tap(autopy.key.Code.CONTROL, [], 2)
#对autopy.key.toggle进行的封装,方便使用,按下并自动释放.
autopy.key.type_string(‘Hello123’, 0)
#输入相应的字符串
#参数2 每分钟的字符个数,0表示最快
常量
class autopy.key.Code
type | code |
---|---|
Code | F1 |
Code | F2 |
Code | F3 |
Code | F4 |
Code | F5 |
Code | F6 |
Code | F7 |
Code | F8 |
Code | F9 |
Code | F10 |
Code | F11 |
Code | F12 |
Code | ALT |
Code | BACKSPACE |
Code | CAPS_LOCK |
Code | CONTROL |
Code | DELETE |
Code | DOWN_ARROW |
Code | END |
Code | ESCAPE |
Code | HOME |
Code | LEFT_ARROW |
Code | PAGE_DOWN |
Code | PAGE_UP |
Code | RETURN |
Code | RIGHT_ARROW |
Code | SHIFT |
Code | SHIFT |
Code | SPACE |
Code | UP_ARROW |
class autopy.key.Modifier
type | code |
---|---|
Modifier | META |
Modifier | ALT |
Modifier | CONTROL |
Modifier | SHIFT |
2. 鼠标
有些窗口可能需要管理员运行,才能移动鼠标!!!
这个模块包含获取发鼠标状态和控制鼠标光标的函数.非特殊情况下,都使用屏幕坐标系,原点在左上角.
函数
#移动鼠标到指定点–相对于屏幕,没有移动过程
autopy.mouse.move(100, 100)
#移动鼠标到指定点–相对于屏幕,有直线移动过程
autopy.mouse.smooth_move(500, 500)
#返回鼠标的当前位置–相对于屏幕
i = autopy.mouse.location()
#i => (500.0, 500.0)
#按下指定鼠标键
autopy.mouse.toggle(autopy.mouse.Button.LEFT, True)
#autopy.mouse.Button.LEFT 鼠标左键
#autopy.mouse.Button.RIGHT 鼠标右键
#autopy.mouse.Button.MIDDLE 鼠标中键
#None 默认左键
#释放指定鼠标键
autopy.mouse.toggle(autopy.mouse.Button.LEFT, False)
#注意释放,要不然会电脑会一直按着那个键,直到你终止程序
autopy.mouse.click(None) # 点左键一下,然后松开
autopy.mouse.click(autopy.mouse.Button.RIGHT) # 点右键一下,然后松开
autopy.mouse.click(autopy.mouse.Button.LEFT, 3) # 按住左键保持3秒,然后松开
autopy.mouse.Button:Code
type | code |
---|---|
Button | LEFT |
Button | RIGHT |
Button | MIDDLE |
3.控制屏幕的模块screen
这个模块包含控制屏幕的函数
函数
- autopy.screen,scale()->float
获得屏幕的scale值,多少像素代表一个点. - autopy.screen.size()->(float,float)
返回tuple(width,height),代表屏幕宽高有多少个像素点. - autopy.screen.is_point_visible(x:float,y:float)->bool
如果这个点在屏幕的范围内,返回True - autopy.screen.get_color(x:float,y:float)->(int,int,int)
返回屏幕上对应点的颜色值.异常:如果点不在屏幕上,抛出ValueError.
autopy 也有图像识别,但是是位图识别,它的处理图像不适合脚本编写,我们会使用其他的模块,更加适合。
二、屏幕截图
Pillow
Pillow是PIL的一个派生分支,但如今已经发展成为比PIL本身更具活力的图像处理库。pillow可以说已经取代了PIL,将其封装成python的库(pip即可安装),且支持python2和python3,目前最新版本是3.0.0。
安装:
pip3 install pillow
屏幕截图:
from PIL import ImageGrab
im = ImageGrab.grab((0,0,800,200)) #截取屏幕指定区域的图像
im = ImageGrab.grab() #不带参数表示全屏幕截图
im.save("D:\\test.jpg"); #储存为.jpg
其他图像处理功能,如缩放、颜色等操作,百度。
三、窗口控制
pywin32
一般系统自带,如果没有,安装可能是pip install pywin32
使用 win32gui
win32api
win32con
等模块进行窗体控制。
import win32gui, win32api, win32con
def main():
global game_hwnd, window_size
game_hwnd = win32gui.FindWindow(game_class, game_title) #获得句柄 <hwnd: int>
print("窗口句柄:",game_hwnd)
win32gui.SetForegroundWindow(game_hwnd) #窗口浮现
window_size = win32gui.GetWindowRect(game_hwnd) #pos: (x1, y1, x2, y2) <pos: tuple>
1、获取窗口句柄
win32gui.FindWindow(game_class, game_title) #获得句柄 <hwnd: int>
关键在于 窗口类名 和 窗口标题 ** 的获取, 可以使用spy**++ 软件 来抓取窗口信息。
2、窗口移到最前
win32gui.SetForegroundWindow(game_hwnd) #窗口浮现
把窗口移到前方,但窗口最小化后会有错误,待解决。
3、获取窗口坐标位置
window_size = win32gui.GetWindowRect(game_hwnd) #pos: (x1, y1, x2, y2) <pos: tuple>
获取窗口的位置,可以通过点坐标计算窗口大小,获取的位置数据类型为 元组 (X1, Y1, X2, Y2) 对应窗体左上角和右下角的坐标。
4、更好的获取句柄的方式
因为有时候应用程序会更新,标题可能会更改,所以可以通过 窗口的标题进行关键字匹配,从而找到窗体句柄。
四、图像识别
CV2 [ opencv-python ]
安装:
pip3 install opencv-python
会自动安装依赖的 numpy 模块。
可能的import 错误:
>>>import cv2
ImportError: numpy.core.multiarray failed to import
原因是因为 numpy 模块的版本太新,将版本降低即可。
pip3 install -U numpy==1.14.5
具体版本和 opencv 的 版本有关。可以直接降低到 1.12.0。 如果版本太低,会有提示:
opencv-python 4.4.0.46 has requirement numpy>=1.14.5, but you'll have numpy 1.12.0 which is incompatible.
基本库函数
1、读入一张图像
cv2.imread(filepath, flags) #读入一张图像
- filepath:要读入图片的完整路径
- flags:读入图片的模式
- cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道
- cv2.IMREAD_GRAYSCALE:读入灰度图片
- cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道
2、显示图像
cv2.imshow(wname,img) #显示图像
- 第一个参数是显示图像的窗口的名字
- 第二个参数是要显示的图像(imread读入的图像),窗口大小自动调整为图片大小
cv2.imshow('image',img)
cv2.waitKey(0) #等待键盘输入,单位为毫秒,即等待指定的毫秒数看是否有键盘输入,若在等待时间内按下任意键则返回按键的ASCII码,程序继续运行。
#若没有按下任何键,超时后返回-1。参数为0表示无限等待。不调用waitKey的话,窗口会一闪而逝,看不到显示的图片。
cv2.destroyAllWindow() #销毁所有窗口
cv2.destroyWindow(wname) #销毁指定窗口
3、保存图像
cv2.imwrite(file,img,num) #保存一张图像
- 第一个参数是要保存的文件名
- 第二个参数是要保存的图像。可选的第三个参数,它针对特定的格式:对于JPEG,其表示的是图像的质量,用0 - 100的整数表示,默认95。
- 第三个参数表示的是压缩级别。默认为3.
4、其他部分函数
img.copy() #图像复制
cv2.resize(image, image2,dsize) #图像缩放:(输入原始图像,输出新图像,图像的大小)
cv2.flip(img,flipcode) #图像翻转,flipcode控制翻转效果。
#flipcode = 0:沿x轴翻转;flipcode > 0:沿y轴翻转;flipcode < 0:x,y轴同时翻转
实现屏幕上找到特定图像
cv2.matchTemplate(image, templ, method, result=None, mask=None)
image:待搜索图像
templ:模板图像
result:匹配结果
method:匹配模式参数
method:
- CV_TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;最好的匹配值为0;匹配越差,匹配值越大。
- CV_TM_CCORR 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
- CV_TM_CCOEFF 相关系数匹配法:1表示完美的匹配;-1表示最差的匹配。
- CV_TM_SQDIFF_NORMED 归一化平方差匹配法
- CV_TM_CCORR_NORMED 归一化相关匹配法
- CV_TM_CCOEFF_NORMED 归一化相关系数匹配法
模板匹配具有自身的局限性,主要表现在它只能进行平行移动,若原图像中的匹配目标发生旋转或大小变化,该算法无效。
一般,寻找的目标和容器,尺寸必须相同
实例代码
import cv2
'''
* @brief 在容器图片里寻找样例. 使用OPONCV TM_CCOEFF_NORMED 模式识别
* @param template the target will find in container ex. "D:\\a.jpg"
* @param container the pic where to find ex. "D:\\b.jpg"
* @param thres the threshold of finding simlike, default 0.8, range(0, 1)
* @retval 0 means not found, > 0 means level of similarity range(0 ,1)
'''
def match_sim_img(template, container, thres = 0.80):
template = cv2.imread(template,0)
container = cv2.imread(container,0)
res = cv2.matchTemplate(container, template, cv2.TM_CCOEFF_NORMED)
if res.max() >= thres:
return res.max()
return 0
返回值 res 为列表变量,所以取res.max() 判断是否大于阈值,大于就说明可能找到了该图。
但我们的目的大部分的在屏幕上查找坐标,然后进行鼠标点击操作。那么如何获得图片相对于屏幕的坐标呢?
import numpy
from PIL import Image
import cv2
'''
* @brief 在容器图片里寻找样例并返回坐标值. 使用OPONCV TM_CCOEFF_NORMED 模式识别
* @param template_path the target will find in container ex. "D:\\a.jpg"
* @param container_path the pic where to find ex. "D:\\b.jpg"
* @param thres the threshold of finding simlike, default 0.8, range(0, 1)
* @retval tuple[X, Y], if return (0, 0) means not found.
'''
def get_img_pos(template_path, container_path, thres = 0.80):
template = cv2.imread(template_path,0)
container = cv2.imread(container_path,0)
res = cv2.matchTemplate(container, template, cv2.TM_CCOEFF_NORMED)
if res.max() < thres:
return (0, 0)
container_size = Image.open(template_path).size
loc = numpy.where(res==res.max())
X = int(loc[1]+container_size[0]/2)
Y = int(loc[0]+container_size[1]/2)
return (X,Y)
五、配置文件
configparser
python使用自带的configparser模块用来读取配置文件,配置文件的形式类似windows中的ini文件,在使用前需要先安装该模块,使用pip安装即可
pip install configparser
实例:
可以新建一个.ini文件 输入以下内容进行测试:
[user-info]
name = ZhangSan
passwd = 123456
level = 12
[email]
address = 123455678@qq.com
passwd = 123123
python code:
import configparser
def main():
cf = configparser.ConfigParser()
cf.read("C://temp/config.ini") # 读取配置文件,如果写文件的绝对路径,就可以不用os模块
secs = cf.sections() # 获取文件中所有的section, 一个配置文件中可以有多个配置
print(secs)
sec_userinfo = cf.options("user-info") # 获取sec名为user-info 所有的键
print(sec_userinfo)
items_userinfo = cf.items("user-info") # 获取sec名为user-info 所有的键值对
print(items_userinfo)
username = cf.get("user-info", "name") # 获取[user-info]name 对应的值
print(username)
level = cf.getint("user-info", "level")
print(level + 2)
if __name__ == "__main__":
main()
执行结果如下:
>>>
['user-info', 'email']
['name', 'passwd']
[('name', 'ZhangSan'), ('passwd', '123456')]
ZhangSan
14
实例2
import configparser
'''
config.ini 内容:
[game-info]
title = NBA2KOL2 0.3.230.675
classname = "NBAWindowClass"
[debug-config]
debug_enable = False
'''
def main():
cf = configparser.ConfigParser()
cf.read("F:\MarsCloud\MyFortune\Program\mycode\python\code\gameAssistant_2kol2\config.ini")
title = cf.get("game-info", "title")
print(title)
classname = cf.get("game-info", "classname")
print(name)
val_bool = cf.getboolean("debug-config", "debug_enable")
print(val_bool)
结果:
>>>
NBA2KOL2 0.3.230.675
"NBAWindowClass"
False
六、其他知识
1、判断是否以管理员模式运行
使用 pywin32 模块
代码如下:
from win32com.shell import shell
def main():
if shell.IsUserAnAdmin():
print("now is admin mode.")
else:
print("just a poor man.")
2、获取程序当前绝对路径
使用 os 模块
import os
print(os.getcwd())
七、NBA2kOL2 挂机实例
'''
@brief This is for NBA2KOL2 game.
@author firestaradmin 2020年12月16日
'''
import time
import numpy
from PIL import Image,ImageGrab
from cv2 import cv2
import autopy
import win32gui
from win32com.shell import shell
import os
import configparser
# Configuartion
debug_enable = True # config if print debug
# 'GLOBAL VARIABLE'
game_title = "NBA2KOL2 0.3.230.675"
game_class = "NBAWindowClass"
game_hwnd = 0
window_size = (0,0,0,0)
game_completed_cnt = 0
game_win_cnt = 0
game_fail_cnt = 0
game_one_game_timeS = 0
cwd_path = ""
'''
* @brief 在容器图片里寻找样例并返回坐标值. 使用OPONCV TM_CCOEFF_NORMED 模式识别
* @param template the path of target will find in container. ex- "D:\\a.jpg"
* @param container the path of pic where to find. ex- "D:\\b.jpg"
* @param img_mode the img open mode.
- cv2.IMREAD_COLOR: 默认参数,读入一副彩色图片,忽略alpha通道
- cv2.IMREAD_GRAYSCALE:读入灰度图片
- cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道
* @param thres the threshold of finding simlike level. default 0.7, range(0, 1)
* @retval (X, Y), find_falg, matching_value
(X, Y): The relative center coordinate of matched template.
find_flag: if have found return True
matching_value: return finding max value
'''
def get_img_pos(template_path, container_path, img_mode = cv2.IMREAD_GRAYSCALE, thres = 0.7):
template = cv2.imread(template_path, img_mode)
container = cv2.imread(container_path, img_mode)
res = cv2.matchTemplate(container, template, cv2.TM_CCOEFF_NORMED)
if res.max() < thres:
return (0, 0), False, res.max()
container_size = Image.open(template_path).size
loc = numpy.where(res==res.max())
X = int(loc[1]+container_size[0]/2)
Y = int(loc[0]+container_size[1]/2)
return (X,Y), True, res.max()
# @brief capture window and save it.
def grab_window():
global game_hwnd, window_size, cwd_path
win32gui.SetForegroundWindow(game_hwnd) #窗口浮现
mydelay(0.3)
im = ImageGrab.grab(window_size) #截取屏幕指定区域的图像
im.save(cwd_path + "/resource/window.jpg"); #储存为.jpg
mydelay(0.2)
# @brief find a template and click it.
def finding_in_window_and_click_it(template_path, click_delay = 0.5, img_mode = cv2.IMREAD_GRAYSCALE, thres = 0.7):
global cwd_path
grab_window() #截取窗口
err_code = 0
relative_pos, flag, value = get_img_pos(template_path, cwd_path + "/resource/window.jpg", img_mode, thres)
mydebug("匹配度:"+ str(value)+ "of"+ template_path)
if(not flag):
err_code = 1 #have not found template.
return
abs_pos = (relative_pos[0] + window_size[0], relative_pos[1] + window_size[1])
autopy.mouse.move(abs_pos[0], abs_pos[1])
mydelay(0.5)
autopy.mouse.click(autopy.mouse.Button.LEFT, click_delay)
return err_code
# @brief detect a page, until timeOut.
def detect_page_apper(detect_img_path, timeOut_S, detect_period_S = 5, img_mode = cv2.IMREAD_GRAYSCALE, thres = 0.7):
print("开始侦测 ", detect_img_path, "=============================")
global cwd_path
timeOut = timeOut_S
while(timeOut > 0):
grab_window() #截取窗口
pos, flag, value = get_img_pos(detect_img_path, cwd_path + "/resource/window.jpg", img_mode, thres)
mydebug(detect_img_path + " 匹配度= "+ str(value)) #for debug
if(flag):
return True
mydelay(detect_period_S - 0.5) #why need sub 0.5s, because grab_window() will delay 0.5s.
timeOut -= detect_period_S
mydebug("Time_elapsed: "+ str(timeOut_S - timeOut)+ "S")
return False
# only used in detect game over.
def detect_game_over(timeOut_S, detect_period_S = 5, img_mode = cv2.IMREAD_GRAYSCALE, thres = 0.7):
global game_win_cnt, game_fail_cnt, cwd_path
print("开始侦测, 等待比赛结束 =============================")
timeOut = timeOut_S
while(timeOut > 0):
grab_window() #截取窗口
pos, flag, value = get_img_pos(cwd_path + "/resource/fail_label.jpg", cwd_path + "/resource/window.jpg", img_mode, thres)
mydebug("失败标志匹配度= "+ str(value)) #for debug
if(flag):
game_fail_cnt += 1
return True
pos, flag, value = get_img_pos(cwd_path + "/resource/win_label.jpg", cwd_path + "/resource/window.jpg", img_mode, thres)
mydebug("胜利标志匹配度= "+ str(value)) #for debug
if(flag):
game_win_cnt += 1
return True
mydelay(detect_period_S - 0.5) #why need sub 0.5s, because grab_window() will delay 0.5s.
timeOut -= detect_period_S
mydebug("Time_elapsed: " + str(timeOut_S - timeOut) + "S")
return False
def mydebug(str):
global debug_enable
if(debug_enable):
print("[debug] ", str)
def mydelay(timeS):
global game_one_game_timeS
time.sleep(timeS)
game_one_game_timeS += timeS
def if_admin_runing():
if shell.IsUserAnAdmin():
return True
else:
return False
def myinit():
print("======== 使用前注意把游戏调至窗口模式 分辨率 1440x900 !! =====I,me,justice AG====")
print("======== 使用前注意把游戏调至窗口模式 分辨率 1440x900 !! ===== is the most ====")
print("======== 注意游戏版本号,因为标题会随着版本号改变 ===most cool perfect====")
print("======== 标题改变要修改 根目录下的 config.ini 文件 配置 =====cool superman!======")
print(" ")
print(" ")
time.sleep(0.5)
print("初始化中......")
time.sleep(0.5)
# 初始化变量
global game_hwnd, window_size, debug_enable, cwd_path
cwd_path = os.getcwd().replace('\\', '/')
cwd_temp = cwd_path+"/config.ini"
# 读取Config文件
cf = configparser.ConfigParser()
cf.read(cwd_temp)
game_title = cf.get("game-info", "title")
game_class = cf.get("game-info", "classname")
debug_enable = cf.getboolean("debug-config", "debug_print_enable")
# 获取窗口句柄
game_hwnd = win32gui.FindWindow(game_class, game_title) #获得句柄
if(game_hwnd == 0):
print("未检测到游戏窗口,有以下几种原因可能:")
print("1.游戏未运行。。真的呆哦")
print("2.游戏更新了,版本号有变动,请到脚本根目录下的config.ini文件进行修改标题名称")
print("3.可能你不配吧,嘻嘻")
print("请打开游戏正确配置后再运行脚本,谢谢合作。")
os.system('pause')
exit()
time.sleep(0.5)
print("游戏窗口句柄为:",game_hwnd)
time.sleep(0.5)
win32gui.SetForegroundWindow(game_hwnd) #窗口浮现
window_size = win32gui.GetWindowRect(game_hwnd) #pos: x1, y1, x2, y2
def myexit():
print("== 异常错误!请重新运行脚本 ==")
global game_completed_cnt, game_win_cnt, game_fail_cnt
print("已完成", game_completed_cnt, " 场比赛。")
print("胜利", game_win_cnt, " 场比赛。")
print("失败", game_fail_cnt, " 场比赛。")
print("===============================")
os.system("pause")
exit()
# =================================== main ======================================
def main():
# 检查是否以管理员模式运行
if(not if_admin_runing()):
print("请以管理员模式 重新运行本脚本!")
os.system('pause')
exit()
# 初始化
global game_completed_cnt, game_win_cnt, game_fail_cnt, game_one_game_timeS
myinit()
#开始循环进行比赛
print("开始比赛循环")
time.sleep(0.5)
while (True):
print("开始新一局比赛============")
mydelay(1)
err = finding_in_window_and_click_it(cwd_path + "/resource/main_page_start.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.58)
if(err == 0):
print("[开始比赛] 点击成功")
else:
print("[开始比赛] 点击失败")
myexit()
mydelay(1)
err = finding_in_window_and_click_it(cwd_path + "/resource/start_page_paiwei.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.6)
if(err == 0):
print("[排位] 点击成功")
else:
print("[排位] 点击失败")
myexit()
mydelay(1)
err = finding_in_window_and_click_it(cwd_path + "/resource/start_paiwei_page_jingli.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.6)
if(err == 0):
print("[经理模式] 点击成功")
else:
print("[经理模式] 点击失败")
myexit()
mydelay(1)
err = finding_in_window_and_click_it(cwd_path + "/resource/start_paiwei_jingli_page_enter.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.6)
if(err == 0):
print("[进入] 点击成功")
else:
print("[进入] 点击失败")
myexit()
#********等待匹配, 准备*********
ret = detect_page_apper(cwd_path + "/resource/zhunbei_page_judge.jpg", 60*10, 3, cv2.IMREAD_GRAYSCALE, 0.7)
if(not ret):
print("准备阶段错误!")
myexit()
mydelay(2)
err = finding_in_window_and_click_it(cwd_path + "/resource/zhunbei_page_ready.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.6)
if(err == 0):
print("[准备] 点击成功")
else:
print("[准备] 点击失败")
myexit()
#********等待比赛完毕, 结算*********
# ret = detect_page_apper(cwd_path + "/resource/end_page_judge.jpg", 60*40, 5, cv2.IMREAD_GRAYSCALE, 0.58)
ret = detect_game_over(60*40, 5, cv2.IMREAD_GRAYSCALE, 0.7)
if(not ret):
print("err:未匹配到比赛完毕界面!")
myexit()
mydelay(3)
err = finding_in_window_and_click_it(cwd_path + "/resource/end_page_confirm.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.7)
if(err == 0):
print("[确定比赛结果] 点击成功")
else:
print("[确定比赛结果] 点击失败")
myexit()
mydelay(3)
err = finding_in_window_and_click_it(cwd_path + "/resource/end_jiesuan_page_next.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.7)
if(err == 0):
print("[下一页] 点击成功")
else:
print("[下一页] 点击失败")
myexit()
mydelay(1)
err = finding_in_window_and_click_it(cwd_path + "/resource/end_jiesuan2_page_next.jpg", 1, cv2.IMREAD_GRAYSCALE, 0.7)
if(err == 0):
print("[下一页] 点击成功")
else:
print("[下一页] 点击失败")
myexit()
#********结算完毕, 开始下一局*********
print("成功完成一局比赛! 花费", game_one_game_timeS, "秒")
game_one_game_timeS = 0
game_completed_cnt += 1
print("已完成", game_completed_cnt, " 场比赛。")
print("胜利", game_win_cnt, " 场比赛。")
print("失败", game_fail_cnt, " 场比赛。")
print("10秒后开始下一局比赛=============")
print("===============================")
mydelay(10)
if __name__ == "__main__":
main()
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!