I have made two very similar Othello AIs. In the first one, the board is represented as an array of length 100 (10x10) where the 8x8 board is represented in the "middle" of the array and the rest of the array is buffer spaces around the edge (index 11 is top left corner of 8x8 board, index 88 is bottom right corner). To make a move at position index, I use the following code:
changed = [index]
for direction in (1, 9, 10, 11):
shift = index - direction
while board[shift] == opp:
shift -= direction
if board[shift] == player:
changed += [*range(shift + direction, index, direction)]
for direction in (1, 9, 10, 11):
shift = index + direction
while board[shift] == opp:
shift += direction
if board[shift] == player:
changed += [*range(index + direction, shift, direction)]
To generate moves, I then go through the possible indices (which are tiles in the inner 8x8 board) and check if len(changed) > 1. If it is, I set the tiles of the board in changed to that player.
In the second AI, which I had expected would be faster in making moves, the board is represented as two 64-bit bitboards - one for the player aiming to maximize the score and another for the player trying to minimize the score. To make a move, I use the same code as here just converted into Python:
new_disk = 1 << index
captured = 0
newmy_disks = my_disks | new_disk
for direction in range(8):
x = shift(new_disk, direction) & opp_disks
for i in range(5):
x |= shift(x, direction) & opp_disks
bounding_disk = shift(x, direction) & my_disks
if bounding_disk != 0:
captured_disks |= x
newmy_disks = newmy_disks ^ captured_disks
newopp_disks = opp_disks ^ captured_disks
With the bitboard representation, playing 1000 random games takes around 7 seconds, while the array representation takes 4 seconds.
How can I make the bitboard representation faster in generating and making moves, and would it be possible to check possible moves and return the new bitboards at the same time?
Related
Write a function traverse() that takes in a list tb of n strings each containing n lower case characters
(a-z).
tb represents a square table with n rows and n columns. The function returns a string st generated by the procedure below that traverses the grid starting from the top left cell and ending at the bottom right cell.
At every step, the procedure moves either horizontally to the right or vertically down, depending on which of the two cells has a \smaller" letter (i.e., a letter that appears earlier in the alphabetical order).
The letter in the visited cell is then added to st. In case of ties, either direction can be taken.
When the right or bottom edges of the table are reached, there is obviously only a unique next cell to move to. As an example, traverse(["veus", "oxde", "oxlx", "hwuj"]) returns "veudexj"
so the table would look like this:
v o o h
e x x w
u d l u
s e x j
I am new in python and I wrote this code ... but it only prints "veuexj" I would say the problem is in this line if new_list[a - 1][b - 1] == new_list[a - 1][-2]: which force the parameter to skip the 'd' character. #And I don't know how to solve it.
def traverse(tb_list):
new_list = tb_list.copy()
st = new_list[0][0]
parameter = ''
handler = 1
for a in range(1, len(new_list)):
for b in range(handler, len(new_list[a])):
if new_list[a - 1][b - 1] == new_list[a - 1][-2]:
parameter = new_list[a][b]
elif new_list[a - 1][b - 1] > min(new_list[a - 1][b], new_list[a][b - 1]):
parameter = min(new_list[a - 1][b], new_list[a][b - 1])
elif new_list[a - 1][b - 1] < min(new_list[a - 1][b], new_list[a][b - 1]):
parameter = min(new_list[a - 1][b], new_list[a][b - 1])
st += parameter
handler = b
return st
print(traverse(["veus", "oxde", "oxlx", "hwuj"]))
You can try something like this (explanation added as comments):
def traverse(tb_list):
lst = tb_list.copy() #Takes a copy of tb_list
lst = list(zip(*[list(elem) for elem in lst])) #Transposes the list
final = lst[0][0] #Sets final as the first element of the list
index = [0,0] #Sets index to 0,0
while True:
x = index[0] #The x coordinate is the first element of the list
y = index[1] #The y coordinate is the second element of the list
if x == len(lst) - 1: #Checks if the program has reached the right most point of the table
if y == len(list(zip(*lst))) - 1: #Checks if program has reached the bottommost point of the table
return final #If both the conditions are True, it returns final (the final string)
else:
final += lst[x][y+1] #If the program has reached the right most corner, but not the bottommost, then the program moves one step down
index = [x, y+1] #Sets the index to the new coordinates
elif y == len(list(zip(*lst))) - 1: #Does the same thing in the previous if condition button in an opposite way (checks if program has reached bottommost corner first, rightmost corner next)
if x == len(lst) - 1:
return final
else:
final += lst[x + 1][y] #If the program has reached the bottommost corner, but not the rightmost, then the program moves one step right
index = [x + 1, y]
else: #If both conditions are false (rightmost and bottommost)
if lst[x+1][y] < lst[x][y+1]: #Checks if right value is lesser than the bottom value
final += lst[x+1][y] #If True, then it moves one step right and adds the alphabet at that index to final
index = [x+1,y] #Sets the index to the new coords
else: #If the previous if condition is False (bottom val > right val)
final += lst[x][y+1] #Moves one step down and adds the alphabet at that index to final
index = [x,y+1] #Sets the index to the new coords
lst = ["veus", "oxde", "oxlx", "hwuj"]
print(traverse(lst))
Output:
veudexj
I have added the explanation as comments, so take your time to go through it. If you are still not clear with any part of the code, feel free to ask me. Any suggestions to optimize/shorten my code are most welcome.
I have a basic maze game in python "*" are walls, "W" water buckets which allow you to travel through fire "F" (if you have none you lose), "A" is the player and " " is air and "X" and "Y" are start and endpoints. There are also numbers from 1 - 9 which have 1 other matching number so the player can teleport. Example map:
*X**********
* 1 W F*
* FFFF 1 *
**********Y*
So I have a graph (dictionary) where I go through each character in the maze and find their neighbours like {"0,0": [None, "0,1", None, "0,1"]}
{"node": "LeftNeighbour", "RightNeighbour", "NeighbourAbove", "NeighbourBelow"}
and I am using coordinates "x,y" to denote neighbours.
Then the DFS code is ...
visited = []
def DFS_recursive(graph, node, nodeToFind):
global visited
for neighbour in graph[node]:
if neighbour == nodeToFind:
visited.append(neighbour)
break
if neighbour not in visited and neighbour != None:
# get x and y by breaking up the string
x = ""
y = ""
yFlag = False
for char in neighbour:
if char == ",":
yFlag = True
elif yFlag == False:
x += str(char)
else:
y += str(char)
x, y = int(x), int(y)
if grid[y][x].display == "*": # if the neighbour's display property is a wall, append the neighbour but don't call the function again ...
visited.append(neighbour)
else:
visited.append(neighbour)
DFS_recursive(graph, neighbour, nodeToFind)
So this works until I add teleport pad code.
if neighbour == (number from 1 - 9):
find the coordinates of the other teleport pad and make the coordinates the new node
DFS_recursive(graph, newNode, nodeToFind)
And yes I haven't added in the "nodeToFind" bit yet, couldn't quite get that to work so I added a separate function to deal with that. Probably should remove the parameter but anyway that's not the point.
So 1. Not sure if this is the right way to go about it (am new to programming)
and 2. No idea how to handle the teleport pads.
Thanks in advance!
This is somewhat similar to this question : Python generate all possible configurations of numbers on a "board" but I'm trying to implement in Python and I want to include generated boards that are just partially complete.
In tic tac toe there are 3^9 (19683) board positions.
Here is my code to generate each board where each element of the board array is a single board :
boards = []
temp_boards = []
for i in range(0 , 19683) :
c = i
temp_boards = []
for ii in range(0 , 9) :
temp_boards.append(c % 3)
c = c // 3
boards.append(temp_boards)
0 corresponds to O
1 corresponds to X
2 corresponds to 'position not yet filled'
Could I have missed any positions ?
If you consider that players take turns and the game stops when someone wins, there will be fewer possible boards than the maximum combinations of X, O and blank cells would suggest. You also cannot count boards filled with all Xs or all Os or any combinations that would have a difference greater than 1 between the number of Xs and Os.
You can obtain that number by recursively simulating moves starting with X and starting with O:
axes = [(0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6)]
def isWin(board):
return any("".join(board[p] for p in axis) in ["XXX","OOO"] for axis in axes)
def validBoards(board="."*9,player=None):
if player == None:
yield board # count the empty board
for b in validBoards(board,player="X"): yield b # X goes 1st
for b in validBoards(board,player="O"): yield b # O goes 1st
return
opponent = "XO"[player=="X"]
for pos,cell in enumerate(board):
if cell != ".": continue
played = board[:pos]+player+board[pos+1:] # simulate move
yield played # return the new state
if isWin(played): continue # stop game upon winning
for nextBoard in validBoards(played,opponent):
yield nextBoard # return boards for subsequent moves
output:
distinctBoards = set(validBoards()) # only look at distinct board states
allStates = len(distinctBoards)
print(allStates) # 8533 counting all intermediate states
winningStates = sum(isWin(b) for b in distinctBoards)
print(winningStates) # 1884 (so 942 for a specific starting player)
filledStates = sum(("." not in b) for b in distinctBoards)
print(filledStates) # 156 states where all cells are filled
finalStates = sum(isWin(b) or ("." not in b) for b in distinctBoards)
print(finalStates) # 1916 end of game states (win or draw)
earlyWins = sum(isWin(b) and ("." in b) for b in distinctBoards)
print(earlyWins) # 1760 wins before filling the board
draws = finalStates - winningStates
print(draws) # 32 ways to end up in a draw
lastWins = filledStates-draws
print(lastWins) # 124 wins on the 9th move (i.e filling the board)
fastWins = sum( isWin(b) and b.count(".") == 4 for b in distinctBoards)
print(fastWins) # 240 fastest wins by 1st player (in 3 moves)
fastCounters = sum( isWin(b) and b.count(".") == 3 for b in distinctBoards)
print(fastCounters) # 296 fastest wins by 2nd player (in 3 moves)
If you need a faster implementation, here is an optimized version of the function that only returns distinct states and leverages this to skip whole branches of the move sequence tree:
def validBoards(board="."*9,player=None,states=None):
if player == None:
result = {board} # count the empty board
result |= validBoards(board,player="X",states=set()) # X goes 1st
result |= validBoards(board,player="O",states=set()) # O goes 1st
return result
opponent = "XO"[player=="X"]
for pos,cell in enumerate(board):
if cell != ".": continue
played = board[:pos]+player+board[pos+1:] # simulate move
if played in states : continue # skip duplicate states
states.add(played) # return the new state
if isWin(played): continue # stop game upon winning
validBoards(played,opponent,states) # add subsequent moves
return states
I have a problem where I have to find the largest square in an n * n grid.
e.g.
. . . . .
. # . # .
. # . . .
. # . # .
. # . . .
where the biggest square would be 3 by 3 in the bottom corner.
I am supposed to return the most steps someone could take before turning right so that they can repeat this infinitely without hitting a wall "#" or going outside the n * n square which is why the output is one less that the width/length of the square.
My code loops through the grid left to right, top to bottom looking for vertices that face down and to the right. Once it finds one it then looks for the biggest possible vertex facing up and to the right and when it finds that checks all four sides to see whether or not they are made up or .. This code works in under 1 second for me on anything around n = 100, however I need it to run at 1 second for n = 500. Any tips on how I can speed this up?
import sys
input = sys.stdin.readline
n = int(input())
maze = [list(input()) for _ in range(n)]
squares = 0
for r in range(n - 1):
for c in range(n - 1):
if maze[r][c] == '.' and maze[r][c + 1] == '.' and maze[r + 1] [c] == '.':
sides = []
for i in range(min(n - r - 1, n - c - 1), -1, -1):
if maze[r + i][c + i] == '.' and maze[r + i][c + i - 1] == '.' and maze[r + i - 1][c + i] == '.':
sides = i
if maze[r][c : c + sides] == ['.'] * sides and maze[r + sides][c : c + sides] == ['.'] * sides:
a = True
for j in range(sides):
if maze[r + j][c] != '.' or maze[r + j][c + sides] != '.':
a = False
if a and sides > squares:
squares = sides
break
if squares == n - 1:
break
print(squares)
I can think of a O(n^3) algorithm as follows:
Precompute 4 arrays: top[][], bottom[][], left[][], right[][], each stores the maximum length of a direction that you can go from (i,j)
For each (i,j) , use it as a square's bottom left corner, for each its diagonal points (i-1, j+1), (i-2, j+2)...etc., test if those points can be used as the square's top right corner. Store the maximum square side in the process
For step 1, all 4 arrays can be precomputed in O(n^2)
For step 2, as we loop through all (i,j), and for each (i,j) we have to see at most all diagonal points which is at most n of them, total we get O(n^3)
The test in step 2 can be done in O(1) using the 4 precomputed arrays, simply check if the 4 corners of the "possible squares" can be joined by checking the corresponding directions (top, bottom, left, right)
Of course, there are many minor things which can be done to speed up, for example:
In step 2, for each (i,j), only check for diagonal points which is in the range [current_maximum_diagonal_found ... max(right[i][j], top[i][j])]
Update current_maximum_diagonal_found along the whole algorithm, so that we hope for some (i,j), we do not need to check whole n diagonal points.
But strictly speaking, it is still O(n^3), but as far as I know it should be able to run in 1 second for n~500
that's an interesting problem. I tried out some things and ended up with this implementation which is O(n^3). I commented the code so that you can follow the idea hopefully. There's still room for speed improvements, but this version already does the job (e.g. with maze size 500x500):
Finished after 0.708 seconds.
Result: 112581 squares found, maximum square (x=13, y=270, size=18).
This is the source code (Python 3):
import random
import pprint
import time
# small sample maze
maze = ['.....',
'...#.',
'.#...',
'.#.#.',
'.#...']
# convert to boolean maze
maze_bin = [[True if cell == '.' else False for cell in line] for line in maze]
# uncomment to generate a random maze
# maze_size = 500
# threshold = 0.2
# maze_bin = [[1 if random.random() >= threshold else 0 for _ in range(maze_size)] for _ in range(maze_size)]
# take start time
t1 = time.time()
# rotate the maze (first column becomes first row, first row becomes first column)
maze_bin_rot = [[maze_bin[i][j] for i in range(len(maze_bin))] for j in range(len(maze_bin[0]))]
# horizontal_lengths is a two-dimensional list that contains the number of possible steps to the right for every cell.
horizontal_lengths = []
for line in maze_bin:
num = 0
line_lengths = []
for i in reversed(line):
line_lengths.append(i*num)
num = i * (num + i)
horizontal_lengths.append(tuple(reversed(line_lengths)))
# vertical_lengths is a two-dimensional list that contains the number of possible steps to the bottom for every cell.
vertical_lengths_rot = []
for line in maze_bin_rot:
num = 0
line_lengths = []
for i in reversed(line):
line_lengths.append(i*num)
num = i * (num + i)
vertical_lengths_rot.append(tuple(reversed(line_lengths)))
# do the rotation again to be back in normal coordinates
vertical_lengths = [[vertical_lengths_rot[i][j] for i in range(len(vertical_lengths_rot))] for j in range(len(vertical_lengths_rot[0]))]
# calculate the maximum size of a square that has it's upper left corner at (x, y).
# this is the minimum of the possible steps to the right and to the bottom.
max_possible_square = []
for y in range(len(maze_bin)):
line = []
for x in range(len(maze_bin[0])):
line.append(min(horizontal_lengths[y][x], vertical_lengths[y][x]))
max_possible_square.append(line)
# search for squares
results = []
max_size_square = (-1, -1, -1)
for y in range(len(max_possible_square)):
for x in range(len(max_possible_square[0])):
# start with maximum possible size and decrease size until a square is found.
for size in reversed(range(1, max_possible_square[y][x]+1)):
# look at the upper right (x+size,y) and bottom left corner (x,y+size).
# if it's possible to make at least size steps to the right from the bottom left corner
# and at least size steps to the bottom from the upper right corner, this is a valid square.
if horizontal_lengths[y+size][x] >= size and vertical_lengths[y][x+size] >= size:
results.append((x, y, size+1))
if size+1 > max_size_square[2]:
max_size_square = (x, y, size+1)
# break after the the largest square with upper left corner (x,y) has been found.
break
t2 = time.time()
# comment this print section if you use larger grids
print('Maze:')
pprint.pprint(maze_bin)
print('\n')
print('Horizontal possible steps:')
pprint.pprint(horizontal_lengths)
print('\n')
print('Vertical possible steps:')
pprint.pprint(vertical_lengths)
print('\n')
print('Maximum possible size of square:')
pprint.pprint(max_possible_square)
print('\n')
print('Results:')
for square in results:
print('Square: x={}, y={}, size={}'.format(*square))
print('\n')
# final results
print('Finished after {:.3f} seconds.'.format(t2-t1))
print('Result: {} squares found, maximum square (x={}, y={}, size={}).'.format(len(results), *max_size_square))
I hope this is what you were looking for. If you have any questions, just leave a comment below ;)
If we do not want to enumerate all results, one optimization that may be worth considering is the following. It's based on the strategy - "do not proceed further with this cell if it can not lead to the optimal solution"
for y in range(possible_y_value):
for x in range(possible_x_value):
# We are ready to process cell identified by (x,y).
# Check if max_possible_square_length at this cell is greater than size of best_result seen so far. If so, proceed further, otherwise skip this cell
if max_possible_square[y][x]+1 > best_result.size:
# proceed further with the inner most for loop
....
Even from within the inner most for loop, we can break out of the loop at the iteration when it falls below the best_result's size seen so far
So far, I have a program where 2 players can click to place an X and an O in turns. I'm not sure how to make the program recognize a winner/ draw. If you guys could help me make a function that indicated a win/ draw on the screen in any way, I would love you forever. Thanks.
from graphics import *
import sys
def player_o(win, center):
'''
Parameters:
- win: the window
'''
outline_width = 5
circle = Circle(center, boxsize/2)
circle.setOutline('red')
circle.setWidth(outline_width)
circle.draw(win)
def player_x(win, p1x, p1y):
'''
Parameters:
- win: the window
'''
for i in range(2):
deltaX = (-1) ** i * (boxsize / 2)
deltaY = (boxsize / 2)
line = Line(Point(p1x - deltaX, p1y - deltaY),
Point(p1x + deltaX, p1y + deltaY))
line.setFill('red')
line.setWidth(5)
line.draw(win)
def game():
global win
global boxsize
try:
winsize = int(input("How large would you like the window? (Between 100 and 3000): "))
if winsize < 100 or winsize > 3000:
print("Invalid window size")
quit()
squares = int(input("How many squares per row? (Between 3 and 10):"))
boxsize = winsize/ squares
if squares < 3 or squares > winsize / 10:
print("Invalid number")
quit()
except ValueError:
sys.exit("Not a valid number")
win = GraphWin("Tic Tac Toe", winsize, winsize)
for i in range(squares - 1):
hline = Line(Point(0, (winsize/squares) * (i + 1)), Point(winsize, (winsize/squares) * (i + 1)))
hline.draw(win)
vline = Line(Point((winsize/squares) * (i + 1), 0), Point((winsize/squares) * (i + 1), winsize))
vline.draw(win)
for i in range((squares ** 2) // 2):
print("X, click a square.")
p1mouse = win.getMouse()
p1x = p1mouse.getX()
p1y = p1mouse.getY()
player_x(win, p1x, p1y)
print("O, click a square.")
p2mouse = win.getMouse()
p2x = p2mouse.getX()
p2y = p2mouse.getY()
player_o(win, Point(p2x, p2y))
if squares % 2 == 1:
print("X, click a square.")
p1mouse = win.getMouse()
p1x = p1mouse.getX()
ply = p1mouse.getY()
player_x(win, p1x, p1y)
game()
Keep data and representation of data separated. That's how. Right now you're just drawing things, rather than that you should be generating some representation of the playing field (e.g. a list of the boxes and their state, as in, checked by p1, checked by p2, or unchecked), and then use that to draw when needed. The advantage should be immediately obvious - if you know the state of the game, determining if there's a winner (and who it is) is trivial.
After 3 turns (minimum turns to win) check your 2d array if there is a token next to the last played by adding/substracting one, if found repeat same operation to array indices else break out.
If 2nd control structure is reached break and announce winner.
With each move in the game, a 2D array or a dictionary (with values being lists) should be used. Then, you can just check each way of winning. This way, you can also check if the move is valid or not--- whether or not the spot on the board is taken.
I would also suggest using a numerical or a coordinate system to dictate movement.
The board would look like this:
1 2 3
4 5 6
7 8 9
The numbers are corresponding spots on the board.
For example:
In the initialization:
moves = 0
positions = {'1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9':0}
# '1' - '9' are the spots on the board.
# 0 means the spot is empty, 'X' means the spot is taken by 'X', 'O' means the spot is taken by 'O'. You can use any other naming system, but this is simple.
In the movement code:
while 1 == 1: # just a loop until the input is valid. See the 'break' statement below
new_move = input("X, enter what space to move to: ")
if positions[new_move] == 0: # if that board spot is empty
moves += 1 #moves = moves + 1
positions[new_move] == 'X' # board spot is now occupied by 'X'
# code to show the piece on the board
if moves >= 5: # least possible moves to win is 5
win_check(positions)
break
Alternatively, you can use the movement as a function, and have it recursively call itself until the input is valid:
def move_X():
new_move = input("X, enter what space to move to: ")
if positions[new_move] == 0: # if that board spot is empty
moves += 1 #moves = moves + 1
positions[new_move] == 'X' # board spot is now occupied by 'X'
# code to show the piece on the board
if moves >= 5: # least possible moves to win is 5
win_check(positions)
move_O() # this should be defined similarly to 'move_X' except that it would correlate to 'O'.
else:
move_X()
The the win checking method:
def win_check(positions):
if positions['1'] == 'X' and positions['2'] == 'X' and positions['3'] == 'X':
return "Winner: X"
elif # similar things, checking all of the other ways to win.
You need 1 if statement (in the beginning) and 15 elif statements, as there are 8 ways to win for each player, so 16 checks have to be made.