A* algorithm can't find the goal in Python - python

I'm still new to Python and this my first ever question on stackoverflow and I've been having trouble for a week to implement an A* algorithm.
The code I've got finds a goal with a straight wall but as you will see, as soon as I extend the wall below the start point and it has to go backwards or around it, it starts looping forever.
I've been banging my head against the wall trying to fix it and how to implement a raise code so it stops looping. Any help will be very much appreciated.
My code:
class Node:
"""A node class for A* Pathfinding"""
def __init__(self, parent=None, position=None):
self.parent = parent
self.position = position
self.g = 0
self.h = 0
self.f = 0
def __eq__(self, other):
return self.position == other.position
def astar(maze, start, end):
"""Returns a list of tuples as a path from the given start to the given end in the given maze"""
# Create start and end node
start_node = Node(None, start)
start_node.g = start_node.h = start_node.f = 0
end_node = Node(None, end)
end_node.g = end_node.h = end_node.f = 0
# Initialize both open and closed list
open_list = []
closed_list = []
# Add the start node
open_list.append(start_node)
# Loop until you find the end
while len(open_list) > 0:
# Get the current node
current_node = open_list[0]
current_index = 0
for index, item in enumerate(open_list):
if item.f < current_node.f:
current_node = item
current_index = index
# Pop current off open list, add to closed list
open_list.pop(current_index)
closed_list.append(current_node)
# Found the goal
if current_node == end_node:
path = []
current = current_node
while current is not None:
path.append(current.position)
current = current.parent
return path[::-1] # Return reversed path
# Generate children
children = []
for new_position in [(0, -1), (0, 1), (-1, 0), (1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]: # Adjacent squares
# Get node position
node_position = (current_node.position[0] + new_position[0], current_node.position[1] + new_position[1])
# Make sure within range
if node_position[0] > (len(maze) - 1) or node_position[0] < 0 or node_position[1] > (
len(maze[len(maze) - 1]) - 1) or node_position[1] < 0:
continue
# Make sure walkable terrain
if maze[node_position[0]][node_position[1]] != 0:
print("node -", node_position[0],node_position[1], "is blocked")
continue
# Create new node
new_node = Node(current_node, node_position)
# Append
children.append(new_node)
# Loop through children
for child in children:
# Child is on the closed list
for closed_child in closed_list:
if child == closed_child:
continue
# Create the f, g, and h values
child.g = current_node.g + 1
child.h = ((child.position[0] - end_node.position[0]) ** 2) + (
(child.position[1] - end_node.position[1]) ** 2)
child.f = child.g + child.h
# Child is already in the open list
for open_node in open_list:
if child == open_node and child.g > open_node.g:
continue
# Add the child to the open list
open_list.append(child)
def main():
maze = [[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
start = (0, 0)
end = (1, 8)
path = astar(maze, start, end)
print(path)
main()

You have these two pieces:
# Child is on the closed list
for closed_child in closed_list:
if child == closed_child:
continue
# Child is already in the open list
for open_node in open_list:
if child == open_node and child.g > open_node.g:
continue
The continue will continue the inner for loop. Consequently, it does not have any effect. You are rather looking for something like this:
# Child is on the closed list
is_in_closed = False
for closed_child in closed_list:
if child == closed_child:
is_in_closed = True
break
if is_in_closed:
continue
# Child is already in the open list
is_in_open = False
for open_node in open_list:
if child == open_node and child.g > open_node.g:
is_in_open = True
break
if is_in_open:
continue
Two more comments:
Your distance measure only counts the number of steps. Therefore, a diagonal is as expensive as a horizontal / vertical step. You might want to change this and get the actual length or an approximation of it to find the truly shortest path.
Your heuristic is the squared distance to the target. This is not an admissible heuristic because it over-estimates the actual cost to the target. As a result, you might not find the correct shortest path. For your cost function (number of steps), a better and admissible heuristic would be max(child.position[0] - end_node.position[0], child.position[1] - end_node.position[1]) (you need at least this number of steps to get to the target).

Related

NoneType is not subscriptable for Minimax tic-tac-toe game

I have a tic tac toe game using a minimax algorithm for the computer "player." The Tkinter portion works, and the actual person player works correctly. However, when the computer is supposed to play, it gives the error: "NoneType is not subscriptable." I'm not sure why. Am I missing an input for one of my variables? Thank you in advance.
Here is my code:
from tkinter import *
import customtkinter
import random
import minimax
customtkinter.set_appearance_mode("Dark")
#creating CTk window for app
root = customtkinter.CTk()
#setting window width and height
root.geometry('500x300')
#Creating label
label = customtkinter.CTkLabel(master=root,
text="Tic Tac Toe",
width=120,
height=50,
font=("normal", 20),
corner_radius=8)
label.place(relx=0.25, rely=0.8, anchor=CENTER)
#Handling clicks
DEPTH=8
def clickbutton(r, c):
buttons[r][c]["text"]="X"
board[r][c]="X"
buttons[r][c]['state']=DISABLED
label = customtkinter.CTkLabel(master=root,
text=checkwin(board),
width=120,
height=25,
corner_radius=8)
label.place(relx=0.25, rely=0.9, anchor=CENTER)
computerplay()
DEPTH=DEPTH-1
#Button matrix
buttons = [
[0,0,0],
[0,0,0],
[0,0,0]]
#Matrix identifying whether buttons are active or inactive
board=[[0,0,0],[0,0,0],[0,0,0]]
for i in range(3):
for j in range(3):
buttons[i][j] = Button(height = 3, width = 6, font = ("Normal", 20),
command = lambda r = i, c = j : clickbutton(r,c))
buttons[i][j].grid(row = i, column = j)
def computerplay():
bestmove=minimax.minimax(board, DEPTH, 1)
buttons[bestmove[0]][bestmove[1]]['text']="O"
buttons[bestmove[0]][bestmove[1]]['state']=DISABLED
board[bestmove[0]][bestmove[1]]="O"
def checkwin(b):
score=minimax.evaluate(b)
if score==10:
return 'Computer won!'
elif score==-10:
return 'You won!'
else:
return 'Player vs. Computer'
root.mainloop()
My minimax code:
import math
def change_board(board):
#changes board into -1, 0, and 1s instead of X, O, and 0 (zero).
new_board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
for i in range(3):
for j in range(3):
if board[i][j]=='X':
new_board[i][j]==-1
elif board[i][j]=='O':
new_board[i][j]==1
return new_board
def empty_cells(board):
cells=[]
for i in range(3):
for j in range(3):
if board[i][j]==0:
cells.append([i, j])
return False
def game_over(board):
#check for wins using evaluate
if evaluate(board)==10 or evaluate(board)==-10:
return True
#check for full board
if (not empty_cells(board)):
return True
return False
def evaluate(board):
#check score
if board[0][0]==board[1][1] and board[1][1]==board[2][2]:
if board[0][0]==-1:
return -10
elif board[0][0]==1:
return 10
if board[0][2]==board[1][1] and board[1][1]==board[2][0]:
if board[0][2]==-1:
return -10
elif board[0][2]==1:
return 10
for row in range(3):
if board[row][0]==board[row][1] and board[row][1]==board[row][2]:
if board[row][0]==-1:
return -10
elif board[row][0]==1:
return 10
for col in range(3):
if board[0][col]==board[1][col] and board[1][col]==board[2][col]:
if board[0][col]==-1:
return -10
elif board[0][col]==1:
return 10
def minimax(board, depth, player):
if player==1:
#1 is computer. -1 is human player.
best=[-1, -1, -math.inf]
else:
best=[-1, -1, math.inf]
if depth==0 or game_over(board):
score=evaluate(board)
return score
#checking scores of valid moves
for cell in empty_cells(board):
x, y = cell[0], cell[1]
board[x][y] = player
score = minimax(board, depth - 1, -player)
board[x][y] = 0
score[0], score[1] = x, y
if player == 1:
if score[2] > best[2]:
best = score
else:
if score[2] < best[2]:
best = score
return best
There are these issues with your minimax code:
In change_board you are not assigning anything to new_board cells, as you perform a comparison, not an assignment. Change new_board[i][j]==-1 to new_board[i][j]=-1 and similarly in the else clause.
In empty_cells you are not doing anything with the work done on cells but just return False. Surely the caller needs the cells list, so you should return it.
In evaluate you should also return a numerical value when no winning line was detected: the caller needs a number to compare it with the best score so far, so leaving it to just return None is not good. Add a return 0 at the bottom of the function.
In minimax you should always return the same structure, i.e. a list with three members. This is not done in the base case where you just return a number. To fix that, that base case should return [-1, -1, score]. Related to this, you should consider using a different name for when the score is a number (like here), or when the score is that list, like you do elsewhere in the code. This is confusing.
With those fixes it will work.
Still, there are some things you could improve, including:
Where you return True or return False based on a boolean condition, you can just return the evaluation of that condition. For instance, game_over can do return abs(evaluate(board))==10 or not empty_cells(board)
You can chain comparison operators, so like board[0][0]==board[1][1]==board[2][2]
Avoid code repetition in game_over: put the information of where the winning lines are in a data structure and then loop over it to only have the three-cell comparisons coded once.
Use tuples instead of lists when there is no need to mutate it, like for x/y coordinates.
With math.inf you're using a float where otherwise the score would be an integer. It is better practice to avoid that type mix. In this case you can use the value 11 instead.
Here is how it could look:
def change_board(board):
return [
[(cell == "O") - (cell == "X") for cell in row]
for row in board
]
def empty_cells(board):
return [(i, j) for i in range(3) for j in range(3) if board[i][j] == 0]
def game_over(board):
return abs(evaluate(board)) == 10 or not empty_cells(board)
lines = (
(0, 0, 1, 1, 2, 2),
(0, 2, 1, 1, 2, 0),
(0, 0, 0, 1, 0, 2),
(1, 0, 1, 1, 1, 2),
(2, 0, 2, 1, 2, 2),
(0, 0, 1, 0, 2, 0),
(0, 1, 1, 1, 2, 1),
(0, 2, 1, 2, 2, 2),
)
def evaluate(board):
def iswin(line):
arow, acol, brow, bcol, crow, ccol = line
if board[arow][acol] == board[brow][bcol] == board[crow][ccol]:
return board[arow][acol]*10
return next((win for win in map(iswin, lines) if win), 0)
def minimax(board, depth, player):
best = (-1, -1, -11*player)
if depth <= 0 or game_over(board):
return (-1, -1, evaluate(board)) # must return same structure
for x, y in empty_cells(board):
board[x][y] = player
score = minimax(board, depth - 1, -player)[2] # only get the score
board[x][y] = 0
if (player == 1) == (score > best[2]):
best = (x, y, score) # only inject x, y when move is best
return best
More optimisation is possible when you use a bit representation of the board instead of a 2D matrix.

question in ckecing Fringe and Expanded list in A-Star alogrithm Python

How would you delete an element in a fringe that is similar to the expanded list? In A-Star once you expanded a node you cannot revisit that node in the fringe.
Because simple test cases such as [0, 1, 2, 3, 8, 4, 6, 7, 5] work fine but when it comes to more complex cases such as [6, 4, 0, 1, 5, 3, 7, 8, 2] the program runs and it seems like there are repeated items being expanded.
Here is my code:
from math import sqrt
import time
#fring- to keep the nodes
#expand: the function to expand one node at a time
#heuristic: calculate the cheapest cost
#f()- total cost
#h()- the number index to the goal
#expanded_nodes- have all the visited
startime = time.time_ns()
def astar(puzzle):
#intializing the variables
cost = 0
node = Node(puzzle,cost)
loop = True
#the possible nodes
fringe = []
#After expanding the list
expanded = []
# maybe need it keep it for now
visit = set() #keep track of all the visit
#the goal state
goal = [0,1,2,3,4,5,6,7,8]
#possible combination move
possible_move = [[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]
#intialization of the first node
fringe = [node]
#print('state:'+str(node.state))
#start the loop
while loop:
print('\nthe state of this game position is:\n ' + str(node.state) +"\n\n")
#find the lowest cost in the fringe then expand the lowest cost node
min_cost = fringe[0].cost
min_node = fringe[0]
for node in fringe:
if node.cost < min_cost:
min_cost = node.cost
min_node = node
#append the node that was just expaneded into the expanded list but keep the list not expaneded in fringe
expanded.append(min_node)
print('the min node '+str(min_node.state)+'\nthe cheapest cost: '+ str(min_cost) + '\n')
#removes the min cost node from fringe
for node in fringe[:]:
if node.state == min_node.state:
fringe.remove(node)
#when there is a solution in the expanded
for node in expanded:
if node.state == goal:
loop = False
#checking the node in fringe
for node in fringe:
print(node.state)
# key = tuple(node.state)
# if key in visit:
# continue
# visit.add(key)
#traverse the nodes that was expanded and add the children to the fringe
# for node in expanded[:]:
for node in expanded[:]:
#append all the successor/children and set the children's parent to fringe
blank = node.state.index(8)
print('the index of the blank is '+ str(blank))
print('\n')
possible_pos = possible_move[blank]
print('possible pos '+ str(possible_pos))
for i in possible_pos:
#if node not in visit:
print('\n')
possible_sw = node.state[:]
print('index swap = '+ str(i))
possible_sw[blank] = possible_sw[i]
possible_sw[i] = 8
print('the child node is ' + str(possible_sw))
node.cost = manhattan(possible_sw, goal)
fringe.append(Node(possible_sw,node.cost,node))
print('the cost this node state: '+ str(node.cost))
for node in expanded[:]:
if node.cost > min_cost:
expanded.pop(0)
#finding the solution
solution = expanded
move = 0
while node.parent:
solution.append(node.state.index(8))
node = node.parent
move += 1
print('moves made '+ str(move))
solution.reverse()
print('moves list '+ str(solution))
endtime = time.time_ns()
executionTime = ( endtime - startime)
print('Execution time in ns: ' + str(executionTime))
return solution
#Try the Manhattan Distance for moving only four direction up,down,left,right
def manhattan(a, b):
return sum(abs(val1-val2) for val1, val2 in zip(a,b))
class Node:
def __init__(self,state,cost,parent = None):
self.parent = parent
self.state = state
self.cost = cost
self.children = []
#test case
p = [0, 1, 2, 3, 4, 5, 6, 8, 7]
p = [0, 1, 2, 3, 8, 4, 6, 7, 5]
#p= [6, 4, 0, 1, 5, 3, 7, 8, 2]
#p = [1, 8, 2, 0, 3, 5, 6, 4, 7]
#p = [1, 3, 2, 0, 5, 7, 6, 8, 4]
print("++++++++++A*++++++++++++")
astar(p)
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 8 20:24:42 2023
#author: Jenna Schulte
"""
from math import sqrt
import time
startime = time.time_ns()
def astar(puzzle):
cost = 0
node = Node(puzzle,cost)
loop = True
fringe = []
expanded = []
visit = set()
goal = [0,1,2,3,4,5,6,7,8]
possible_move = [[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]
fringe = [node]
while loop:
print('\nthe state of this game position is:\n ' + str(node.state) +"\n\n")
min_cost = fringe[0].cost
min_node = fringe[0]
for node in fringe:
if node.cost < min_cost:
min_cost = node.cost
min_node = node
expanded.append(min_node)
print('the min node '+str(min_node.state)+'\nthe cheapest cost: '+ str(min_cost) + '\n')
for node in fringe[:]:
if node.state == min_node.state:
fringe.remove(node)
for node in expanded:
if node.state == goal:
loop = False
for node in expanded[:]:
blank = node.state.index(8)
possible_pos = possible_move[blank]
for i in possible_pos:
possible_sw = node.state[:]
possible_sw[blank] = possible_sw[i]
possible_sw[i] = 8
node.cost = manhattan(possible_sw, goal)
child_node = Node(possible_sw,node.cost,node)
key = tuple(child_node.state)
if key not in visit:
visit.add(key)
fringe.append(child_node)
expanded.pop(0)
solution = expanded
move = 0
while node.parent:
solution.append(node.state.index(8))
node = node.parent
move += 1
print('moves made '+ str(move))
solution.reverse()
print('moves list '+ str(solution))
endtime = time.time_ns()
executionTime = ( endtime - startime)
print('Execution time in ns: ' + str(executionTime))
return solution
def manhattan(a, b):
return sum(abs(val1-val2) for val1, val2 in zip(a,b))
class Node:
def __init__(self,state,cost,parent = None):
self.parent = parent
self.state = state
self.cost = cost
self.children = []
p = [0, 1, 2, 3, 8, 4, 6, 7, 5]
p =[0, 4, 1, 3, 8, 2, 6, 7, 5]
p= [6, 4, 0, 1, 5, 3, 7, 8, 2]
astar(p)

Maximum recursion depth exceeded in searching for a path

I am trying to write a program that takes in a matrix representing a board filled with 0s and 1s.
The goal is to find a path from the top left position to target position, using backtracking. You can only move up, down, left and right one space at a time. The code below raises
RecursionError: maximum recursion depth exceeded in comparison
Why is it causing the error? Is there a way to move both up, down, right and left without causing this error?
class Maze:
def __init__(self, maze, target):
self.x, self.y = target
self.b = maze
self.n = len(self.b)
self.sb = [[0 for _ in range(self.n)] for _ in range(self.n)]
def is_safe(self, row, col):
if 0 <= row < self.n and 0 <= col < self.n and self.b[row][col] == 1:
return True
return False
def find_path(self):
move_x = [1, -1, 0, 0]
move_y = [0, 0, 1, -1]
if not self.find_path_rec(move_x, move_y, 0, 0):
print("No path")
else:
self.print_maze()
def find_path_rec(self, move_x, move_y, curr_x, curr_y):
if curr_y == self.y and curr_x == self.x and self.b[self.x][self.y] == 1:
self.sb[curr_x][curr_y] = 1
return True
for i in range(4):
new_x = move_x[i] + curr_x
new_y = move_y[i] + curr_y
if self.is_safe(new_x, new_y):
self.sb[new_x][new_y] = 1
if self.find_path_rec(move_x, move_y, new_x, new_y):
return True
self.sb[new_x][new_y] = 0
return False
def print_maze(self):
for i in range(self.n):
for j in range(self.n):
print(self.sb[i][j], end="")
print()
maze = [[1, 1, 1, 1],
[1, 1, 0, 1],
[1, 0, 0, 1],
[1, 1, 0, 1]]
The Wikipedia maze generation algorithm page https://en.wikipedia.org/wiki/Maze_generation_algorithm has your answer
A disadvantage of the [recursive] approach is a large depth of recursion – in the worst case, the routine may need to recur on every cell of the area being processed, which may exceed the maximum recursion stack depth in many environments.
The alternative is to a use a stack to store your path so far rather than using recursion. Appending each move to the stack, and back-tracking when you hit a dead end.
I believe the problem is in this line:
if self.is_safe(new_x, new_y):
which should be:
if self.is_safe(new_x, new_y) and self.sb[new_x][new_y] == 0:
That is, don't revisit positions as you'll get into a circular motion and thus recursive stack overflow. Below is my rework of your code in my own thinking about your problem:
COL, ROW = (0, 1)
class Maze:
def __init__(self, maze, target):
self.target = target
self.maze = maze
self.visited = set()
def is_safe(self, position):
col, row = position
return 0 <= row < len(self.maze) and 0 <= col < len(self.maze[row]) and self.maze[row][col] == 1
def find_path(self):
start = (0, 0)
moves = [(1, 0), (-1, 0), (0, 1), (0, -1)]
self.visited.add(start)
return self.find_path_recursive(moves, start)
def find_path_recursive(self, moves, current):
if current == self.target and self.maze[self.target[ROW]][self.target[COL]] == 1:
return True
for move in moves:
new = move[COL] + current[COL], move[ROW] + current[ROW]
if self.is_safe(new) and new not in self.visited:
self.visited.add(new)
if self.find_path_recursive(moves, new):
return True
self.visited.remove(new)
return False
def print_maze(self):
for row in range(len(self.maze)):
for col in range(len(self.maze[row])):
print(1 if (col, row) in self.visited else 0, end="")
print()
if __name__ == '__main__':
maze = [
[1, 1, 1, 1],
[1, 1, 0, 1],
[1, 0, 0, 1],
[1, 1, 0, 1]
]
game = Maze(maze, (3, 3))
if game.find_path():
game.print_maze()
else:
print("No path")
My sense of coordinates my be different than yours, so take that into account when evaluating results.
OUTPUT
> python3 test.py
1111
0001
0001
0001
>
If we move the target to (1, 3), then we get:
> python3 test.py
1100
1100
1000
1100
>
I also changed your logic to potentially handle rectangular mazes instead of just square ones.
The error is in this function:
def is_safe(self, row, col):
if 0 <= row < self.n and 0 <= col < self.n and self.b[row][col] == 1:
return True
return False
The condition should include a check that the cell has not yet been visited:
if (0 <= row < self.n and 0 <= col < self.n and self.b[row][col] == 1
and self.sb[row][col] == 0):
# ^^^^^^^^^^^^^^^^^^^^^^^^^^
I would also advise to use names that are meaningful:
self.b should better be named self.maze
self.sb should better be named self.visited

Converting a noughts and crosses game onto an 4 x 4 grid

I keep on running into list index errors within my sub routine that decides when there is a winner and I'm not sure what to do, I a not fussed with finding the diagonal winning option though, If more info is needed just ask. Here is my original code that was on the 3 x 3 grid:
Board = [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
def CheckXOr0HasWon(Board):
XOrOHasWon = False
for Column in range(1,4):
if (Board[Column][1] == Board[Column][2]) and (Board[Column][2] == Board[Column][3]) and
(Board[Column][2] != ' '):
XOrOHasWon = True
for Row in range(1,4):
if (Board[1][Row] == Board[2][Row]) and (Board[2][Row] == Board[3][Row]) and (Board[2][Row] != ' '):
XOrOHasWon = True
if (Board[1][1] == Board[2][2]) and (Board[2][2] == Board[3][3]) and (Board[2][2] != ' '):
XOrOHasWon = True
if (Board[3][1] == Board[2][2]) and (Board[2][2] == Board[1][3]) and (Board[2][2] != ' '):
XOrOHasWon = True
return XOrOHasWon
And here is the what i have done for the 4 x 4 grid:
Board = [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
def CheckXOr0HasWon(Board):
XOrOHasWon = False
for Column in range(1,5):
if (Board[Column][1] == Board[Column][2]) and (Board[Column][2] == Board[Column][3]) and
(Board[Column][3] == Board[Column][4]) and (Board[Column][2][3] != ' '):
XOrOHasWon = True
for Row in range(1,5):
if (Board[1][Row] == Board[2][Row]) and (Board[2][Row] == Board[3][Row]) and (Board[3][Row] == Board[4][Row]) and (Board[2][3][Row] != ' '):
XOrOHasWon = True
if (Board[1][1] == Board[2][2]) and (Board[2][2] == Board[3][3]) and (Board[2][2] != ' '):
XOrOHasWon = True
if (Board[3][1] == Board[2][2]) and (Board[2][2] == Board[1][3]) and (Board[2][2] != ' '):
XOrOHasWon = True
return XOrOHasWon

How to compare two vectors in vPython

I am trying to simulate Rubik's Cube. In order to detect if user has actually solved the Cube I would like to remember all initial position vectors and then just compare it.
However, when you start my program and mess the cube, then press 'k' to solve it you can see in the console that values in fact are the same, however they have different precision. For example z-value is -0.99999 instead of -1. The result of this flaw is that even though the values are quite the same, the program still won't consider the Cube solved. I guess that while rotating, when calculations are performed on the vector, the precision changes and as a result in the end the values are different. How can I solve this problem? To print out the initial position vectors and current vectors press 'a' key anytime you wish to :)
from visual import *
import string
import random
class Cube:
def __init__(self):
self.surfaces = { 'r': (color.red, (1, 0, 0)),
'o': (color.orange, (-1, 0, 0)),
'y': (color.yellow, (0, 1, 0)),
'b': (color.blue, (0, -1, 0)),
'w': (color.white, (0, 0, 1)),
'g': (color.green, (0, 0, -1))}
self.fps = 30
self.wholeSurfaces = []
self.initialPosition = []
self.commandsList = []
def createCube(self):
for colour, axis in self.surfaces.itervalues():
for x in (-1, 0, 1):
for y in (-1, 0, 1):
# Start with all powierzchniaBoczna on the top face, then rotate them "down"
# to the appropriate face.
powBoczna = box(color=colour, pos=(x, y, 1.5),
length=0.98, height=0.98, width=0.05)
cos_kat = dot((0, 0, 1), axis)
if cos_kat == 0: #alfa = 90 + kPI
obliczonaOsObrotu = cross((0, 0, 1), axis) #iloczyn wektorowy
else:
obliczonaOsObrotu=(1, 0, 0)
powBoczna.rotate(angle=acos(cos_kat), axis=obliczonaOsObrotu, origin=(0, 0, 0))
self.wholeSurfaces.append(powBoczna)
#remember initial position
v = (float(powBoczna.pos.x), float(powBoczna.pos.y), float(powBoczna.pos.z))
self.initialPosition.append(v)
def solveCube(self):
print self.commandsList
self.commandsList.reverse()
print self.commandsList
for i in self.commandsList:
self.rotateCube(self.reverseCommand(i), 10000)
self.commandsList = []
def reverseCommand(self, key):
if (key.islower()): return key.upper()
else: return key.lower()
def rotateCube(self, key, refreshRate):
colour, axis = self.surfaces[key.lower()]
if (key.isupper()): kat = (pi / 2.0)
else: kat = -pi/2.0
for r in arange(0, kat, kat / self.fps):
rate(refreshRate)
for surface in self.wholeSurfaces:
if dot(surface.pos, axis) > 0.5:
surface.rotate(angle=kat / self.fps, axis=axis, origin=(0, 0, 0))
def beginLoop(self):
while True:
key = scene.kb.getkey()
if (key.lower() in self.surfaces):
self.commandsList.append(key)
self.rotateCube(key, self.fps)
elif key == "k":
self.solveCube()
elif key == "a":
i = 0
print "================="
for surface in self.wholeSurfaces:
print "%s\n(%s,%s,%s)" % (self.initialPosition[i], surface.pos.x, surface.pos.y, surface.pos.z)
if self.initialPosition[i][0] == float(surface.pos.x) and self.initialPosition[i][1] == float(surface.pos.y) and self.initialPosition[i][2] == float(surface.pos.z): print "equal"
else: print "not equal"
print ""
i+=1
if __name__ == "__main__":
myCube = Cube()
myCube.createCube()
myCube.beginLoop()
The solution is simple, you need to use numpy.allclose method with given precision.
for surface in self.wholeSurfaces:
print "%s\n%s" % (self.initialPosition[i], powierzchnia.pos)
if np.allclose(self.initialPosition[i], surface.pos.astuple(), 1e-5, 1e-5): print "are equal"
else: print "arent equal"
i+=1

Categories

Resources