Tic-Tac-Toe/tictactoe.py
neogeek23 35a23fc8f2 Add files via upload
Added new display feature for 3 space.  3 space now displays as horizontal repetition of 2 space.
Fixed issue with some victory paths in 4 space an above not being recognized.  Previously some intermediate/interior cross sectional paths were not recognized when filled with n+1 of the same token as being a successful victory condition.
2017-05-13 01:33:54 -05:00

338 lines
11 KiB
Python

import sys
import math
import random
import itertools
class Player:
def __init__(self, index):
self.__index = index
self.__move_list = list()
if index < 0:
self.__token = "-"
elif index == 0:
self.__token = "X"
else:
self.__token = "O"
def get_id(self):
return self.__index
def get_token(self):
return self.__token
def save_move(self, move):
self.__move_list.append(move)
def get_move_history(self):
return self.__move_list
def get_last_move(self):
return self.__move_list[len(self.__move_list)]
class Board:
class Space:
def __init__(self):
self.__owner = " " # This means unclaimed
def place_token(self, owner):
if self.__owner == " ": # If unclaimed
self.__owner = owner
return True
else:
return False
def get_owner(self):
return self.__owner
def __init__(self, dimensions):
self.__dimensions = dimensions
self.players = [Player(0), Player(1)]
self.__spaces = self.__board_creator(dimensions)
self.__round = 0
self.__winner = None
self.__winning_path = list()
def __board_creator(self, dimensions):
result = list()
for i in range(self.__dimensions + 1):
if dimensions == 1:
result.append(self.Space())
if dimensions > 1:
result.append(self.__board_creator(dimensions - 1))
return result
def is_full(self):
return self.__round == math.pow(self.__dimensions + 1, self.__dimensions)
def has_winner(self):
return self.__winner is not None
def get_winner(self):
return self.__winner
def display(self):
self.__display_recur(self.__spaces, self.__dimensions)
def __display_recur(self, list_of_list, d):
if d < 3:
header = "\n "
for i in range(self.__dimensions + 1):
if i > 9: # lets be honest 100^99 will destroy memory; already dies a dim 8, 7 is struggle
header += "| " + str(i)
else:
header += "| " + str(i) + " "
print(header)
for i in list_of_list:
row_string = " " + str(list_of_list.index(i))
if list_of_list.index(i) < 10:
row_string += " "
for j in i:
row_string += "| " + j.get_owner() + " "
print(row_string)
elif d < 4:
header = "\n"
for i in range(self.__dimensions + 1):
if i != 0:
header += "\t"
header += " {:2s}".format(str(i)[:1])
for j in range(self.__dimensions + 1):
header += "| {:2s}".format(str(j)[:1])
print(header)
for i in range(self.__dimensions + 1):
row_string = ""
for j in range(self.__dimensions + 1):
if j != 0:
row_string += "\t"
row_string += " {:2s}".format(str(i)[:1])
for k in range(self.__dimensions + 1):
# One of these days I'll think about why it is j-i-k not i-j-k #madness
row_string += "| " + list_of_list[j][i][k].get_owner() + " "
print(row_string)
else:
for i in range(self.__dimensions + 1):
if i < 10:
print("\n-------" + str(i) + "-------\tDimension:\t" + str(d))
else:
print("\n-------" + str(i) + "------\tDimension:\t" + str(d))
self.__display_recur(list_of_list[i], d - 1)
def __get_space(self, index_list):
if len(index_list) != self.__dimensions:
print("\nCoordinates for a space were impossible.")
return None
else:
int_indexes = list()
for i in index_list:
int_indexes.append(int(i))
return self.__get_space_recur(self.__spaces, int_indexes)
def __get_space_recur(self, list_of_lists, index_list):
if len(index_list) == 1:
return list_of_lists[int(index_list[0])]
else:
return self.__get_space_recur(list_of_lists[index_list[0]], index_list[1:])
def place_token(self, coordinate, player):
indexes = coordinate.split(".")
for index in indexes:
if not index.isnumeric():
return 1 # This will be a key value to denote a failure of type
if len(indexes) != self.__dimensions:
return 2 # This will be a key value to denote a failure of quantity
if self.__get_space(indexes).place_token(player.get_token()):
self.__round += 1
self.__winner = self.__is_winning_move(coordinate, player)
player.save_move(coordinate)
return 0 # This will be a key value to denote ultimate success
else:
return 3 # This will be a key value to denote a occupancy of space
def __get_random_coordinate(self, start, stop):
result = ""
for i in range(self.__dimensions):
result += str(random.randrange(start, stop))
if i < self.__dimensions - 1:
result += "."
return result
def get_center_coordinates(self):
center = self.__dimensions / 2
return self.__get_random_coordinate(center, center + 1)
def place_random(self, token):
return self.place_token(self.__get_random_coordinate(0, self.__dimensions + 1), token)
def __is_winning_move(self, coordinate, player):
coord_list = list()
for s in coordinate.split("."):
coord_list.append(int(s))
freedom_set = self.__get_dimension_locks()
intersecting_pathsets = self.__get_winning_paths(coord_list, freedom_set)
for paths in intersecting_pathsets:
for path in paths:
solution_in_path = True
for space in path:
solution_in_path = solution_in_path and self.__get_space(space).get_owner() == player.get_token()
if solution_in_path:
self.__winning_path = path
return player.get_id()
return None
def __get_dimension_locks(self):
final = list()
initial = list()
for i in range(self.__dimensions):
initial.append(True)
for i in range(len(initial)):
if i > 0:
initial[i - 1] = False
for j in list(itertools.permutations(initial)):
temp = list()
for k in list(j):
temp.append(k)
if temp not in final:
final.append(temp)
return final
def __get_winning_paths(self, coord_list, freedoms):
path_set = list()
# This will get all possible paths that are 5 long from the starting point
for freedom in freedoms:
result = list()
for edit_pattern in freedoms:
patterned_edit = list()
for i in range(self.__dimensions + 1):
temp = coord_list.copy()
for j in range(len(temp)):
if freedom[j]:
if edit_pattern[j]:
temp[j] = (temp[j] + i + (self.__dimensions + 1)) % (self.__dimensions + 1)
else:
temp[j] = (temp[j] - i - (self.__dimensions + 1)) % (self.__dimensions + 1)
patterned_edit.append(temp)
if patterned_edit not in result:
result.append(patterned_edit)
if result not in path_set:
path_set.append(result)
# This will prune out impossible paths (paths that wrap around the space)
for paths in path_set:
for path in paths:
slope_is_legit = True
for i in range(len(path)):
found_a_slope_buddy = False
for j in range(len(path)):
if i != j:
point_has_good_slope = True
for k in range(len(path[i])):
if freedoms[k]:
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
slope_is_legit = slope_is_legit and found_a_slope_buddy
if not slope_is_legit:
paths.remove(path)
return path_set
def get_winning_path(self):
result = ""
for coord in self.__winning_path:
result += "("
index = 0
for p in coord:
index += 1
result += str(p)
if index != len(coord):
result += " "
result += ")\n"
return result
# Setup Inputs
print("\nWelcome to multidimensional Tic-Tac-Toe!\n\nThe board will contain (n+1)^n 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 row 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:
difficulty_attempts += 1
print("Remaining attempts: " + str(3 - difficulty_attempts) + " failure will result in termination.")
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)
if dimension < 2:
print("Tic Tac Toe requires at least 2 dimensions and you chose " + difficulty + ", so we will use 2 dimensions.")
dimension = 2
board = Board(dimension)
if dimension > 2 and dimension % 2 == 0:
middle_attempts = 1
center_selectable = input("Do you want the center most space selectable (Y/N)? ")
while not (center_selectable == "Y" or center_selectable == "N") and middle_attempts < 3:
middle_attempts += 1
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()
if center_selectable == "N":
# Player(-1) is a player for the board to claim the center location
board.place_token(board.get_center_coordinates(), Player(-1)) # This will always work #usefulcomments
# Actually Playing the game
turn = 0
while not board.is_full() and not board.has_winner():
board.display()
player_to_play = turn % 2
move_attempts = 1
coordinate_move = input("\nPlayer " + str(player_to_play + 1) + " please input coordinates (<nth index>.<nth - 1 "
"index>. ... .<3rd index>.<rows>.<cols>) of move: ")
move_result = board.place_token(coordinate_move, board.players[player_to_play])
while move_result > 0 and move_attempts < 3:
if move_result == 1:
print("Input Error on Coordinate. There is a non-integer type between the '.' separators.")
if move_result == 2:
print("Input Error on Coordinate. There are not enough indexes for each dimension.")
if move_result == 3:
print("Input Error on Coordinate. There space described by coordinate is already claimed.")
print("Remaining attempts: " + str(3 - move_attempts) + " failure will result in random placement.")
coordinate_move = input("Player " + str(player_to_play + 1) + " please input coordinates (<nth index>.<nth - 1 "
"index>. ... .<3rd index>.<rows>.<cols>) of move: ")
move_attempts += 1
move_result = board.place_token(coordinate_move, board.players[player_to_play])
if move_result > 0 and move_attempts >= 3:
move_result = board.place_random(board.players[player_to_play])
while move_result > 0:
move_result = board.place_random(board.players[player_to_play])
turn += 1
# Reporting on End of Game
board.display()
if board.is_full():
print("Board has filled a no victor has been found. Game Over. Thanks for playing, try again.")
if board.has_winner():
print("\nVictory to Player " + str(board.players[board.get_winner()].get_id() + 1) + " - respect. The "
+ board.players[board.get_winner()].get_token() + "'s win!\nThe winning path is:\n" + board.get_winning_path()
+ "\nGame Over. Thanks for playing, play again.")