Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #tictactoe
- #by crazyjunkie
- 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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement