Yesterday I created an sudoku solver using backtracking, which, works like its supposed to without performance issues. I decided to create an pygame application, which you with your mouse and keyboard can fill in the cells, and then press the "solve" button to finish the puzzle. I copy pasted the exact code from the Solver-algorithm to the pygame application (which is found in the Solver-class).
In the pygame application you can fill in cells and solve the puzzle most of the times, so it works as its supposed to. However, on more hard puzzles as the one found below, I encounter CPU issues, causing the application to use all of my CPU and eventually crash (im on a Mac OS. HighS system with I5):
Puzzle from telegraph
To sum up my problem:
The sudoku solver algorithm works fine when not called from within the pygame application, (the telegraph puzzle is solved in 2.s roughly), but when called from within the pygame application, it can solve easy puzzles, but the harder ones causes the application to crash due to overuse of CPU.
Heres a picture of the initial setup that causes the crash:
Code is found below:
class Sudoku():
def __init__(self):
self.W,self.H = (600,600)
pygame.init()
pygame.mixer.quit()
self.screen = pygame.display.set_mode((self.W+200,self.H))
self.clock = pygame.time.Clock()
self.board = Board()
self.focused = None
self.solve = Button((0,140,0),650,200,100,50,"Solve")
self.solver = Solver(self.board.sudoku)
self.run()
### Takes care of pygame events from mouse and keyboard
def events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.quit()
if event.type == pygame.MOUSEBUTTONUP:
self.focused = self.getCellFromMousePos(pygame.mouse.get_pos())
if self.solve.isOver(pygame.mouse.get_pos()):
self.solver.solve()
if event.type == pygame.KEYDOWN:
print("key")
if self.focused!=None:
try:
self.board.set_value(self.focused[0],self.focused[1],int(event.unicode))
except:
pass
## Calls paint functions from working units (button, board)
def paint(self):
self.screen.fill((255,229,204))
self.board.paint(self.screen,self.W,self.H)
self.solve.draw(self.screen)
pygame.display.flip()
## Main loop
def run(self):
self.running = True
while self.running:
self.dt = self.clock.tick(60)/1000
self.update()
## Update called from main loop
def update(self):
self.events()
self.paint()
## Set value on board (Unused)
def set_value(self,row,col,value):
self.board.set_value(row,col,value)
## Get a cell (0-9,0-9) from the mouse position.
def getCellFromMousePos(self,coord):
return (math.floor(coord[0]/(self.W/9)),math.floor(coord[1]/(self.H/9)))
class Board():
def __init__(self):
self.sudoku = [ [0]*9 for _ in range(9) ]
self.font = pygame.font.SysFont('comicsans',81)
## Takes a preset board as input - NOT USED
def set_preset(self,board):
if len(board)==9 and len(board[1])==9:
for row in board:
for cell in row:
if board[row][cell]>9 or board[row][cell]<0:
return None
self.sudoku = board
## Sets value in a cell
def set_value(self,row,col,value):
if self.value_is_valid(value):
self.sudoku[row][col] = value
## Check if an value is valid
def value_is_valid(self,value):
if int(value)<=9 and int(value)>=0:
return True
return False
## Paints grid and numbers to pygame.screen
def paint(self,screen,width,height):
## DRAW background board itself:
for row in range(10):
k = row*(height/9)
pygame.draw.line(screen,(0,0,0),(0,k),(width,k))
for col in range(10):
k = col*(width/9)
pygame.draw.line(screen,(0,0,0),(k,0),(k,height))
## Draw numbers:
for r in range(9):
for c in range(9):
value = self.sudoku[r][c]
if value != 0:
text = self.font.render(str(value),2,(0,0,0))
screen.blit(text,((width/9)*r+(text.get_width()/2),(height/9)*c))
## Just a button.
class Button:
def __init__(self,color,x,y,width,heigth,text):
self.x = x
self.y = y
self.width = width
self.heigth = heigth
self.text = text
self.color = color
def draw(self,window):
pygame.draw.rect(window,self.color,(self.x,self.y,self.width,self.heigth))
if self.text!="":
font = pygame.font.SysFont('comicsans',61)
text = font.render(self.text,2,(0,0,0))
window.blit(text,(self.x+(self.width/2 - text.get_width()/2), self.y + (self.heigth/2 -text.get_height()/2)))
def isOver(self,pos):
if pos[0] > self.x and pos[0]< (self.x+self.width):
if pos[1]> self.y and pos[1]< self.y+self.heigth:
return True
return False
## Solving algorithm
class Solver:
def __init__(self,board):
self.sudoku = board
def valid(self,row,column,value):
original = self.sudoku[row][column]
self.sudoku[row][column] = value
validity = self.duplicates()
self.sudoku[row][column] = original
return not validity
## Checks if an array contains duplicates
def arrayContainsDuplicates(self,array):
if len(array) == len(set(array)):
return False
return True
## Trims an array from empty spaces (0's)
def trimarray(self,array):
trimmed = []
for cell in array:
if cell != 0:
trimmed.append(cell)
return trimmed
## Finds the next empty cell. Used for backtracking.
def find_empty(self):
for i in range(len(self.sudoku)):
for j in range(len(self.sudoku[i])):
if self.sudoku[i][j] == 0:
return (i,j)
return None
## Checks if the board contains any duplicates in rows, blocks and columns.
def duplicates(self):
for row in self.sudoku:
if self.arrayContainsDuplicates(self.trimarray(row)):
return True
for col in map(list,zip(*self.sudoku)):
if self.arrayContainsDuplicates(self.trimarray(col)):
return True
blocks=[[self.sudoku[int(m//3)*3+i][(m%3)*3+j] for i in range(3) for j in range(3)] for m in range(9)]
for block in blocks:
if self.arrayContainsDuplicates(self.trimarray(block)):
return True
return False
## Backtrakcing solving algorithm.
def solve(self):
find = self.find_empty()
if not find:
return True
else:
row,col = find
for i in range(1,10):
if self.valid(row,col,i):
self.sudoku[row][col] = i
if self.solve():
return True
else:
self.sudoku[row][col] = 0
s = Sudoku()
Try this solver:
known = [ [8,0,0, 0,0,0, 0,0,0],
[0,0,3, 6,0,0, 0,0,0],
[0,7,0, 0,9,0, 2,0,0],
[0,5,0, 0,0,7, 0,0,0],
[0,0,0, 0,4,5, 6,0,0],
[0,0,0, 1,0,0, 0,3,0],
[0,0,1, 0,0,0, 0,6,8],
[0,0,8, 5,0,0, 0,1,0],
[0,9,0, 0,0,0, 4,0,0]
]
import random
groups = [ p//27*3+p%9//3 for p in range(81) ]
colNums = [ set(range(1,10)) for _ in range(9) ]
rowNums = [ set(range(1,10)) for _ in range(9) ]
grpNums = [ set(range(1,10)) for _ in range(9) ]
sudoku = [ [0]*9 for _ in range(9) ]
for pos in range(81):
row,col,group = pos//9,pos%9,groups[pos]
fixed = known[row][col]
if fixed:
sudoku[row][col] = fixed
colNums[col].discard(fixed)
rowNums[row].discard(fixed)
grpNums[group].discard(fixed)
pos = 0
availables = [ None for _ in range(81)]
while pos < 81:
row,col,group = pos//9,pos%9,groups[pos]
number = sudoku[row][col]
fixed = known[row][col]
if number != 0 and not fixed:
sudoku[row][col] = 0
colNums[col].add(number)
rowNums[row].add(number)
grpNums[group].add(number)
if availables[pos] is None:
availables[pos] = {fixed} if fixed else colNums[col] & rowNums[row] & grpNums[group]
if availables[pos]:
number = fixed or min(availables[pos])
if not fixed:
sudoku[row][col] = number
colNums[col].discard(number)
rowNums[row].discard(number)
grpNums[group].discard(number)
availables[pos].discard(number)
pos += 1
else:
availables[pos] = None
pos -= 1
if pos < 0 : break
if pos < 81:
print("FAILED!")
else :
for r,line in enumerate(sudoku):
print(*[line[i:][:3] for i in range(0,9,3)],"\n"*(r%3==2))
It finds a solution in 0.15 sec (excluding printing time):
[8, 4, 9] [2, 7, 1] [3, 5, 6]
[2, 1, 3] [6, 5, 4] [8, 7, 9]
[6, 7, 5] [8, 9, 3] [2, 4, 1]
[3, 5, 2] [9, 6, 7] [1, 8, 4]
[1, 8, 7] [3, 4, 5] [6, 9, 2]
[9, 6, 4] [1, 8, 2] [7, 3, 5]
[7, 2, 1] [4, 3, 9] [5, 6, 8]
[4, 3, 8] [5, 2, 6] [9, 1, 7]
[5, 9, 6] [7, 1, 8] [4, 2, 3]
Note: it will take up to 4 seconds to figure out that there are no solution if the case may be. This means that the worst case scenario for an extremely complex problem would be less than 4 seconds. The world's supposedly hardest was solved in 0.11 seconds )
Related
Here is the code: (from MAIN file)
def BestFirstSearch(startingBoard):
Q = [startingBoard]
Visited = []
while (len(Q) != 0):
Q.sort()
currentQ = Q.pop(0)
Visited.append(currentQ)
# print(currentQ)
if (currentQ.Board == currentQ.GOAL):
return True
currentQ.createChildrenBoards()
for items in currentQ.Children:
if items not in Visited:
Q.append(items)
print(len(Q))
print(currentQ)
return False
(from CLASS file):
def createChildrenBoards(self):
""" Creates the set of potential children Boards from the current Board """
row = self.X
col = self.Y
assert( (row >=0 and row < BoardClass.N)
and
(col >=0 and col < BoardClass.N) )
newChildrenBoards = []
#print(self.Board[row][col])
# UP(NORTH): slide empty (0) space up
if ( row != 0 ):
newChildBoard = self.copyCTOR()
newChildBoard.Parent = self
newChildBoard.X = row-1
newChildBoard.Y = col
holdCell = newChildBoard.Board[newChildBoard.X][newChildBoard.Y]
newChildBoard.Board[newChildBoard.X][newChildBoard.Y] = 0
newChildBoard.Board[row][col] = holdCell
newChildrenBoards.append(newChildBoard)
for puzzle in newChildrenBoards:
puzzle.computeDistanceFromGoal()
self.Children = newChildrenBoards
Here are portions of the code I'm working with. I initialized the starting board in a class that constructs the puzzle. Then in my main I would call the create children function which creates a list of children based on where you can move the zero (north being an example of how I would move 0).
The puzzle looks like this:
Goal = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]
Puzzle = [ [3, 1, 2], [4, 7, 5], [6, 8, 0] ]
I'm not getting why the queue won't add more children from the children created from the starting board. I'm hoping that I can get feedback that will help me understand why my loop isn't registering the "grandchildren". Thank you!
I'm junior programmer, I am trying to solve 8-puzzle problem with breadth first search, but it took too long time to solve it, i want to optimize my code.
Configuration: [[5, 4, 3], [0, 7, 2], [6, 1, 8]] is going to solve in 22.623718615 seconds,
configuration [[8, 0, 6], [5, 4, 7], [2, 3, 1]] took among 235.721346421 seconds.
I want to decrease solve time.
There is my code:
from copy import deepcopy
from collections import deque
from time import perf_counter
most_hard = [[8, 0, 6], [5, 4, 7], [2, 3, 1]] # 30 moves
class CheckPuzzle:
def __init__(self, puzzle: list):
self.puzzle = puzzle
self.len = len(puzzle)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
if not self.is_valid():
raise TypeError("Puzzle is not valid")
elif not self.is_solvable():
raise Exception("Unsolvable puzzle")
# создай ф-ию check
def sum_of_numbers(self) -> int:
return sum(self.convert_to_1d(self.goal))
def sum_of_squares(self) -> int:
return sum([i ** 2 for i in self.convert_to_1d(self.goal)])
def is_valid(self) -> bool:
sum_of_numbers = 0
sum_of_squares = 0
for row in range(self.len):
for column in range(self.len):
sum_of_numbers += self.puzzle[row][column]
sum_of_squares += (self.puzzle[row][column]) ** 2
return sum_of_numbers == self.sum_of_numbers() and sum_of_squares == self.sum_of_squares()
def convert_to_1d(self, board) -> list:
one_dimension_matrix = []
for row in range(self.len):
for column in range(self.len):
one_dimension_matrix.append(board[row][column])
return one_dimension_matrix
def inversion(self, board) -> int:
inversion = 0
one_dimension_matrix = self.convert_to_1d(board)
for index in range(len(one_dimension_matrix)):
temp = one_dimension_matrix[index]
if temp == 0 or temp == 1:
continue
for elem in one_dimension_matrix[index:]:
if elem == 0:
continue
if temp > elem:
inversion += 1
return inversion
def is_solvable(self) -> bool:
inv_of_matrix = self.inversion(self.puzzle)
inv_of_goal_matrix = self.inversion(self.goal)
return (inv_of_matrix % 2 == 0 and inv_of_goal_matrix % 2 == 0) or \
(inv_of_matrix % 2 == 1 and inv_of_goal_matrix % 2 == 1)
class Puzzle:
def __init__(self, board: list):
self.board = board
self.len = len(board)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
def print_matrix(self) -> str:
output = ''
for row in self.board:
for elem in row:
output += str(elem) + " "
output += '\n'
return output
def get_index(self, matrix, value) -> tuple:
for i in range(self.len):
for j in range(self.len):
if matrix[i][j] == value:
return i, j
def manhattan(self):
distance = 0
for i in range(self.len):
for j in range(self.len):
if self.board[i][j] != 0:
x, y = divmod(self.board[i][j] - 1, self.len)
distance += abs(x - i) + abs(y - j)
return distance
def list_of_possible_moves(self) -> list:
x, y = self.get_index(self.board, 0)
possible_moves = []
if x > 0:
possible_moves.append((x - 1, y))
if x < self.len - 1:
possible_moves.append((x + 1, y))
if y > 0:
possible_moves.append((x, y - 1))
if y < self.len - 1:
possible_moves.append((x, y + 1))
return possible_moves
def move(self, to: tuple) -> list:
moving_board = deepcopy(self.board)
x, y = self.get_index(self.board, 0)
i, j = to
moving_board[x][y], moving_board[i][j] = moving_board[i][j], moving_board[x][y]
return moving_board
def solved(self) -> bool:
return self.board == self.goal
def __str__(self) -> str:
return ''.join(map(str, self))
def __iter__(self):
for row in self.board:
yield from row
class Node:
def __init__(self, puzzle, parent=None):
self.puzzle = puzzle
self.parent = parent
if self.parent:
self.g = parent.g + 1
else:
self.g = 0
def state(self) -> str:
return str(self)
def path(self):
node, p = self, []
while node:
p.append(node)
node = node.parent
yield from reversed(p)
def solved(self) -> bool:
return self.puzzle.solved()
def pretty_print(self) -> str:
return self.puzzle.print_matrix()
def h(self) -> int:
return self.puzzle.manhattan()
def f(self) -> int:
return self.h() + self.g
def all_moves(self) -> list:
return self.puzzle.list_of_possible_moves()
def __str__(self) -> str:
return str(self.puzzle)
def make_a_move(self, to: tuple) -> list:
return self.puzzle.move(to)
class GameTree:
def __init__(self, root):
self.root = root
def solve(self):
queue = deque([Node(self.root)])
seen = set()
seen.add(queue[0].state())
while queue:
queue = deque(sorted(list(queue), key=lambda node: node.f()))
node = queue.popleft()
if node.solved():
return node.path()
for move in node.all_moves():
moved = node.make_a_move(move)
child = Node(Puzzle(moved), node)
if child.state() not in seen:
queue.append(child)
seen.add(child.state())
def main():
a = [[5, 4, 3], [0, 7, 2], [6, 1, 8]]
c = Puzzle(a)
d = GameTree(c)
tic = perf_counter()
p = d.solve()
toc = perf_counter()
step = 0
for i in p:
print(i.pretty_print())
step += 1
print(step)
print(toc-tic)
if __name__ == "__main__":
main()
Description:
The 15-puzzle (also called Gem Puzzle, Boss Puzzle, Game of Fifteen,
Mystic Square and many others) is a sliding puzzle that consists of a
frame of numbered square tiles in random order with one tile missing.
The puzzle also exists in other sizes, particularly the smaller
8-puzzle. If the size is 3×3 tiles, the puzzle is called the 8-puzzle
or 9-puzzle, and if 4×4 tiles, the puzzle is called the 15-puzzle or
16-puzzle named, respectively, for the number of tiles and the number
of spaces. The object of the puzzle is to place the tiles in order by
making sliding moves that use the empty space.
https://en.wikipedia.org/wiki/15_puzzle
#Uses python3
import sys
def previsit(v, clock, pre):
pre[v] = clock
clock += 1
return pre
def postvisit(v, clock, post):
post[v] = clock
clock += 1
return post
def reverseg(adj):
edges = []
for i in range(len(adj)):
if adj[i] != []:
for vertex in adj[i]:
edges.append([i, vertex])
edgesr = [[j, i] for i, j in edges]
adjr = [[] for _ in range(len(adj))]
for a, b in edgesr:
adjr[a].append(b)
return adjr
def explore(adj, v, visited, path, clock, pre, post):
visited[v] = True
path.append(v)
pre[v] = clock
clock += 1
for w in adj[v]:
if (visited[w] is None):
explore(adj, w, visited, path, clock, pre, post)
post[v] = clock
clock += 1
def dfs(adj, visited, clock, pre, post):
n = len(adj)
for v in range(n):
if visited[v] is None:
explore(adj, v, visited, path, clock, pre, post)
def acyclic(adj):
visited = [None for _ in range(len(adj))]
adjr = reverseg(adj)
return 0
edges = [[1, 2], [2, 3], [1, 3], [3, 4], [1, 4], [2, 5], [3, 5]]
edges2 = [[], [], [], [], []]
edges1 = [[1, 2], [4, 1], [2, 3], [3, 1]]
adj = [[] for _ in range(4)]
for (a, b) in edges1:
adj[a - 1].append(b - 1)
#adj[b - 1].append(a - 1)
clock = 1
pre = [None for _ in range(len(adj))]
post = [None for _ in range(len(adj))]
visited = [None for _ in range(len(adj))]
path = []
#explore(adj, 0, visited, cc, ccnum)
#print(dfs(adj, visited), acyclic(adj))
v = 0
print(adj)
print(dfs(adj, visited, clock, pre, post))
print(pre)
print(post)
print(path)
Hi, please help check this code, I implemented the pesudocode, but the output of the previsite number and postvisited number is wrong. My task is to find the cycle in a graph, so I have to find the strongly
connected components first, running dfs in a reversed graph, and explore the vertex with largest postnumber in the original graph.I stuck at the postnumber
This code is based on the pesudocode, which is a depth-first-search(DFS) algorithm for graph searching, and I'm going to use it to find strongly connected components(SCC) in a graph:
Explore(v):
visited(v) ← true
previsit(v)
for (v,w) ∈ E:
if not visited(w):
explore(w)
postvisit(v)
and here:
previsit(v):
pre(v) ← clock
clock ← clock + 1
postvisit(v):
post(v) ← clock
clock ← clock + 1
the clock here should be a global variable.
I'm trying to build a solution to the N-Puzzle problem using breadth first search in Python.
My solution is adept at finding an answer if all of the numbers bar the zero are in order. e.g.
initial_state = [1,2,3,4,0,5,6,7,8]
or
initial_state = [1,2,3,4,5,6,7,0,8]
but fails with
initial_state = [1,2,5,3,4,0,6,7,8]
Pleases find below my implementation. If someone could point out the flaw in my logic it'd much appreciated.
Thanks in advance!
def right(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if (i+1) % n != 0:
del items[i]
items.insert(i+1, 0)
return tuple(items)
else:
pass
def left(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if i % n != 0:
del items[i]
items.insert(i-1, 0)
return tuple(items)
else:
pass
def up(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if n**2 < i <= (n**2 - n):
del items[i]
items.insert(i+n, 0)
return tuple(items)
else:
pass
def down(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if i > n:
del items[i]
items.insert(i-n, 0)
return tuple(items)
else:
pass
class Problem(object):
def __init__(self, initial, goal=None):
self.initial = initial
self.goal = goal
self.n = len(initial)
self.size = int(math.sqrt(self.n))
self.blank = self.initial.index(0)
self.top_row = [i for i in range(self.n) if i < self.size]
self.bottom_row = [i for i in range(self.n) if self.n - (self.size) <= i < self.n]
self.left_column = [i for i in range(self.n) if i % self.size == 0]
self.right_column = [i for i in range(self.n) if (i + 1) % self.size == 0]
def actions(self):
result_list = ["UP","DOWN","LEFT","RIGHT"]
return result_list
def result(self, state, action):
if action == "RIGHT":
return right(state)
if action == "LEFT":
return left(state)
if action == "UP":
return up(state)
if action == "DOWN":
return down(state)
def goal_test(self, state):
return state == self.goal
def path_cost(self, c):
return c + 1
class Node:
def __init__(self, state, parent=None, action=None, path_cost=0):
self.state = state
self.parent = parent
self.action = action
self.path_cost = path_cost
self.depth = 0
if parent:
self.depth = parent.depth + 1
def __repr__(self):
return "<Node %s>" % (self.state,)
def __lt__(self, node):
return self.state < node.state
def expand(self, problem):
return [self.child_node(problem, action)
for action in problem.actions() if self.child_node(problem,action) is not None]
def child_node(self, problem, action):
next = problem.result(self.state, action)
if next:
return Node(next, self, action,
problem.path_cost(self.path_cost))
else:
pass
def solution(self):
return [node.action for node in self.path()[1:]]
def path(self):
node, path_back = self, []
while node:
path_back.append(node)
node = node.parent
return list(reversed(path_back))
def __eq__(self, other):
return isinstance(other, Node) and self.state == other.state
def __hash__(self):
return hash(self.state)
def bfs(problem):
node = Node(problem.initial)
frontier = deque([node])
explored = set()
while frontier:
node = frontier.pop()
explored.add(node.state)
if problem.goal_test(node.state):
return node
for child in node.expand(problem):
if child.state not in explored and child not in frontier:
frontier.append(child)
return [child for child in explored]
p = Problem((1,2,5,3,4,0,6,7,8), (0,1,2,3,4,5,6,7,8))
bfs(p)
#returns
"""[(1, 2, 5, 3, 4, 0, 6, 7, 8),
(1, 2, 0, 5, 3, 4, 6, 7, 8),
(0, 1, 2, 5, 3, 4, 6, 7, 8),
(1, 2, 5, 3, 0, 4, 6, 7, 8),
(1, 2, 5, 0, 3, 4, 6, 7, 8),
(1, 0, 2, 5, 3, 4, 6, 7, 8)]"""
If you process the neighbors (children) of a node (state) by moving the space in UP, DOWN, LEFT, RIGHT order, the solution of an 8-puzzle with bfs starting with the initial state 1,2,5,3,4,0,6,7,8 will be like the following (you can check out where it's differing with your solution):
path_to_goal: ['Up', 'Left', 'Left']
cost_of_path: 3
You may want to refer to this https://sandipanweb.wordpress.com/2017/03/16/using-uninformed-informed-search-algorithms-to-solve-8-puzzle-n-puzzle/?frame-nonce=9e97a821bc for more details.
This condition in up is never true: if n**2 < i <= (n**2 - n).
And this condition in down is off by one: if i > n.
Whether the rest of your code is correct or not is unclear, but you need to debug the fundamentals of your board representation and manipulation code first.
In your space-moving code, I personally would turn your index into an x and y coordinate:
x, y = i % n, i // n
Then you can test more naturally: x>0 for left, x<n-1 for right, y<n-1 for up and y>0 for down.
this is my first time posting here and also I'm new to Python,so please be patient with me if I'm something wrong! I've seen a similar question to this but couldn't see a clear solution. This must have a simple solution but it has frustrated me all day.
I'm trying to make multiple objects of the class 'Slot' inside the constructor
of the class 'Board'. After each object is made, I'm appending it to a list.
Finally I'm going through each object in list and calling 'getHandles' method, which should
output their unique results. Instead the results of last slot object made are displayed.
Board class constructor :
class Board:
slots = []
def __init__(self):
slot1 = Slot(1); self.slots.append(slot1);
slot2 = Slot(2); self.slots.append(slot2);
slot3 = Slot(3); self.slots.append(slot3);
slot4 = Slot(4); self.slots.append(slot4);
slot5 = Slot(5); self.slots.append(slot5);
slot6 = Slot(6); self.slots.append(slot6);
slot7 = Slot(7); self.slots.append(slot7);
slot8 = Slot(8); self.slots.append(slot8);
slot9 = Slot(9); self.slots.append(slot9);
print("\nprinting slot objects in list 'slot'\n");
counter = 0;
while counter < len(self.slots):
print("slot number :"+str(self.slots[counter].getPosition()));
print("slot value :"+str(self.slots[counter].getHandles()));
counter +=1;
slot class constructor :
#Slot class constructor
def __init__(self,n):
self.setPosition(n);
positions = coords.getLargeGridPosition(n);
self.U[2] = positions[1]; self.D[2] = positions[2];
self.L[2] = positions[3]; self.R[2] = positions[4];
print("made slot object : "+str(self.getPosition()));
print("handle values: ");
self.getHandles();
print(" - ");
def getHandles(self):
print(" U:"+str(self.U)+" D:"+str(self.D)+" L:"+str(self.L)+" R:"+str(self.R));
This is what I get when I make a 'Board' object :
1st part out output (making slots and appending in Board constructor):
>>> b = Board()
made slot object : 1
handle values:
U:[1, 2, 2] D:[2, 1, 20] L:[3, 4, 10] R:[4, 3, 12]
-
made slot object : 2
handle values:
U:[1, 2, 5] D:[2, 1, 23] L:[3, 4, 13] R:[4, 3, 15]
-
made slot object : 3
handle values:
U:[1, 2, 8] D:[2, 1, 26] L:[3, 4, 16] R:[4, 3, 18]
-
made slot object : 4
handle values:
U:[1, 2, 29] D:[2, 1, 47] L:[3, 4, 37] R:[4, 3, 39]
-
and so on for all nine objects.
2nd part out output (calling getHandle method for each object in list) :
printing slot objects in list 'slot'
slot number :1
U:[1, 2, 62] D:[2, 1, 80] L:[3, 4, 70] R:[4, 3, 72]
slot value :None
slot number :2
U:[1, 2, 62] D:[2, 1, 80] L:[3, 4, 70] R:[4, 3, 72]
slot value :None
slot number :3
U:[1, 2, 62] D:[2, 1, 80] L:[3, 4, 70] R:[4, 3, 72]
slot value :None
slot number :4
U:[1, 2, 62] D:[2, 1, 80] L:[3, 4, 70] R:[4, 3, 72]
slot value :None
slot number :5
U:[1, 2, 62] D:[2, 1, 80] L:[3, 4, 70] R:[4, 3, 72]
and so on for all 9 objects appended to list. How can I make the variables slot1/slot2
refer to individual objects made?
I've tried passing the board object to slot constructor, and making slot constructor pass 'self' back to Board object. still the results for a single object are outputted for all.
I'd appreciate any help. Thank you.
[edit] full code of the file : hope the indenting comes out right
import coords;
#store hanndle position values
class Slot: #slot class<------------------------------------------------
U = [1,2,"gridPosition"] #1 up
D = [2,1,"gridPosition"] #2 down
L = [3,4,"gridPosition"] #3 left
R = [4,3,"gridPosition"] #4 right
handles = [U,D,L,R] #handles 0/1/2/3
tileValue = 0; #value 0 - 8
tilePosition = 0; #slot 1 - 9
overallStrain = 0; # |0-->+
#Slot class constructor
def __init__(self,n):
self.setPosition(n);
positions = coords.getLargeGridPosition(n);
self.U[2] = positions[1]; self.D[2] = positions[2];
self.L[2] = positions[3]; self.R[2] = positions[4];
print("made slot object : "+str(self.getPosition()));
print("handle values: ");
self.getHandles();
print(" - ");
def getHandles(self):
print(" U:"+str(self.U)+" D:"+str(self.D)+" L:"+str(self.L)+" R:"+str(self.R));
#set/get position of slot(1-9)
def setPosition(self,position):
self.tilePosition = position;
def getPosition(self):
return self.tilePosition;
#set/get tile value
def setTileValue(self,value):
self.TileValue = value;
def getTileValue(self):
return self.TileValue;
#get handle(n) position value
def getHandle(self,number):
return self.handles[number]
#get overall strain of tile
def getTotalStrain(self):
return self.totalStrain;
#calculate total strain from strains of handles
#def calculateOverallStrain(self):#<---------------
#for handle in handles:
#if
#get linked tile,
class Board: #Boardclass<------------------------------------------------
slots = []
def __init__(self):
slot1 = Slot(1); self.slots.append(slot1);
slot2 = Slot(2); self.slots.append(slot2);
slot3 = Slot(3); self.slots.append(slot3);
slot4 = Slot(4); self.slots.append(slot4);
slot5 = Slot(5); self.slots.append(slot5);
slot6 = Slot(6); self.slots.append(slot6);
slot7 = Slot(7); self.slots.append(slot7);
slot8 = Slot(8); self.slots.append(slot8);
slot9 = Slot(9); self.slots.append(slot9);
print("\nprinting slot objects in list 'slot'\n");
counter = 0;
while counter < len(self.slots):
print("slot number :"+str(self.slots[counter].getPosition()));
print("slot value :"+str(self.slots[counter].getHandles()));
counter +=1;
#set tiles
def setTiles(self,state):
counter = 0;
for slot in self.slots:
slot.setTileValue(state[counter]);
counter +=1;
def getStrain(self,slot):
tileValue = self.slots[slot].getTileValue();
print("tile value:"+str(tileValue));
print("handles:");
self.slots[slot].getHandles();
if tileValue != 0:
linkedTiles = coords.getLinked(tileValue);
print(str(linkedTiles));
counter = 0;
for l in linkedTiles:#for each linked tile
if l != 0: #if not empty link
linkedTileSlot = [];#slot linked tile is set in
for slot in self.slots:
linkedTileValue = slot.getTileValue();
print(str(linkedTileValue));
if l == linkedTileValue:#found slot with linked tile
print("yes");
linkedTileSlot = slot; break;
ownHandle = slot.getHandle(counter);
print("own handle : "+str(ownHandle));
linkingHandle = linkedTileSlot.getHandle(ownHandle[1]-1);
print("linking handle : "+str(linkingHandle));
positions = [ownHandle[2],linkingHandle[2]]
print(str(positions));
counter += 1;
As Pavel says in the comments, anything defined at the class level is a class variable, shared between all instances. You need to define those variables within __init__ by assigning them to self:
class Slot: #slot class<------------------------------------------------
def __init__(self,n):
self.U = [1,2,"gridPosition"] #1 up
self.D = [2,1,"gridPosition"] #2 down
self.L = [3,4,"gridPosition"] #3 left
self.R = [4,3,"gridPosition"] #4 right
self.handles = [U,D,L,R] #handles 0/1/2/3
self.tileValue = 0; #value 0 - 8
self.tilePosition = 0; #slot 1 - 9
self.overallStrain = 0; # |0-->+
You should also know that there are quite a few examples of extremely unidiomatic Python here. For example, the entire Board init should simply be:
def __init__(self):
self.slots = [Slot(i+1) for i in range(10)]
print("\nprinting slot objects in list 'slot'\n");
for slot in self.slots:
print("slot number :%s" % slot.getPosition());
print("slot value :%d" slot.getHandles());
Also, it's discouraged to define getters and setters unless there is a very good reason, which you don't have here since you're just accessing the variable. You should assign to and return self.position and so on.