How I beat Bejeweled Blitz
Tired of being beated week after week by my facebook friends, I decided to investigate the most hidden secrets of Bewejeled Blitz. After one or two thousands games, I finally realize that the only secret was to be the fastest. As I was not the fastest, there was only one thing I could do to beat my friends… I would program a script that beat them for me.
Of course, it was an incredible opportunity to learn a little more about python also.
How the script works
Basically, the script do what a human do while playing:
- Look at the board. The script captures the Bejeweled Blitz board, and analyzes where the jewels are. To do so, I used the PIL library to capture the screen. After that, I posterize the image reducing the number of colors, making it easy to distinguish the jewels between each other.
- Find a goal. The script simulates a jeweled move in memory, and looks up for a goal (three-in-a-row). If there is a goal, the script moves the mouse (PyWin32 API) and actually makes the move.
- Repeat everything again and again until game-time ends.
How to use it
The first thing that you need is to find out the (x, y) coordinates of the Bejeweled Blitz Board. To do this, start a Bejewelled Blitz and capture the screen with the –save option of the script. The next command makes a capture of the screen and saves it with the name “my_capture.bmp”.
c:>python Bwbot.pyw --save my_capture.bmp 0 0 |
Then open the image with a program like GIMP (http://www.gimp.org/) and look up the upper left coordinates of the Bejewelled Blitz Board.

To make the script play, just start a new game, and execute the script passing it the x and y coordinates you has just find out, for example:
c:>python Bwbot.pyw 527 455 |
The script
## ## Bwbot.pyw ## ## A python script to play Bejeweled Blitz ## ## Author: Fernando García ## Licence: The script is under public domain. You can use, distribute or ## modify it at your own risk. ## import win32api, win32con import random import Image import ImageChops import math, operator from PIL import ImageGrab from PIL import ImageOps import time import argparse def saveImage(filename): "Capture screen and save it to a bmp file" _im = ImageGrab.grab() _im.save(filename, "BMP") class Bwbot: WIDTH = 320 # width of the board HEIGHT = 320 # height of the board COLS = 8 # number of cols of the board ROWS = 8 # number of rows of the board LAST_ROW_INDEX = 7 # index of the last row of the board LAST_COL_INDEX = 7 # index of the last row of the board SQUARE_WIDTH = WIDTH / COLS # width of a grid square MID_SQUARE_WIDTH = SQUARE_WIDTH / 2 # mid width of a grid square SQUARE_HEIGHT = HEIGHT / ROWS # heigth of a grid square MID_SQUARE_HEIGHT = SQUARE_HEIGHT / 2 # mid height of a grid square def __init__(self, ox, oy): "Initializes instance variables" # # play time in secconds # self.playtime = 55 # # drag delay in miliseconds # 20.-human 15.-optimal 10.-quick # self.drag_delay = 20 # # screen position of the board in pixels # self.ox = ox # x screen coordinate of the board self.oy = oy # y screen coordinate of the board # # rect of screen to capture de board # self.grid = (self.ox, self.oy, self.ox + Bwbot.WIDTH, self.oy + Bwbot.HEIGHT) # # Inner representation of the board # self.matrix = [[None for _col in xrange(Bwbot.COLS)] \ for _row in xrange(Bwbot.ROWS)] # # Sample size. The size in pixels of the square used to sample colors # self.sample_size = 8 def toMouseCoordinates(self, pos): "Translates grid coordinates to mouse coordinates" _x = self.ox + pos[0] * Bwbot.SQUARE_WIDTH + Bwbot.MID_SQUARE_WIDTH _y = self.oy + pos[1] * Bwbot.SQUARE_HEIGHT + Bwbot.MID_SQUARE_HEIGHT return (_x, _y) def click(self, pos): "Make a mouse click. Pos especified in grid coordinates" _x, _y = self.toMouseCoordinates(pos) win32api.SetCursorPos((_x, _y)) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, _x, _y, 0, 0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, _x, _y, 0, 0) def dragJewel(self, pos1, pos2): "Drags a jewel from pos1 to pos2" self.click(pos1) self.click(pos2) self.wait(self.drag_delay) def wait(self, delay): "Do nothing during delay miliseconds" _delay = delay / 1000.0 _ref = time.time() while (time.time() - _ref) < _delay: pass def captureBoard(self): "Capture and preproceses Bejeweled Blitz board" _im = ImageGrab.grab() # capture and _im = ImageOps.posterize(_im.crop(self.grid), 3) # reduce colors return _im def buildMatrix(self): "Build the matrix of colors representing the board" _im = self.captureBoard() for _row in range(Bwbot.ROWS): for _col in range(Bwbot.COLS): _cx = (_col * Bwbot.SQUARE_WIDTH) + Bwbot.MID_SQUARE_WIDTH _cy = (_row * Bwbot.SQUARE_HEIGHT) + Bwbot.MID_SQUARE_HEIGHT _rect = (_cx, _cy, _cx + self.sample_size, _cy + self.sample_size) _sample_im = _im.crop(_rect) # store only de predominant color self.matrix[_col][_row] = sorted(_sample_im.getcolors())[0][1] def evaluatePos(self, pos): "Returns True if matrix contains a goal in the specified pos" _col, _row = pos _val = self.matrix[_col][_row] _col_m1 = _col - 1 # col minus 1 _col_m2 = _col - 2 # col minus 2 _col_p1 = _col + 1 # col plus 1 _col_p2 = _col + 2 # col plus 2 _row_m1 = _row - 1 # row minus 1 _row_m2 = _row - 2 # row minus 2 _row_p1 = _row + 1 # row plus 1 _row_p2 = _row + 2 # row plus 2 # horizontal if _col_m1 >= 0 and _val == self.matrix[_col_m1][_row]: if _col_m2 >= 0 and _val == self.matrix[_col_m2][_row]: return True elif _col_p1 < Bwbot.COLS and _val == self.matrix[_col_p1][_row]: return True elif _col_p2 < Bwbot.COLS and _val == self.matrix[_col_p1][_row] \ and _val == self.matrix[_col_p2][_row]: return True # vertical if _row_m1 >=0 and _val == self.matrix[_col][_row_m1]: if _row_m2 >= 0 and _val == self.matrix[_col][_row_m2]: return True elif _row_p1 < Bwbot.ROWS and _val == self.matrix[_col][_row_p1]: return True elif _row_p2 < Bwbot.ROWS and _val == self.matrix[_col][_row_p1] \ and _val == self.matrix[_col][_row_p2]: return True # no goal return False def swap(self, pos1, pos2): "Swap the values of pos1 and pos2 in the matrix" _aux = self.matrix[pos1[0]][pos1[1]] self.matrix[pos1[0]][pos1[1]] = self.matrix[pos2[0]][pos2[1]] self.matrix[pos2[0]][pos2[1]] = _aux def tryMove(self, pos1, pos2): "Simulates de move in the matrix and return True if there is a goal" self.swap(pos1, pos2) # swap positions _goal = self.evaluatePos(pos1) or self.evaluatePos(pos2) # evaluate and self.swap(pos1, pos2) # restore matrix return _goal def play(self): "plays the game during" _tnow = _tini = time.time() while(_tnow - _tini) < self.playtime: self.buildMatrix() for _row in xrange(Bwbot.ROWS): for _col in xrange(Bwbot.LAST_COL_INDEX): # evaluating pos _pos1 = (_col, _row) # try swap right _pos2 = (_col + 1, _row) _goal = self.tryMove(_pos1, _pos2) # if not goal, try swap down if _row < Bwbot.LAST_ROW_INDEX and not _goal: _pos2 = (_col, _row + 1) _goal = self.tryMove(_pos1, _pos2) # if goal, do the move if _goal: self.dragJewel(_pos1, _pos2) # time elapsed _tnow = time.time() if __name__ == '__main__': parser = argparse.ArgumentParser(description="Bejeweled Blitz Bot.") parser.add_argument('x', type=int, nargs=1, \ help='xpos of the Bejeweled Blitz board') parser.add_argument('y', type=int, nargs=1, \ help='ypos of the Bejeweled Blitz board') parser.add_argument('--save', nargs=1, required=False, \ help='capture de screen and saves it to a file') args = parser.parse_args() if args.save: saveImage(args.save[0]) else: bwb = Bwbot(args.x[0], args.y[0]) bwb.play() |
Requisites and Limitations
- Phyton 2.7 (http://python.org/download/)
- Phyton Imaging Library 1.1.7 (http://effbot.org/downloads/#pil)
- PyWin32 (http://sourceforge.net/projects/pywin32/files/)
- The script only works under Windows and I have only try it with a true color definition screen.
Future improvements and ToDo List
My only intention was to beat my friends, something that I successfully did. My hi-score is now over 1.000.000 points, much more than any human player could even dream. But the script is far to be perfect. It lacks of a more efficient evaluation routine that selects the best move at each moment, choosing a 5-in-a-row move before the others for example. Another important defect is its inability to detect multipliers and other special jewels.
I am not planning to improve the script, but if you want to improve it, I will appreciate that you email me, just to know how the script evolves.
Enjoy!
Category: python One comment »

February 10th, 2012 at 12:53 pm
Thanks for posting the code. Many bots have been created for bejeweled blitz but for some reason people are reluctant to give out the source code. I’ve been learning programming for a while with VB and python and was planning to create a bot as project. I will use your code as a start and develop it so that it will recognize special gems, evaluate the best move, and also use elite technique. I’m also learning c++ in the mean time so I may be able to optimize it so that it’s quick enough.
The goal I have in mind may seem a bit of a long shot but it is actually to achieve the most optimum play possible. I mean at any given time there must be a way to establish the best possible move and also evaluate and execute fast enough that the limitation of speed no longer resides with the software but how fast the jewels fall. That’s what I mean by best possible play. Which may translate to a high scores that approach the ceiling.