Optimizing my solution for 1D peg solitaire - python

First I will explain the rules of peg solitaire (for 1 dimension):
you initially start with a 1 dimensional board of length n. n-1 elements are pegs (filled) and 1 element is a hole (empty). So a starting position can be [1, 1, 0, 1, 1, 1] where 1s represent pegs and 0s represent holes for n = 6
The goal of the game is to reach a board state where n-1 elements are holes and 1 element is a peg at any given position. So a valid solution can be [0, 0, 0, 1, 0, 0] for n = 6
Your available moves at any given position is to move one peg by two positions to the right or to the left if and only if there is a peg between the two position, then once you make that move, replace the middle peg with a hole.
So for a board such as board = [0, 1, 1, 0, 1, 1, 0] there are two available moves.
move board[1] to board[3] and set board[2] = 0
move board[5] to board[3] and set board[4] = 0
move board[2] to board[0] and set board[1] = 0
move board[4] to board[6] and set board[5] = 0
The goal of the algorithm I am trying to make is to take an input of n where n > 2 and n is an even number, then for a board of length n, find all the positions for a start state at which a hole can be placed to produce a valid solution.
I have created a brute force algorithm which finds all the possible moves until a valid solution is reached, but it starts taking a very long time to find a solution past n > 20. So I was wondering if there are some optimizations I could do or different solution approaches.
Here is my code:
import re
def generateBoard(n):
return [1]*n
def solve(board):
if checkBoard(board):
return True
elif checkUnsolvable(board):
return False
moves = []
for i in range(len(board)):
if i < len(board)-2:
if board[i] and board[i+1] and not board[i+2]:
moves.append((i, 'right'))
if i > 1:
if board[i] and board[i-1] and not board[i-2]:
moves.append((i, 'left'))
for move in moves:
newBoard = makeMove(board, move)
if solve(newBoard):
return True
continue
return False
def makeMove(board, move):
index, direction = move
b = [element for element in board]
if direction == 'right':
b[index] = 0
b[index+1] = 0
b[index+2] = 1
elif direction == 'left':
b[index] = 0
b[index-1] = 0
b[index-2] = 1
return b
def checkBoard(board):
if sum(board) == 1:
return True
return False
def checkUnsolvable(board):
expression1 = '1000+1' #RE for a proven to be unsolvable board
expression2 = '00100' #RE for a proven to be unsolvable board
string = ''.join([str(element) for element in board])
if re.search(expression1, string) or re.search(expression2, string):
return True
return False
def countSolutions(board):
indices = []
for i in range(len(board)):
b = [element for element in board]
b[i] = 0
if solve(b):
indices.append(i+1)
return indices
n = int(input())
print(countSolutions(generateBoard(n)))
Optimizations I have come up with so far:
A board containing [1, 0, 0, ..., 0, 1] is unsolvable. So when we find this patters we skip
Same thing for a board containing [0, 0, .. 0, 1, 0, 0, ..,0]
We only need to check half of the board, as the solutions of the other half would be symmetrical.
But despite these the code is still very slow.

This algorithm for doing the solitaire is covered in this research paper: https://arxiv.org/pdf/math/0006067.pdf.
It claims that a linear time algorithm exists.
A valid solution looks like this:
L = 1
or 011
or 110
or 11 (01)* [ 00 | 00(11)+ | (11)+00 | (11)*1011 | 1101(11)* ] (10)*11 # (A)
or 11 (01)* (11)* 01 # (B)
or 10 (11)* (10)* 11 # (C)
To solve A, you can use regex to check for the string. However, there are multiple cases of it due to the middle section.
First case: 11(01)*00(10)*11
Second case: 11(01)*(00)(11)+(10)*11
Third case: 11(01)*(11)+(00)(10)*11
Fourth case: 11(01)*(11)*(1011)(10)*11
Fifth case: 11(01)*1101(11)*(10)*11
To solve B and C is a simpler regex match:
Solution for B: 11(01)*(11)*01
Solution for C: 10(11)*(10)*11
If you match (you will need to convert it to a string though, such as ''.join([str(i) for i in mylist]) for example) 1, 011, 110, or any of the patterns above, then it will be solvable.

Related

Infinite recursive call minimax algorithm

I have recently implemented the code for a 4X4 tic tac toe game which is using minimax algorithm.
However, my minimax function is calling itself recursively infinite no. of times.
Initial Board (4X4) tic tac toe ->
board = np.array([['_','_','_', '_'],['_','_','_', '_'],['_','_','_', '_'],['_','_','_', '_']])
Code for the computer's turn ->
import math
from utility import *
def minimax(board, spotsLeft, maximizing):
bestIdx = (0,0)
p1 = 'X'
p2 = 'O'
res, stat = checkGameOver(board, spotsLeft)
if res==True:
if stat == 'X': return 17-spotsLeft, bestIdx
elif stat == 'O': return -17+spotsLeft, bestIdx
else: return 0, bestIdx
if maximizing:
# return max score
bestMove = -math.inf
for i in range(4):
for j in range(4):
if board[i][j] == '_':
board[i][j] = p1
score, idx = minimax(board, spotsLeft-1, False)
print(score, idx)
board[i][j] = '_'
if bestMove<score:
bestMove = score
bestIdx = (i,j)
return bestMove, bestIdx
else:
# return min score
bestMove = math.inf
for i in range(4):
for j in range(4):
if board[i][j] == '_':
board[i][j] = p2
score, idx = minimax(board, spotsLeft-1, True)
print(score, idx)
board[i][j] = '_'
if bestMove>score:
bestMove = score
bestIdx = (i,j)
return bestMove, bestIdx
def ai(board):
spotsLeft = 16
p1 = 'X' # Computer
p2 = 'O' # Player
turn = p1
while True:
res, stat = checkGameOver(board, spotsLeft)
if res==False:
if turn == p1:
# AI
boardCopy = board[:]
score, idx = minimax(boardCopy, spotsLeft, True)
board[idx[0]][idx[1]] = turn
turn = p2
spotsLeft-=1
else:
# Human
inp = int(input(f"Your turn: "))
i, j = spotToIdx(inp)
if board[i][j]=='_':
board[i][j] = turn
turn = p1
spotsLeft-=1
else: return stat
printBoard(board)
In the above code
spotsLeft is the empty places on board,
checkGameOver returns "X" (if Player X wins), returns "O" (if Player O wins) & returns "T" (if game is tied)
checkGameOver function ->
def checkWin(board):
# Check Row
for i in board:
if len(set(i)) == 1 and i[0]!='_':
return i[0]
# Check Column
for i in range(4):
if (board[0][i] == board[1][i] == board[2][i] == board[3][i]) and board[0][i]!='_':
return board[0][i]
# Check Diagonals
if (board[0][0]==board[1][1]==board[2][2]==board[3][3]) and board[0][0]!='_':
return board[0][0]
if (board[0][3]==board[1][2]==board[2][1]==board[3][0]) and board[0][3]!='_':
return board[0][3]
# No One Won Yet
return -1
def checkGameOver(board, spotsLeft):
# t -> Game Tied
# x -> Player X won
# o -> Player O won
# if tied - all spots filled
if spotsLeft == 0:
return True, 'T'
# if any player won the game
result = checkWin(board)
if result!=-1:
return True, result
return False, -1
I think your code is fine, and doing what you want it to do. The issue is most likely due to the complexity of the problem, and for a lower dimension tic tac toe it would work fine.
Let's first simplify and look at a 2x2 case. For the first turn, you call minimax from the ai function, which will at the first level call itself 4 times. At the next level, each one of those calls will also call minimax, but one time less than at the previous level. To put it as a list:
level 0 (ai function): 1 call to minimax
level 1: 4 calls
level 2: 4x3 calls (each of the 4 calls above make 3 new calls)
level 3: 4x3x2 calls
level 4: 4x3x2x1 calls
Now using n-factorial notation as n!, we can compute the total number of calls as:
4!/4! + 4!/(4-1)! + 4!/(4-2)! + 4!/(4-3)! + 4!/(4-4!)
Which is the sum of n!/(n-k)! for k between 0 and n (included), n begin the number of cells on your board. The result here is 65 calls to minimax for a 2x2 board.
Put into a python code:
def factorial(n):
if n > 1: return n*factorial(n-1)
else: return 1
def tictactoeComplexity(gridsize):
return sum([factorial(gridsize)/factorial(gridsize-k) for k in range(gridsize+1)])
print(tictactoeComplexity(2*2)) # result is 65
Now let's check for a 3*3 board:
print(tictactoeComplexity(3*3)) # result is 986,410
Just going from 2x2 to 3x3, we go from around 100 to around 1,000,000. You can guess the result for a 4x4 board:
print(tictactoeComplexity(4*4)) # result is 56,874,039,553,217
So your program does what you are asking it to do, but you're asking quite a lot.
As Jenny has very well explained, the search tree is too large. Even if you would make improvements to the data structure and move-mechanics to make them more efficient and reduce the memory footprint, it will still be a challenge to have this search finish within an acceptable time.
In general you would go for the following measures to cut down on the search tree:
Stop at a certain search depth (like 4) and perform a static evaluation of the board instead. This evaluation will be a heuristic. For instance it will give a high value to a 3-in-a-row with a free cell available to complete it to a win. It would also give a significant value to a pair on a line that is not blocked by the opponent, ...etc.
Use alpha-beta pruning to avoid searching in branches that could never lead to a better choice at an earlier stage in the search tree.
Use killer moves: a move that was found good after a certain opponent move, could also turn out to be good in reply to another opponent move. And trying that one first may work well in combination with alpha-beta pruning.
Memoize positions that were already evaluated (by swapping of moves), and mirror positions to equivalent positions to reduce the size of that dictionary.
But to be honest, 4x4 Tic Tac Toe is quite boring: it is very easy to play a draw, and it requires really inferior moves for a player to give the game away to the other. For instance, neither player can make a bad first move. Whatever they play on their first move, it will still be a draw with correct play.
So... I would propose to only use a heuristic evaluation function, and not search at all. Or, perform a shallow minimax, and use such a heuristic evaluation at that fixed depth. But even replacing the minimax algorithm with just an evaluation function works quite well.
What to change
To implement that idea, proceed as follows:
Replace the AI part with this:
if turn == p1:
# AI -- a heuristic based approach
bestScore = -math.inf
bestMove = None
for i in range(4):
for j in range(4):
if board[i][j] == '_':
board[i][j] = turn
score = evaluate(board, turn)
if score > bestScore:
bestScore = score
bestMove = (i, j)
board[i][j] = '_'
i, j = bestMove
board[i][j] = turn
turn = p2
spotsLeft-=1
This calls evaluate which will give a numeric score that is higher the better it is for the given player (argument).
Add the definition for evaluate:
# winning lines
lines = [
[(i, j) for i in range(4)] for j in range(4)
] + [
[(j, i) for i in range(4)] for j in range(4)
] + [
[(i, i) for i in range(4)],
[(3-i, i) for i in range(4)]
]
def evaluate(board, player):
score = 0
for line in lines:
discs = [board[i][j] for i, j in line]
# The more discs in one line the better
value = [1000, 10, 6, 1, 0][sum(ch == "_" for ch in discs)]
if "X" in discs:
if not "O" in discs: # X could still win in this line
score += value
elif "O" in discs: # O could still win in this line
score -= value
# Change the sign depending on which player has just played
return score if player == "X" else -score
That's it! Optionally you can use the evaluate function to simplify the checkWin function:
def checkWin(board):
score = evaluate(board, "X")
if abs(score) > 500: # Take into account there could be some reduction
return "X" if score > 0 else "O"
# No One Won Yet
return -1
With this implementation you don't need the minimax function anymore, but you might want to keep it, and limit the search depth. When that depth is reached, make the call to evaluate, ...etc. Still I found the above implementation to play fine. You can't win a game against it.

FibFrog Codility Problem - Optimising for Performance

I'm trying to solve the FibFrog Codility problem and I came up with the following approach:
If len(A) is 0 we know we can reach the other side in one jump.
If len(A) + 1 is a fibonacci number, we can also reach it in one jump.
Else, we loop through A, and for the positions we can reach, we check if we can either reach them directly from -1 using a fibonacci number (idx + 1 in fibonaccis) or if we can reach them by first jumping to another position (reachables) and then jumping to the current position. In either case, we also check if we can go from the current position to the end of the river - if we can, then we can return because we found the minimum number of steps required.
Finally, if unreachable is True once this loop completes, this means we can't reach any position using a Fibonacci number, so we return -1.
I'm getting 83% correctness and 0% performance with this approach.
I understand the solution is O(n^2), assuming the array consists of only 1, the nested loop for v in reachables: would run n times - however I'm not sure how else I can compute this, since for each of the positions I need to check whether we can reach it from the start of the array, or from any previous positions using a fibonacci number.
def solution(A):
if len(A) == 0: return 1
fibonaccis = fibonacci(len(A) + 3)
if len(A) + 1 in fibonaccis: return 1
leaves = [0] * len(A)
unreachable = True
reachables = []
for idx, val in enumerate(A):
if val == 1:
if idx + 1 in fibonaccis:
unreachable = False
leaves[idx] = 1
if len(A) - idx in fibonaccis:
return 2
reachables.append(idx)
elif len(reachables) > 0:
for v in reachables:
if idx - v in fibonaccis:
leaves[idx] = leaves[v] + 1
if len(A) - idx in fibonaccis:
return leaves[v] + 2
reachables.append(idx)
break
if unreachable: return -1
if len(A) - reachables[-1] in fibonaccis:
return leaves[reachables[-1]] + 1
def fibonacci(N):
arr = [0] * N
arr[1] = 1
for i in range(2, N):
arr[i] = arr[i-1] + arr[i-2]
return arr
Some suggestions for improving performance of your algorithm -
If len(A) = 100000, you are calculating 100003 fibonacci numbers, while we only need fibonacci numbers which are less than 100k, which would be <30 of them.
Your solution is O(n^4), since each X in reachables or Y in fibonaccis operation is O(N) where N is len(A). (and length of fibonaccis being N because of above issue)
Since you are doing a lot of item in list operations on fibonaccis and reachables, consider making it a set or a dictionary for faster(O(1) instead of O(n)) lookup.
Even with the above changes, the algorithm would be O(N^2) because of nested looping across A and reachables, so you need to come up with a better approach.
With your existing implementation, you need to traverse through all the paths and then in the end you will get the smallest number of jumps.
Instead of this approach, if you start at 0, and then keep a count of the number of jumps you have taken so far, and maintain how far(and to which numbers) you can reach after each jump then you can easily find the minimum jumps required to reach the end. (this will also save on redundant work you would have to do in case you have all 1s in A.
e.g. for
A = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
fibonacci = set(1, 2, 3, 5)
At first jump, we can reach following 1-based indexes -
reachable = [1, 2, 3, 5]
jumps = 1
After second jump
reachables = [2, 3, 4, 5, 6, 7, 8]
jumps = 2
After third jump
reachables = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
jumps = 3
so you have reached the end(10) after 3 jumps.
Please check out #nialloc's answer here - https://stackoverflow.com/a/64558623/8677071 which seems to be doing something similar.
Check out also my solution, which scores 100% on Codility tests and is easy to comprehend.
The idea is to track all possible positions of the frog after k jumps. If possible position == n, return k.
def fib_up_to(n):
numbers = [1]
i = 1
while True:
new_num = (numbers[-2] + numbers[-1]) if i > 1 else 2
if new_num > n:
break
numbers.append(new_num)
i += 1
return numbers
def solution(A):
n = len(A)
if n == 0:
return 1
numbers = fib_up_to(n+1)
possible_positions = set([-1])
for k in range(1, n+1):
positions_after_k = set()
for pos in possible_positions:
for jump in numbers:
if pos + jump == n:
return k
if pos + jump < n and A[pos + jump]:
positions_after_k.add(pos + jump)
possible_positions = positions_after_k
return -1

NQueens - Recursive BackTracking Question

I'm currently learning BackTracking algorithms with Python and the first question everyone typically starts with is NQueens. NQueens is where you take a board of size N x N and you have to determine where to place N queens, in such an order they are not attacked by any other queen.
Example:
N = 5
['Q', 0, 0, 0, 0]
[0, 0, 'Q', 0, 0]
[0, 0, 0, 0, 'Q']
[0, 'Q', 0, 0, 0]
[0, 0, 0, 'Q', 0]
Currently, my algorithm returns ALL Possible Solutions. How do I produce ONE outcome. For instance, when N = 8, there are 92 optimal outcomes, how do I just return One Outcome instead of printing 92 separate Outcomes.
#This is our main recursive function that will determine optimal outcome
def NQueens(row,Current,N):
#this tells us when to stop
if row == N:
print("\n")
return printBoard(Current)
for choice in range(0,N):
if isValid(row,choice,Current,N):
#Place Queen in appropriate spot
Current[row][choice] = "Q"
#Go to next row
NQueens(row+1,Current,N)
Current[row][choice] = 0
return "All Solutions Found"
#This function determines whether we can put a Queen in a certain orientation on the board
def isValid(row,col,Current,N):
#check whether current state of game has queen in row/column
for i in range(0,N):
#checks column/row and sees whether a queen exists already
if Current[row][i] == "Q" or Current[i][col] == "Q":
return False
# Check upper diagonal on right side
i = row-1
j = col + 1
#Do while row is greater than 0 and column is less than N
while i >= 0 and j < N:
if Current[i][j] == "Q":
return False
i -= 1
j += 1
# Check upper diagonal on left side
#Do while row is greater than 0 and column is greater than N
i = row-1
j = col - 1
while i >= 0 and j >= 0:
if Current[i][j] == "Q":
return False
i -= 1
j -= 1
#If we pass the diagonal/row/column tests, we can then determine this is a valid move
return True
###############################################################################################################
#These functions deal with board creation and printing them in a proper format
def generateBoard(N):
#generate board based on User Input N in Main()
Board = [[0 for i in range(0,N)] for j in range(0,N)]
return Board
def printBoard(arr):
#Loop through each row to print an organized board
for row in arr:
print(row)
def main():
#ask number from user
print("What sized board would you like?"
" Please input a number higher that 3: ")
#user input used to determine size of board
N = int(input())
#generate Board that will be used
Board = generateBoard(N)
#this is the current status of the board
printBoard(Board)
print("\n")
#Runs Algorithm
print(NQueens(0,Board,N))
if __name__ == "__main__":
main()
NQueens needs to communicate to its caller that a solution has been found; a simple return True will do if that's the case. I made the following changes to your code (your previous lines are commented):
def NQueens(row,Current,N):
#this tells us when to stop
if row == N:
print("\n")
#return printBoard(Current)
return True # found a solution, say so
for choice in range(0,N):
if isValid(row,choice,Current,N):
#Place Queen in appropriate spot
Current[row][choice] = "Q"
#Go to next row
# NQueens(row+1,Current,N)
if NQueens(row+1,Current,N): # recursive call found a solution, let's stop
return True
Current[row][choice] = 0
#return "All Solutions Found"
And in main():
# ...
#Runs Algorithm
#print(NQueens(0,Board,N))
if NQueens(0,Board,N):
printBoard(Board) # found a solution, print it
I have fixed your code to have well-perfoming:
I merge this code https://www.geeksforgeeks.org/n-queen-problem-backtracking-3/ and yours to get it.
count = 1
def printOut(arrayMap):
global count
print('{}: -'.format(count))
count += 1
for i in range(0, len(arrayMap)):
print(arrayMap[i])
# This is our main recursive function that will determine optimal outcome
def NQueens(currentRow, arrayMap, matrixSize):
# this tells us when to stop
if currentRow == matrixSize:
print()
printOut(arrayMap)
return True # found a solution, say so
res = False
for col in range(0, matrixSize):
if isValid(currentRow, col, arrayMap, matrixSize):
# Place Queen in appropriate spot
arrayMap[currentRow][col] = 0
# Go to next row
# NQueens(row+1,Current,N)
res = NQueens(currentRow + 1, arrayMap, matrixSize) or res # recursive call found a solution, let's stop
arrayMap[currentRow][col] = 1
# return "All Solutions Found"
return res
# This function determines whether we can put a Queen in a certain orientation on the board
def isValid(row, col, arrayMap, matrixSize):
# check whether current state of game has queen in row/column
for i in range(0, matrixSize):
# checks column/row and sees whether a queen exists already
if arrayMap[row][i] == 0 or arrayMap[i][col] == 0:
return False
# Check upper diagonal on right side
i = row - 1
j = col + 1
# Do while row is greater than 0 and column is less than N
while i >= 0 and j < matrixSize:
if arrayMap[i][j] == 0:
return False
i -= 1
j += 1
# Check upper diagonal on left side
# Do while row is greater than 0 and column is greater than N
i = row - 1
j = col - 1
while i >= 0 and j >= 0:
if arrayMap[i][j] == 0:
return False
i -= 1
j -= 1
# If we pass the diagonal/row/column tests, we can then determine this is a valid move
return True
###############################################################################################################
if __name__ == "__main__":
# ask number from user
print("What sized board would you like?")
N = int(input('Choose your size: '))
# generate Board that will be used
Board = [[1 for i in range(0, N)] for j in range(0, N)]
count = 0
NQueens(0, Board, N)

Find if valley exists in integer list

A list of integers is said to be a valley if it consists of a sequence of strictly decreasing values followed by a sequence of strictly increasing values. The decreasing and increasing sequences must be of length at least 2. The last value of the decreasing sequence is the first value of the increasing sequence.
Write a Python function valley(l) that takes a list of integers and returns True if l is a valley and False otherwise.
Here are some examples to show how your function should work.
>>> valley([3,2,1,2,3])
True
>>> valley([3,2,1])
False
>>> valley([3,3,2,1,2])
False
I have been sleepless for 2 days and the best i could write is this code
def valley(list):
first =False
second=False
midway=0
if(len(list)<2):
return False
else:
for i in range(0,len(list)):
if(list[i]<list[i+1]):
first=True
midway=i
break
for j in range(midway,len(list)-1):
if(list[j]<list[j+1] and j+1==len(list)):
Second=True
break
if(list[j]>=list[j+1]):
second=False
break
if(first==True and second==True):
return True
else:
return False
The solution i found that also works if the numbers are not in perfect sequence and it is not necessary that the lowest value must be equal to 1, what i'm trying to say is if the list is suppose [14,12,10,5,3,6,7,32,41], here also a valley is formed, as the values are decreasing up to 3(lowest) and then it's again increasing. List's such as [4,3,2,1,2,3,4] is a perfect valley.
Solution:
def valley(lst):
if len(lst)<2:
return False
else:
p = lst.index(min(lst))
for i in range (0,p):
x = (lst[i] > lst[i+1])
for q in range (p,len(lst)-1):
y = (lst[q]< lst[q+1])
return (x==y)
Don't forget to accept it if this solves the problem and is most helpful, thank you.
It seems saurav beat me to the punch, but if you'll allow for some NumPy magic:
import numpy as np
def valley(arr):
diff = arr[:-1] - arr[1:]
gt = np.where(diff > 0)[0]
lt = np.where(diff < 0)[0]
d = np.sum(diff == 0)
if gt.size == 0 or lt.size == 0:
# Doesn't have ascendings or decendings
return False
elif d > 0:
# Has a flat
return False
elif gt[-1] > lt[0]:
# Not strictly one descent into one ascent
return False
else:
return True
a = np.array([3, 2, 1, 2, 3])
b = np.array([3, 3, 2, 1, 2])
c = np.array([3, 2, 1])
d = np.array([1, 2, 3, 2, 1])
print(valley(a), valley(b), valley(c), valley(d))
>>> True False False False
You can also use plain old Python builtins to do it:
def valley(arr):
diff = [i1-i2 for i1, i2 in zip(arr, arr[1:])]
gt = [i for i, item in enumerate(diff) if item > 0]
lt = [i for i, item in enumerate(diff) if item < 0]
d = sum([True for d in diff if d == 0])
if len(gt) == 0 or len(lt) == 0:
# Doesn't have ascendings or decendings
return False
elif d > 0:
# Has a flat
return False
elif gt[-1] > lt[0]:
# Not strictly one descent into one ascent
return False
else:
return True
a = [3, 2, 1, 2, 3]
print(valley(a), ...)
>>> True False False False
Actually I did not want to send a complete solution but I just wanted to solve and for the first, and hopefully last, time I'm posting a solution for a task.
Here is my solution, of course there may be other solutions this is the first one my fingers typed.
def valley(heights):
directions = []
# Check input
if not heights:
return False
# Traverse array and compare current height with previous one
# if we are going down or up.
pre = heights[0]
for h in heights[1:]:
if h > pre:
# If we are going upward add 1
directions.append(1)
elif h < pre:
# If we are going downward add -1
directions.append(-1)
pre = h
# We have many -1s and 1s in out directions list.
# However, if it is a valley then it should first down and up
# which is [-1, 1]. Return the comparison result with [-1, 1]
return set(directions) == set([-1, 1])
The result of variable n in valley function, is the pairwise difference of the numbers in the input list, so if
input = [3,2,1,2,3]
n = [-1, -1, 1, 1]
Now the next variable h is, again pairwise difference of the n, so h will be
h = ['0', '2', '0']
So, every time you will have a valley, you just have to check the pattern "020". Use re module in python to do so,
import re
def valley(f):
n = [j-i for i, j in zip(f[:-1], f[1:])]
h = [str(j-i) for i, j in zip(n[:-1], n[1:])]
result = "".join(h)
m = re.search('020', result)
if m:
return True
else:
return False
Please let me know if its correct or not.

Tic Tac Toe diagonal check

I'm building a game of Tic-Tac-Toe, and I have a vertical and horizontal check that look like this:
def check_win_left_vert (board):
win = True
x = 0
for y in range (2):
if board[y][x] != board[y+1][x]:
win = False
return win
It looks through the board by incrementing the y axis; I use the same method for the x axis. How would I do this for a diagonal axis? Would I increment both?
You would use the same variable for both on one diagonal, and "2 invert" it on the other:
for x in range(2):
if board[x][x] ...
for x in range(2):
if board[x][2-x] ...
Note that you have to watch out for boundary conditions. I strongly suspect that you haven't bothered to test your horizontal code yet, as it tries to check a space off the right edge of the board. Reduce the range to fix that.
You need to make in the same loop check for diagonal case
if board[y][y] != board[y+1][y+1] or board[2-y][y] != board[1-y][1+y]:
win = False
if win == False:
break;
All checks with list comprehensions
game_board = [ [1, 0, 1],
[0, 1, 0],
[0, 1, 0] ]
# Horizontals
h = [str(i+1) + ' Row' for i, v in enumerate(game_board) if sum(v) == 3]
# Verticals
v = [str(i+1) + ' Col' for i in range(3) if sum([j[i] for j in game_board]) == 3]
# Diagonals
d = [['Left Diag', '','Right Diag'][i+1] for i in [-1, 1] if sum([game_board[0][1+i], game_board[1][1]], game_board[2][1-i]) == 3]
if any([h,v,d]):
print('You won on:', h, v, d)
else:
print('No win yet')

Categories

Resources