前言
2个晚上的时间,累计大约有6个多小时吧,用 python 实现了一个 qq炫舞 的机器人。
qq炫舞这个游戏就是很多年前比较流行的,出现 一排上下左右,在规定时间内正确输入,然后再在关键点狠拍空格键的那种游戏。
里面把关键技术都说的差不多了,我在这里只大概总结一下自己的心得。
实现思路是
一个死循环不停在游戏中截图,
检测到该按方向键的时候, 检测所有方向键,并给游戏发送 方向键 键盘事件
检测到该按空格键的时候, 按下空格键
所有的检测都是基于 像素颜色来检测的
了解到 python 有 autopy 这个库, 可以模拟一些窗口消息。
但是我在这个例子里没有用到,而是用的 win32api 这个库。
win32api 这个库,可以像 windows sdk 一样,调用 windows sdk 的函数,非常方便,也更接近 windows 底层用到了 PIL 库 , 尤其是 PIL 库 截图的部分.
图像检测,是哪个方向键(上下左右?),以及是不是该按方向键,还是该按空格键,都是基于像素颜色的检验。
参考的文章中,给出了 匹配相似图的算法,但是我发现在我这里 貌似不能用。
他们的做法我大概看了一下,印象是取图片 所有像素 数值的和,与 目标图片的 像素和 做比对。
但是我这里 上下左右4个按钮 ,像素和应该基本一样,所以这种方法不能用,采取了最笨拙的 ,比对关键点颜色是否为蓝色 来判定的。
4.了解了一下 python的多线程,虽然最后没有用上。
windows sdk 的 keybd_event 第二个参数是 硬件扫描码。
如果传0 的话,在模拟 键盘消息时候,会出现 在游戏中模拟 无效的情况,必须用 MapVirtualKey() 填写正确的 值,游戏才认为输入有效。大量使用了 windows 的画图工具量坐标。。
本来想用 python 面向对象一下, 想封装一下。。可是太晚了,今天把功能实现了很高兴了,来不及封装。代码就先这样吧。
由于本轮开始的判定,写的不那么精确,导致偶尔出现 本轮开始判定不准确,不会输入方向键的情况。但是只要多花点时间,是能够改好的。
用自己的 "挂" 和其他 “选手“ pk了一把, 3个人里面我排第2。。至少证明我的挂在不做调优的情况下,是能够战胜一批对手的。。是管用的。
嗯,大概关于这个脚本就到这里吧。
最后上代码,功能实现了,但是很乱,凑合看吧,当个留念。纪念自己第一个没啥技术含量的 “外挂"
#!/usr/bin/python #coding=utf-8
import math
import win32gui
import win32con
import win32api
# import autopy from PIL import ImageGrab
from PIL import Image
import time
import threading
# game window handler g_game_window = None;
# upper-left corner coordinate of game window g_game_window_x = None;
g_game_window_y = None;
g_game_rect = None;
# upper-left corner coordinate of arrows box g_input_box_x = None;
g_input_box_y = None;
g_input_box_width = 330;
g_input_box_height = 44;
# const numbers k_window_name = u"QQ炫舞" k_input_offset_x = 366;
k_input_offset_y = 486;
k_arrow_width = 25;
k_arrow_height = 25;
#k_arrow_space = 9; k_arrow_space = 10;
k_arrow_first_offset_x = 33;
k_arrow_first_offset_y = 10;
k_max_arrows_count = 8;
# enum of dir k_dir_up = 0;
k_dir_down = 1;
k_dir_left= 2;
k_dir_right = 3;
k_dir_unknown = 4;
# color of flag k_flag_r = 255 k_flag_g = 125 k_flag_b = 90
# flag pos start_x = 539 start_y = 464
dest_x = 652 dest_y = 464
check_offset = 100;
def getGameWindow():
print 'getGameWindow';
global k_window_name
wind = win32gui.FindWindow(None,k_window_name)
return wind;
'''''
初始化 参数值
''' def initSettings():
global g_game_window;
global g_game_window_x;
global g_game_window_y;
global g_input_box_x;
global g_input_box_y;
global k_input_offset_x;
global k_input_offset_y;
# get window handle
g_game_window = getGameWindow();
if g_game_window == 0:
print "Please launch game before run this script!"
return False;
print "Game window handle: " + str(g_game_window);
# place window to foreground
win32gui.ShowWindow(g_game_window,win32con.SW_RESTORE);
win32gui.SetForegroundWindow(g_game_window);
# get game window aabb box
g_game_rect = win32gui.GetWindowRect(g_game_window);
g_game_window_x = g_game_rect[0];
g_game_window_y = g_game_rect[1];
# input box coordinate
g_input_box_x = g_game_window_x + k_input_offset_x;
g_input_box_y = g_game_window_y + k_input_offset_y;
print "input box coordinate: (" + str(g_input_box_x) + "," + str(g_input_box_y) + ")"
return True;
'''''
判断是否是箭头的颜色
''' def isArrowColor(colorVal):
if colorVal[2] >= 190 and colorVal[0] < 140: # blue 210?
return True;
return False;
'''''
检查箭头数量是否是奇数
''' def isArrowsNumOdd(imgInputBox):
global g_input_box_x
global g_input_box_y
# for curCheckBoxNum in range(k_max_arrows_count):
# for eachPt in range()
isOdd = False;
imgWidth = imgInputBox.size[0];
imgHeight = imgInputBox.size[1];
if isArrowColor(imgInputBox.getpixel((imgWidth/2,imgHeight/2))):
isOdd = True;
return isOdd;
'''''
获取箭头坐标列表
''' def getArrowsList(img,isOdd):
imgWidth = img.size[0];
imgHeight = img.size[1];
length = 0;
loopTimes = int(k_max_arrows_count / 2);
if isOdd:
length = 1;
loopTimes = loopTimes - 1;
baseCoordX = 0;
baseCoordY = imgHeight / 2 - k_arrow_height / 2;
if isOdd:
baseCoordX = imgWidth / 2 - k_arrow_width/2 - loopTimes * (k_arrow_width + k_arrow_space);
else:
baseCoordX = imgWidth / 2 - k_arrow_space / 2 - (loopTimes - 1) * k_arrow_space - loopTimes * k_arrow_width;
#baseCoordX = baseCoordX + g_input_box_x
baseCoordX = baseCoordX + g_input_box_x + 1 # +1 fix offset
baseCoordY = baseCoordY + g_input_box_y
arrowsList = [];
for i in range(k_max_arrows_count):
curX = baseCoordX + i * (k_arrow_width + k_arrow_space);
curY = baseCoordY;
arrowBox = (curX,curY,curX + k_arrow_width,curY + k_arrow_height);
image = ImageGrab.grab(arrowBox);
#image.save("./output/" + str(i) + ".png","png");
arrowsList.append(image);
return arrowsList;
'''''
箭头截图
''' def grabArrows():
print 'grabArrows';
global g_input_box_x;
global g_input_box_y;
global k_arrow_space;
global k_arrow_width;
global k_arrow_height;
global k_max_arrows_count;
global g_input_box_width;
global g_input_box_height;
inputBox = (g_input_box_x,g_input_box_y,g_input_box_x + g_input_box_width , g_input_box_y + g_input_box_height);
imgInputBox = ImageGrab.grab(inputBox);
#imgInputBox.save("./output/inputBox.png","png");
isOdd = isArrowsNumOdd(imgInputBox);
return getArrowsList(imgInputBox,isOdd);
'''''
根据图片,检测是哪个方向键
''' def checkDir(img):
'''''
key points coordinate:
(6,8) (18,8)
(6,17) (18,17)
'''
if isArrowColor(img.getpixel((6,8))) and isArrowColor(img.getpixel((18,8))):
print "up"
return k_dir_up;
elif isArrowColor(img.getpixel((6,17))) and isArrowColor(img.getpixel((18,17))):
print "down"
return k_dir_down;
elif isArrowColor(img.getpixel((6,8))) and isArrowColor(img.getpixel((6,17))):
print "left"
return k_dir_left;
elif isArrowColor(img.getpixel((18,8))) and isArrowColor(img.getpixel((18,17))):
print "right"
return k_dir_right;
print "unknown"
return k_dir_unknown;
'''''
根据箭头列表,确定方向键列表
''' def getArrowsKeys(arrowsList):
print "keys -------------------- "
keysList = [];
for i in range(len(arrowsList)):
checkResult = checkDir(arrowsList[i]);
if checkResult != k_dir_unknown:
keysList.append(checkResult);
return keysList;
def pressDirKey(keyList):
for i in range(len(keyList)):
dir = keyList[i];
keyCode = 0;
if dir == k_dir_up:
keyCode = 38;
elif dir == k_dir_down:
keyCode = 40;
elif dir == k_dir_left:
keyCode = 37;
elif dir == k_dir_right:
keyCode = 39;
hardwareScanCode = win32api.MapVirtualKey(keyCode,0)
win32api.keybd_event(keyCode,hardwareScanCode,0,0);
win32api.keybd_event(keyCode,hardwareScanCode,win32con.KEYEVENTF_KEYUP,0);
def pressSpaceKey():
keyCode = 32;
hardwareScanCode = win32api.MapVirtualKey(keyCode,0)
win32api.keybd_event(keyCode,hardwareScanCode,0,0);
win32api.keybd_event(keyCode,hardwareScanCode,win32con.KEYEVENTF_KEYUP,0);
'''''
检测是否是 flag 节奏标 的 颜色
''' def isFlagColor(colorVal):
# for i in range(len(colorVal)):
# print colorVal[i];
#if colorVal[0] == k_flag_r and colorVal[1] == k_flag_g and colorVal[2] == k_flag_b:
#if colorVal[0] < 255:
if colorVal[0] < 240:
return False;
if colorVal[1] <= 90 or colorVal[1] >= 120:
return False;
if colorVal[2] <= 50 or colorVal[2] >= 85:
return False;
return True;
is_in_turn = True;
def startTurn():
print "start turn";
global is_in_turn;
is_in_turn = True;
arrowsList = grabArrows();
keyList = getArrowsKeys(arrowsList);
pressDirKey(keyList);
def endTurn():
print "end turn";
global is_in_turn;
is_in_turn = False;
def triggerSpace():
print "trigger space"
pressSpaceKey();
'''''
todo
检测 什么时候 开始新一轮,什么时候 要按 space 键
# 并且按下 空格键
''' def checkTimer():
global start_x
global start_y
global dest_x
global dest_y
global is_in_turn;
start_x = start_x + g_game_window_x;
start_y = start_y + g_game_window_y;
dest_x = dest_x + g_game_window_x;
dest_y = dest_y + g_game_window_y;
# startBlockImg = ImageGrab.grab((start_x,start_y,start_x + check_offset,start_y + check_offset));
# startBlockImg.save("./output/check.png","png");
while True:
# time.sleep(0.01);
# print "is in turn?" + str(is_in_turn);
if not is_in_turn:
startBlockImg = ImageGrab.grab((start_x,start_y,start_x + check_offset,start_y + check_offset));
if isFlagColor(startBlockImg.getpixel((1,3))):
startTurn();
else:
destBlockImg = ImageGrab.grab((dest_x,dest_y,dest_x + check_offset,dest_y + check_offset));
if isFlagColor(destBlockImg.getpixel((1,3))):
triggerSpace();
endTurn();
def mainFunc():
if not initSettings():
return;
checkTimer();
# th = threading.Thread(target = checkTimer)
# th.setDaemon(True);
# th.start();
# dealFunc();
if __name__ == '__main__':
print 'AutoDance'
mainFunc();
print 'AutoDance exit!'作者:korekara88730
转载至:https://blog.csdn.net/korekara88730/article/details/48796831


随时随地看视频