eight_puzzle.py 4,51 ko
Newer Older
# author ad71
from tkinter import *
from functools import partial

import time
import random
import numpy as np

import sys
import os.path
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from search import astar_search, EightPuzzle
import utils

root = Tk()

state = [1, 2, 3, 4, 5, 6, 7, 8, 0]
puzzle = EightPuzzle(tuple(state))
solution = None

b = [None]*9

# TODO: refactor into OOP, remove global variables

def scramble():
	""" Scrambles the puzzle starting from the goal state """

	global state
	global puzzle
	possible_actions = ['UP', 'DOWN', 'LEFT', 'RIGHT']
	scramble = []
	for _ in range(60):
		scramble.append(random.choice(possible_actions))

	for move in scramble:
		if move in puzzle.actions(state):
			state = list(puzzle.result(state, move))
			puzzle = EightPuzzle(tuple(state))
			create_buttons()

def solve():
	""" Solves the puzzle using astar_search """

	return astar_search(puzzle).solution()

def solve_steps():
	""" Solves the puzzle step by step """

	global puzzle
	global solution
	global state
	solution = solve()
	print(solution)
	
	for move in solution:
		state = puzzle.result(state, move)
		create_buttons()
		root.update()
		root.after(1, time.sleep(0.75))

def exchange(index):
	""" Interchanges the position of the selected tile with the zero tile under certain conditions """

	global state
	global solution
	global puzzle
	zero_ix = list(state).index(0)
	actions = puzzle.actions(state)
	current_action = ''
	i_diff = index//3 - zero_ix//3
	j_diff = index%3 - zero_ix%3
	if i_diff == 1:
		current_action += 'DOWN'
	elif i_diff == -1:
		current_action += 'UP'

	if j_diff == 1:
		current_action += 'RIGHT'
	elif j_diff == -1:
		current_action += 'LEFT'

	if abs(i_diff) + abs(j_diff) != 1:
		current_action = ''

	if current_action in actions:
		b[zero_ix].grid_forget()
		b[zero_ix] = Button(root, text=f'{state[index]}', width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, zero_ix))
		b[zero_ix].grid(row=zero_ix//3, column=zero_ix%3, ipady=40)
		b[index].grid_forget()
		b[index] = Button(root, text=None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, index))
		b[index].grid(row=index//3, column=index%3, ipady=40)
		state[zero_ix], state[index] = state[index], state[zero_ix]
		puzzle = EightPuzzle(tuple(state))

def create_buttons():
	""" Creates dynamic buttons """

	# TODO: Find a way to use grid_forget() with a for loop for initialization
	b[0] = Button(root, text=f'{state[0]}' if state[0] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 0))
	b[0].grid(row=0, column=0, ipady=40)
	b[1] = Button(root, text=f'{state[1]}' if state[1] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 1))
	b[1].grid(row=0, column=1, ipady=40)
	b[2] = Button(root, text=f'{state[2]}' if state[2] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 2))
	b[2].grid(row=0, column=2, ipady=40)
	b[3] = Button(root, text=f'{state[3]}' if state[3] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 3))
	b[3].grid(row=1, column=0, ipady=40)
	b[4] = Button(root, text=f'{state[4]}' if state[4] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 4))
	b[4].grid(row=1, column=1, ipady=40)
	b[5] = Button(root, text=f'{state[5]}' if state[5] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 5))
	b[5].grid(row=1, column=2, ipady=40)
	b[6] = Button(root, text=f'{state[6]}' if state[6] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 6))
	b[6].grid(row=2, column=0, ipady=40)
	b[7] = Button(root, text=f'{state[7]}' if state[7] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 7))
	b[7].grid(row=2, column=1, ipady=40)
	b[8] = Button(root, text=f'{state[8]}' if state[8] != 0 else None, width=6, font=('Helvetica', 40, 'bold'), command=partial(exchange, 8))
	b[8].grid(row=2, column=2, ipady=40)

def create_static_buttons():
	""" Creates scramble and solve buttons """

	scramble_btn = Button(root, text='Scramble', font=('Helvetica', 30, 'bold'), width=8, command=partial(init))
	scramble_btn.grid(row=3, column=0, ipady=10)
	solve_btn = Button(root, text='Solve', font=('Helvetica', 30, 'bold'), width=8, command=partial(solve_steps))
	solve_btn.grid(row=3, column=2, ipady=10)

def init():
	""" Calls necessary functions """

	global state
	global solution
	state = [1, 2, 3, 4, 5, 6, 7, 8, 0]
	scramble()
	create_buttons()
	create_static_buttons()

init()
root.mainloop()