Skip to content

Commit

Permalink
Merge pull request #43 from coolsonu39/sudoku-adb
Browse files Browse the repository at this point in the history
Adds android sudoku solver
  • Loading branch information
harshareddy794 authored Oct 7, 2020
2 parents f58da30 + 67066a9 commit 8304578
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Python/Android sudoku solver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# sudoku-adb
Python script to automate solving a sudoku game in android over ADB

### See it in action [here](https://www.linkedin.com/posts/sonalagr_python-sudoku-automated-activity-6710469597004488704--Oji).

## How does it work?
- Extract the numbers into a 2D Matrix from the screenshot
- Solve the incomplete grid using backtracking algoithm
- Update the values by sending touch commands to device

### Game download [link](https://play.google.com/store/apps/details?id=com.hagstrom.henrik.sudoku)
77 changes: 77 additions & 0 deletions Python/Android sudoku solver/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# import required modules
from ppadb.client import Client
from PIL import Image
import pytesseract
import copy
from sudoku import solve, print_board

# connect to local adb server
adb = Client()
devices = adb.devices()
if len(devices) == 0:
print("No devices attached")
quit()
device = devices[0]

# save a screenshot & load it using PIL
result = device.screencap()
with open("screen.png", "wb") as fp:
fp.write(result)
im = Image.open("screen.png")

# starting position of the grid on screen
x, y = 3, 285

# width & height of single cell
dx, dy = 112, 114

# grid contains the incomplete sudoku (9x9 matrix)
# touch is also a 9x9 matrix where each element is a tuple containing pixel coordinate
grid, touch = [], []

for j in range(1,10):
grid_row, t_row = [], []
xcopy = x

for i in range(1,10):
# crop & zoom into individual cells
imcrop = im.crop((x,y,x+dx,y+dy)).crop((16,13,98,104))

t_row.append( ((2*x+dx)//2, (2*y+dy)//2) )

# every third vertical line is thicker
x += 11 if i%3==0 else 7

# extract the number from a cell
temp = pytesseract.image_to_string(imcrop, config='--psm 10')
temp = int(temp[0]) if temp[0].isnumeric() else 0

grid_row.append(temp)
x += dx
x = xcopy

# every third horizontal divider is thicker
y += dy + (12 if j%3==0 else 8)

grid.append(grid_row)
touch.append(t_row)

# solve & print the board
orig_grid = copy.deepcopy(grid)
solve(grid)
print_board(grid)

def click(i, j):
device.shell(f'input touchscreen tap {touch[i][j][0]} {touch[i][j][1]}')

def select(n):
arr = [123, 212, 314, 410, 515, 623, 724, 839, 937]
device.shell(f'input touchscreen tap {arr[n-1]} 1453')

# update values on screen
for i in range(len(grid)):
for j in range(len(grid[0])):
# only click on cells that were empty initially
if (orig_grid[i][j] == 0):
select(grid[i][j])
click(i, j)
Binary file added Python/Android sudoku solver/screen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions Python/Android sudoku solver/sudoku.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# copied from https://techwithtim.net/tutorials/python-programming/sudoku-solver-backtracking/

# board = [
# [7,8,0,4,0,0,1,2,0],
# [6,0,0,0,7,5,0,0,9],
# [0,0,0,6,0,1,0,7,8],
# [0,0,7,0,4,0,2,6,0],
# [0,0,1,0,5,0,9,3,0],
# [9,0,4,0,6,0,0,0,5],
# [0,7,0,3,0,0,0,1,2],
# [1,2,0,0,0,7,4,0,0],
# [0,4,9,2,0,6,0,0,7]
#

# board = [[6, 0, 0, 0, 9, 0, 0, 1, 0], [3, 9, 0, 5, 0, 2, 0, 0, 0], [0, 0, 2, 0, 0, 0, 0, 0, 4], [0, 0, 0, 9, 0, 5, 0, 2, 0], [0, 4, 0, 0, 2, 0, 7, 0, 3], [0, 0, 6, 0, 8, 0, 9, 4, 0], [0, 3, 0, 4, 0, 1, 0, 6, 7], [0, 5, 0, 0, 0, 0, 8, 0, 9], [0, 0, 7, 0, 0, 9, 4, 0, 1]]

def solve(bo):
find = find_empty(bo)
if not find:
return True
else:
row, col = find

for i in range(1,10):
if valid(bo, i, (row, col)):
bo[row][col] = i

if solve(bo):
return True

bo[row][col] = 0

return False


def valid(bo, num, pos):
# Check row
for i in range(len(bo[0])):
if bo[pos[0]][i] == num and pos[1] != i:
return False

# Check column
for i in range(len(bo)):
if bo[i][pos[1]] == num and pos[0] != i:
return False

# Check box
box_x = pos[1] // 3
box_y = pos[0] // 3

for i in range(box_y*3, box_y*3 + 3):
for j in range(box_x * 3, box_x*3 + 3):
if bo[i][j] == num and (i,j) != pos:
return False

return True


def print_board(bo):
for i in range(len(bo)):
if i % 3 == 0 and i != 0:
print("- - - - - - - - - - - - - ")

for j in range(len(bo[0])):
if j % 3 == 0 and j != 0:
print(" | ", end="")

if j == 8:
print(bo[i][j])
else:
print(str(bo[i][j]) + " ", end="")


def find_empty(bo):
for i in range(len(bo)):
for j in range(len(bo[0])):
if bo[i][j] == 0:
return (i, j) # row, col

return None

# print_board(board)
# solve(board)
# print("___________________")
# print_board(board)

0 comments on commit 8304578

Please sign in to comment.