Advertisement
Hend_Sayed

Untitled

May 12th, 2024 (edited)
690
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.37 KB | None | 0 0
  1. #eightpuzzle
  2.  
  3. from tkinter import *
  4. from tkinter import messagebox
  5. import random
  6. import copy
  7. import time
  8.  
  9. #    init:      goal:
  10. #    7 2 4      0 1 2
  11. #    5 0 6      3 4 5
  12. #    8 3 1      6 7 8
  13.  
  14. _goal_state = '012345678'
  15. _init_state = '724506831'
  16.  
  17. # neighbors of each square
  18. _neighbors = {0: [1,3],
  19.               1: [0,2,4],
  20.               2: [1,5],
  21.               3: [0,4,6],
  22.               4: [1,3,5,7],
  23.               5: [2,4,8],
  24.               6: [3,7],
  25.               7: [4,6,8],
  26.               8: [5,7]}
  27.  
  28. # Manhattan Distances from any square to another
  29. _distance = [[0,1,2,1,2,3,2,3,4],
  30.              [1,0,1,2,1,2,3,2,3],
  31.              [2,1,0,3,2,1,4,3,2],
  32.              [1,2,3,0,1,2,1,2,3],
  33.              [2,1,2,1,0,1,2,1,2],
  34.              [3,2,1,2,1,0,3,2,1],
  35.              [2,3,4,1,2,3,0,1,2],
  36.              [3,2,3,2,1,2,1,0,1],
  37.              [4,3,2,3,2,1,2,1,0]]
  38.  
  39.  
  40. _algo = {1:"Breadth-First Search",
  41.         2:"Iterative Deepening Search",
  42.         3:"A* Search",
  43.          }
  44.  
  45. class EightPuzzle(object):
  46.  
  47.     def __init__(self, input_state=None):
  48.         if input_state:
  49.             self.state = copy.deepcopy(input_state)
  50.         else:      
  51.             # generate a solvable state randomly
  52.             self.state = copy.deepcopy(_goal_state)
  53.             self.shuffle()
  54.            
  55.     # shuffle the current state
  56.     def shuffle(self):
  57.         pos0 = self.state.index('0')
  58.         for i in range(100):
  59.             choices = _neighbors[pos0]
  60.             pos = choices[random.randint(0, len(choices)-1)]
  61.             self.swap(pos)
  62.             pos0 = self.state.index('0')
  63.  
  64.         # # generate a 8-puzzle problem with 1/2 chance to be unsolvable
  65.         # l = list('012345678')
  66.         # random.shuffle(l)
  67.         # self.state = ''.join(l)
  68.  
  69.     # swap 0 with its neighbor pos
  70.     def swap(self, pos):
  71.         pos0 = self.state.index('0')
  72.         l = list(self.state)
  73.         l[pos0], l[pos] = l[pos], l[pos0]
  74.         self.state = ''.join(l)
  75.  
  76.     # get all the possible next states
  77.     def get_next(self, current):
  78.         pos0 = current.index('0')
  79.         nextStates = []
  80.  
  81.         for pos in _neighbors[pos0]:
  82.             l = list(current)
  83.             l[pos0], l[pos] = l[pos], l[pos0]
  84.             step = ''.join(l)
  85.             nextStates.append(step)
  86.         return nextStates  
  87.  
  88.     # BFS algorithm
  89.     def solve_by_BFS(self):
  90.  
  91.         root = self.state
  92.         goal = '012345678'
  93.         previous = {root: None}
  94.         visited = {root: True}
  95.         solved = (root == goal)
  96.         q = [root]
  97.         while q and not solved:
  98.             current = q.pop(0)
  99.             for next_node in self.get_next(current):
  100.                 if not next_node in visited:
  101.                     visited[next_node] = True
  102.                     previous[next_node] = current
  103.                     q.append(next_node)
  104.                 if next_node == goal:
  105.                     solved = True
  106.                     break
  107.        
  108.         # return shortest path and number of states explored
  109.         if solved:
  110.             return self.retrieve_path(goal, previous), len(visited)
  111.         return None, len(visited)
  112.  
  113.  
  114.     # Iterative Deepening search algorithm
  115.     def solve_by_IDS(self):
  116.  
  117.         # DFS with depth limit
  118.         def explore(current, depth):
  119.             nonlocal goal, solved, limit
  120.             if current == goal:
  121.                 solved = True
  122.                 return
  123.             if depth >= limit:
  124.                 return
  125.             next_depth = depth+1
  126.             for next_node in self.get_next(current):
  127.                 if not next_node in visited:
  128.                     visited[next_node] = True
  129.                     previous[next_node] = current
  130.                     if not next_depth in level:
  131.                         level[next_depth] = []
  132.                     level[next_depth].append(next_node)
  133.                     explore(next_node, next_depth)
  134.                 if solved:
  135.                     break
  136.  
  137.         root = self.state
  138.         goal = '012345678'
  139.         previous = {root: None}
  140.         visited = {root: True}
  141.         level = {0:[root]}
  142.         solved = (root == goal)
  143.         limit = 0
  144.         while not solved and limit in level:
  145.             depth = limit
  146.             limit += 1
  147.             for node in level[depth]:
  148.                 explore(node, depth)
  149.  
  150.         if solved:
  151.             return self.retrieve_path(goal, previous), len(visited)
  152.         return None, len(visited)
  153.  
  154.  
  155.     # A* algorithm
  156.     def solve_by_Astar(self, method):
  157.  
  158.         class Node(object):
  159.             def __init__(self, state):
  160.                 self.state = state
  161.                 self.g = 100000
  162.                 self.h = 100000
  163.             def __str__(self):
  164.                 return self.state
  165.             def f(self):
  166.                 return self.g+self.h
  167.             def heuristic(self, method):
  168.                 goal = '012345678'
  169.                 count = 0
  170.                 if method == 1: # misplaced tiles
  171.                     for i in range(9):
  172.                         if self.state[0] != goal[0]:
  173.                             count += 1
  174.                 else:   # Manhattan distance
  175.                     for i in range(9):
  176.                         pos = goal.index(self.state[i])
  177.                         count += _distance[pos][i]
  178.                 self.h = count
  179.  
  180.         root = Node(self.state)
  181.         root.g = 0
  182.         root.heuristic(method)
  183.         goal = '012345678'
  184.         previous = {str(root): None}
  185.         visited = {str(root): True}
  186.         solved = (str(root) == goal)
  187.         q = {root.f():[root]}
  188.  
  189.         while not solved and q:
  190.             # pop from the min-queue
  191.             try:
  192.                 i = min(q)
  193.             except Exception as e:
  194.                 continue
  195.             try:
  196.                 current = q[min(q)].pop()
  197.             except Exception as e:
  198.                 del q[min(q)]
  199.                 continue
  200.            
  201.             visited[str(current)] = True
  202.             for temp in self.get_next(str(current)):
  203.                 if temp in visited:
  204.                     continue
  205.                 node = Node(temp)
  206.                 if node.g > current.g+1:
  207.                     node.g = current.g+1
  208.                     previous[temp] = str(current)
  209.                 node.heuristic(method)
  210.                 if not node.f() in q:
  211.                     q[node.f()] = []
  212.                 q[node.f()].append(node)
  213.  
  214.                 if temp == goal:
  215.                     solved = True
  216.                     break
  217.  
  218.         if solved:
  219.             return self.retrieve_path(goal, previous), len(visited)
  220.         return None, len(visited)
  221.  
  222.     # retrieve the shortest path
  223.     def retrieve_path(self, goal, previous):
  224.         path = [goal]
  225.         current = goal
  226.         while previous[current]:
  227.             path.insert(0, previous[current])
  228.             current = previous[current]
  229.         return path
  230.  
  231.  
  232. puzzle = EightPuzzle(_init_state)
  233.  
  234. # display the current puzzle state
  235. def display():
  236.     color = 'DeepPink' if puzzle.state != _goal_state else 'green'
  237.  
  238.     for i in range(9):
  239.         if puzzle.state[i] != '0':
  240.             var[i].set(str(puzzle.state[i]))
  241.             label[i].config(bg=color)
  242.         else:
  243.             var[i].set('')
  244.             label[i].config(bg='white')
  245.  
  246. # solve 8-puzzle using specific algorithm
  247. def solve():
  248.     for b in button:
  249.         b.configure(state='disabled')
  250.     option.configure(state='disabled')
  251.  
  252.     run = {1: puzzle.solve_by_BFS,
  253.         2: puzzle.solve_by_IDS,
  254.         3: lambda:puzzle.solve_by_Astar(1)}
  255.  
  256.     temp = select.get()
  257.     index = 1
  258.     for k,e in _algo.items():
  259.         if e == temp:
  260.             index = k
  261.             break
  262.    
  263.     print('Solving...')
  264.    
  265.     # get solving time
  266.     stime = time.time()
  267.     path, n = run[index]()
  268.     ttime = time.time()
  269.  
  270.     # if 8-puzzle is unsolvable
  271.     if not path:    
  272.         print('This 8-puzzle is unsolvable!')
  273.         for i in range(9):
  274.             label[i].config(bg='red' if puzzle.state[i] != '0' else 'white')
  275.         for b in button:
  276.             b.configure(state='normal')
  277.         option.configure(state='normal')
  278.         return
  279.  
  280.     info = 'Algorithm: '+_algo[index]+'\n' \
  281.          + 'Time: '+str(round(ttime-stime, 6))+'s\n' \
  282.          + 'States Explored: '+str(n)+'\n' \
  283.          + 'Shortest Path: '+str(len(path)-1)+' steps.'
  284.     print(info)
  285.     display_procedure(path)    
  286.  
  287. # demonstrate the shortest path
  288. def display_procedure(path):
  289.     if not path:
  290.         for b in button:
  291.             b.configure(state='normal')
  292.         option.configure(state='normal')
  293.         return
  294.     puzzle.state = path.pop(0)
  295.     display()
  296.     win.after(500, lambda: display_procedure(path))
  297.  
  298. # shuffle the state
  299. def shuffle():
  300.     puzzle.shuffle()
  301.     display()
  302.  
  303. # reset to the initial state
  304. def reset():
  305.     puzzle.state = copy.deepcopy(_init_state)
  306.     display()
  307.  
  308. # move with mouse clicking
  309. def move(event):
  310.     text = event.widget.cget('text')
  311.     if not text:
  312.         return
  313.    
  314.     pos = puzzle.state.index(text)
  315.     pos0 = puzzle.state.index('0')
  316.     if _distance[pos0][pos] > 1:
  317.         return
  318.  
  319.     puzzle.swap(pos)
  320.     display()
  321.  
  322. #
  323. # Set up of Basic UI
  324. #
  325. win = Tk()
  326. win.geometry('+300+100')
  327. win.title('8-Puzzle')
  328. algoFrame = Frame(win, width=260, relief=RAISED)
  329. algoFrame.pack()
  330. select = StringVar(algoFrame)
  331. select.set(_algo[1]) # default value
  332. option = OptionMenu(algoFrame, select, _algo[1], _algo[2], _algo[3])
  333. option.pack()
  334. board = Frame(win, width=260, height=260, relief=RAISED)
  335. board.pack()
  336. var = [StringVar() for i in range(9)]
  337. label = [Label(board, textvariable=var[i], bg='gray', font=('Calibri', 48)) for i in range(9)]
  338. for i in range(3):
  339.     for j in range(3):
  340.         label[i*3+j].bind("<Button-1>", lambda event: move(event))
  341.         label[i*3+j].place(x=85*j+5,y=85*i+5, width=80, height=80)
  342.        
  343. buttonFrame = Frame(win, relief=RAISED, borderwidth=1)
  344. buttonFrame.pack(fill=X, expand=True)
  345. button = []
  346. button.append(Button(buttonFrame, width='8', relief=RAISED, text="Reset", command=reset))
  347. button.append(Button(buttonFrame, width='8', relief=RAISED, text="Random", command=shuffle))
  348. button.append(Button(buttonFrame, width='8', relief=RAISED, text="Solve", command=solve)) # to be initialized
  349. for b in button:
  350.     b.pack(side=LEFT, padx=5, pady=7)
  351.  
  352.  
  353. # initialization of the game
  354. def main():
  355.     display()
  356.     win.mainloop()
  357.  
  358. if __name__ == "__main__":
  359.     main()
  360.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement