mirror of
https://github.com/neogeek23/Tic-Tac-Toe.git
synced 2026-02-04 02:48:16 +00:00
Add files via upload
Many comments were added to make it clear what was going on. Global scope execution removed and "main" function added. Enum type added to clearly communicate significant returns.
This commit is contained in:
parent
ebee044cba
commit
3d55ed4a48
221
tictactoe.py
221
tictactoe.py
@ -2,6 +2,14 @@ import sys
|
|||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
import itertools
|
import itertools
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class PlacementResult(Enum):
|
||||||
|
success = 0
|
||||||
|
non_numeric_input = 1
|
||||||
|
dimension_size_error = 2
|
||||||
|
unavailable_placement = 3
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
@ -122,7 +130,7 @@ class Board:
|
|||||||
self.__display_recur(list_of_list[i], d - 1)
|
self.__display_recur(list_of_list[i], d - 1)
|
||||||
|
|
||||||
def __get_space(self, index_list):
|
def __get_space(self, index_list):
|
||||||
if len(index_list) != self.__dimensions:
|
if len(index_list) != self.__dimensions: # this is safety that is helpful only in the building process
|
||||||
print("\nCoordinates for a space were impossible.")
|
print("\nCoordinates for a space were impossible.")
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@ -142,18 +150,18 @@ class Board:
|
|||||||
|
|
||||||
for index in indexes:
|
for index in indexes:
|
||||||
if not index.isnumeric():
|
if not index.isnumeric():
|
||||||
return 1 # This will be a key value to denote a failure of type
|
return PlacementResult.non_numeric_input
|
||||||
|
|
||||||
if len(indexes) != self.__dimensions:
|
if len(indexes) != self.__dimensions:
|
||||||
return 2 # This will be a key value to denote a failure of quantity
|
return PlacementResult.dimension_size_error
|
||||||
|
|
||||||
if self.__get_space(indexes).place_token(player.get_token()):
|
if self.__get_space(indexes).place_token(player.get_token()):
|
||||||
self.__round += 1
|
self.__round += 1
|
||||||
self.__winner = self.__is_winning_move(coordinate, player)
|
self.__winner = self.__is_winning_move(coordinate, player)
|
||||||
player.save_move(coordinate)
|
player.save_move(coordinate)
|
||||||
return 0 # This will be a key value to denote ultimate success
|
return PlacementResult.success
|
||||||
else:
|
else:
|
||||||
return 3 # This will be a key value to denote a occupancy of space
|
return PlacementResult.unavailable_placement
|
||||||
|
|
||||||
def __get_random_coordinate(self, start, stop):
|
def __get_random_coordinate(self, start, stop):
|
||||||
result = ""
|
result = ""
|
||||||
@ -164,6 +172,7 @@ class Board:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def get_center_coordinates(self):
|
def get_center_coordinates(self):
|
||||||
|
# this will only be called for odd dimensions, it should probably have some more safety on it
|
||||||
center = self.__dimensions / 2
|
center = self.__dimensions / 2
|
||||||
return self.__get_random_coordinate(center, center + 1)
|
return self.__get_random_coordinate(center, center + 1)
|
||||||
|
|
||||||
@ -178,6 +187,8 @@ class Board:
|
|||||||
freedom_set = self.__get_dimension_locks()
|
freedom_set = self.__get_dimension_locks()
|
||||||
intersecting_pathsets = self.__get_winning_paths(coord_list, freedom_set)
|
intersecting_pathsets = self.__get_winning_paths(coord_list, freedom_set)
|
||||||
|
|
||||||
|
# we want to look through all of the found possible paths a placed token in a space could finish a continuous
|
||||||
|
# streak and see if it did
|
||||||
for paths in intersecting_pathsets:
|
for paths in intersecting_pathsets:
|
||||||
for path in paths:
|
for path in paths:
|
||||||
solution_in_path = True
|
solution_in_path = True
|
||||||
@ -189,17 +200,16 @@ class Board:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def __get_dimension_locks(self):
|
def __get_dimension_locks(self):
|
||||||
# This gets all unique permutations of True-False for each dimension so 2^n elements.
|
# This gets all unique permutations of True-False for each dimension so 2^n elements
|
||||||
# This is used as a matrix to know when to freeze a dimension to create cross sections of a n-space,
|
# This is used to create a matrix of all the possible cross sections of a n-space
|
||||||
# it gets all possibilities.
|
# This is also used to pattern all the ways to manipulate a 1-dimensional element, increase/decrease it
|
||||||
# This is also used to pattern all the ways to manipulate a 2-dimensional element, increase/decrease it.
|
|
||||||
final = list()
|
final = list()
|
||||||
initial = list()
|
initial = list()
|
||||||
|
|
||||||
for i in range(self.__dimensions):
|
for i in range(self.__dimensions):
|
||||||
initial.append(True)
|
initial.append(True)
|
||||||
|
|
||||||
for i in range(len(initial)): # Don't do +1 b/c +1 item will get all False
|
for i in range(len(initial)): # Don't do +1 b/c +1 item will get all False, which will mean the 0th dimension
|
||||||
if i > 0:
|
if i > 0:
|
||||||
initial[i - 1] = False
|
initial[i - 1] = False
|
||||||
for j in list(itertools.permutations(initial)):
|
for j in list(itertools.permutations(initial)):
|
||||||
@ -212,37 +222,48 @@ class Board:
|
|||||||
|
|
||||||
def __get_winning_paths(self, coord_list, freedoms):
|
def __get_winning_paths(self, coord_list, freedoms):
|
||||||
path_set = list()
|
path_set = list()
|
||||||
# This will get all possible paths that are n+1 long from the starting point, including wraparounds
|
# This will get all possible paths that are n+1 long from the starting point
|
||||||
for freedom in freedoms:
|
for freedom in freedoms: # Each freedom is a permutation of whether each dimension should change
|
||||||
result = list()
|
result = list()
|
||||||
for edit_pattern in freedoms:
|
for edit_pattern in freedoms: # Each edit_pattern is a permutation of how to change each dimension
|
||||||
patterned_edit = list()
|
patterned_edit = list()
|
||||||
for i in range(self.__dimensions + 1):
|
for i in range(self.__dimensions + 1): # dim + 1 i is number of spaces for a continuous streak
|
||||||
temp = coord_list.copy()
|
temp = coord_list.copy() # we want a new memory location to edit our coord for a new coord
|
||||||
for j in range(len(temp)):
|
for j in range(len(temp)): # j is which dimension in our new coord we are manipulating
|
||||||
if freedom[j]:
|
if freedom[j]:
|
||||||
if edit_pattern[j]:
|
if edit_pattern[j]:
|
||||||
|
# We want to increase or decrease such that we are always between 0 and the dimension.
|
||||||
|
# Doing this will create a wraparound problem though e.g. (1,0),(0,1),(2,2). We will
|
||||||
|
# abstain from including these soon. We also want to only shift in a dimension by a
|
||||||
|
# single space.
|
||||||
temp[j] = (temp[j] + i + (self.__dimensions + 1)) % (self.__dimensions + 1)
|
temp[j] = (temp[j] + i + (self.__dimensions + 1)) % (self.__dimensions + 1)
|
||||||
else:
|
else:
|
||||||
temp[j] = (temp[j] - i - (self.__dimensions + 1)) % (self.__dimensions + 1)
|
temp[j] = (temp[j] - i - (self.__dimensions + 1)) % (self.__dimensions + 1)
|
||||||
patterned_edit.append(temp)
|
patterned_edit.append(temp)
|
||||||
|
# we only want unique coordinate sets and we do not want wraparounds or other non-continuous paths
|
||||||
if patterned_edit not in result and self.__is_path_continuous(patterned_edit, freedom):
|
if patterned_edit not in result and self.__is_path_continuous(patterned_edit, freedom):
|
||||||
result.append(patterned_edit)
|
result.append(patterned_edit)
|
||||||
|
# we only want unique coordinate groups
|
||||||
if result not in path_set:
|
if result not in path_set:
|
||||||
path_set.append(result)
|
path_set.append(result)
|
||||||
return path_set
|
return path_set
|
||||||
|
|
||||||
def __is_path_continuous(self, path, freedoms):
|
def __is_path_continuous(self, path, freedoms):
|
||||||
# This will prune out impossible paths (paths that wrap around the space)
|
# This will prune out impossible paths (paths that wrap around the space)
|
||||||
# Though this could be static, it doesn't feel right as static, so I haven't made it so.
|
# Though this could be static, it doesn't make sense as static, so I haven't made it so.
|
||||||
slope_is_legit = True
|
slope_is_legit = True
|
||||||
|
# We want to look at each element in a list against every other element in that list, except itself so it is a
|
||||||
|
# for i for j where i != j kind of thing - this is more detail that necessary in a comment, the so it is a part
|
||||||
for i in range(len(path)):
|
for i in range(len(path)):
|
||||||
found_a_slope_buddy = False
|
found_a_slope_buddy = False
|
||||||
for j in range(len(path)):
|
for j in range(len(path)):
|
||||||
if i != j:
|
if i != j:
|
||||||
point_has_good_slope = True
|
point_has_good_slope = True
|
||||||
for k in range(len(path[i])):
|
for k in range(len(path[i])):
|
||||||
if freedoms[k]:
|
if freedoms[k]: # we include freedom because a dimension may be "locked", which means we
|
||||||
|
# may be looking at a victory path in some dimension r (such that r < n) cross section, when
|
||||||
|
# this is the case we don't want to check if there is a change of 1 in those dimensions that
|
||||||
|
# are locked because there will be a change of 0 and the test will give us a false negative
|
||||||
point_has_good_slope = point_has_good_slope and abs(path[i][k] - path[j][k]) == 1
|
point_has_good_slope = point_has_good_slope and abs(path[i][k] - path[j][k]) == 1
|
||||||
found_a_slope_buddy = found_a_slope_buddy or point_has_good_slope
|
found_a_slope_buddy = found_a_slope_buddy or point_has_good_slope
|
||||||
slope_is_legit = slope_is_legit and found_a_slope_buddy
|
slope_is_legit = slope_is_legit and found_a_slope_buddy
|
||||||
@ -261,87 +282,109 @@ class Board:
|
|||||||
result += ")\n"
|
result += ")\n"
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Setup Inputs
|
|
||||||
print("\nWelcome to multidimensional Tic-Tac-Toe!\n\nThe board will contain (n+1)^n spaces for n dimensions.\nFor even "
|
|
||||||
"dimensions starting with the 4th dimension, the center space can be setup to be unplayable.\nClaim n+1 "
|
|
||||||
"spaces in a continuous streak to win!\n")
|
|
||||||
difficulty_attempts = 1
|
|
||||||
difficulty = input("How many dimensions of Tic Tac Toe would you like to attempt? ")
|
|
||||||
|
|
||||||
while not difficulty.isnumeric() and difficulty_attempts < 3:
|
def main():
|
||||||
difficulty_attempts += 1
|
board = create_board_from_inputs()
|
||||||
print("Remaining attempts: " + str(3 - difficulty_attempts) + " failure will result in termination.")
|
play_tic_tac_toe(board)
|
||||||
difficulty = input("We require more integers for dimension count. How many dimensions of tic tac toe would you "
|
|
||||||
"like to attempt? ")
|
|
||||||
|
|
||||||
if difficulty_attempts >= 3 and not difficulty.isnumeric():
|
|
||||||
print("Let me know when you find your number pad. Please come again.")
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
dimension = int(difficulty)
|
def create_board_from_inputs():
|
||||||
if dimension < 2:
|
print(
|
||||||
print("Tic Tac Toe requires at least 2 dimensions and you chose " + difficulty + ", so we will use 2 dimensions.")
|
"\nWelcome to multidimensional Tic-Tac-Toe!\n\nThe board will contain (n+1)^n spaces for n dimensions.\nFor "
|
||||||
dimension = 2
|
"even dimensions starting with the 4th dimension, the center space can be setup to be unplayable.\nClaim n+1 "
|
||||||
else:
|
"spaces in a continuous streak to win!\n")
|
||||||
print("You have requested a board of " + str(math.pow(dimension + 1, dimension)) + " spaces.")
|
difficulty_attempts = 1
|
||||||
if dimension > 5:
|
max_attempts = 3
|
||||||
print("This is a very large board. It may take some time to create.")
|
difficulty = input("How many dimensions of Tic Tac Toe would you like to attempt? ")
|
||||||
|
|
||||||
board = Board(dimension)
|
while not difficulty.isnumeric() and difficulty_attempts < max_attempts:
|
||||||
if dimension > 2 and dimension % 2 == 0:
|
difficulty_attempts += 1
|
||||||
middle_attempts = 1
|
print("Remaining attempts: " + str(max_attempts - difficulty_attempts) + ", failure will result in termination.")
|
||||||
center_selectable = input("Do you want the center most space selectable (Y/N)? ")
|
difficulty = input(
|
||||||
|
"We require more integers for dimension count. How many dimensions of tic tac toe would you "
|
||||||
|
"like to attempt? ")
|
||||||
|
|
||||||
while not (center_selectable == "Y" or center_selectable == "N") and middle_attempts < 3:
|
if difficulty_attempts >= max_attempts and not difficulty.isnumeric():
|
||||||
middle_attempts += 1
|
print("Let me know when you find your number pad. Please come again.")
|
||||||
print("Remaining attempts: " + str(3 - middle_attempts) + " failure will result in termination.")
|
|
||||||
center_selectable = input("Y/N type input is required. Do you want the center most space selectable (Y/N)? ")
|
|
||||||
|
|
||||||
if not (center_selectable == "Y" or center_selectable == "N") and middle_attempts >= 3:
|
|
||||||
print("Let me know when you find your 'Y' and 'N' keys. Please come again.")
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if center_selectable == "N":
|
dimension = int(difficulty)
|
||||||
# Player(-1) is a player for the board to claim the center location
|
if dimension < 2:
|
||||||
board.place_token(board.get_center_coordinates(), Player(-1)) # This will always work #usefulcomments
|
print(
|
||||||
|
"Tic Tac Toe requires at least 2 dimensions and you chose " + difficulty + ", so we will use 2 dimensions.")
|
||||||
|
dimension = 2
|
||||||
|
else:
|
||||||
|
print("You have requested a board of " + str(int(math.pow(dimension + 1, dimension))) + " spaces.")
|
||||||
|
if dimension > 5:
|
||||||
|
print("This is a very large board. It may take some time to create.")
|
||||||
|
|
||||||
# Actually Playing the game
|
board = Board(dimension)
|
||||||
turn = 0
|
if dimension > 2 and dimension % 2 == 0:
|
||||||
while not board.is_full() and not board.has_winner():
|
middle_attempts = 1
|
||||||
board.display()
|
center_selectable = input("Do you want the center most space selectable (Y/N)? ")
|
||||||
player_to_play = turn % 2
|
|
||||||
move_attempts = 1
|
while not (center_selectable == "Y" or center_selectable == "N") and middle_attempts < max_attempts:
|
||||||
coordinate_move = input("\nPlayer " + str(player_to_play + 1) + " please input coordinates (<nth index>.<nth - 1 "
|
middle_attempts += 1
|
||||||
"index>. ... .<3rd index>.<rows>.<cols>) to place your '"
|
print("Remaining attempts: " + str(max_attempts - middle_attempts) + ", failure will result in termination.")
|
||||||
+ board.players[player_to_play].get_token() + "' token: ")
|
center_selectable = input(
|
||||||
move_result = board.place_token(coordinate_move, board.players[player_to_play])
|
"Y/N type input is required. Do you want the center most space selectable (Y/N)? ")
|
||||||
while move_result > 0 and move_attempts < 3:
|
|
||||||
if move_result == 1:
|
if not (center_selectable == "Y" or center_selectable == "N") and middle_attempts >= max_attempts:
|
||||||
print("Input Error on Coordinate. There is a non-integer type between the '.' separators.")
|
print("Let me know when you find your 'Y' and 'N' keys. Please come again.")
|
||||||
if move_result == 2:
|
sys.exit()
|
||||||
print("Input Error on Coordinate. The number of indexes in your coordinate does not match the dimension.")
|
|
||||||
if move_result == 3:
|
if center_selectable == "N":
|
||||||
print("Input Error on Coordinate. That space is already claimed.")
|
# Player(-1) is a player for the board to claim the center location
|
||||||
print("Remaining attempts: " + str(3 - move_attempts) + " failure will result in random placement.")
|
board.place_token(board.get_center_coordinates(), Player(-1)) # This is half why Player is not a subclass
|
||||||
coordinate_move = input("\nPlayer " + str(player_to_play + 1) + " please input coordinates (<nth index>.<nth "
|
return board
|
||||||
"- 1 index>. ... .<3rd index>.<rows>.<cols>) to place your '"
|
|
||||||
+ board.players[player_to_play].get_token() + "' token: ")
|
|
||||||
move_attempts += 1
|
def play_tic_tac_toe(board):
|
||||||
|
turn = 0
|
||||||
|
while not board.is_full() and not board.has_winner():
|
||||||
|
board.display()
|
||||||
|
player_to_play = turn % 2
|
||||||
|
move_attempts = 1
|
||||||
|
max_attempts = 3
|
||||||
|
coordinate_move = input(
|
||||||
|
"\nPlayer " + str(player_to_play + 1) +
|
||||||
|
" please input coordinates (<nth index>.<nth - 1 index>. ... .<3rd index>.<rows>.<cols>) to place your '" +
|
||||||
|
board.players[player_to_play].get_token() + "' token: ")
|
||||||
move_result = board.place_token(coordinate_move, board.players[player_to_play])
|
move_result = board.place_token(coordinate_move, board.players[player_to_play])
|
||||||
|
|
||||||
if move_result > 0 and move_attempts >= 3:
|
while move_result != PlacementResult.success and move_attempts < max_attempts:
|
||||||
move_result = board.place_random(board.players[player_to_play])
|
if move_result == PlacementResult.non_numeric_input:
|
||||||
while move_result > 0:
|
print("Input Error on Coordinate. There is a non-integer type between the '.' separators.")
|
||||||
move_result = board.place_random(board.players[player_to_play])
|
if move_result == PlacementResult.dimension_size_error:
|
||||||
|
s = "Input Error on Coordinate. The number of indexes in your coordinate does not match the dimension."
|
||||||
|
print(s)
|
||||||
|
if move_result == PlacementResult.unavailable_placement:
|
||||||
|
print("Input Error on Coordinate. That space is already claimed.")
|
||||||
|
s = "Remaining attempts: " + str(max_attempts - move_attempts)
|
||||||
|
s += ", failure will result in random placement."
|
||||||
|
print(s)
|
||||||
|
s = "\nPlayer " + str(player_to_play + 1)
|
||||||
|
s += " please input coordinates (<nth index>.<nth - 1 index>. ... .<3rd index>.<rows>.<cols>) to place "
|
||||||
|
s += "your '" + board.players[player_to_play].get_token() + "' token: "
|
||||||
|
coordinate_move = input(s)
|
||||||
|
move_attempts += 1
|
||||||
|
move_result = board.place_token(coordinate_move, board.players[player_to_play])
|
||||||
|
|
||||||
turn += 1
|
if move_result != PlacementResult.success and move_attempts >= max_attempts:
|
||||||
|
while move_result != PlacementResult.success:
|
||||||
|
move_result = board.place_random(board.players[player_to_play])
|
||||||
|
turn += 1
|
||||||
|
|
||||||
# Reporting on End of Game
|
# Reporting on End of Game
|
||||||
board.display()
|
board.display()
|
||||||
if board.is_full():
|
if board.is_full():
|
||||||
print("Board has filled and no victor has been found. Game Over. Thanks for playing, try again.")
|
print("Board has filled and no victor has been found. Game Over. Thanks for playing, try again.")
|
||||||
|
|
||||||
if board.has_winner():
|
if board.has_winner():
|
||||||
print("\nVictory to Player " + str(board.players[board.get_winner()].get_id() + 1) + " - respect. The "
|
s = "\nVictory to Player " + str(board.players[board.get_winner()].get_id() + 1)
|
||||||
+ board.players[board.get_winner()].get_token() + "'s win!\nThe winning path is:\n" + board.get_winning_path()
|
s += " - respect. The " + board.players[board.get_winner()].get_token()
|
||||||
+ "\nGame Over. Thanks for playing, play again.")
|
s += "'s win!\nThe winning path contains:\n" + board.get_winning_path()
|
||||||
|
s += "\nGame Over. Thanks for playing, play again."
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
main()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user