# 2048 AI in Python 3

I am working on an 2048 AI and this is my code so far.

In the game 2048 you have a 4x4 grid in that some random so named tiles spawn. Each tile has a number. The lowest number is 2. By using the left, right, up and down-key you can move all tiles into the specified direction. If two tiles with the same number "collide" they merge into one and sum up there numbers.

Example:

 ___ ___ ___ ___
|___|___|___|_2_|
|___|___|_2_|___|
|___|___|___|_2_|
|___|___|___|___|

DOWN
___ ___ ___ ___
|___|___|___|___|
|___|___|___|___|
|___|___|___|___|
|___|___|_2_|_4_|


You can get all needed files on Github. My AI uses the Minimax-algorithm to calculate the best move.

I've tested it here and got scores around 10,000 Points with getting the 1024-tile.

import pyautogui
import collections
import operator
import math
import time
import cv2
import numpy as np
import random

game  = [0] * 16

UP    = 0
DOWN  = 1
LEFT  = 2
RIGHT = 3

step  = 6

def printGame(game=game):
print('')
for i in range(3):
print(end=' ')
for j in range(3):
print(str(game[i * 4 + j] if game[i * 4 + j] else '').center(4), '| ', end='')
print(str(game[i * 4 + 3] if game[i * 4 + 3] else '').center(4))
print('------|------|------|------')
print(end=' ')
for j in range(3):
print(str(game[12 + j] if game[12 + j] else '').center(4), '| ', end='')
print(str(game[12 + 3] if game[12 + 3] else '').center(4))
print('')

def getTiles():
tiles = {}
screen = cv2.cvtColor(np.array(pyautogui.screenshot()), cv2.COLOR_BGR2GRAY)
for tileNumber in [0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]:
template = cv2.imread('tile' + str(tileNumber) + '.png', 0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(screen, template, cv2.TM_CCOEFF_NORMED)
locations = np.where(res >= .8)
for location in zip(*locations[::-1]):
location = pyautogui.center((location[0], location[1], w, h))
tiles[round(location[0] / 100), round(location[1] / 100)] = tileNumber
od = collections.OrderedDict(sorted(tiles.items()))
i = 0
for k, v in od.items():
game[i % 4 * 4 + math.floor(i / 4)] = v
i += 1

def ij(direction, i, j, offset=0):
if direction == UP:
return (3 - j - offset) * 4 + i
elif direction == DOWN:
return (j + offset) * 4 + i
elif direction == LEFT:
return i * 4 + 3 - j - offset
elif direction == RIGHT:
return i * 4 + j + offset

def move(game, direction, step=step):
tmpgame = game.copy()
for i in range(4):
lj = -1
for j in range(4):
if game[ij(direction, i, j)] != 0:
if lj > -1 and game[ij(direction, i, j)] == game[lj]:
game[lj] = 0
game[ij(direction, i, j)] *= 2
lj = -1
else:
lj = ij(direction, i, j)
for i in range(4):
l = []
for j in range(4):
l.append(game[ij(direction, 3 - i, 3 - j)])
l = [x for x in l if x != 0]
while len(l) < 4:
l.append(0)
for j in range(4):
game[ij(direction, 3 - i, 3 - j)] = l[j]
if tmpgame == game:
return 0
elif step > 0:
return multispawn(game, step)
else:
return sum(game) * game.count(0)

def multimove(game, step):
l = []
for i in range(4):
l.append(move(game.copy(), i, step))
return max(l)

def spawn(game, tile, i, j, step=step):
game[i * 4 + j] = 2
if step > 0:
return multimove(game, step - 1)
else:
return sum(game) * game.count(0)

def multispawn(game, step):
l = []
for i in range(4):
for j in range(4):
if game[i * 4 + j] == 0:
l.append(spawn(game.copy(), 2, i, j, step - 1))
l.append(spawn(game.copy(), 4, i, j, step - 1))
if 0 in l:
return 0
if len(l) == 0:
if step > 0:
return multimove(game, step - 1)
else:
return sum(game) * game.count(0)
else:
return min(l)

def main():
time.sleep(2)
while True:
getTiles()
printGame()

count = {}

count['up'] = move(game.copy(), UP)
print('up', count['up'])

count['down'] = move(game.copy(), DOWN)
print('down', count['down'])

count['left'] = move(game.copy(), LEFT)
print('left', count['left'])

count['right'] = move(game.copy(), RIGHT)
print('right', count['right'])

count = [x for x,y in count.items() if y == max(count.values())]

pyautogui.press(count[random.randint(0, len(count) - 1)])

time.sleep(0.2)

if __name__ == '__main__':
main()