Determine three in a row regardless of the board size - python

I have programmed a program of tic tac toe in python where I use the following code to determine if a row / column / diagonal consists of only ones or twos to determine if any of player one or two have won (I use ones and twos instead of x and o). The problem is that I want the winner to be chosen when there are three in a row regardless of the size of the board and with this code this only works for a board of size 3x3.
def check_row(board, player):
'''
Checks row for a win
'''
return any(all(row == player)
for row in board)
def check_column(board, player):
'''
Checks column for a win
'''
# We use transpose() to be able to check the column by using the definition
# for check_row
return check_row(board.transpose(), player)
def check_diagonal(board, player):
'''
Checks diagonal for a win
'''
# We use np.diag() and np.fliplr() to be able to check the diagonals
# of the game board
return any(all(np.diag(board_rotation) == player)
for board_rotation in [board, np.fliplr(board)])
For example, if I choose a board with size 5x5, the winner will be, with this code, when it is five in a row and not three in a row. I use np.zeros ((board_size, board_size), dtype = int) to create a game board and I have considered whether you can use something like column_1 == column_2 == column_3 to determine if there are three in a row:
if board[1][1] != 0:
if board[1][1] == board[0][0] == board[2][2]:
return board[1][1]
elif board[1][1] == board[0][2] == board[2][0]:
return board[1][1]
But also for this to work in a 5x5 game board I have to write down all possible ways to get three in a row in a 5x5 board which will give an extremely long code. Any tips to improve this?
This is my fully code now:
''' 403-0047-MPO '''
import numpy as np
import random
import matplotlib.pyplot as plt
# scenario = What scenario you would like to play, scenario one or scenario two
# game_size = The size of game board, 3x3, 5x5 or 7x7
# games = The number of game rounds you would like to play
# board = The game board
def create_board(board_size):
'''
Creating an empty board
'''
return np.zeros((board_size,board_size), dtype=int)
def available_spaces(board):
'''
Looking for available spaces on the game board
This is used in both scenario one and two
'''
lst = []
for i in range(len(board)):
for j in range(len(board)):
if board[i][j] == 0:
lst.append((i, j))
return(lst)
def make_random_move(board, player):
'''
Randomly places the player's marker on the game board
This is used in both scenario one and two
'''
spaces = available_spaces(board)
space = random.choice(spaces)
board[space] = player
return board
def available_space_middle(board, board_size):
'''
Checks that the center position is available for all game boards
This is only used in scenario two
'''
# Using if-statements to find the middle for all board_size ∈ {3, 5, 7}
# and check that they are empty, this also avoids placing the marker there repeatedly.
lst = []
for i in range(len(board)):
for j in range(len(board)):
if board_size == 3:
if board[i][j] == 0:
lst.append((1, 1))
if board_size == 5:
if board[i][j] == 0:
lst.append((2, 2))
if board_size == 7:
if board[i][j] == 0:
lst.append((3, 3))
return(lst)
def make_move_middle(board, player, board_size):
'''
Places the first marker for player one in the center of the game board.
This is only used in scenario two
'''
# Using player = 1 we define that the first move in scenario two should be
# made by player one using the marker 1
player = 1
selection = available_space_middle(board, board_size)
current_loc = random.choice(selection)
board[current_loc] = player
return board
def check_row(board, player):
'''
Checks row for a win
'''
return any(all(row == player)
for row in board)
def check_column(board, player):
'''
Checks column for a win
'''
# We use transpose() to be able to check the column by using the definition
# for check_row
return check_row(board.transpose(), player)
def check_diagonal(board, player):
'''
Checks diagonal for a win
'''
# We use np.diag() and np.fliplr() to be able to check the diagonals
# of the game board
return any(all(np.diag(board_rotation) == player)
for board_rotation in [board, np.fliplr(board)])
def evaluate(board):
'''
Evaluates the game board for a winner
'''
# Here we bring out the winner and define 1 for when player one wins,
# 2 for when player two wins and -1 for ties.
winner = 0
for player in [1, 2]:
if (check_row(board, player) or
check_column(board,player) or
check_diagonal(board,player)):
winner = player
if np.all(board != 0) and winner == 0:
winner = -1
return winner
def play_game(scenario, board_size):
'''
Defines the game for scenario one and scenario two
'''
# By using if-statements and a while-loop we define how the game should
# go in scenario one and scenario two respectively.
# By using break, we also ensure that the loop (game) ends if a winner has been found.
board, winner, counter = create_board(board_size), 0, 1
if scenario == 1:
while winner == 0:
for player in [1, 2]:
board = make_random_move(board, player)
# Remove the hashtags below to check the game board step by step
print("Board after " + str(counter) + " move")
print(board)
counter += 1
winner = evaluate(board)
if winner != 0:
break
return winner
if scenario == 2:
while winner == 0:
for player in [2, 1]:
board = make_move_middle(board, player, board_size)
board = make_random_move(board, player)
# Remove the hashtags below to check the game board step by step
print("Board after " + str(counter) + " move")
print(board)
counter += 1
winner = evaluate(board)
if winner != 0:
break
return winner
def save_stats(games, scenario, board_size):
'''
Saves data from all game rounds
'''
# By using the previously defined 1,2 and -1 we add one point for each win
# for player1wins, player2wins and ties.
player1wins=0
player2wins=0
ties=0
for game in range(games):
result=play_game(scenario, board_size)
if result==-1: ties+=1
elif result==1: player1wins+=1
else: player2wins+=1
return [player1wins, player2wins, ties] # for returning
def print_stats(games, scenario, board_size):
'''
Presents data from all game rounds
'''
player1wins, player2wins, ties = save_stats(games, scenario, board_size)
print('Player 1 wins:',player1wins)
print('Player 2 wins:',player2wins)
print('Tie:',ties)
# Data
height = [player1wins, player2wins, ties]
bars = ('Player 1', 'Player 2', 'Tie')
y_pos = np.arange(len(bars))
# Create bars and choose color
plt.bar(y_pos, height, color = (0.5,0.1,0.5,0.6))
# Limits for the Y axis
plt.ylim(0,games)
# Create names
plt.xticks(y_pos, bars)
# Saves a pdf with data
plt.savefig('utfall.pdf') # Saves the data as 'utfall.pdf'
plt.close()
def main():
'''
This is the main body of the program
'''
# Using try: and except: to raise ValueError and
# while-loops to handle incorrect input. The question will be asked again
# if the input value is wrong and continue via break if it is correct.
try:
while True:
games = int(input("How many games do you want to simulate? "))
if games <1:
print('The number of game rounds should be grater than zero, try again')
else:
break
while True:
board_size = int(input("How big playing surface (3/5/7)? "))
if not (board_size == 3 or board_size == 5 or board_size == 7):
print('Board size does not exist, try again')
else:
break
while True:
scenario = int(input('What scenario (1/2)? '))
if not (scenario == 1 or scenario == 2):
print('Scenario does not exist, try again')
else:
break
print_stats(games, scenario, board_size)
except ValueError:
print('All questions must be answered correctly for the game to start, try again')
main()

I will post a idea using numpy.split
You split your board in chunks of 3, but starting in different positions: 0, 1 and 2.
Explanation: Consider [a,b,c,d,f,g]
Starting from 0 -> [a,b,c], [d,f,g]
Starting from 1 -> [b,c,d]
Starting from 2 -> [c,d,f]
so, all possibilities are there
You check if the elements are equal 1 or 2 and sum over the boolean mask. If the sum is 3, then there is a chunk with 3 equal elements.
Example code to search from winners on the rows:
import numpy as np
#random array just for test
board = np.array([[1,-1,-1,-1,1,0],
[1,1,0,1,1,0],
[1,1,0,1,1,0],
[1,1,0,1,1,0],
[1,1,0,1,1,0]])
winner = None
#iterate on i to start from position 0, 1 and 2
for i in range(3):
#We want the lengh of the array a multiple of 3, so we must correct were it ends:
rest = -(board[:,i:].shape[1]%3)
if rest == 0:
#0 would return a empty list, so if it is the case, we will change to None
rest = None
#Now, we calculate how manny divisions we need to get chunks of 3 (on the rows)
n = (board[:,i:].shape[1])//3
#Now divide the board in chunks
chunks = np.split(board[:,i:rest],n,axis = 1)
for matrix in chunks:
#sum over all rows of 3 elements
sumation = np.sum(matrix == 1,axis = 1)
if 3 in sumation:
winner = 1
sumation = np.sum(matrix == 2,axis = 1)
if 3 in sumation:
winner = 2
Well, maybe a little confusing, but i believe it will work, or at least give you some idea

Related

What is the best way to generate random ships in an array in Python?

Recently, I've been working on a battleship game for my CS2 class. The focus of this project is to create a board game using arrays, and I decided to create a battleship. I have most of the code, but I cannot figure out how to get ships to get randomly generated on a 10x10 array without the ships being...
A. The wrong length
B. Looping around the array
Below is the function I currently have.
def createShips(board):
shipLen = [5,4,3,3,2]
shipAvalible = 5
directionposibilities = ["vertical", "horizontal"]
j = 0
for i in range(shipAvalible):
boatMade = False
#REGULAR VAR STATMENTS
direction = random.choice(directionposibilities)
col = randint(0,9)
row = randint(0,9)
while boatMade == False:
if direction == "vertical":
buildCount = 0
if col + int(shipLen[i]) <= 11:
colission = False
for i in range(0, int(shipLen[i])):
buildCount += 1
if board[int(row-i)][int(col)-1] == "X":
if colission:
pass
else:
colission = True
if colission:
col = randint(0,9)
row = randint(0,9)
else:
for j in range(buildCount):
board[int(row-j)][int(col)-1] = "X"
boatMade = True
else:
col = randint(0,9)
row = randint(0,9)
if direction == "horizontal":
if col + int(shipLen[i]) <= 10:
colission = False
buildCount = 0
for i in range(0, int(shipLen[i])):
buildCount += 1
if board[int(row)][int(col)+i-1] == "X":
if colission:
pass
else:
colission = True
if colission:
col = randint(0,9)
row = randint(0,9)
else:
for j in range(buildCount):
board[int(row)][int(col)+j-1] = "X"
boatMade = True
else:
col = randint(0,9)
row = randint(0,9)
shipAvalible = shipAvalible - 1
return(board)
board = [["■"] * 10 for x in range(10)]
print(createShips(board))
If you have any idea why this may not work please let me know!
P.S. I am using another function that prints the array nicely, if you would like that for convenience, it is seen below:
def showBoard(board):
print(" A B C D E F G H I J")
print(" ------------------------------")
rownumb = 1
for r in board:
if rownumb == 10:
space = ""
else:
space = " "
print("%d|%s|" % (rownumb, space + "|".join(r)))
rownumb += 1
Edit: #Eumel was a bit faster and said it better here
First of all, you have a inner and outer loop with the variable i, which might not actually interfere but it makes it harder to read the code. Consider renaming one of the variables.
When you create a vertical ship, you check the col rather than the row for index error, and does so against a value of 11 instead of your board size 10.
In the vertical case you build the ship "backwards" from the selected position, even though you checked that your board could fit the ship in the positive direction (And you place it in a column to the left). These negative values makes it such that your ships can wrap around.
In the horizontal case you are indexing a bit strange again where board[int(row)][int(col)+i-1] this gives that when i=0 you place your ship an index further to the left than intended, again causing the ship to wrap when col=0.
Further more, you could remove a few redundant if statements and duplicate lines of code, by moving:
direction = choice(directionposibilities)
col = randint(0, 9)
row = randint(0, 9)
Inside the while loop.
Ok lets go over this:
length check
if col + int(shipLen[i]) <= 9: since arrays start at 0 in python the maximum index for a 10x10 board is 9, therefore you need to chek against 9 instead of 11
loop variables
for k in range(0, int(shipLen[i])): using the same variable name for the outer and inner loop (while working in this specific case) is really bad form
collision check
if board[row+k][col] == "X": you want to build the ships "forwards" so you use row+k, you can also forego int conversions when you only use integers to begin with
collsion check 2
if colission: pass else: colission = True This whole block can be shortened to collision = True break since you dont need to keep checking for multiple collisions
buildcount
for j in range(buildCount): unless you get a collision (then you wouldnt be in this branch) buildCount is always the same as shipLen[i] so it can be completly removed from your code
boat building
board[row+j][col] = "X": same as before we build forwards
honorable mentions
shipAvailable = len(shipLen) you could also remove this part completly and iterate over your ships directly in your out for loop
Instead of radoomising your direction row and col on every break condition you could randomize it once at the start of your while loop
j = 0 unlike C you dont have to define your variables before using them in a loop, this statement does nothing

TicTacToe code to check the state of the board not working

I made an algorithm to check whether which player won the game or is it a tie or the game is still not finished yet but it doesn't work well with some testcases.
I don't know what's wrong with my code. but it doesn't work so I think somethings off with my algorithm or the code itself.
so my algorithm basically is:
separate the moves of player1 and player 2
check if the moves of player 1 is in the winning moves if it is return 'player1'
check if the moves of player 2 is in the winning moves if it is return 'player2'
if there is no winner check if the number of moves is equal to 9. If it is return 'tie'
else if the number of moves is less than 9 return 'uncertain'
here is the code:
def match(a1,a2):
a1.sort()
for solution in a2:
if a1 == solution:
return True
return False
def ticTacToeWinner(moves, n):
# --- Variables ---
p1 = []
p2 = []
winning_moves = [
[[0,0],[0,1],[0,2]],
[[1,0],[1,1],[1,2]],
[[2,0],[2,1],[2,2]],
[[0,0],[1,0],[2,0]],
[[0,1],[1,1],[2,1]],
[[1,0],[2,1],[3,1]],
[[0,0],[1,1],[2,2]],
[[0,2],[1,1],[2,0]],
]
winner = None
# --- Loops ---
for i in range(1,n+1): # separate the moves for each player
if i % 2 == 0:
p2.append(moves[i-1])
else:
p1.append(moves[i-1])
# --- Conditionals ---
if len(p1) >= 3: # check if the player1 has more than 3 moves
if match(p1,winning_moves) == True: # check if the moves of player1 is in the winning moves
winner = 'player1' # winner is player 1
return winner
if len(p2) >= 3: # check if the player2 has more than 3 moves
if match(p2,winning_moves) == True: # check if the moves of player 2 is in the winning moves
winner = 'player2' # winner is player 2
return winner
if not winner: # there are no winners
if n == 9: # board is full so it's draw
return 'draw'
elif n < 9: # board is not full
return 'uncertain'
This code is my solution for this question:
https://www.codingninjas.com/codestudio/problems/tic-tac-toe-winner_1214545?topList=top-apple-coding-interview-questions
The code only works for 2 testcases there and incorrect for everything else
Example input and output:
ticTacToeWinner([[0,0],[2,2],[0,1],[1,1],[0,2]],5) -> 'player1'
since the moves of player1 is: [[0,0],[0,1],[0,2]] which makes a triple X at the top of the board
The match function at the top is the one that checks whether the move of a player is in the winning moves table.
here is one of the testcases that don't work with the code:
5
9
2 0
0 1
1 1
1 0
1 2
0 0
2 1
2 2
0 2
4
1 2
1 0
2 0
1 1
1
2 0
9
2 0
1 2
0 1
0 0
0 2
2 1
2 2
1 1
1 0
3
0 2
2 0
0 0
These are some issues:
winning_moves has a completely wrong row, which also has a 3 -- which is an invalid index.
Change:
[[1,0],[2,1],[3,1]],
to:
[[0,2],[1,2],[2,2]],
When len(p1) is greater than 3, you'll never find a win, because winning_moves does not have entries that have this size. Yet it is obvious that there could be a win
Even when len(p1) is equal to 3, you may still miss a win, because the order of the moves may not be like they are in winning_moves.
Not a problem, but elif n < 9 can just be else, as there are no other possible values for n.
You should really take a different approach: play the moves on a 3x3 board (a 2D list), and then use winning_moves to see what is at those winning positions. If it turns out they are all 3 a move of player1, you know that player1 won the game.
So here is what your function should look like:
def ticTacToeWinner(moves, n):
winning_moves = [
[[0,0],[0,1],[0,2]],
[[1,0],[1,1],[1,2]],
[[2,0],[2,1],[2,2]],
[[0,0],[1,0],[2,0]],
[[0,1],[1,1],[2,1]],
[[0,2],[1,2],[2,2]], # was wrong
[[0,0],[1,1],[2,2]],
[[0,2],[1,1],[2,0]],
]
# create a board:
board = [
[".", ".", "."],
[".", ".", "."],
[".", ".", "."],
]
# play the moves on the board:
for i, (row, col) in enumerate(moves):
board[row][col] = "12"[i % 2] # store the player's number in this cell
# check who has played on the winning patterns
for winning_line in winning_moves:
played = [board[row][col] for row, col in winning_line]
if played[0] != "." and played[0] == played[1] == played[2]:
return "player" + played[0]
if n == 9: # board is full so it's draw
return 'draw'
else: # board is not full
return 'uncertain'
I'm pretty sure that one of your problems is that you don't handle the case of more than 3 moves...
I'd advise to change the type of a single "move" to a tuple, which is hashable. then, you'd be able to use this elegant match code:
def match(player_moves_list: List[tuple], winning_moves:List[List[tuple]]):
for solution in winning_moves:
if set(solution).issubset(player_moves):
return True
return False
to change the list elements to tuple elements, simply turn brackets to parenthesis:
winning_moves = [
[(0,0),(0,1),(0,2)],
...,
[(0,2),(1,1),(2,0)],
]

Generate all possible board positions of tic tac toe

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

Python: List not searching diagonally

I am implementing a Python version of the game Othello / Reversi. However, my algorithm seems to be having trouble when searching in the southwest direction.
Here are some important functions to understand how my current code works:
def _new_game_board(self)->[[str]]:
board = []
for row in range(self.rows):
board.append([])
for col in range(self.columns):
board[-1].append(0)
return board
def _is_valid_position(self, turn:list)->bool:
'''return true if the turn is a valid row and column'''
row = int(turn[0]) - 1
column = int(turn[1]) - 1
if row >= 0:
if row < self.rows:
if column >= 0:
if column < self.columns:
return True
else:
return False
def _is_on_board(self, row:int, col:int)->bool:
'''returns true is coordinate is on the board'''
if row >=0:
if row < self.rows:
if col >=0:
if col < self.columns:
return True
def _searchNorthEast(self)->None:
'''Search the board NorthEast'''
print("NorthEast")
row = self.move_row
column = self.move_column
should_be_flipped = list()
row += 1
column -= 1
if self._is_on_board(row, column):
print("column searching NorthEast on board")
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
while True:
row += 1
column -= 1
if self._is_on_board(row, column):
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
continue
elif self.board[row][column] == self.turn:
self._to_be_flipped.extend(should_be_flipped)
break
else:
break
else:
self._to_be_flipped.extend(should_be_flipped)
else:
pass
def _searchSouthWest(self)->None:
'''Search the board SouthWest'''
print("in SouthWest")
row = self.move_row
column = self.move_column
should_be_flipped = list()
row -= 1
column += 1
if self._is_on_board(row, column):
print("column searching SouthWest on board")
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
while True:
row -= 1
column += 1
if self._is_on_board(row, column):
if self.board[row][column] == self._opponent:
should_be_flipped.append([row, column])
continue
elif self.board[row][column] == self.turn:
self._to_be_flipped.extend(should_be_flipped)
break
else:
break
else:
self._to_be_flipped.extend(should_be_flipped)
else:
pass
def _move_is_valid(self, turn:list)->bool:
'''Verify move is valid'''
self._to_be_flipped = list()
self._opponent = self._get_opposite(self.turn)
if self._is_valid_position(turn):
self.move_row = int(turn[0]) - 1
self.move_column = int(turn[1]) - 1
self._searchRight()
self._searchLeft()
self._searchUp()
self._searchDown()
self._searchNorthWest()
self._searchNorthEast
self._searchSouthEast()
self._searchSouthWest()
if len(self._to_be_flipped) > 0:
return True
else:
return False
Now let's say the current board looks like the following:
. . . .
W W W .
. B B .
. B . .
Turn: B
and the player makes a move to row 1 column 4, it says invalid because it does not detect the white piece in row 2 column 3 to be flipped. All my other functions are written the same way. I can get it to work in every other direction except in this case.
Any ideas why it is not detecting the piece in this diagonal direction?
Don't Repeat Yourself. The _search* methods are extremely redundant which makes it difficult to see that the signs in
row -= 1
column += 1
are correct. Since you've given only two directions (NE, SW) and no documentation of the board orientation, I cannot tell if the signs agree with the board layout or even agree with themselves.
The _search* methods are also too long and should be divided into multiple functions, but that's a secondary concern.
I agree with msw about not repeating stuff. It is tempting to go ahead and do what you can once you see it, but generalizing will save you the headaches of debugging.
Here is some pseudocode that should give the general idea. I might not be able to finagle working with your code, but hopefully this shows how to reduce repetitive code. Note it doesn't matter if -1 is up or down. The board class is simply a 2x2 array of (open square/player 1's piece/player 2's piece,) along with whose turn it is to move.
# x_delta and y_delta are -1/0/1 each based on which of the up to 8 adjacent squares you are checking. Player_num is the player number.
def search_valid(x_candidate, y_candidate, x_delta, y_delta, board, player_num):
y_near = y_candidate + y_delta
x_near = x_candidate + x_delta
if x_near < 0 or x_near >= board_width:
return False
if y_near < 0 or y_near >= board_width:
return False # let's make sure we don't go off the board and throw an exception
if board.pieces[x_candidate+x_delta][y_candidate+y_delta] == 0:
return False #empty square
if board.pieces[x_candidate+x_delta][y_candidate+y_delta] == player_num:
return False #same color piece
return True #if you wanted to detect how many flips, you could use a while loop
Now a succinct function can loop this search_valid to see whether a move is legal or not e.g.
def check_valid_move(x_candidate, y_candidate, board, player_num):
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if not x and not y:
continue # this is not a move. Maybe we don't strictly need this, since board.pieces[x_candidate+x_delta][y_candidate+y_delta] == player_num anyway, but it seems like good form.
if search_valid(x_candidate, y_candidate, dx, dy, board, player_num):
return True
return False
A similar function could actually flip all the opposing pieces, but that's a bit trickier. You'd need a while function inside the for loops. But you would not have to rewrite code for each direction.

How would I find the winner of my Python Tic Tac Toe game?

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.

Categories

Resources