Python Forum
My first python project
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
My first python project
#1
Hello to everyone here. I have been learning python for a few weeks now with the book 'Learn Python 3 the Hard Way' and Jose Portilla's Udemy Complete Python Bootcamp course. The first milestone project for the Bootcamp course is the creation of a Tic Tac Toe game. These are the requirements:

  • 2 players should be able to play the game (both sitting at the same computer).
  • The board should be printed out every time a player makes a move.
  • You should be able to accept input of the player position and then place a symbol on the board
.
I attempted this challenge without looking at the suggestions in the lecture or the solutions, simply trying to figure out how it could be done on my own, then trying to make it better. The code works, but it is very different from the code solution given in the course (which can be found here). Here is my code below. I would be very grateful if someone were willing to make comments on how it could be improved, whether there are any glaring problems or anything of that sort.

player1 = 'X'
player2 = 'O'
#The grid variable defines the spaces that will be printed to make up
#the board. 
grid = [['\t  ', '1', ' ', ' ', ' ', '2', ' ', ' ', ' ', '3', ' ', ' '],
		['\t1 ', '_', '_', '_', '|', '_', '_', '_', '|', '_', '_', '_'], 
		['\t2 ', '_', '_', '_', '|', '_', '_', '_', '|', '_', '_', '_'],
		['\t3 ', ' ', ' ', ' ', '|', ' ', ' ', ' ', '|', ' ', ' ', ' ']]

def print_grid():
	'''takes the grid variable and prints it as a tic tac toe board.'''
	print(''.join(grid[0]), '\n', ''.join(grid[1]), '\n', ''.join(grid[2]), '\n', ''.join(grid[3]))

def play_tic():
	'''This function runs the game play.'''
	player_turn(player1, 'Player one')
	player_turn(player2, 'Player two')
	play_tic()

def player_turn(player, player_name):
	#Allows player to choose a position, places marker in position, checks for winner.
	print(f"\t{player_name}, it's your turn.")
	choice(player)
	print_grid()
	check_winner(player)

def choice(player):
	'''Takes the value returned by the input_request function and places
	an 'X' or an 'O' in the space chosen by the player.'''
	x = input_request('horizontal row')
	y = input_request('vertical column')

	if y == 2:
		y = 6
	elif y == 3:
		y = 10

	if grid[x][y] == 'X' or grid[x][y] == 'O':
		print("\tThat space is taken. Please choose another.")
		choice(player)
	else:
		grid[x][y] = player

def input_request(axis):
	'''Asks player where they would like to position marker, 
	and checks to make sure that the input corresponds to position.'''
	user_input = input(f"\tWhich {axis} do you choose - 1, 2, or 3?")
	while user_input not in ['1', '2', '3']:
		print("\tSorry, but that answer is unacceptable.")
		user_input = input("\tPlease choose a digit - 1, 2, or 3.")
	return int(user_input)

def check_winner(player):
	'''Checks to see if the last move resulted in a winning configuration. 
	###I feel that there must be a way to make this shorter###'''
	draw_list = [grid[1][1], grid[1][6], grid[1][10], grid[2][1], grid[2][6], grid[2][10], grid[3][1], grid[3][6], grid[3][10]]

	if ((grid[1][1] == player and grid[1][6] == player and grid[1][10] == player)
	 	or (grid[2][1] == player and grid[2][6] == player and grid[2][10] == player)
	 	or (grid[3][1] == player and grid[3][6] == player and grid[3][10] == player)
	 	or (grid[1][1] == player and grid[2][1] == player and grid[3][1] == player)
	 	or (grid[1][6] == player and grid[2][6] == player and grid[3][6] == player)
	 	or (grid[1][10] == player and grid[2][10] == player and grid[3][10] == player)
	 	or (grid[1][1] == player and grid[2][6] == player and grid[3][10] == player)
	 	or (grid[1][10] == player and grid[2][6] == player and grid[3][1] == player)
	 	):
		print(f"Congratulations, {player}, you are the winner!!!")
		play_again()
	elif '_' and ' ' not in draw_list:
		print("The game is a draw.")
		play_again()
	else:
		pass

def play_again():
	answer = input("Do you want to play again? Please answer Y or N.")
	#I not clear on what global does here. I'm not sure why I thought this would help my code, but it does.
	# If I remove it, then the grid variable
	#does not reset to the value shown below, but instead remains the same as at the end of the last game play#
	#this works, but it seems unsatisfactory. It seems there must be a better way. 	
	global grid
	if answer.upper() == 'N':
		print("Thank you for playing. Have a nice day!")
		exit()
	elif answer.upper() == 'Y': 
		#I don't like repeating the grid variable assignment here, but I couldn't think of another way
		#to reset the board to begin a second game. Well, I tried, but couldn't think of anything that worked. 
		grid = [['\t  ', '1', ' ', ' ', ' ', '2', ' ', ' ', ' ', '3', ' ', ' '],
				['\t1 ', '_', '_', '_', '|', '_', '_', '_', '|', '_', '_', '_'], 
				['\t2 ', '_', '_', '_', '|', '_', '_', '_', '|', '_', '_', '_'],
				['\t3 ', ' ', ' ', ' ', '|', ' ', ' ', ' ', '|', ' ', ' ', ' ']]
		print_grid()
		play_tic()
	else:
		print("That is not an acceptable answer.")
		play_again()

print("\n\tLet's play tic tac toe.")
print("\tPlayer one will be 'X'.")
print_grid()
play_tic()
Reply
#2
I will not address overall structure of the programm, just small observations. These are ideas how to take advantage of Python built-ins to make your program more concise and avoid repetition. Separating data (board state) and it's display (output of board state to screen) makes things simpler.

Board state.
Simple way is to create initial board state with empty strings and then replace them with actual players choices:

board = [[''] * 3 for i in range(3)]
board[1][1] = 'x'
board[0][0] = 'o'
Board display
To display board while avoiding repetition: construct separator line and lines holding board state:

def display_board(board):
    separator = f'\n{"+".join("-" * 3 for i in range(3))}\n'
    print(*('|'.join(state.center(3) for state in row) for row in board), sep=separator)
With players choices above board display will look like that:

Output:
o | | ---+---+--- | x | ---+---+--- | |
Determine winner
To avoid repetition another approach can be made: board is 3x3 matrice, winning combinations can be in rows, columns, main and secondary diagonal of matrice.

Rows is board itself, columns one can get with zip, and diagonals with indices. So winning state is if all values in one of these locations are identical and are choices of one player:

def stream_locations(board):
    cols = zip(*board)
    main_diag = [board[i][i] for i in range(3)]
    sec_diag = [board[-i][i-1] for i in range(1, 4)]
    return (*board, *cols, main_diag, sec_diag)
We have winner if one of these locations have all same values. There is built-in set() which is very well suited for the task at hand: 'if any set made of location is subset of current move then we have a win' (set is always subset of itself, if all values are '' then it is not subset of move, if there is move and '' then this is also not subset of move, if there are two moves this also not subset of move).

So this logic can be expressed in Python something like that (move is char what player is 'entering') :

for row in stream_locations(board):
    if set(row).issubset({move}):
        print(f'Winner is {move}')
        break
else:                       # no-break
    print('Next move')
Depending how you want to structure your program you can make separate function out of it or add it to stream_locations function (and rename it to something like check_winner).

There can be winner starting from 5th move so checking winner can be skipped before that.
I'm not 'in'-sane. Indeed, I am so far 'out' of sane that you appear a tiny blip on the distant coast of sanity. Bucky Katt, Get Fuzzy

Da Bishop: There's a dead bishop on the landing. I don't know who keeps bringing them in here. ....but society is to blame.
Reply
#3
(Oct-01-2020, 10:13 AM)perfringo Wrote: I will not address overall structure of the programm, just small observations. These are ideas how to take advantage of Python built-ins to make your program more concise and avoid repetition. Separating data (board state) and it's display (output of board state to screen) makes things simpler.
Perfringo, thank you very much for the comments. I can imagine that experienced coders would have many criticisms of the "overall structure of the program" as well :) Your comments address precisely the kind of problem that I was trying to solve, so thanks. This is my first attempt at learning a programming language, and I feel quite clueless!
Reply
#4
I try to give feedback not criticism ;-)

I also try to explain why something could and/or should be done differently (unfortunately I have not enough time to do it always properly not to mention knowledge Smile ).
I'm not 'in'-sane. Indeed, I am so far 'out' of sane that you appear a tiny blip on the distant coast of sanity. Bucky Katt, Get Fuzzy

Da Bishop: There's a dead bishop on the landing. I don't know who keeps bringing them in here. ....but society is to blame.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020