import random class BadInputError(Exception): pass class LogicError(Exception): pass #===========GAMEBOARDS===========# blankBoard = { 'UL' : ' ', 'UM' : ' ', 'UR' : ' ', 'CL' : ' ', 'CM' : ' ', 'CR' : ' ', 'BL' : ' ', 'BM' : ' ', 'BR' : ' ', } debugBoard = { 'UL' : ' ', 'UM' : ' ', 'UR' : ' ', 'CL' : ' ', 'CM' : ' ', 'CR' : ' ', 'BL' : ' ', 'BM' : ' ', 'BR' : ' ', } invertedSpaces = { 'LU' : 'UL', 'MU' : 'UM', 'RU' : 'UR', 'LC' : 'CL', 'MC' : 'CM', 'RC' : 'CR', 'LB' : 'BL', 'MB' : 'BM', 'RB' : 'BR', } #===========DEFINITIONS===========# '''Spaces''' spaces = ('UL','UM','UR','CL','CM','CR','BL','BM','BR') '''Wins''' oWin = ('O','O','O') xWin = ('X','X','X') '''Doubles''' oDoubles = [(' ','O','O'),('O',' ','O'),('O','O',' ')] xDoubles = [(' ','X','X'),('X',' ','X'),('X','X',' ')] '''Input''' possibleInput = [key for key in blankBoard] for key in invertedSpaces: possibleInput.append(key) '''Space Types''' corners = ('UL','UR','BL','BR') sides = ('CL','CR', 'UM', 'BM') '''Space Inversions''' horizontalFlip = { 'UL' : 'UR','UR' : 'UL', 'CL' : 'CR','CR' : 'CL', 'BL' : 'BR','BR' : 'BL', } verticalFlip = { 'UL' : 'BL', 'UM' : 'BM', 'UR' : 'BR', 'BL' : 'UL', 'BM' : 'UM', 'BR' : 'UR', } #===========OBJECTS===========# class ticBoard(): def __init__(self, mode='blank', copyBoard=None): if mode == 'blank': self.board = {space:blankBoard[space] for space in blankBoard} elif mode == 'debug': self.board = {space:debugBoard[space] for space in debugBoard} elif mode == 'copy' and copyBoard != None: self.board = {space:copyBoard.board[space] for space in copyBoard.board} def draw(self): '''Draw board''' print() print(' L M R ') print('U: {} | {} | {} '.format(self.board['UL'], self.board['UM'], self.board['UR'])) print(' -----------') print('C: {} | {} | {} '.format(self.board['CL'], self.board['CM'], self.board['CR'])) print(' -----------') print('B: {} | {} | {} '.format(self.board['BL'], self.board['BM'], self.board['BR'])) print() def place(self, symbol, space): '''Places a symbol at the designated space.''' try: self.board[space] = symbol except: raise BadInputError("{} is not a valid space for {}.".format(space, symbol)) def clear(self): '''Clears board of all symbols.''' self.board = {space:' ' for space in self.board} def fieldReport(self): '''Returns dictionary of triads.''' report = {} report[('UL','UM','UR')] = (self.board['UL'],self.board['UM'],self.board['UR']) report[('CL','CM','CR')] = (self.board['CL'],self.board['CM'],self.board['CR']) report[('BL','BM','BR')] = (self.board['BL'],self.board['BM'],self.board['BR']) report[('UL','CL','BL')] = (self.board['UL'],self.board['CL'],self.board['BL']) report[('UM','CM','BM')] = (self.board['UM'],self.board['CM'],self.board['BM']) report[('UR','CR','BR')] = (self.board['UR'],self.board['CR'],self.board['BR']) report[('UL','CM','BR')] = (self.board['UL'],self.board['CM'],self.board['BR']) report[('UR','CM','BL')] = (self.board['UR'],self.board['CM'],self.board['BL']) return report def returnDoubles(self, report): '''Filters out report to only include triads close to winning. ie "[X,X, ]" or '[O, ,O]"''' doubles = {} for triad in report: if report[triad] in oDoubles or report[triad] in xDoubles: doubles[triad] = report[triad] return doubles def checkWin(self): '''Returns True if there are three symbols in a row. False if otherwise.''' report = self.fieldReport() for triad in report: if report[triad] == oWin or report[triad] == xWin: return True return False def checkEntry(self, entry, selected): '''Returns the entry and whether or not it is valid.''' entry = entry.upper() if entry in invertedSpaces: entry = invertedSpaces[entry] if entry not in possibleInput: return {'valid':False,'entry':entry, 'message':'\n{} is not a valid entry!'} if entry not in selected: return {'valid':True,'entry':entry} else: return {'valid':False,'entry':entry, 'message':'\n{} has already been selected!'} def buildString(self, string): if len(string) != 9: print('String is not correct length. Reformatting will occur.') string = string[:9] while len(string) < 9: string += '0' for i in range(9): if string[i] == '0': self.board[spaces[i]] = ' ' elif string[i] == '1': self.board[spaces[i]] = 'O' elif string[i] == '2': self.board[spaces[i]] = 'X' def blankSpaces(self): '''Returns list of free spaces remianing.''' return [space for space in self.board if self.board[space] == ' '] class player(): def __init__(self, identity): self.id = identity self.score = 0 self.match = 0 self.symbol = '' def setName(self, name): '''Define player's name.''' if 0 < len(str(name)) < 20: self.name = name.title() return False else: return True def setSymbol(self, symbol): if symbol.upper() in ['X','O']: self.symbol = symbol.upper() return False else: return True def win(self): self.score += 1 def matchWin(self): self.match += 1 def resetMatch(self): self.match = 0 def getSymbol(self): return self.symbol def getName(self): return self.name def getIdentity(self): return self.id def getScore(self): return self.score def getMatches(self): return self.match class computer(player): def __init__(self, difficulty='E'): self.id = 'comp' self.difficulty = difficulty[0] self.setName('Computer') self.setSymbol('X') self.score = 0 self.match = 0 self.strategy = '' self.tactic = '' self.lastMove = '' self.reiterate = False def mapCoordinates(self, triad): '''Converts a entry from a triad tuple to a dictionary of symbol : coordinate values.''' mapped = {} coor = 0 for coordinate in triad[0]: mapped[coordinate] = triad[1][coor] coor+=1 return mapped def analyzeMap(self, mappedCoordinates): '''Returns empty value from a mapped coordinates dictionary.''' for key in mappedCoordinates: if mappedCoordinates[key] == ' ': return key def defineStrategy(self, strategy): '''Play offensively (first turn) or defensively.''' if strategy in ['offensive','defensive']: self.strategy = strategy def decideTactic(self, board): '''Decide tactic based on the first move or by making first move.''' if self.strategy == 'offensive': firstMove = random.choice(['center','corner']) #firstMove = 'corner' self.tactic = firstMove elif self.strategy == 'defensive': for space in board.board: if board.board[space] == 'O': if space in corners: self.tactic = 'corner' elif space == 'CM': self.tactic = 'center' else: self.tactic = 'side' def clearStrategy(self): self.strategy = '' self.tactic = '' def counter(self, doubles): '''Either place winning piece or stop opponent from winning.''' if doubles != {}: triad = doubles.popitem() entry = self.analyzeMap(self.mapCoordinates(triad)) debug(d,'Countering') return {'counter':True, 'entry':entry} return {'counter':False, 'entry':''} def trapSimulation(self, board, report, pool): '''Simulate different moves to trap opponent.''' for coordinate in pool: simulatedBoard = ticBoard('copy',board) simulatedBoard.place(self.getSymbol(),coordinate) simulatedDoubles = board.returnDoubles(simulatedBoard.fieldReport()) soDoubles = {key:simulatedDoubles[key] for key in simulatedDoubles if 'O' in simulatedDoubles[key]} sxDoubles = {key:simulatedDoubles[key] for key in simulatedDoubles if 'X' in simulatedDoubles[key]} if len(soDoubles) == 0 and len(sxDoubles) > 1: debug(d,'Trapping') return {'trap':True, 'entry':coordinate} return {'trap':False, 'entry':coordinate} def offensiveStrategy(self, board): '''Provide offensive move based on a certain tactic.''' if self.tactic == 'center': if len(board.blankSpaces()) == 9: debug(d,'Begin Center') return {'offensive':True, 'entry':'CM'} elif len(board.blankSpaces()) == 7: for corner in corners: if board.board[corner] == 'O' and board.board[verticalFlip[horizontalFlip[corner]]] == ' ': debug(d,'Countering Corner') return {'offensive':True, 'entry': verticalFlip[horizontalFlip[corner]]} return {'offensive':False, 'entry':''} else: return {'offensive':False, 'entry':''} elif self.tactic == 'corner': if len(board.blankSpaces()) == 9: debug(d,'Begin Corner') return {'offensive':True, 'entry':random.choice(corners)} else: if board.board['CM'] != 'O': if self.lastMove != '': if board.board[horizontalFlip[self.lastMove]] == ' ' and board.board[self.lastMove[0] + 'M'] != 'O': debug(d,'Horizontal Flip') return {'offensive':True, 'entry':horizontalFlip[self.lastMove]} elif board.board[horizontalFlip[self.lastMove]] == 'O': debug(d,'Invert') return {'offensive':True, 'entry':verticalFlip[horizontalFlip[self.lastMove]]} else: debug(d,'Vertical Flip') return {'offensive':True, 'entry':verticalFlip[self.lastMove]} if board.board['CM'] == 'O': for space in board.board: if board.board[space] == 'X' and space in corners: debug(d,'Form XOX') return {'offensive':True, 'entry':horizontalFlip[verticalFlip[space]]} else: return {'offensive':False, 'entry':''} def defensiveStrategy(self, board): '''Provide defensive move based on a certain tactic.''' if self.tactic == 'center': #Keep Selecting Corners for corner in corners: if board.board[corner] == ' ': debug(d,'Get Corners') return {'defense':True, 'entry':corner} elif self.tactic == 'corner': if board.board['CM'] == ' ': #Get Center debug(d,'Secure Center') return {'defense':True, 'entry':'CM'} else: if len(board.blankSpaces()) == 6: cornersFound = 0 for corner in corners: if board.board[corner] == 'O': cornersFound += 1 if cornersFound == 2: for side in sides: if board.board[side] == ' ': debug(d,'Two Corners') return {'defense':True, 'entry':side} else: self.strategy = 'offensive' self.tactic = 'center' self.reiterate = True debug(d,'Retrategizing') return {'defense':False, 'entry':''} elif self.tactic == 'side': if board.board['CM'] == ' ': #Get Center return {'defense':True, 'entry':'CM'} else: if len(board.blankSpaces()) == 6: report = board.fieldReport() for triad in report: if triad == ('O','X','O'): debug(d,'OXO Kill') return {'defense':True, 'entry':random.choice(corner)} return {'defense':False, 'entry':''} def think(self, board): '''Return best possible move for a given situation.''' ### Query Board for Information ### while True: report = board.fieldReport() totalDoubles = board.returnDoubles(report) oDoubles = {key:totalDoubles[key] for key in totalDoubles if 'O' in totalDoubles[key]} xDoubles = {key:totalDoubles[key] for key in totalDoubles if 'X' in totalDoubles[key]} pool = board.blankSpaces() if pool == []: return ### Check for Winning Counters ### counterMove = self.counter(xDoubles) if counterMove['counter']: self.lastMove = counterMove['entry'] return counterMove['entry'] ### Check for Losing Counters ### counterMove = self.counter(oDoubles) if counterMove['counter']: self.lastMove = counterMove['entry'] return counterMove['entry'] ### Check for Trapping Moves ### trapMove = self.trapSimulation(board, report, pool) if trapMove['trap']: self.lastMove = trapMove['entry'] return trapMove['entry'] ### Strategize ### if self.strategy == '': if len(board.blankSpaces()) == 9: self.strategy = 'offensive' else: self.strategy = 'defensive' if self.tactic == '': self.decideTactic(board) if self.strategy == 'offensive': offenseMove = self.offensiveStrategy(board) if offenseMove['offensive']: self.lastMove = offenseMove['entry'] return offenseMove['entry'] else: defenseMove = self.defensiveStrategy(board) if defenseMove['defense']: self.lastMove = defenseMove['entry'] return defenseMove['entry'] ### Random Guess ### if self.reiterate: self.reiterate = False else: debug(d,'Random Entry') entry = random.choice(pool) self.lastMove = entry return entry class debugger(): def __init__(self): self.active = True #===========HELPER FUNCTIONS===========# def nextTurn(turnList, currentTurn): currentIndex = turnList.index(currentTurn) if (currentIndex + 1) == len(turnList): return turnList[0] else: return turnList[currentIndex+1] def debug(debugObject,statement): if debugObject.active: print(statement) #===========GAME FUNCTIONS=============# d = debugger() def TicTacToe(debugging=True): if not debugging: d.active = False ###MENUS### def mainMenu(): difficulty = 'Easy' players = {} debugStatus = '' while True: if d.active: debugStatus = 'Enabled' else: debugStatus = 'Disabled' print('\t\tTic-Tac-Toe') print('\n\t1. One Player') print('\t2. Two Players') if players != {}: print('\t\tA. Rematch') print('\n\t3. Computer Difficulty:',difficulty) print('\t4. Debugging',debugStatus) selection = str(input('\nSelect Game Mode: ')) while selection not in ['1', '2', '3', '4', 'A', 'a', 'escape']: print('\nSelection Invalid') selection = str(input('\nSelect Game Mode: ')) if selection == '1': print() players = singlePlayer(difficulty) print() players = gameplay(players) elif selection == '2': print() players = multiPlayer() print() players = gameplay(players) elif selection == '3': print() if difficulty == 'Easy': difficulty = 'Medium' elif difficulty == 'Medium': difficulty = 'Hard' elif difficulty == 'Hard': difficulty = 'Impossible' else: difficulty = 'Easy' elif selection == '4': print() if d.active: d.active = False else: d.active = True elif selection in ['A','a']: if players != {}: print() players = gameplay(players) else: print('Not an Option') elif selection == 'escape': break else: raise BadInputError('Data Provided Has No Function') def singlePlayer(difficulty): '''Returns dictionary of players for singleplayer gameplay.''' players = {} newPlayer = player('play1') print('Player 1',end=' ') if not d.active: nameEntry = str(input('please enter your name: ')) while newPlayer.setName(nameEntry): print('Invalid Entry!') print('Player 1',end=' ') nameEntry = str(input('please enter your name: ')) else: newPlayer.setName("Debug") newPlayer.setSymbol('O') players['play1'] = newPlayer players['comp'] = computer(difficulty) return players def multiPlayer(): '''Returns dictionary of players for multiplayer gameplay.''' symbols = ['X','O'] players = {} for identity in ['play1','play2']: if identity == 'play1': title = 'Player 1' else: title = 'Player 2' newPlayer = player(identity) print(title,end=' ') nameEntry = str(input('please enter your name: ')) while newPlayer.setName(nameEntry): print('Invalid Entry!') print(title,end=' ') nameEntry = str(input('please enter your name: ')) if identity == 'play1': symbolEntry = str(input('O or X: ')) while newPlayer.setSymbol(symbolEntry): print('Invalid Entry!') symbolEntry = str(input('O or X: ')) symbols.remove(symbolEntry.upper()) else: newPlayer.setSymbol(symbols[0]) players[identity] = newPlayer return players def gameplay(players): '''Provides turn system for a Tic Tac Toe Game.''' if 'comp' not in players: print('Beginning Game, {} vs {}'.format(players['play1'].getName(),players['play2'].getName())) else: print('Beginning Game, {} vs the Computer'.format(players['play1'].getName())) print("Win Two Matches In a Row to Be Victorious") board = ticBoard(mode='blank') turnList = list(players.keys()) firstTurn = random.choice(turnList) #firstTurn = 'comp' turn = firstTurn selected = [] while True: board.draw() if turn != 'comp': print(players[turn].getName(),'please select a space.') selection = str(input('Space: ')).upper() if not d.active or selection not in ["RESET", "END"]: errorCheck = board.checkEntry(selection,selected) while not errorCheck['valid']: errorMessage = errorCheck['message'].format(selection) print(errorMessage) print(players[turn].getName(),'please select a space.') selection = str(input('Space: ')).upper() errorCheck = board.checkEntry(selection,selected) if selection == 'END': break else: print('Computer Turn') selection = players['comp'].think(board) errorCheck = board.checkEntry(selection,selected) print('Computer Chooses {}'.format(selection)) if selection != 'RESET' or not d.active: board.place(players[turn].getSymbol(), errorCheck['entry']) selected.append(selection) if board.checkWin() and selection != 'RESET': board.draw() selected = [] winner = turn loser = nextTurn(turnList, turn) if players[winner].getMatches() == 1: print(players[turn].getName(),end=' ') str(input('wins!')) players[turn].win() players[loser].resetMatch() players[winner].resetMatch() break #END GAME elif players[winner].getMatches() == 0: print(players[turn].getName(),end=' ') players[winner].matchWin() players[loser].resetMatch() str(input('won a match! Beginning next round.')) if 'comp' in players: players['comp'].clearStrategy() board.clear() turn = loser firstTurn = loser else: if len(board.blankSpaces()) == 0 or selection == 'RESET': board.draw() str(input('Draw! Beginning next round.')) if 'comp' in players: players['comp'].clearStrategy() players[turn].resetMatch() players[nextTurn(turnList, turn)].resetMatch() board.clear() firstTurn = nextTurn(turnList, firstTurn) turn = firstTurn selected = [] else: turn = nextTurn(turnList, turn) try: print() print('\t\tCurrent Score\n') print('\t'+players[winner].getName()+'\t\t\t'+str(players[winner].getScore())) print('\t'+players[loser].getName()+'\t\t\t'+str(players[loser].getScore())) print('\n==========================================\n') except: print('\tNo Scores to Show.\n') return players mainMenu() #Load Main Menu First activeDebug = False TicTacToe(activeDebug) #Begin Program