I have recently enrolled into cs50 Artificial Intelligence with python online course and the first project is to create a tic tac toe game using the minimax algorithm and I have attempted it. But when I run the runner.py file provided with the zip file from their website, it gives me some errors like for this statement:
i = action[0] ,
saying "'NoneType' object is not subscriptable"
can you please correct the code or at least tell me what the problem exactly is
Thanks
import math
import numpy as npy
import sys
import copy
X = "X"
O = "O"
EMPTY = None
def initial_state():
"""
Returns starting state of the board.
"""
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
"""
Returns player who has the next turn on a board.
"""
if board == initial_state():
return X
numpy_board = npy.array(board)
Xno = npy.count_nonzero(numpy_board = X)
Ono = npy.count_nonzero(numpy_board = O)
if Xno > Ono:
return O
elif Ono > Xno:
return X
def actions(board):
"""
Returns set of all possible actions (i, j) available on the board.
"""
Result = set()
for k in range(3):
for l in range(3):
if board[k][l] == EMPTY:
Result.add(board[k][l])
return Result
def result(board, action):
"""
Returns the board that results from making move (i, j) on the board.
"""
i = action[0]
j = action[1]
if board[i][j] != EMPTY:
raise Exception("Invalid Action")
new_player = player(board)
new_board = copy.deepcopy(board)
new_board[i][j] = new_player
return new_board
def winner(board):
"""
Returns the winner of the game, if there is one.
"""
for i in range(3):
if (board[i][0] == board[i][1] == board[i][2] and board[i][0] != EMPTY):
return board[i][0]
if (board[0][0] == board[1][1] == board[2][2] or (board[0][2] == board[1][1] == board[2][0]) and board[1][1] != EMPTY):
return board[1][1]
if (board[0][i] == board[1][i] == board[2][i] and board[0][i] != EMPTY):
return board[1][i]
else:
return None
def terminal(board):
"""
Returns True if game is over, False otherwise.
"""
if winner(board) != None:
return True;
numpy_board = npy.array(board)
empty_no = npy.count_nonzero(numpy_board == EMPTY)
if (empty_no == 0):
return True
else:
return False
def utility(board):
"""
Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
win_player = winner(board)
if (win_player == X):
return 1
elif (win_player == O):
return -1
else:
return 0
def minimax(board):
"""
Returns the optimal action for the current player on the board.
"""
if terminal(board):
return None
currentPlayer = player(board)
if currentPlayer == X:
return max_value(board)[1]
else:
return min_value(board)[1]
def max_value(board):
if terminal(board):
return (utility(board), None)
value = -sys.maxsize-1
optimalAction = None
for action in actions(board):
possibleResult = min_value(result(board, action))
if possibleResult[0] > value:
value = possibleResult[0]
optimalAction = action
if value == 1:
break
return (value, optimalAction)
def min_value(board):
if terminal(board):
return (utility(board), None)
value = sys.maxsize
optimalAction = None
for action in actions(board):
possibleResult = max_value(result(board, action))
if possibleResult[0] < value:
value = possibleResult[0]
optimalAction = action
if value == -1:
break
return (value, optimalAction)
Several issues:
A syntax error in Xno = npy.count_nonzero(numpy_board = X). You missed an equal sign there. It should be ==. Same error in the next similar statement
The condition in elif Ono > Xno: will never be true (think about it). What's more, this condition leaves a possibility to fall through this if..elif without entering either block, giving a None return value. Either it is X's turn or it is not. In latter case it is always O's turn. You should not need a second test. So correct this line to just else:
Result.add(board[k][l]) does not add a coordinate pair, but the contents of the square. This is not what you want. You want to store the coordinates. So this should be Result.add((k, l)). NB: don't use Pascal case for such a name, but camel case.
In the function winner the for loop will aways exit on its first iteration. It never performs the other iterations. You cannot know enough in the first iteration to return None. So remove that else: return None: in that case the loop must just continue. NB: the test for diagonals should better be moved outside of the loop, as it makes no sense to repeat that test 3 times. It does not depend on the loop variable.
If you make those corrections it should work.
Some other remarks:
If you are going to create a numpy array out of the list, then why not create only the numpy array once from the start, and work with only that and not the list? Making the conversion each time in player and terminal has a performance impact.
Also, counting the number of X and then the number of O needs two iterations, while you could count the empty cells in one sweep, and deduct from that how many are not empty. Even faster would be to just maintain a counter, and increase it when playing a move, and decreasing it when backtracking.
The above mentioned counter can be used to quicly determine the current player. If the number of played moves is even, then it's X's turn, otherwise it's O's turn.
deepcopy has a performance cost. Consider using the same list/array without duplicating it. You just need to add an "undo" operation after the recursive call.
Instead of recreating the set of possible moves, also consider maintaining one set incrementatlly: remove an action from that set when you play the move, and put it back while backtracking. This will increase performance.
Don't use this pattern:
if (empty_no == 0):
return True
else:
return False
First of all, the parentheses are not necessary, but more importantly: when you already have a boolean expression (empty_no == 0), then just return it. Don't do this if..else stuff:
return empty_no == 0
The minimax algorithm only returns values -1, 0 or 1, meaning that it doesn't favour quick wins over slow wins. This may lead to surprising moves, where a direct win is not played. To improve on that, consider using a more dynamic value. One idea is to change the utility function so that for a win with X it returns the number of free cells, plus 1. For O it would be the negation of that value. That way quick wins are favoured.
Related
I'm simply working on a Minimax algorithm that can play TicTacToe, For some reason the max_value and min_value functions occasionally return None.
def player(board):
if Terminal(board) != False:
return None
else:
if turn(board) == "X":
value,move = max_value(board)
return move
else:
value,move = min_value(board)
return move
def max_value(board):
if Terminal(board) != False:
return Utility(board),None
else:
v = -1000
move = None
for action in Actions(board):
aux,act = min_value(Result(board,action))
print(aux)
if aux > v:
v = aux
move = action
if v == 1:
return v,move
return v,move
def min_value(board):
if Terminal(board) != False:
return Utility(board),None
else:
v = 1000
move = None
for action in Actions(board):
aux,act = max_value(Result(board,action))
print(aux)
if aux < v:
v = aux
move = action
if v == -1:
return v,move
return v,move
Terminal returns state of the game,Action returns possible moves and result creates a board given an action.
The error I get is '>' not supported between instances of 'NoneType' and 'int' pops up for if aux < v: and if aux > v:
When I print aux, it occasionally appears as None.
Thanks.
The code you have shared is fine.
The error message indicates that Utility returns None when you call it. This should not happen. Utility should always return an integer when the game is over, and looking at your code it should return -1, 0 or 1.
There is also the possibility that Utility returns None when the game is not over, but that Terminal has a bug such that it returns something else than False even when the game is not over.
Here is an implementation of the functions Terminal and Utility that do not produce the error you got, but make it work as expected.
In fact, I added all missing functions based on a board representation that is a string of 9 characters, and a game loop so you can play against the minimax algorithm:
import re
def Utility(board):
m = re.findall(r"([XO])(?:\1\1(?:...)*$|..\1..\1|...\1...\1|.\1.\1..$)", board)
return -1 if "O" in m else len(m)
def Terminal(board):
return "O X"[Utility(board)+1].strip() or not board.count(".") and "draw"
def turn(board):
return "OX"[board.count(".") % 2]
def Actions(board):
return (i for i in range(9) if board[i] == ".")
def Result(board, action):
return board[:action] + turn(board) + board[action+1:]
board = "." * 9
while True: # Interactive game between human and minimax
print(f"{board[:3]}\n{board[3:6]}\n{board[6:]}")
winner = Terminal(board)
if winner:
print(f"Winner: {winner}")
break
if turn(board) == "X":
action = int(input("Your move (0..8)? "))
if board[action] != ".":
print("Invalid move. Try again.")
continue
else:
action = player(board)
print("minimax chooses", action)
board = Result(board, action)
I'm making a very simple Python chess engine using the standard Python chess library with a very simple evaluation function; the sum of the total black piece weights (positive) plus the sum of the total white piece weights (negative). The engine always plays as black.
I used the Negamax Wikipedia page for guidance and the depth is to the fourth ply. I don't expect grandmaster performance, but the engine makes very questionable moves, for example: e2e4 and f1c4 for white causes the engine to freely give up it's pawn via b7b5.
Can anyone help me out? I'm completely lost as to what I did wrong. The negamax (called search) and the evaluation function is shown below:
import chess
import time
import math
from time import sleep
from chessboard import display
scoreMovePair = {}
def colorMap(color):
if color == True:
return -1
return 1
def pieceMap(pieceNum):
if pieceNum == 1:
return 1
elif pieceNum == 2:
return 3
elif pieceNum == 3:
return 3
elif pieceNum == 4:
return 5
elif pieceNum == 5:
return 9
return pieceNum
def posEval(board):
score = 0
for i in range(0, 64):
piece = board.piece_at(i)
if piece != None:
score = score + pieceMap(piece.piece_type)*colorMap(piece.color)
return score
def search(board, level, a, b, moveSet, color):
if level == 4:
score = posEval(board)
scoreMovePair[score] = moveSet[0]
return score*color
if board.is_checkmate():
return 1000*colorMap(board.turn)
value = -10000
for move in board.legal_moves:
board.push(move)
moveSet.append(move)
value = max(value, -search(board, level + 1, -b, -a, moveSet, -color))
a = max(a, value)
moveSet.pop()
board.pop()
if (a >= b):
break
return value
def main():
global scoreMovepair
board = chess.Board()
display.start(board.fen())
while not display.checkForQuit():
validMoves = list(board.legal_moves)
if len(validMoves) == 0:
break
else:
move = input("Enter move: ")
t0 = time.time()
move = str(move)
myMove = chess.Move.from_uci(move)
if myMove in validMoves:
board.push_san(move)
value = search(board, 0, -10000, 10000, [], 1)
move = scoreMovePair[value]
print(scoreMovePair)
print("FINAL -> "+str(value))
board.push(move)
print(board.fen())
display.update(board.fen())
sleep(1)
t1 = time.time()
print(t1-t0)
else:
continue
display.terminate()
if __name__ == "__main__":
main()
Just based on a first glance, I would say you may be missing a "quiescence search" (meaning a search for quietness). Also called "captures only search".
https://www.chessprogramming.org/Quiescence_Search
This is a search that is called instead of an evaluation function on your leaf nodes (nodes where max depth is reached). The search makes only capture moves until there are no more captures (with unlimited depth).
In short, without this search, whoever gets the last move in the search (determined by depth) will be able to do anything without consequences. This can lead to some weird results.
I'm trying to implement the minimax algorithm in my tic tac toe game. I watched several videos, analysed multiple programs with minimax algorithm and I think I do know how it works now. My program is working but it seems like the algorithm has no clue what he is doing. It outputs pads on the board but it doesn't block me or tries to win. Like it's random. It would be nice if someone could have a look at my minimax algorithm and tell what's wrong! It would also be nice to tell me whats wrong with my explanation and don't just downvote.
from copy import deepcopy
class Board:
def __init__(self, board=None):
self.winning_combos = (
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6])
if board is not None:
self.board = board
else:
self.board = [None for i in range(9)]
def check_combos(self):
""" checks every combo if its used """
for symbol in ['X', 'O']:
for win_comb in self.winning_combos:
sum = 0
for field in win_comb:
if self.board[field] == symbol:
sum += 1
if sum == 3:
return symbol
return None
def complete(self):
""" check if the game is complete, caused by win or draw """
cc = self.check_combos()
if cc is not None:
return cc
if len(self.empty_pads()) <= 0:
return "DRAW"
return False
def show(self):
""" print board """
print(str(self.board[0:3]) + "\n" +
str(self.board[3:6]) + "\n" +
str(self.board[6:9]))
def empty_pads(self):
""" returns list with indexes of every unused/empty field/pad """
list = []
for pad in range(len(self.board)):
if self.board[pad] is None:
list.append(pad)
return list
def set(self, position, player):
""" sets the players symbol on the given position """
self.board[position] = player
def copy(self):
return deepcopy(self)
def get_enemy_player(player):
if player == 'X':
return 'O'
return 'X'
def get_player_value(player):
""" X = max, O = min """
if player == 'X':
return 1
else:
return -1
def get_player_by_value(value):
if value == -1:
return "O"
elif value == 1:
return "X"
else:
return "NONE"
def max_v(node):
if node.depth == 0 or node.board.complete():
return get_player_value(node.board.complete())
bestVal = -100
for child in node.children:
v = minimax(child)
if v >= bestVal:
bestVal = v
node.bestmove = child.move
return bestVal
def min_v(node):
if node.depth == 0 or node.board.complete():
return get_player_value(node.board.complete())
bestVal = 100
for child in node.children:
v = minimax(child)
if v <= bestVal:
bestVal = v
node.bestmove = child.move
return bestVal
def minimax(node):
if node.depth == 0 or node.board.complete():
return get_player_value(node.board.complete())
if get_player_value(node.player) == 1:
return max_v(node)
elif get_player_value(node.player) == -1:
return min_v(node)
class Node:
def __init__(self, depth, player, board, pad):
self.depth = depth
self.player = player
self.board = board
self.move = pad
self.board.set(pad, self.player)
self.bestmove = int
self.children = []
self.CreateChildren()
def CreateChildren(self):
if self.depth > 0 and not self.board.complete():
for index in self.board.empty_pads():
board = self.board.copy()
self.children.append(Node(self.depth - 1, get_enemy_player(self.player), board, index))
if __name__ == "__main__":
board = Board()
board.show()
while not board.complete():
player = 'X'
player_move = int(input('Move: ')) - 1
if player_move not in board.empty_pads():
continue
board.set(player_move, player)
board.show()
if board.complete():
break
player = get_enemy_player(player)
node = Node(9, player, board.copy(), player_move)
minmax = minimax(node)
print(node.bestmove+1)
for child in node.children:
print("move: " + str(child.move + 1) + " --> " + get_player_by_value(minmax) + " win")
board.set(node.bestmove, player)
board.show()
print(board.complete())
PS: I do know why the "moves: " ouput is always the same, but that's not the point.
I see multiple issues in your program.
As for your actual question: Your program acts as if the computer does not distinguish between a loss for it and a draw. Nowhere in your code can I find you assigning a value of 0 for a draw, while it appears you assign 1 for a win and -1 for a loss. Your code should prefer a draw to a loss but it sees no difference. That is why it looks "Like it's random". My analysis here may be off, but the following issues explain why it is difficult for me to tell.
Your style should be improved, to improve readability and ease of maintenance and to avoid bugs. Your code is much too difficult for me to understand, partly because...
You have far too few comments for anyone other than you to understand what the code is trying to do. In a few months you will not be able to remember, so write it down in the code.
You have too many blank lines, violating PEP8 and making harder to see much code on the screen.
You do not take your output seriously enough, as shown when you say "ou[t]put is always the same, but that's not the point." It is hard for anyone, including you, to tell what is happening in your code without good output. Work on that, and add some temporary print or logging statements that tell you more about what is happening inside.
Some of your routines return values of varying types. The complete() function sometimes returns the string "DRAW", sometimes the Boolean False, and sometimes a value from self.check_combos(), whatever type that is. Your routines max_v() and min_v() sometimes return a string value from get_player_value() and sometimes an integer from variable bestVal.
Can anyone tell me why my code prints 1 and not 8? It seems to not be going through very single state. Why is that?
using the minimax algorithm find the best possible move to make based on a game state, a possible tic tac toe board. Usually, it would branch off into a large tree of game states, each new branch called when the game doesn't end on an ending state, repeated, then finding the best possible move by recursively going down the tree finding the best moves for each player.
I was following the "tutorial" at http://giocc.com/concise-implementation-of-minimax-through-higher-order-functions.html.
My code:
#!/usr/bin/env python3
'''Minimax finds the best possible moves by applying a set of rules.
A win = 1, tie = 0, loss = -1 (for us). Assuming that each player chooses the best move
(we choose 1 if possible, opponent chooses -1). Starting at the top of a 'game tree',
generate the possible moves we can make. If It reaches a terminal state, stop. Otherwise keep searching in depth.
We find max.
'''
#[0,1,2,3,4,5,6,7,8]
class GameState: #a game state is a certain state of the board
#http://stackoverflow.com/questions/1537202/variables-inside-and-outside-of-a-class-init-function
x_went_first = True
def __init__(self,board):
self.board = board
self.winning_combos = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,8]]
def is_gameover(self):
if self.board.count('X') + self.board.count('O') == 9:
return True
for combo in self.winning_combos:
if (self.board[combo[0]] == 'X' and self.board[combo[1]] == 'X' and self.board[combo[2]] == 'X') or (self.board[combo[0]] == 'O' and self.board[combo[1]] == 'O' and self.board[combo[2]] == 'O'):
return True
return False
def get_possible_moves(self):
squares = []
for square in self.board:
if square != 'X' and square != 'O':
squares.append(int(square))
return squares
def get_next_state(self, move):
copy = self.board
num_of_x = copy.count('X')
num_of_o = copy.count('O')
#x starts, o's turn 1 > 0 o's turn
#o starts, x's turn 1 < 0 x's turn
#x starts, x's turn 1 > 1
#o starts, o's turn 1 < 1
if (self.x_went_first and num_of_x > num_of_o) or (self.x_went_first is not True and num_of_o == num_of_x):
copy[move] = 'O'
else:
copy[move] = 'X'
return GameState(copy)
def evals(game_state):
for combo in [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,8]]:
if game_state.board[0] == 'X' and game_state.board[1] == 'X' and game_state.board[2] == 'X':
return 1
elif game_state.board[0] == 'O' and game_state.board[1] == 'O' and game_state.board[2] == 'O':
return -1
else:
return 0
def min_play(game_state):
if game_state.is_gameover():
return evals(game_state)
moves = game_state.get_possible_moves()
best_move = moves[0]
best_score = 2 #not possible, best score is -1
for move in moves:
clone = game_state.get_next_state(move)
score = max_play(clone)
if score < best_score:
best_move = move
best_score = score
return best_score
def max_play(game_state):
if game_state.is_gameover():
return evals(game_state)
moves = game_state.get_possible_moves()
best_score = -2 #not possible, best score is 1
for move in moves:
clone = game_state.get_next_state(move)
score = min_play(clone)
if score > best_score:
best_move = move
best_score = score
return best_score
def minimax(game_state):
moves = game_state.get_possible_moves()
best_move = moves[0]
best_score = -2
for move in moves:
clone = game_state.get_next_state(move)
score = min_play(clone)
if score > best_score:
best_move = move
best_score = score
return best_move
game = GameState(['X',1,2,
3,'O',5,
6,7,8])
print(minimax(game))
My evals was always returning 0, and one of the winning combinations was messed up. New evals:
def evals:
for combo in [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]:
if game_state.board[0] == 'X' and game_state.board[1] == 'X' and game_state.board[2] == 'X':
return 1
elif game_state.board[0] == 'O' and game_state.board[1] == 'O' and game_state.board[2] == 'O':
return -1
return 0
I also modified it so the index isn't in every empty slot. View the full code at https://github.com/retep-mathwizard/pyai/blob/master/minimax_ttt
I am writing the game of life in python using a Sparse Matrix. My program takes coordinates from user input and sets the cells at the chosen coordinates to be alive. I have it to where it will print the first generation, but I can't seem to figure out why it wont print any subsequent generations. Any help would be appreciated.
class SparseLifeGrid:
generations = list()
def __init__(self):
"""
"pass" just allows this to run w/o crashing.
Replace it with your own code in each method.
"""
self._mat = list()
self.col = []
self.row = []
def minRange(self):
"""
Return the minimum row & column as a tuple.
"""
for i in range(len(self.row)):
if self.row[i] < self.minRow:
self.minRow = self.row[i]
if self.col[i] < self.minCol:
self.minCol = self.col[i]
min_Range = [self.minRow,self.minCol]
return min_Range
def maxRange(self):
"""
Returns the maximum row & column as a tuple.
"""
for i in range(len(self.row)):
if self.row[i] > self.maxRow:
self.maxRow = self.row[i]
if self.col[i] > self.maxCol:
self.maxCol = self.col[i]
max_Range = [self.maxRow,self.maxCol]
return max_Range
def configure(self,coordList):
"""
Set up the initial board position.
"coordlist" is a list of coordinates to make alive.
"""
# for i in coordList:
# self.setCell(i[0],i[1])
self._mat = list()
self.coordList = coordList
for i in range(len(self.coordList)):
spot = self.coordList[i]
self.row += [spot[0]]
self.col += [spot[1]]
self._mat += [[self.row[i],self.col[i]]]
self.maxRow = self.minRow = self.row[0]
self.maxCol = self.minCol = self.col[0]
def clearCell(self,row, col):
"""
Set the cell to "dead" (False)
"""
self[row,col] = 0
def setCell(self,row, col):
"""
Set the cell to "live" (True") and if necessary, expand the
minimum or maximum range.
"""
self[row,col] = 1
def isLiveCell(self,row,col):
n = len(self.coordList)
for i in range(n):
if (self._mat[i] == [row,col]):
return True
return False
def numLiveNeighbors(self, row,col):
"""
Returns the number of live neighbors a cell has.
"""
neighbors = 0
if self.isLiveCell(row+1,col): #checks below the current cell
neighbors += 1
if self.isLiveCell(row-1,col): #checks above the current cell
neighbors += 1
if self.isLiveCell(row,col+1): #checks to the right of the current cell
neighbors += 1
if self.isLiveCell(row,col-1): #checks to the left of the current cell
neighbors += 1
if self.isLiveCell(row+1,col+1): #checks downwards diagonally to the right of the current cell
neighbors += 1
if self.isLiveCell(row+1,col-1): #checks downwards diagonally to the left of the current cell
neighbors += 1
if self.isLiveCell(row-1,col+1): #checks upwards diagonally to the right of the current cell
neighbors += 1
if self.isLiveCell(row-1,col-1): #checks upawards diagonally to the left of the current cell
neighbors += 1
return neighbors
def __getitem__(self,ndxTuple):
row = ndxTuple[0]
col = ndxTuple[1]
if(self.isLiveCell(row,col)==1):
return 1
else:
return 0
def __setitem__(self,ndxTuple, life):
"""
The possible values are only true or false:
True says alive, False for dead.
Also, check to see if this cell is outside of the maximum row and/or
column. If it is, modify the maximum row and/or maximum column.
"""
ndx = self._findPosition(ndxTuple[0],ndxTuple[1])
if ndx != None:
if life != True:
self._mat[ndx].value = life
else:
self._mat.pop[ndx]
else:
if life != True:
element = _GoLMatrixElement(ndxTuple[0],ndxTuple[1],life)
self._mat.append(element)
def _findPosition(self,row,col):
''' Does a search through the matrix when given the row&col and
returns the index of the element if found
'''
n = len(self._mat)
for i in range(n):
if (row == self._mat[i]) and (col == self._mat[i]):
return i
return None
def __str__(self):
"""
Print a column before and after the live cells
"""
s=""
maxRange=self.maxRange()
minRange=self.minRange()
for i in range(minRange[0]-1,maxRange[0]+2):
for j in range(minRange[1]-1,maxRange[1]+2):
s+=" "+str(self[i,j])
s+="\n"
return s
def getCopy(self):
"""
Return a copy of the current board object, including the max and min
values, etc.
"""
return SparseLifeGrid()
def evolve(self):
"""
Save the current state to the "generations" list.
Based on the current generation, return the next generation state.
"""
self.generations.append(self._mat)
for row in range(len(self.row)):
for col in range(len(self.col)):
if ((self[row,col] == True) and (self.numLiveNeighbors(row,col) == 2)):
self.setCell(row,col)
if ((self[row,col] == True) and (self.numLiveNeighbors(row,col) == 3)):
self.setCell(row,col)
if ((self[row,col] == True) and (self.numLiveNeighbors(row,col) < 2)):
self.clearCell(row,col)
if ((self[row,col] == True) and (self.numLiveNeighbors(row,col) > 3)):
self.clearCell(row,col)
if ((self[row,col] == False) and (self.numLiveNeighbors(row,col) == 3)):
self.setCell(row,col)
self.generations.append(self._mat)
return self._mat
def hasOccurred(self):
"""
Check whether this current state has already occured.
If not, return False. If true, return which generation number (1-10).
"""
for i in range(len(self.generations)):
if len(self.generations) > 0:
print("This is generation",len(self.generations))
return self.generations[i]
else:
print("No Generations")
return False
def __eq__(self,other):
"""
This is good method if we want to compare two sparse matrices.
You can just use "sparseMatrixA == sparseMatrixB" once this method
is working.
"""
pass
class _GoLMatrixElement:
"""
Storage class for one cell
"""
def __init__(self,row,col):
self.row = row
self.col = col
self.next = None #
# Since this node exists, this cell is now alive!
# To kill it, we just delete this node from the lists.
from SparseLifeGrid import SparseLifeGrid
import sys
def readPoints(lifeGrid):
"""
Reads the locations of life and set to the SparseMatrix
"""
print("1. Enter positions of life with row,col format (e.g., 2,3).")
print("2. Enter empty line to stop.")
life=input()
coordList=[]
while life:
points=life.split(",")
try:
coord=[int(points[0]),int(points[1])]
coordList.append(coord)
except ValueError:
print("Ignored input:" + life+ ", row, col not valid numbers")
except:
print("Unexpected error:", sys.exc_info()[0])
print("added, keep entering or enter empty line to stop.")
life=input()
print("Thanks, finished entering live cells")
lifeGrid.configure(coordList)
def main():
"""
Runs for ten generations if a stable (repeating) state is not found.
"""
lifeGrid= SparseLifeGrid()
readPoints(lifeGrid)
patterns=0
i=0
while i <10 :
"""
Evolve to the next generation
"""
lifeGrid.evolve()
print(lifeGrid)
"""
Check whether this generation is a repetition of any of the
previous states.
If yes return the previous matching generation (1-10).
"""
patterns=lifeGrid.hasOccurred()
if patterns != -1:
break
i+=1
if i==10:
print("No pattern found")
else:
print("Pattern found at: " + str(i)+ " of type: " + str(patterns))
main()
The loop only executes once because of this:
patterns=lifeGrid.hasOccurred()
if patterns != -1:
break
patterns will never equal -1 under any circumstance, since hasOccurred can only return False or a member of self.generations or None. As a result, the loop will always break in the first iteration, and no subsequent generations will be printed.
Incidentally, your hasOccurred logic is strange. if len(self.generations) equals zero, then the loop will not get executed at all, and none of your return statements will be evaluated, so the result will be None. If the length is greater than zero, then the condition within the loop is always True, so self.generations[i] is always returned in the first iteration. Perhaps you meant to do:
#changed the name from `hasOcurrence`, since that implies the method can only return True or False.
def get_last_occurrence(self):
for i in range(len(self.generations)):
#todo: somehow compare self.generations[i] to the current generation
if the generations match:
return i
#the loop ended without finding a match!
return False
Then, within main:
patterns=lifeGrid.hasOccurred()
if patterns != False:
break