Skip to content

Instantly share code, notes, and snippets.

@Meshiest
Last active February 17, 2020 15:55
Show Gist options
  • Select an option

  • Save Meshiest/b016ddc073cd1dfec3ca9a008c980f5a to your computer and use it in GitHub Desktop.

Select an option

Save Meshiest/b016ddc073cd1dfec3ca9a008c980f5a to your computer and use it in GitHub Desktop.
reheatedcake's Split or Steal game bot for splitting
"""
## cake's splitbot
You **will** get banned for using this bot. It may take a few hours but it will happen.
This will probably need tuning if you actually want to use it.
I wrote this to learn opencv and because I was too lazy to sit idly clicking a few buttons.
Tested on Windows Python 3.8 64bit. I have a 1440p display running the game in 1080p windowed mode
### Installation
pip install pyautogui imutils numpy
Install Windows Sdk, follow the pywin32 install instructions
Follow online instructions for python opencv
### What it does
1. screenshot the game
2. identify all the rectangle/oval buttons by their hues
3. determine which page we're on based on button size, count, and placement
4. click on the right button
"""
import psutil, imutils
import math, random, time
import win32gui, win32con, win32api, win32process
import pyautogui
import cv2, numpy
from functools import reduce
# Get window from a pid
def getHWnds(pid):
def callback(hwnd, hwnds):
if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
if found_pid == pid:
hwnds.append(hwnd)
return True
hwnds = []
win32gui.EnumWindows(callback, hwnds)
return hwnds
# Find the split or steal process
def getProcess():
# Iterate over all running process
for proc in psutil.process_iter():
try:
# Return process that is split or steal exe
if proc.name() == 'Split Or Steal.exe':
return proc
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
# image saving for debug purposes
def saveImg(img):
file = 'screenshot {}.bmp'.format(int(time.time()))
cv2.imwrite(file, img)
# cropped screenshot of a window
def screenshot(hwnd):
x, y, x1, y1 = win32gui.GetClientRect(hwnd)
x, y = win32gui.ClientToScreen(hwnd, (x, y))
x1, y1 = win32gui.ClientToScreen(hwnd, (x1 - x, y1 - y))
im = pyautogui.screenshot(region=(x, y, x1, y1))
return cv2.cvtColor(numpy.array(im), cv2.COLOR_RGB2BGR)
# left clicks
def leftClick():
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0)
time.sleep(.1)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0)
print('Click.')
# set relative mouse position
def setMouse(cord):
x, y, x1, y1 = win32gui.GetClientRect(hwnd)
x, y = win32gui.ClientToScreen(hwnd, (x, y))
win32api.SetCursorPos((x + cord[0], y + cord[1]))
# get relative mouse position
def getMouse():
mx, my = win32api.GetCursorPos()
x, y, x1, y1 = win32gui.GetClientRect(hwnd)
x, y = win32gui.ClientToScreen(hwnd, (x, y))
return (mx - x, my - y)
# distance between two points
def dist(a, b):
return math.hypot(b[1]-a[1], b[0]-a[0])
# move a point along a vector
def translate(a, b, len, curve=0):
theta = math.atan2(b[1]-a[1], b[0]-a[0])
theta += curve
return (
int(a[0] + math.cos(theta) * len),
int(a[1] + math.sin(theta) * len),
)
# smoothly move the mouse
def moveMouse(coord):
# throw in a nice little curve
curve = random.random() * 0.3 + 0.2
if random.random() < 0.5: curve = -curve
# move the mouse towards the button until it is within 50px
while dist(coord, getMouse()) > 50:
pos = getMouse()
setMouse(translate(pos, coord, 20, curve))
time.sleep(.005)
setMouse(coord)
# opencv finds the buttons based on color masks
def findButtons(img, debug=False, save=False):
# mask based on button colors
hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
all_mask = cv2.inRange(hsv_img, (0, 110, 110), (360, 250, 250))
blue_mask = cv2.inRange(hsv_img, (10, 110, 110), (25, 250, 250))
green_mask = cv2.inRange(hsv_img, (50, 120, 120), (80, 220, 220))
orange_mask = cv2.inRange(hsv_img, (100, 155, 155), (115, 210, 210))
mask = cv2.bitwise_or(green_mask, blue_mask, mask=None)
mask = cv2.bitwise_or(mask, orange_mask, mask=None)
# join the mask
img = cv2.bitwise_and(img, img, mask=mask)
# extract shapes
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
buttons = []
if save:
saveImg(img)
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.04 * peri, True)
(x, y, w, h) = cv2.boundingRect(approx)
# fit this button to a line
[vx,vy] = cv2.fitLine(c, cv2.DIST_L2,0,0.01,0.01)[:2]
# determine if this is a rectangle and it is sufficiently large
if w > 130 and h > 50 and vx > 0.98:
M = cv2.moments(c)
cX = int(M['m10'] / M['m00'])
cY = int(M['m01'] / M['m00'])
buttons.append(((cX, cY), w/h, len(approx), len(c)))
cv2.drawContours(img, [c], -1, (255, 255, 0), 2)
cv2.rectangle(img, (x, y), (x + w,y + h),(0,255,0),2)
cv2.putText(img, '{}, {}, {}'.format(len(approx), len(c), int(w/h*10)/10),(x + 10,y+h//2),1,1,(255,255,255))
if debug:
print(buttons)
cv2.imshow('img', img)
cv2.waitKey(0)
return buttons
# deterine if the ratios of button sizes matches
def ratioMatch(buttons, ratios):
for r in ratios:
if len(buttons) == 0:
return False
matches = [b for b in buttons if b[1] > r[0]-0.1 and b[1] < r[0]+0.1]
if len(matches) != r[1]:
return False
return True
def classify(buttons):
# return menu button from taking too long
if len(buttons) == 1 and ratioMatch(buttons, [(2.2, 1)]):
return ('return', buttons[0][0], 1)
# return menu button from winning/losing
if len(buttons) == 1 and ratioMatch(buttons, [(9.4, 1)]):
return ('return', buttons[0][0], 1)
# two menus have two button menus
if len(buttons) == 2:
vdist = reduce(lambda a, b: abs(a[0][1] - b[0][1]), buttons)
hdist = reduce(lambda a, b: abs(a[0][0] - b[0][0]), buttons)
if vdist < hdist and ratioMatch(buttons, [(3.5, 2)]):
# select left most accept button
return ('accept', sorted(buttons, key = lambda p: p[0][0])[0][0], 5)
if vdist < hdist and ratioMatch(buttons, [(4.1, 2)]):
# select left most accept button
return ('complete', sorted(buttons, key = lambda p: p[0][0])[0][0], 1)
# select split or steal
if len(buttons) == 3 and ratioMatch(buttons, [(3.4, 2), (2.9, 1)]):
# select top button (split)
# return ('select', sorted(buttons, key = lambda p: p[0][1])[0][0], 1)
return ('select', (1703, 754), 1)
# lock
if len(buttons) == 4:
# select lock button
return ('lock', sorted(buttons, key = lambda p: -p[0][1])[1][0], 2)
# main menu, 4 buttons on bottom
if len(buttons) >= 7 and len(list(filter(lambda b: b[0][1] > 500, buttons))) == 4:
# select second to last button vertically
selected = sorted(buttons, key = lambda p: -p[0][1])[1]
# is generally a rectangle
if selected[2] == 4 and selected[3] < 30:
return ('join', selected[0], 5)
return None
# tester function
def test(file, expected, debug=False):
shot = cv2.imread(file)
buttons = findButtons(shot, debug=debug)
choice = classify(buttons)
if debug: print(choice)
print('test {} = {} ({})'.format(
file,
choice and choice[0] == expected or
not choice and not expected,
choice and choice[0]
))
# get the split or steal process and window
proc = getProcess()
if not proc:
print('Failed to find process')
exit(1)
print('Process ID: ', proc.pid)
hwnds = getHWnds(proc.pid)
if len(hwnds) != 1:
print('Failed to find window')
exit(1)
print('hWnd: ', hwnds)
hwnd = hwnds[0]
# if live is false, this will run the tests
live = True
# if looping is false, this will only run once
looping = True
if live:
win32gui.SetForegroundWindow(hwnd)
time.sleep(.1)
while True:
shot = screenshot(hwnd)
buttons = findButtons(shot, save=False)
choice = classify(buttons)
if choice:
(name, pos, duration) = choice
print(name, 'sleeping', duration)
moveMouse(pos)
leftClick()
time.sleep(duration)
else:
time.sleep(2)
if not looping:
break
else:
# I wrote some tests to make sure it clicked on the right thing
# you might be able to do it too
# set debug=True if you want to see what the computer sees
test('t1join.bmp', 'join')
test('accept.bmp', 'accept')
test('send.bmp', None)
test('debug1.bmp', 'lock')
# test('select.bmp', 'select', debug=True)
test('select2.bmp', 'select')
test('lock.bmp', 'lock')
test('done.bmp', 'return')
test('complete.bmp', 'complete')
# test('leave.bmp', None, debug=True)
# test('dog.bmp', 'lock')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment