I'm currently trying to implement an AI for my Python TicTacToe game.
Everything performs greatly, apart from one single situation.
My current code:
def testLine(line):
'''
' :param line: Liste containing 3 ints
' :return: 1, if all elements of the list == 1
' -1, if all elements of the list == -1
' 0, otherwise
'''
if line[0] == 1 and line[1] == 1 and line[2] == 1:
return 1
elif line[0] == -1 and line[1] == -1 and line[2] == -1:
return -1
return 0
def getWinner(board):
# test columns
for idx in range(3):
line = [board[0][idx], board[1][idx], board[2][idx]]
if not testLine(line) == 0:
return line[0]
# test rows
for idx in range(3):
line = board[idx]
if not testLine(line) == 0:
return line[0]
# test diagonals
line = [board[0][0], board[1][1], board[2][2]]
if not testLine(line) == 0:
return line[0]
line = [board[0][2], board[1][1], board[2][0]]
if not testLine(line) == 0:
return line[0]
# no winner
return 0
def count(board, obj):
c = 0
for r in range(len(board)):
for col in range(len(board[r])): # FIXED IT
if board[r][col] == obj:
c += 1
return c
def nextMove(board, player):
if len(board[0]) + len(board[1]) + len(board[2]) == 1: return 0, 4
nextPlayer = player * (-1)
if not getWinner(board) == 0:
if player is 1: return -1, (-1, -1)
else: return 1, (-1, -1)
listOfResults = [] # empty array
if count(board, 0) == 0: # there is no empty field
return 0, (-1, -1)
_list = []
for i in range(len(board)):
for j in range(len(board[i])):
if board[i][j] == 0:
_list.append((i, j))
for (i, j) in _list:
board[i][j] = player
ret, move = nextMove(board, nextPlayer)
listOfResults.append(ret)
board[i][j] = 0
if player is 1:
maxPossibleValue = max(listOfResults)
return maxPossibleValue, _list[listOfResults.index(maxPossibleValue)]
else:
minPossibleValue = min(listOfResults)
return minPossibleValue, _list[listOfResults.index(minPossibleValue)]
if __name__ == '__main__':
print(str(nextMove([[ 1, -1, 0],
[ -1, -1, 1],
[ 1, 1, 0]],
-1)))
Output: (0, (0, 2))
I can say for sure that count, getWinner and testLine work perfectly.
But the output of the scenario at the very bottom of the code is simply wrong, as it should be (0, 2, 2) because the computer has to "block" my chance to win in the bottom line.
Do you have suggestions on how to fix my minimax algorithm?
EDIT: I've fixed it. The error was in the count method. You shouldn't say
for col in board[r]
but
for col in range(len(board[r]))
Because otherwise it won't keep the elements in the right order and the whole method returned a false value.
I've fixed it. The error was in the count method. You shouldn't say
for col in board[r]
but
for col in range( len(board[r]) )
Because otherwise it won't keep the elements in the right order and the whole method returned a false value.
The first thing you need to know is that return a, b is similar to return (a,b), because defining a tuple does not need parenthesis (exept for an empty tuple).
So you can easily return (0, 0, 2) instead of (0, (0, 2)) :
return (maxPossibleValue,) + _list[listOfResults.index(maxPossibleValue)]
# use (a,) for a tuple of len 1
But I'm aware this solves only the half of your problem.
Related
I'm writing the conway's game code in python. My code is like this:
def update_board(board: list[list[int]]) -> list[list[int]]:
rows = len(board)
cols = len(board[0])
count = 0
copy_board = deepcopy(board)
indx = [-1, -1, -1, 0, 0, 1, 1, 1]
indy = [-1, 0, 1, -1, 1, -1, 0, 1]
for i in range(rows):
for j in range(cols):
for k, value in enumerate(indx):
if check(i + value, j + indy[k], rows,
cols) and copy_board[i + indx[k]][j + indy[k]]:
count += 1
# Apply the rule to each cell
if count < 2 or count > 3:
board[i][j] = 0
elif count == 3:
board[i][j] = 1
# Reset the count for the next cell
count = 0
return board
It need to pass through the identity test:
def test_is_not_identity() -> None:
"""Test board update is not done in place."""
board = [[0 for _ in range(4)] for _ in range(4)]
assert board is not update_board(board)
and the stills test (I don't know what it does):
def test_stills(name: str, proto: str) -> None:
"""Test still lifes."""
board = parse_board(proto)
assert board == update_board(deepcopy(board)), name
but it doesn't pass although I updated the board in place so I don't understand.
You should return the copy_board instead of board. Also moodify copy_booard instead of board itself
def update_board(board: list[list[int]]) -> list[list[int]]:
rows = len(board)
cols = len(board[0])
count = 0
copy_board = deepcopy(board)
indx = [-1, -1, -1, 0, 0, 1, 1, 1]
indy = [-1, 0, 1, -1, 1, -1, 0, 1]
for i in range(rows):
for j in range(cols):
for k, value in enumerate(indx):
if check(i + value, j + indy[k], rows,
cols) and board[i + indx[k]][j + indy[k]]:
count += 1
# Apply the rule to each cell
if count < 2 or count > 3:
copy_board[i][j] = 0
elif count == 3:
copy_board[i][j] = 1
# Reset the count for the next cell
count = 0
return copy_board
I'm trying to build 4 in row game so when I try to make the players have the same choice it give this error
list index out of range
this is an example of what happedenter image description here
it seems the probleme with winning condition but I don't understand why this happend
it gives right answer when the input is correct like make the one player win but when make them have same choice it is break.
the code is
Width = 4
Height = 4
game_board = []
for x in range(Width): game_board.append(list(['a'] * Height))
def make_move(board, borad_row, board_col, piece):
board[borad_row][board_col] = piece
# check if the slot is empty
def is_slot_empty(board, board_col):
return board[Width - 1][board_col] == 'a'
# get the next available
def next_available_slot(board, board_col):
for i in range(Width):
if board[i][board_col] == 'a':
return i
# method for winning conditions
def winning(board, piece):
# check horizontally
for i in range(Height):
for j in range(Width):
if board[j][i] == piece and board[j][i + 1] == piece and board[j][i + 2] == piece and board[j][
i + 3] == piece:
return True
# check vertically
for i in range(Height):
for j in range(Width):
if board[j][i] == piece and board[j + 1][i] == piece and board[j + 2][i] == piece and board[j + 3][
i] == piece:
return True
# positive diagonal
for i in range(Height):
for j in range(Width):
if board[j][i] == piece and board[j + 1][i + 1] == piece and board[j + 2][i + 2] == piece and board[j + 3][
i + 3] == piece:
return True
# negative diagonal
for i in range(Height):
for j in range(Width):
if board[j][i] == piece and board[j - 1][i + 1] == piece and board[j - 2][i + 2] == piece and board[j - 3][
i + 3] == piece:
return True
game_end = False
turn_1 = 0
while not game_end:
if turn_1 == 0:
user_input = int(input("player_1:"))
if is_slot_empty(game_board, user_input):
user_input_row = next_available_slot(game_board, user_input)
make_move(game_board, user_input_row, user_input, 'X')
if winning(game_board, 'X'):
print("player 1 wins")
game_end = True
else:
user_input: int = int(input("player_2:"))
if is_slot_empty(game_board, user_input):
user_input_row = next_available_slot(game_board, user_input)
make_move(game_board, user_input_row, user_input, 'Z')
if winning(game_board, 'Z'):
print("player_2 wins")
game_end = True
for row in reversed(game_board):
print(row)
# alternating between 2 users
turn_1 += 1
turn_1 = turn_1 % 2
To fix winning(), try something like this:
def winning(board, piece):
# check horizontally
for i in range(Height):
if all(board[j][i] == piece for j in range(Width)):
return True
# check vertically
for j in range(Width):
if all(board[j][i] == piece for i in range(Height)):
return True
# positive diagonal
if all(board[i][i] == piece for i in range(Width)):
return True
# negative diagonal
if all(board[i][-(i+1)] == piece for i in range(Width)):
return True
This is only designed to handle a board with equal values for Height and Width.
When you do the checks, make sure to check only the indices that are within the board.
For example, for the horizontal check, both the left end (i) and the right end (i + 3) need to be within the 0..Width-1 range. So you should limit the value of the i index so that i + 3 is still less than Width:
...
# check horizontally
for i in range(Width - 3):
for j in range(Height):
if board[j][i] == piece and board[j][i + 1] == piece and board[j][i + 2] == piece and board[j][i + 3] == piece:
return True
...
I am trying to create a tic tac toe game with an adjustable game size and a computer that uses the minimax algorithm. The game sizes can only be odd numbers, to make sure that diagonal wins are always possible. The game runs with no errors, but I know that the minimax algorithm isn't working 100% correct because I can still beat the computer. I've looked extensively over my code and cannot find where the algorithm is going wrong. Here is my code:
Main.py
import TicTacToe
import Minimax
if (__name__ == "__main__"):
t = TicTacToe.ttt(3)
m = Minimax.Minimax(3, t)
while (t.winner == None):
if (t.turn == 1):
playerInputI = int(input("Input row: "))
playerInputJ = int(input("Input column: "))
bestIndex = (playerInputI, playerInputJ)
else:
winner, bestIndex = m.minimax(t.grid, (-1, -1), 15, -1)
t.winner = None
t.findWinner(bestIndex, t.grid)
t.updateGameGrid(bestIndex)
print(t.grid)
print(t.grid)
Minimax.py
class Minimax:
def __init__(self, gs, t):
self.gridSize = gs
self.ttt = t
def minimax(self, state, currIndex, depth, turn):
if (currIndex[0] != -1 and currIndex[1] != -1):
winner = self.ttt.findWinner(currIndex, state)
if (winner == -1):
return winner - depth, currIndex
elif (winner == -1):
return winner + depth, currIndex
elif (winner == 0):
return 0, currIndex
if (depth==0 and winner==None):
return 0, currIndex
evalLimit = -turn * 1000
bestIndex = None
for i in range(self.gridSize):
for j in range(self.gridSize):
if (state[i][j] == 0):
state[i][j] = turn
eval, newIndex = self.minimax(state, (i, j), depth-1, -turn)
state[i][j] = 0
if (turn > 0 and eval > evalLimit):
bestIndex = newIndex
evalLimit = eval
elif (turn < 0 and eval < evalLimit):
bestIndex = newIndex
evalLimit = eval
return evalLimit, bestIndex
Tictactoe.py
from random import randint
class ttt:
def __init__(self, size):
self.gridSize = size
self.grid = self.createGrid()
# If using minimax algorithm, user is maximizer(1) and computer is minimizer(-1)
# If single player, then user is 1, computer is -1
# If multiplayer, user1 is 1, user2 = -1
self.turn = 1
self.winner = None
def createGrid(self):
grid = []
for i in range(self.gridSize):
grid.append([])
for j in range(self.gridSize):
grid[i].append(0)
# grid = [[-1, 1, 0], [0, -1, 0], [0, 0, 0]]
return grid
def updateGameGrid(self, index):
if (self.grid[index[0]][index[1]] != 0):
return
self.grid[index[0]][index[1]] = self.turn
winner = self.findWinner(index, self.grid)
self.turn = -self.turn
def randomIndex(self):
x = randint(0, self.gridSize-1)
y = randint(0, self.gridSize-1)
while (self.grid[x][y] != 0):
x = randint(0, self.gridSize-1)
y = randint(0, self.gridSize-1)
return (x, y)
def findWinner(self, index, grid):
# Row
found = True
for j in range(self.gridSize-1):
if (grid[index[0]][j] != grid[index[0]][j+1] or grid[index[0]][j] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Column
found = True
for i in range(self.gridSize-1):
if (grid[i][index[1]] != grid[i+1][index[1]] or grid[i][index[1]] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Top Left to Bottom Right Diagonal
if (index[0] == index[1]):
found = True
for i in range(self.gridSize-1):
if (grid[i][i] != grid[i+1][i+1] or grid[i][i] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Top Right to Bottom Left Diagonal
if (index[0] + index[1] == self.gridSize-1):
found = True
for i in range(self.gridSize-1):
if (grid[self.gridSize-i-1][i] != grid[self.gridSize-i-2][i+1] or grid[self.gridSize-i-1][i] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
tie = True
for i in range(self.gridSize):
for j in range(self.gridSize):
if (grid[i][j] == 0):
tie = False
if (tie):
self.winner = 0
return 0
return None
The grid is represented as a 2d array, with each element being either a -1 for O, 1 for X and 0 for nothing. The player is 1 and the computer is -1. Who's turn it is is represented as a -1 or 1, corresponding to O or X. If anyone is able to find where the error in my code is that would be a great.
I see two troubles :
a) the two following conditions are the same in minimax
if (winner == -1):
return winner - depth, currIndex
elif (winner == -1):
return winner + depth, currIndex
b) When you return bestIndex, you actually return the winning move, the one that completes a line or a row or a diagonal, at one of the leaves of the game tree. What you really want is the next move to play. Write instead bestIndex = (i, j) in the condition if eval > evalLimit
I didn't check for everything but that is a good start. Run your code at depth=1 or 2 with many printf inside the minimax function, and look at the different moves, with the corresponding score, if they look correct or not.
I want to inset new key,value pair into a variable that was not previously defined
Here is what i want to do:
def drawboard(nextmove):
result = nextmove
pos = 1
print result
for i in range(7):
for j in range(7):
if (j == 0 or i % 2 == 0 or j % 2 == 0):
print '*',
if (i % 2 != 0 and j % 2 != 0):
print pos,
pos += 1
if (j == 6):
print '\n'
move = raw_input("Input you move(i.e. x,3[position, your symbol]: ")
if move == 'q':
exit()
calcres(move)
def calcres(move):
nextmove = dict([move.split(',')])
drawboard(nextmove)
drawboard({0: 0})
Inside drawboard function i want to concatenate nextmove with result, and save all the moves inside the result finally. I tried initializing result = {} but as expected that removes all items from result and re-initializes it resulting only one item inside that dictionary.
Use setdefault to initialize the result dictionary value to an empty list whenever the key is not present
def drawboard(nextmove):
result.setdefault(nextmove[0],[]).append(nextmove[1])
pos = 1
#print result
for i in range(7):
for j in range(7):
if (j == 0 or i % 2 == 0 or j % 2 == 0):
print '*',
if (i % 2 != 0 and j % 2 != 0):
print pos,
pos += 1
if (j == 6):
print '\n'
move = raw_input("Input you move(i.e. x,3[position, your symbol]: ")
if move == 'q':
exit()
calcres(move)
def calcres(move):
nextmove = move.split(',')
drawboard(nextmove)
result = {}
drawboard([0,0])
I'm working on an exercise where given a set of connections between two points (ie. 12 is a connection between 1 and 2 ect.). I decided to tackle the approach recursively in order to have it systematically check every path and return when it finds one that hits every node and starts and ends with one.
However upon debugging this it seems that as I pass down the adjMatrix further into the recursion it's also editing the upper levels and causing it not to search any further as it goes back up the tree. I think it has something to when I set newMatrix = adjMatrix, but I'm not exactly sure.
def checkio(teleports_string):
#return any route from 1 to 1 over all points
firstnode, secondnode, size = 0, 0, 8
#Makes the adjacency matrix
adjMatrix = [[0 for i in range(size)] for j in range(size)]
for x in teleports_string:
#Assigns Variables
if firstnode == 0 and x != ",":
#print("Node1:" + x)
firstnode = x
elif secondnode == 0 and x != ",":
#print("Node2:" + x)
secondnode = x
#Marks connections
if firstnode != 0 and secondnode != 0:
adjMatrix[int(firstnode) - 1][int(secondnode) - 1] = 1
adjMatrix[int(secondnode) - 1][int(firstnode) - 1] = 1
firstnode, secondnode = 0, 0
print(adjMatrix)
return findPath(adjMatrix, 1, "1")
def findPath(adjMatrix, currentnode, currentpath):
if isFinished(currentpath):
return currentpath
for x in range(0, 8):
if adjMatrix[currentnode - 1][x] == 1:
print(currentpath + "+" + str(x+1))
newMatrix = adjMatrix
newMatrix[currentnode - 1][x] = 0
newMatrix[x][currentnode - 1] = 0
temp = currentpath
temp += str(x+1)
newpath = findPath(newMatrix, x+1,temp)
print(newpath)
if isFinished(newpath):
print ("Returning: " + newpath)
return newpath
return ""
def isFinished(currentpath):
#Checks if node 1 is hit at least twice and each other node is hit at least once
if currentpath == "":
return False
for i in range(1, 9):
if i == 1 and currentpath.count(str(i)) < 2:
return False
elif currentpath.count(str(i)) < 1:
return False
#Checks if it starts and ends with 1
if not currentpath.startswith(str(1)) or not currentpath.endswith(str(1)):
return False
return True
#This part is using only for self-testing
if __name__ == "__main__":
def check_solution(func, teleports_str):
route = func(teleports_str)
teleports_map = [tuple(sorted([int(x), int(y)])) for x, y in teleports_str.split(",")]
if route[0] != '1' or route[-1] != '1':
print("The path must start and end at 1")
return False
ch_route = route[0]
for i in range(len(route) - 1):
teleport = tuple(sorted([int(route[i]), int(route[i + 1])]))
if not teleport in teleports_map:
print("No way from {0} to {1}".format(route[i], route[i + 1]))
return False
teleports_map.remove(teleport)
ch_route += route[i + 1]
for s in range(1, 9):
if not str(s) in ch_route:
print("You forgot about {0}".format(s))
return False
return True
assert check_solution(checkio, "13,14,23,25,34,35,47,56,58,76,68"), "Fourth"
The line
newMatrix = adjMatrix
merely creates another reference to your list. You'll need to actually create a new list object. As this is a matrix, do so for the contents:
newMatrix = [row[:] for row in adjMatrix]
This creates a new list of copies of your nested lists.