Skip to content

Instantly share code, notes, and snippets.

@jsettlem
Created July 29, 2019 18:52
Show Gist options
  • Save jsettlem/6ba2f666c3b6135f47ba7fcc58456ed9 to your computer and use it in GitHub Desktop.
Save jsettlem/6ba2f666c3b6135f47ba7fcc58456ed9 to your computer and use it in GitHub Desktop.
import win32api
import win32con
import win32com.client
import time
import random
import win32gui
import pyscreenshot as ImageGrab
import cv2
import cv2.cv as cv
import numpy
import math
import sys
from collections import Counter
import threading
import pygame as pg
'''
Block IDs:
-1 - empty
0 - unused
1 - blue
2 - yellow
3 - green
4 - red
5 - purple
6 - dragon
7 - pokeball
8 - garbage
'''
blocks = [[-1 for i in range(6)] for j in range(12)]
blocklock = False
mp = 'mp' in sys.argv
agro = 'agro' in sys.argv
'''
Tries to find the windows with "Pokemon Puzzle League" in the title
a = window x
b = window y
c = window width
d = window height
'''
a,b,c,d = 0,0,0,0
def callback(hwnd, extra):
global a
global b
global c
global d
rect = win32gui.GetWindowRect(hwnd)
x = rect[0]
y = rect[1]
w = rect[2]
h = rect[3]
if "Pokemon Puzzle League" in win32gui.GetWindowText(hwnd):
a = x
b = y
c = w
d = h
win32gui.EnumWindows(callback, None)
cursor = cv2.imread('cursor.png')
electric = cv2.imread('electric.png')
fire = cv2.imread('fire.png')
grass = cv2.imread('grass.png')
heart = cv2.imread('heart.png')
water = cv2.imread('water.png')
dragon = cv2.imread('dragon.png')
pokeball = cv2.imread('pokeball.png')
poketypes = [water, electric, grass, fire, heart, dragon, pokeball]
#Keybindings in the Win32 API
VK_CODE = {
'A':0x5A,
'Z':0x58,
"Left":0x41,
"Right":0x44,
"Down":0x53,
"Up":0x57
}
def spiral(N, M, x, y): #http://stackoverflow.com/questions/398299/looping-in-a-spiral/398302#398302
dx, dy = 0, -1
for dufdsafmb in xrange(N*M):
if abs(x) == abs(y) and [dx,dy] != [1,0] or x>0 and y == 1-x:
dx, dy = -dy, dx # corner, change direction
if abs(x)>N/2 or abs(y)>M/2: # non-square
dx, dy = -dy, dx # change direction
x, y = -y+dx, x+dy # jump
yield x, y
x, y = x+dx, y+dy
#Screenshot the board and locate the cursor
def findMii():
global im
global open_cv_image
global coords
global yoffset
global curX
global curY
global blocklock
#Crop the image to the location of the field
if mp:
im = ImageGrab.grab(bbox=(a+3+43,b+44+55,222,392)).convert('RGB')
else:
im = ImageGrab.grab(bbox=(a+3+223,b+44+55,a+3+223+222,b+44+55+392)).convert('RGB')
open_cv_image = numpy.array(im)
#Convert RGB to BGR
coords = open_cv_image[:, :, ::-1].copy()
#Search for the cursor's x and y position
result = cv2.matchTemplate(coords,cursor,cv2.TM_CCOEFF_NORMED)
rawCurY, rawCurX = numpy.unravel_index(result.argmax(),result.shape)
#Determine if the cursor is big or small and use that to find the
#offset of the board
if(rawCurX in [0, 36, 72, 108, 144]):
yoffset = (rawCurY - 33) % 32
rawCurY += 4
else:
yoffset = (rawCurY - 37) % 32
#Calculate the cursor's position on the grid
curX = int(math.floor(rawCurX / 36))
curY = int(math.floor((rawCurY - 37) / 32)) + 1
#press buttons
def press(*args):
'''
press, release
eg press('x', 'y', 'z')
'''
for i in args:
win32api.keybd_event(VK_CODE[i], 0, 0, 0)
time.sleep(0.05)
#Uncomment for fun debug
#print "Pressing: " + i
win32api.keybd_event(VK_CODE[i],0 ,win32con.KEYEVENTF_KEYUP ,0)
time.sleep(0.05)
#move the cursor to the specified row
def goToRow(row):
global curY
while row > curY:
curY += 1
press("Down")
while row < curY:
curY -= 1
press("Up")
#move the cursor to the specified column
def goToCol(col):
global curX
while col > curX:
curX += 1
press("Right")
while col < curX:
curX -= 1
press("Left")
#solve the row the cursor is currently on
def solveRow():
print "TRYING TO SOLVE A ROW"
global curY
curRow = blocks[curY]
toSolve = Counter(curRow).most_common(1)[0][0]
targets = [i for i,val in enumerate(curRow) if val==toSolve]
if len(targets) > 2:
if targets[1] - targets[0] > 1:
for i in range(targets[1] - targets[0] - 1):
goToCol(targets[1]-1)
press("A")
targets[1] -= 1
if targets[2] - targets[1] > 1:
for i in range(targets[2] - targets[1] - 1):
goToCol(targets[2]-1)
press("A")
targets[2] -= 1
else:
pass
#go to row 'row' then move the block at block_col to col target
def resolveUpity(row, target, block_col):
goToRow(row)
targets = [target, block_col]
if targets[1] > targets[0]:
for i in range(int(math.fabs(targets[1] - targets[0]))):
goToCol(targets[1]-1)
press("A")
targets[1] -= 1
#print "THE UPITY PROBLEM IS RESOLVED"
else:
for i in range(int(math.fabs(targets[0] - targets[1]))):
goToCol(targets[1])
press("A")
targets[1] += 1
#print "THE REVERSE UPITY PROBLEM IS RESOLVED"
#press buttons randomly and hope something good happens
def panic():
for i in range(8):
press(random.choice(("Left","Right","Up","Down","Down")))
press("A")
def main():
global blocks
global blocklock
while True:
#reset the block list
blocks = [[-1 for i in range(6)] for j in range(12)]
#locate the cursor and populate 'coords' with the image of the field
findMii()
#It's thread safe!
#Or comment out this line to have the pygame display show the blocks as they're found
blocklock = True
for blocko in range(len(poketypes)):
#It won't search for pokeballs unless it's in multiplayer mode
if blocko != 6 or mp:
#finds all matches for the specified block above threshold of 0.999
#TODO: Optimize the shit out of this. It searches much more than it needs to
result2 = cv2.matchTemplate(coords,poketypes[blocko],cv2.TM_CCOEFF_NORMED)
match_indices = numpy.arange(result2.size)[(result2>0.999).flatten()]
oak = numpy.unravel_index(match_indices,result2.shape)
blockYs = oak[0]
blockXs = oak[1]
#Add each block individually to the blocks list
for i in range(blockYs.size):
tempY = blockYs[i] - 25 - yoffset
tempX = blockXs[i] - 11
tempX = int(math.floor(tempX / 36))
tempY = int(math.floor((tempY + 37) / 32))
blocks[tempY][tempX] = blocko + 1
blocklock = False
#Get a copy of blocks[][] without empty cells
new_blocks = [[i for i in j if i not in (-1,0)] for j in blocks]
#Make an array listing the counts of the most common blocks in each row
row_counts = [Counter(i).most_common(1)[0][1] if len(i) > 0 else 0 for i in new_blocks]
#Find the row with the greatest number of blocks in common
best_row = row_counts.index(max(row_counts))
#If there are a non-zero number of blocks:
if not cmp(blocks, [[-1 for i in range(6)] for j in range(12)]) == 0:
#If the blocks are high enough or agro is disabled
if not cmp(blocks[:6], [[-1 for i in range(6)] for j in range(6)]) == 0 or not agro:
if row_counts[best_row] > 2:
#BAD BAD BAD BAD BAD
goToRow(best_row)
solveRow()
else:
found = False
#Search out in a spiral from the cursor's position
#TODO: Make this less shit
for j,i in spiral(12,6,0,0):
j = min(max(j+curY,0),11)
i = min(max(i+curX,0),5)
if not found and blocks[j][i] not in (-1,0):
#Wreck it with an empty space to the right
if ((i < 5 and blocks[j][i+1] == -1 and blocks [j+1][i+1] == -1 and blocks [j+1][i] != -1 and blocks [j-1][i+1] == -1)):
print "I'M GONNA WRECK IT TO THE RIGHT!"
goToRow(j)
goToCol(i)
press("A")
found = True
#Wreck it with an empty space to the left
elif ((i > 0 and blocks[j][i-1] == -1 and blocks [j+1][i-1] == -1 and blocks [j+1][i] != -1 and blocks [j-1][i-1] == -1)):
print "I'M GONNA WRECK IT TO THE LEFT!"
goToRow(j)
goToCol(i-1)
press("A")
found = True
#Solve very specific 6-upity problem
elif j > 1 and j < 11 and i < 5 and blocks[j][i] in blocks[j-1] and blocks[j][i] in blocks[j+1] and blocks[j][i+1] in blocks[j-1] and blocks[j][i+1] in blocks[j+1] and -1 not in blocks[j] and -1 not in blocks[j-1] and blocks[j+1]:
print "SOLVING THE MAGICAL 6-UPITY PROBLEM"
resolveUpity(j-1, i+1, blocks[j-1].index(blocks[j][i]))
resolveUpity(j+1, i+1, blocks[j+1].index(blocks[j][i]))
resolveUpity(j-1, i, blocks[j-1].index(blocks[j][i+1]))
resolveUpity(j+1, i, blocks[j+1].index(blocks[j][i+1]))
goToRow(j)
goToCol(i)
press("A")
#Solve any 5-upity problem
elif j < 8 and blocks[j][i] in blocks[j-1] and blocks[j][i] in blocks[j+1] and blocks[j][i] in blocks[j+2] and blocks[j][i] in blocks[j+3]:
print "SOLVING THE GENERIC 5 UPITY PROBLEM"
resolveUpity(j-1, i, blocks[j-1].index(blocks[j][i]))
resolveUpity(j+2, i, blocks[j+2].index(blocks[j][i]))
resolveUpity(j+3, i, blocks[j+3].index(blocks[j][i]))
resolveUpity(j+1, i, blocks[j+1].index(blocks[j][i]))
found = True
#Solve the 4-upity problem with 2 adjacent
elif j < 9 and blocks[j-1][i] == blocks[j][i] and blocks[j][i] in blocks[j+1] and blocks[j][i] in blocks[j+2]:
print "SOLVING A SPECIFIC 4-UPITY PROBLEM"
resolveUpity(j+2, i, blocks[j+2].index(blocks[j][i]))
resolveUpity(j+1, i, blocks[j+1].index(blocks[j][i]))
found = True
elif j > 1 and blocks[j+1][i] == blocks[j][i] and blocks[j][i] in blocks[j-1] and blocks[j][i] in blocks[j-2]:#DEATH
print "SOLVING A SPECIFIC 4-UPITY PROBLEM"
resolveUpity(j-2, i, blocks[j-2].index(blocks[j][i]))
resolveUpity(j-1, i, blocks[j-1].index(blocks[j][i]))
found = True
#Solve any 4-upity problem
elif j < 9 and blocks[j][i] in blocks[j-1] and blocks[j][i] in blocks[j+1] and blocks[j][i] in blocks[j+2]:
print "SOLVING THE GENERIC 4 UPITY PROBLEM"
resolveUpity(j+2, i, blocks[j+2].index(blocks[j][i]))
resolveUpity(j-1, i, blocks[j-1].index(blocks[j][i]))
resolveUpity(j+1, i, blocks[j+1].index(blocks[j][i]))
found = True
#Solve simple 3-upity
elif blocks[j-1][i] == blocks[j][i] and blocks[j][i] in blocks[j+1]:
resolveUpity(j+1, i, blocks[j+1].index(blocks[j][i]))
found = True
elif blocks[j+1][i] == blocks[j][i] and blocks[j][i] in blocks[j-1]:
resolveUpity(j-1, i, blocks[j-1].index(blocks[j][i]))
found = True
elif ((j < 10) and (blocks[j+2][i] == blocks[j][i]) and (blocks[j][i] in blocks[j+1])):
resolveUpity(j+1, i, blocks[j+1].index(blocks[j][i]))
found = True
#Get desperate, solve any 3-upity problem
elif j > 3 and blocks[j][i] in blocks[j-1] and blocks[j][i] in blocks[j-2]:
print "GETTING DESPERATE, WHORING BODY OUT TO 3-UPITY PROBLEM"
resolveUpity(j-1, i, blocks[j-1].index(blocks[j][i]))
resolveUpity(j-2, i, blocks[j-2].index(blocks[j][i]))
found = True
if not found:
panic()
print "ANARCHY OR RIOT"
else:
#If agro is enabled and the tower is too short
press("Z")
print "TOO EASY"
else:
#If no blocks can be found, try to "navigate" the menus
press("A")
#Uses pygame to make the pretty output window
def output():
pg.init()
fps = pg.time.Clock()
window = pg.display.set_mode((20*6,23*12))
pg.display.set_caption("PPL Output")
pgCursor = pg.image.load('cursor.png')
pgElectric = pg.image.load('electric.png')
pgFire = pg.image.load('fire.png')
pgGrass = pg.image.load('grass.png')
pgHeart = pg.image.load('heart.png')
pgWater = pg.image.load('water.png')
pgDragon = pg.image.load('dragon.png')
pgPokeball = pg.image.load('pokeball.png')
pgpoketypes = [pgWater, pgElectric, pgGrass, pgFire, pgHeart, pgDragon, pgPokeball]
while True:
if not blocklock:
window.fill(pg.Color(0,0,0))
for i in range(12):
for j in range(6):
if blocks[i][j] in range(1,8):
window.blit(pgpoketypes[blocks[i][j]-1], (j*20,i*23))
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit()
pg.display.update()
fps.tick(60)
main = threading.Thread(target=main)
main.dameon = True
main.start()
out = threading.Thread(target=output)
out.dameon = True
out.start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment