# TicTacToe: If an error messages do not appear straight away

I am fairly new to Python and have tried to create a tic tac toe game that is completely automatic, where you can see if a player1 has an advantage if you let it start in the middle and I would love some feedback on it and improvement areas. That is, is it something that looks crazy, something that I do wrong or something that I should clarify regarding the structure and coding?

I ask because I'm so new to python that I have a hard time seeing where I should settle and where I should spend more time improving the code. I will also address where I think my code is weak below and would love to hear your input on this comment.

Briefly about how it works is that you start by calling main() and decide how many games you would like to play by answering the first question, then you have to decide how big playing surface you would like out of 3, 5 and 7 by answering the second question and last you pick your scenario (1 or 2) by answering the last question. 1 is the scenario when all moves are automatic and 2 is the scenario where player1 put its first move in the middle. Then you will get a plot with the statistic from all game rounds.

My biggest concern about it is that the error messages if you call the wrong value to board_size, games or scenario appears after you have answered all the questions and not immediately - what do you think about that? I have tried to specify the error messages to compensate that though I didn't find a way to have them appear straight away.

import numpy as np
import random
import matplotlib.pyplot as plt

# creating an empty board
def create_board(board_size):
return np.zeros((board_size,board_size), dtype=int)

def find_placement_scenario_one(board):

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 random_placeout_scenario_one(board, player):
selection = find_placement_scenario_one(board)
current_loc = random.choice(selection)
board[current_loc] = player
return(board)

def find_placement_scenario_two(board, board_size):
l=[]

for i in range(len(board)):
for j in range(len(board)):

if board_size == 3:
if board[i][j] == 0:
l.append((1, 1))
if board_size == 5:
if board[i][j] == 0:
l.append((2, 2))
if board_size == 7:
if board[i][j] == 0:
l.append((3, 3))
return(l)

def placeout_scenario_two(board, player, board_size):

player = 1
selection = find_placement_scenario_two(board, board_size)
current_loc = random.choice(selection)
board[current_loc] = player
return(board)

def check_row(board, player):
for x in range(len(board)):
win = True

for y in range(len(board)):
if board[x, y] != player:
win = False
continue

if win == True:
return(win)
return(win)

def check_column(board, player):
for x in range(len(board)):
win = True

for y in range(len(board)):
if board[y][x] != player:
win = False
continue

if win == True:
return(win)
return(win)

def check_diagonal(board, player):
win = True
y = 0
for x in range(len(board)):
if board[x, x] != player:
win = False
if win:
return win
win = True
if win:
for x in range(len(board)):
y = len(board) - 1 - x
if board[x, y] != player:
win = False
return win

def evaluate(board):
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 choose_scenario(scenario, board_size):

if board_size ==3 or board_size == 5 or board_size == 7:
if scenario == 1 or scenario == 2:

board, winner, counter = create_board(board_size), 0, 1

if scenario == 1:

while winner == 0:
for player in [1, 2]:
board = random_placeout_scenario_one(board, player)
counter += 1
winner = evaluate(board)
if winner != 0:
break
return(winner)

board, winner, counter = create_board(board_size), 0, 1

if scenario == 2:

board, winner, counter = create_board(board_size), 0, 1

while winner == 0:
for player in [2, 1]:
board = placeout_scenario_two(board, player, board_size)
board = random_placeout_scenario_one(board, player)
counter += 1
winner = evaluate(board)
if winner != 0:
break
return(winner)

else:
print('Playing surface does not exist, try again')

def save_stats(games, scenario, board_size):

player1wins=0
player2wins=0
ties=0

for game in range(games):
result=choose_scenario(scenario, board_size)
if result==-1: ties+=1
elif result==1: player1wins+=1
else: player2wins+=1

return [player1wins, player2wins, ties] # for returning

def print_and_save_stats(games, scenario, board_size):

if board_size ==3 or board_size == 5 or board_size == 7:
if scenario == 1 or scenario == 2:

player1wins, player2wins, ties = save_stats(games, scenario, board_size)

print('Player 1 wins:',player1wins)
print('Player 2 wins:',player2wins)
print('Tie:',ties)

# Fake dataset
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))

# Add title and axis names
plt.title('My title')
plt.xlabel('')
plt.ylabel('')

# Limits for the Y axis
plt.ylim(0,games)

# Create names
plt.xticks(y_pos, bars)

# Show graphic
plt.show()

else:
print('Scenario does not exist')

def main():

try:

games = int(input("How many games do you want to simulate? "))
board_size = int(input("How big playing surface (3/5/7)? "))
scenario = int(input('What scenario (1/2)? '))

choose_scenario(scenario, board_size)

print_and_save_stats(games, scenario, board_size)

except ValueError:
print('You have to answer all the question to start the game, try again')

• Welcome to Code Review! The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How do I ask a good question?, as well as How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. Jan 9 at 14:35
• Thank you @SᴀᴍOnᴇᴌᴀ, I have now edited the question title and I hope it clarifies what I'm asking for and adheres to the standard of the site.
– Ida
Jan 9 at 17:14

This looks good.

# creating an empty board
def create_board(board_size):
return np.zeros((board_size,board_size), dtype=int)


find_placement_scenario_one is both a noisy name and could be simplified. I'd write it like this.

def available_spaces(board):
return np.argwhere(board==0)


random_placeout_scenario_one is another odd name. The return statement is also odd, as you are mutating the original board anyway. I'd leave out the return, as this makes it very clear the that the original board is being modified.

def make_random_move(board, player):
spaces = available_spaces(board)
space = random.choice(spaces)
board[space] = player


The row and diagonal checks could be simplified with numpy.

def is_winning_by_row(board, player):
return any(
all(row == player)
for row in board
)

def is_winning_by_col(board, player):
return is_winning_by_row(board.transpose(), player)

def is_winning_by_diag(board, player):
return any(
all(np.diag(board_rotation) == player)
for board_rotation in [board, np.fliplr(board)]
)

def is_winning(board, player):
return (
is_winning_by_row(board, player) or
is_winning_by_col(board, player) or
is_winning_by_diag(board, player)
)


You could reduce mutation in save_stats().

def save_stats(games, scenario, board_size):
game_results = [
play_game(scenario, board_size) # formerly choose_scenario
for _ in range(games)
]

player1wins = game_results.count(1)
player2wins = game_results.count(2)
ties = game_results.count(-1)

return player1wins, player2wins, ties


# Other

The board size checks are silly. If there is an invalid size, raise an exception as early as possible. Checking this criteria multiple times introduces noise into your code.

Also your random placement for situation 2 appears to only ever place moves in the middle, seems pretty broken.