{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "provenance": [], "collapsed_sections": [] }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" } }, "cells": [ { "cell_type": "markdown", "source": [ "### Knowledge and Data Mining Demo\n", "\n", "## Minesweeper \n", "\n", "Leonardo Amato (ID:2028621), Lorenzo Corrado (ID:2020623)\n", "\n", "October 05, 2022" ], "metadata": { "id": "yLjSueQiVN8v" } }, { "cell_type": "markdown", "source": [ "# Libraries" ], "metadata": { "id": "eWl70NDLVlh1" } }, { "cell_type": "code", "source": [ "!pip install python-sat\n", "from pysat.solvers import Minisat22\n", "import itertools\n", "import random\n", "import queue" ], "metadata": { "id": "R5r6h8vPnKU-", "colab": { "base_uri": "https://localhost:8080/" }, "outputId": "4eed171e-7de7-4163-aece-19909178a7c5" }, "execution_count": 1, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", "Collecting python-sat\n", " Downloading python_sat-0.1.7.dev19-cp37-cp37m-manylinux2010_x86_64.whl (1.8 MB)\n", "\u001b[K |████████████████████████████████| 1.8 MB 5.1 MB/s \n", "\u001b[?25hRequirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from python-sat) (1.15.0)\n", "Installing collected packages: python-sat\n", "Successfully installed python-sat-0.1.7.dev19\n" ] } ] }, { "cell_type": "markdown", "source": [ "# Implementation" ], "metadata": { "id": "YDYzGVgmV3AN" } }, { "cell_type": "markdown", "source": [ "##Utils\n", "utils used by the various methods\n", "\n" ], "metadata": { "id": "Ae8or0_I9yVz" } }, { "cell_type": "code", "source": [ "def findsubsets(s, n):\n", " return [list(i) for i in itertools.combinations(s, n)]\n", "\n", "def compute_all_position():\n", " position_list = []\n", " for x in range(GRID_HEIGHT):\n", " for y in range(GRID_WIDTH):\n", " position_list.append((x,y))\n", " return position_list\n", "\n", "def compute_adj_position(x,y):\n", " all_adj_position = {(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)}\n", " filtered_adj_position = []\n", " for (x_off,y_off) in all_adj_position:\n", " new_x = x+x_off\n", " new_y = y+y_off\n", " if(new_x >= 0 and new_y >= 0 and new_x < GRID_HEIGHT and new_y < GRID_WIDTH):\n", " filtered_adj_position.append((new_x,new_y))\n", " return filtered_adj_position\n", "\n", "def check_number_in_position(game_state, x, y, min, max):\n", " for n in range (min,max):\n", " if ((n,x,y) in game_state):\n", " return n\n", " return None" ], "metadata": { "id": "1-djwZlMc0Xh" }, "execution_count": 2, "outputs": [] }, { "cell_type": "code", "source": [ "def game_state_print(game_state,mine_set,lost_position,won):\n", " string = \"\" \n", " string += \"╔\" \n", " for x in range(GRID_WIDTH):\n", " string += \"═══\" \n", " if(x < GRID_WIDTH - 1):\n", " string += \"╦\" \n", " string += \"╗\" \n", " string += \"\\n\"\n", " for x in range(GRID_HEIGHT):\n", " for y in range(GRID_WIDTH):\n", " if (x,y) == lost_position:\n", " string += \"║X☼X\" \n", " else: \n", " if((x,y) in mine_set):\n", " if won:\n", " string += \"║▓☼▓\" \n", " else:\n", " string += \"║▓▓▓\" \n", " else: \n", " number = check_number_in_position(game_state,x,y,1,8)\n", " if(number is not None):\n", " string += \"║ \" + str(number) + \" \"\n", " else:\n", " if (0,x,y) in game_state:\n", " string += \"║ \" \n", " else:\n", " string += \"║░░░\" \n", " string += \"║\\n\"\n", " if(x < GRID_HEIGHT - 1):\n", " string += \"╠\" \n", " for x in range(GRID_WIDTH):\n", " string += \"═══\" \n", " if(x < GRID_WIDTH - 1):\n", " string += \"╬\" \n", " string += \"╣\" \n", " string += \"\\n\"\n", " \n", " string += \"╚\" \n", " for x in range(GRID_WIDTH):\n", " string += \"═══\" \n", " if(x < GRID_WIDTH - 1):\n", " string += \"╩\" \n", " string += \"╝\" \n", " string += \"\\n\"\n", " print(string)" ], "metadata": { "id": "mSAizHH4n7Jq" }, "execution_count": 3, "outputs": [] }, { "cell_type": "markdown", "source": [ "##Game state to CNF conversion\n", "Methods used to convert the state of the game to a CNF formula" ], "metadata": { "id": "USf7YY8R-RXG" } }, { "cell_type": "code", "source": [ "def Mine(r,c):\n", " return r*GRID_WIDTH + c + 1" ], "metadata": { "id": "T23YWA9U4SC8" }, "execution_count": 4, "outputs": [] }, { "cell_type": "code", "source": [ "def number_of_mines_rule(minesweaper, number_set, number_of_mines):\n", " ##RULE: there are n mine in the field\n", " if(number_of_mines > -1):\n", " all_positions = compute_all_position()\n", " for position in all_positions:\n", " if check_number_in_position(number_set,position[0],position[1],0,8) is not None:\n", " all_positions.remove(position)\n", " for adj_subset in findsubsets(all_positions, (GRID_WIDTH*GRID_HEIGHT)-number_of_mines+1):\n", " mine_position_list = []\n", " for (x_off,y_off) in adj_subset:\n", " mine_position_list.append((x_off,y_off))\n", " if len(mine_position_list) > 0:\n", " minesweaper.add_clause([Mine(i,j) for (i,j) in mine_position_list] )\n", " for adj_subset in findsubsets(all_positions, number_of_mines+1):\n", " mine_position_list = []\n", " for (x_off,y_off) in adj_subset:\n", " mine_position_list.append((x_off,y_off))\n", " if len(mine_position_list) > 0:\n", " minesweaper.add_clause([-Mine(i,j) for (i,j) in mine_position_list])" ], "metadata": { "id": "GDMQp6Mjf9sw" }, "execution_count": 5, "outputs": [] }, { "cell_type": "code", "source": [ "def state_to_CNF(minesweaper, numbers_set):\n", " #converting number to cnf knowledge\n", " #for each number\n", " for (number,x,y) in numbers_set:\n", " #if we have a number the position is safe\n", " minesweaper.add_clause([-Mine(x,y)])\n", " if(number == 0):\n", " continue\n", " #we get the adj positions\n", " adj_position = compute_adj_position(x,y)\n", " \n", " #we have at least N mines in the adj positions\n", " for adj_subset in findsubsets(adj_position, len(adj_position)-number+1):\n", " mine_position_list = []\n", " for (x_off,y_off) in adj_subset:\n", " mine_position_list.append((x_off,y_off))\n", " if len(mine_position_list) > 0:\n", " minesweaper.add_clause([Mine(i,j) for (i,j) in mine_position_list] )\n", "\n", " #we have at most N mines in the adj positions\n", " for adj_subset in findsubsets(adj_position, number+1):\n", " mine_position_list = []\n", " for (x_off,y_off) in adj_subset:\n", " mine_position_list.append((x_off,y_off))\n", " if len(mine_position_list) > 0:\n", " minesweaper.add_clause([-Mine(i,j) for (i,j) in mine_position_list])\n", " \n", "\n" ], "metadata": { "id": "yg0g7mvanWWy" }, "execution_count": 6, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Field section\n", "This section is used to encapsulate the field to make so that the player can't directly see it" ], "metadata": { "id": "xG7b6Tn87cbn" } }, { "cell_type": "code", "source": [ "##Field\n", "def GenerateMineSweeperMap(height, width, number_of_mines):\n", " field = [[0 for column in range(width)] for row in range(height)]\n", " for num in range(number_of_mines):\n", " while (True):\n", " i = random.randint(0,height-1)\n", " j = random.randint(0,width-1)\n", " if (field[i][j] != 'X'):\n", " break\n", " field[i][j] = 'X'\n", " for adj_pos in compute_adj_position(i,j):\n", " if field[adj_pos[0]][adj_pos[1]] != 'X':\n", " field[adj_pos[0]][adj_pos[1]] += 1\n", " return field\n", "\n", "def box_check(grid,i,j,first_round):\n", " if(grid[i][j] == \"X\" and first_round):\n", " grid[i][j] = 0\n", " for adj_pos in compute_adj_position(i,j):\n", " if grid[adj_pos[0]][adj_pos[1]] != 'X':\n", " grid[adj_pos[0]][adj_pos[1]] -= 1\n", " else:\n", " grid[i][j] += 1\n", " while (True):\n", " x = random.randint(0,GRID_HEIGHT-1)\n", " y = random.randint(0,GRID_WIDTH-1)\n", " if (grid[x][y] != 'X' and (y,x) != (i,j)):\n", " break\n", " grid[x][y] = 'X'\n", " for adj_pos in compute_adj_position(x,y):\n", " if grid[adj_pos[0]][adj_pos[1]] != 'X':\n", " grid[adj_pos[0]][adj_pos[1]] += 1\n", " \n", "\n", " zero_box = queue.Queue()\n", " checked_box = set()\n", " output = set()\n", " if(grid[i][j] != 'X'):\n", " if(grid[i][j] != 0):\n", " output.add((grid[i][j],i,j))\n", " if(grid[i][j] == 0):\n", " zero_box.put((i,j))\n", " while(not zero_box.empty()):\n", " position = zero_box.get()\n", " output.add((0,position[0],position[1]))\n", " checked_box.add(position)\n", " for adj_pos in compute_adj_position(position[0],position[1]):\n", " if(adj_pos not in checked_box):\n", " if(grid[adj_pos[0]][adj_pos[1]] == 0):\n", " zero_box.put(adj_pos)\n", " else:\n", " output.add((grid[adj_pos[0]][adj_pos[1]],adj_pos[0],adj_pos[1]))\n", " checked_box.add(adj_pos)\n", " return output\n", "\n", "def winning_check(mine_set, grid):\n", " count = 0\n", " for position in mine_set:\n", " if(grid[position[0]][position[1]] == 'X'):\n", " count += 1\n", " else:\n", " return False\n", " return count == N_MINES\n", "\n", "\n", " \n" ], "metadata": { "id": "2bN6WrsK0vtf" }, "execution_count": 7, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Player decision section\n", "In the following section we have all the function used by the player to infer new knowledge about the field" ], "metadata": { "id": "VN-nCGgb7YA9" } }, { "cell_type": "code", "source": [ "##Player\n", "def infer_new_knowledge(number_set, number_of_mines):#we check which position are safe, which position have a mine, and which are unknown\n", "\n", " minesweaper = Minisat22()\n", "\n", " number_of_mines_rule(minesweaper,number_set,number_of_mines)\n", " state_to_CNF(minesweaper, number_set)\n", " set_of_safe = set()\n", " set_of_mine = set()\n", " assumptions = []\n", " suggest = False\n", "\n", " for x in range(GRID_HEIGHT):\n", " for y in range(GRID_WIDTH):\n", " check_mine = minesweaper.solve(assumptions=[Mine(x,y)])\n", " check_safe = minesweaper.solve(assumptions=[-Mine(x,y)])\n", " if(check_mine and not check_safe):\n", " set_of_mine.add((x,y))\n", " assumptions.append(Mine(x,y))\n", " if(check_safe and not check_mine):\n", " assumptions.append(-Mine(x,y))\n", " number = check_number_in_position(number_set,x,y,0,8)\n", " if(number is None):\n", " set_of_safe.add((x,y))\n", " if(len(set_of_safe) == 0):\n", " en = minesweaper.enum_models()\n", " prob_matrix = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]\n", " for model in en:\n", " for coordinate in model:\n", " if(coordinate < 0):\n", " coordinate = -(coordinate) - 1\n", " prob_matrix[coordinate // GRID_WIDTH ][coordinate % GRID_WIDTH] += 1\n", "\n", " model_counting_list = []\n", "\n", " for x in range(GRID_HEIGHT):\n", " for y in range(GRID_WIDTH):\n", " number = check_number_in_position(number_set,x,y,0,8)\n", " if(number is None):\n", " model_counting_list.append((prob_matrix[x][y],x,y))\n", " random.shuffle(model_counting_list)\n", " max_value = max(model_counting_list, key = (lambda triplet : triplet[0]))\n", " set_of_safe.add((max_value[1],max_value[2]))\n", " suggest = True\n", " return set_of_safe, set_of_mine, suggest" ], "metadata": { "id": "v0Jr4ROD3ztj" }, "execution_count": 8, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Minesweeper parameters" ], "metadata": { "id": "nIKSH6XX9Wuz" } }, { "cell_type": "code", "source": [ "GRID_WIDTH = 3\n", "GRID_HEIGHT = 6\n", "N_MINES = 3\n", "random.seed(1)" ], "metadata": { "id": "HsCauzfs5ZAV" }, "execution_count": 9, "outputs": [] }, { "cell_type": "code", "source": [ "GRID_WIDTH = 4\n", "GRID_HEIGHT = 6\n", "N_MINES = 3\n", "random.seed(1)" ], "metadata": { "id": "MnN4beyK6716" }, "execution_count": 10, "outputs": [] }, { "cell_type": "code", "source": [ "GRID_WIDTH = 4\n", "GRID_HEIGHT = 4\n", "N_MINES = 5\n", "random.seed(1)" ], "metadata": { "id": "YF659CHC5Wdp" }, "execution_count": 11, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Game\n", "Given the parameters, this section is going to create a field and let the \"player\" make the various move to try to uncover all the mines" ], "metadata": { "id": "sMICZO5Z9ZFD" } }, { "cell_type": "code", "source": [ "print(GRID_HEIGHT,\"x\",GRID_WIDTH,\"Field with\",N_MINES,\"Mines\\n\")\n", "grid = GenerateMineSweeperMap(GRID_HEIGHT,GRID_WIDTH,N_MINES)\n", "mine_set = set()\n", "number_set = set()\n", "first_turn = True\n", "fail = False\n", "lost_position = None\n", "game_state_print(number_set,mine_set,lost_position,False)\n", "while(True):\n", " print(\"The player is thinking \\n\")\n", " safe,mine,suggest = infer_new_knowledge(number_set,N_MINES)\n", " for pos_mine in mine:\n", " mine_set.add(pos_mine)\n", " \n", " if len(mine) == 0:\n", " print(\"Player hasn't discovered any new mine\")\n", " else:\n", " print(\"Player has discovered that \",mine,\" have mines\")\n", " if suggest:\n", " print(\"Player thinks that \",safe,\" might be safe\")\n", " else:\n", " print(\"Player knows that \",safe,\" are safe\")\n", " print(\"\\n-----------------------------------------------------------------\")\n", "\n", " \n", " if(not winning_check(mine_set,grid)):\n", " print(\"checking all the safe positions and flagging mines\")\n", " for position in safe:\n", " new_safe = box_check(grid,position[0],position[1],first_turn)\n", " if(len(new_safe) == 0):\n", " lost_position = position\n", " fail = True\n", " break\n", " for pos_num in new_safe:\n", " number_set.add(pos_num)\n", " if(winning_check(mine_set,grid)):\n", " break\n", " if fail:\n", " break\n", " game_state_print(number_set,mine_set,lost_position,False)\n", " first_turn = False\n", "\n", "if(fail):\n", " print(\"The player has stepped on a mine\")\n", "else:\n", " print(\"The player has found all the mines\")\n", "game_state_print(number_set,mine_set,lost_position,True)\n" ], "metadata": { "id": "WRzR2vkz4Pmh", "colab": { "base_uri": "https://localhost:8080/" }, "outputId": "6ccf7e5f-dc5d-40fe-88f0-56a150bac4b0" }, "execution_count": 12, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "4 x 4 Field with 5 Mines\n", "\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player hasn't discovered any new mine\n", "Player thinks that {(0, 2)} might be safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║░░░║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player hasn't discovered any new mine\n", "Player thinks that {(1, 1)} might be safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║░░░║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player hasn't discovered any new mine\n", "Player thinks that {(1, 2)} might be safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║░░░║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player hasn't discovered any new mine\n", "Player knows that {(2, 3), (2, 1), (2, 2)} are safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║░░░║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 3 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║░░░║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player hasn't discovered any new mine\n", "Player knows that {(3, 1)} are safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║░░░║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 3 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player has discovered that {(1, 0)} have mines\n", "Player thinks that {(0, 1)} might be safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║░░░║ 1 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓▓▓║ 2 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 3 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player has discovered that {(2, 0), (1, 0)} have mines\n", "Player knows that {(0, 0)} are safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║ 1 ║ 1 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓▓▓║ 2 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓▓▓║ 3 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║░░░║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player has discovered that {(2, 0), (1, 0)} have mines\n", "Player thinks that {(3, 2)} might be safe\n", "\n", "-----------------------------------------------------------------\n", "checking all the safe positions and flagging mines\n", "╔═══╦═══╦═══╦═══╗\n", "║ 1 ║ 1 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓▓▓║ 2 ║ 1 ║░░░║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓▓▓║ 3 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║░░░║ 2 ║ 1 ║░░░║\n", "╚═══╩═══╩═══╩═══╝\n", "\n", "The player is thinking \n", "\n", "Player has discovered that {(3, 3), (3, 0), (2, 0), (1, 0), (0, 3)} have mines\n", "Player knows that {(1, 3)} are safe\n", "\n", "-----------------------------------------------------------------\n", "The player has found all the mines\n", "╔═══╦═══╦═══╦═══╗\n", "║ 1 ║ 1 ║ 1 ║▓☼▓║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓☼▓║ 2 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓☼▓║ 3 ║ 1 ║ 1 ║\n", "╠═══╬═══╬═══╬═══╣\n", "║▓☼▓║ 2 ║ 1 ║▓☼▓║\n", "╚═══╩═══╩═══╩═══╝\n", "\n" ] } ] } ] }