Solving Minesweeper using Z3 SMT solver

Sun, Jun 14, 2020 3-minute read

This program solves a 9x9 minesweeper grid it can also be tried for other lengths. It calculates the probability of a mine being present at a particular grid.


#! /usr/bin/env python
#
# Author: Somesh Bhandarkar
# UID: 116715843
# This program is written as a project for the course 
##
# The program uses z3 SMT solver https://github.com/Z3Prover/z3
# I have taken a few references for this approach
#
# https://yurichev.com/writings/SAT_SMT_by_example.pdf
# http://www.hakank.org/z3/
#
# The sum will look something like this
#
#		 00000000000
#        01234567890
#		 02-width--0
#		 03 l      0
#		 04 e      0
#		 05 n      0
#		 06 g      0
#		 07 t      0
#		 08 h      0
#		 09		   0
#		 00000000000
#  
#	The 0 is where the border is and the value inside it will
#   always be 0, as there is no possibility of a mine being there
#
# The program uses a binary 0/1 approach for the mine placement
# The program uses a list of list/matrix entry to store the boolean
# The main unknown here is whether there is a mine in the unknown grid
# I have solved this problem by feeding constraints to the SMT Solver
# and checking satisfiability
#
#
#
#
# 
from z3 import *
import sys
from itertools import product, starmap

s = Solver()

def initial_config(length, width):
	s = Solver()
	global grids
	grids=[]
	#blocks = []
	for r in range(0,  length+2):
		t = []
		for c in range(0,  width+2):
			t += [ Int('(%d %d)' % (r,c)) ]
		grids.append(t)

	for c in range(width+2):
		#s.add(grids[length+1][c]==0)
		for r in range(length+2):
			if r==0 or c==0:
				s.add(grids[r][c]==0)

			if r==length+2:
				s.add(grids[r][c]==0)

			if c==width+2:
				s.add(grids[r][c]==0)

	return grids

#def place_mine(grids):
#	s = Solver()
#	for r in range (1, length +1):
#		for c in range (1, width +1):
#			s.add(grids[r][c]==0,grids[r][c]==1)

def neighbours(r,c):
	neigh = []
	l1 = (-1,0,+1)
	l2 = (-1,0,+1)
	neigh = ([r+a,c+b] for a in l1 for b in l2)
	return (list(neigh))
	#return(neighbors(r, c))


def add_mine(row , col):
	s = Solver()

	for r in range (1, length +1):
		for c in range (1, width +1):
			S = [-1, 0, 1]
			
			temp=the_game[r -1][c -1]
			if temp in "012345678":
				s.add(grids[r][c ]==0)

				listing = neighbours(r,c)
				temp_i,temp_j = listing[0]

				express = grids[temp_i][temp_j]

				for i,j in listing[1:]:
					express += grids[i][j]
				#add += grids[r+a,c+b] for a in S for b in S 
				#					if r + a >= 0 and c + b >= 0 and r + a < length+1 and c + b < width+1
				const= express == int(temp)
				#print express
				#print "\n", listing
				#sys.exit(0)
				s.add(const)

	s.add(grids[row][col]==1)

	#if s.check==sat:
	#	print "There may be a bomb at {} {}".format(row,col)
	if s.check()==unsat:
		print "Safe to play {} {}".format(row,col)



def main():
	global width
	global length
	global the_game
	the_problem = sys.argv[1]
	print "The Problem is from:", the_problem
	the_game = read_problem(the_problem)
	width = len(the_game[0])
	length = len(the_game)
	print_problem(the_game)
	#just_checking()
	#print "w:", width, "l:", length
	#print the_game
	grids = initial_config(length, width)
	#print grids
	for r in range (1, length +1):
		for c in range (1, width +1):
			if the_game[r-1][c-1]== "X":
				#place_mine(grids)
				#print r,c
				add_mine (r, c)


def print_problem(listing):
	print "The grid is:"
	print "\n"
	for i in listing:
		print "\t", i
	print "\n"



def read_problem(file):
	lines = []
	with open(file , 'r') as f:
		lines = f.readlines()
		lst = []
    	for items in lines:
    		items = items.rstrip("\n")
    		lst.append(items)

    	return lst

def just_checking():
	print "w:", width, "l:", length
	print the_game

if __name__ == "__main__":
	main()