#!/usr/bin/python3 # Copyright (C) 2014 by Steve Litt # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import sys import re import random import time def diaprint(s): print(s) def output(s): print(s) def squaretype(squarenum): types = [ 'illegallo', 'corner', 'side', 'corner', 'side', 'center', 'side', 'corner', 'side', 'corner', 'illegalhi' ] return(types[squarenum]) def makeboard(): board = {'squares': [{},{},{},{},{},{},{},{},{},{},{}], 'other': {}} board['squares'][2]['sqtype'] = 'hux' for sqnum in range(0,11): board['squares'][sqnum]['sqtype'] = squaretype(sqnum) board['squares'][sqnum]['slot'] = '' board['other']['winner'] = 'nobody' board['other']['level'] = 0 return board def human_choose_xy(board): #board['other']['xplayer'] = 'human' #return(board) while True: output('First, or second (x or o), please type x or o ...\n') choice = sys.stdin.readline() choice = choice.strip().lower() if choice == 'x' or choice == 'o': break; board['other']['humanrole'] = choice if choice == 'x': board['other']['xplayer'] = 'human' else: board['other']['xplayer'] = 'machine' return board def slot_taken(board, num): slot = board['squares'][num]['slot'] if slot == 'X' or slot == 'O': diaprint('dia slot {} taken with {}'.format(str(num), str(slot))) return True else: diaprint('dia slot {} available'.format(str(num))) return False def slot_available(board, num): return not slot_taken(board, num) def machine_starting_move(board): ### DANGER, IF YOU REMOVE FOLLOWING 2 LINES, ### YOU MUST ALSO ADD *MANY* MORE CASES TO scan_and_exploit()!!! board['squares'][1]['slot'] = 'X' return board random.seed() n = random.randint(0,99) if n < 10: sqtype = 'side' elif n < 40: sqtype = 'center' else: sqtype = 'corner' alts=[] for ss in range(1,10): if board['squares'][ss]['sqtype'] == sqtype: alts.append(ss) n = random.randint(0,len(alts)-1) position = alts[n] board['squares'][position]['slot'] = 'X' board['other']['level'] += 1 return board def print_board(board): diaprint('Dia level is {}'.format(str(board['other']['level']))) guide_string = """ 1 2 3 4 5 6 7 8 9 """ matrix_string = """ | | 1 | 2 | 3 | | ------------------------ | | 4 | 5 | 6 | | ------------------------ | | 7 | 8 | 9 | | """ for sqnum in range(1,10): mark = board['squares'][sqnum]['slot'] if mark == 'X' or mark == 'O': matrix_string = re.sub(str(sqnum), mark, matrix_string) matrix_string = re.sub('[123456789]', ' ', matrix_string) output(guide_string) output(matrix_string) def win_positions(): return [ [1,2,3], [4,5,6], [7,8,9], [1,4,7], [2,5,8], [3,6,9], [1,5,9], [3,5,7] ] def deduce_winner(board): winner = 'nobody' winlines = win_positions() for player in {'X', 'O'}: for winline in winlines: hits = 0 for slot in winline: if board['squares'][slot]['slot'] == player: hits += 1 if hits > 2: winner = player return player return 'nobody' def near_wins(board): near_wins_x= [] near_wins_y= [] winlines = win_positions() for winline in winlines: squares = {'X': [], 'O': [], 'nobody': []} for slot in winline: player = board['squares'][slot]['slot'] if player == '': player = 'nobody' squares[player].append(slot) if len(squares['X']) == 2 and len(squares['nobody']) == 1: near_wins_x.append(squares['nobody'][0]) elif len(squares['O']) == 2 and len(squares['nobody']) == 1: near_wins_y.append(squares['nobody'][0]) return {'X': near_wins_x, 'O': near_wins_y} def fudge_data(board): for i in {1,5,9}: board['squares'][i]['slot'] = 'O' return board def machines_symbol(board): if board['other']['xplayer'] == 'machine': return 'X' else: return 'O' def humans_symbol(board): if board['other']['xplayer'] == 'machine': return 'O' else: return 'X' def abort(premsgs, exitcode = 1, postmsgs=None): for msg in premsgs: errprint(msg) errprint('ABORTING!') errprint('\nHit Enter after reading=>') if postmsgs: for msg in postmsgs: errprint(msg) sys.exit(exitcode) def opposite_symbol(symbol): if symbol == 'X': return 'O' elif symbol == 'O': return 'X' else: abort(['Bad symbol passed to opposite_symbol']) def numfilled(board): num = 0 for square in board['squares']: if square['slot'] == 'X' or square['slot'] == 'O': num += 1 return num def squares_filled(board): filled = 0 for square in board['squares']: if square['slot'] == 'X' or square['slot'] == 'O': filled += 1 return filled def all_filled_up(board): if squares_filled(board) == 9: return True else: return False def board2string(board): strng = '' for ss in range(1,10): mark = board['squares'][ss]['slot'] if mark == 'X' or mark == 'O': strng = strng + mark else: strng = strng + str(ss) return strng def best_guess(board): diaprint('dia top best_guess()') if slot_available(board, 5): return 5 elif slot_available(board, 1): return 1 elif slot_available(board, 3): return 3 elif slot_available(board, 7): return 7 elif slot_available(board, 9): return 9 else: for i in range(1,10): if slot_available(board, i): return i abort(['FATAL ERROR: best_guess() failed to find available slot.']) def scan_and_exploit(board): bstr = board2string(board) diaprint('dia scan_and_exploit bstr={}.'.format(bstr)) if board['other']['xplayer'] == 'machine': ### MACHINE'S SECOND MOVE if bstr == 'X234O6789': #human countered in center return 9 elif bstr == 'X2O456789' or bstr == 'X23456O89': #human countered in adjacent corner, he will lose output('LOL, human, you will lose :-)') return 9 elif bstr == 'X2345678O': #human countered at opposite corner, he will lose output('LOL, human, you will lose :-)') return 7 elif bstr == 'XO3456789' or bstr == 'X2345O789' or bstr == 'X234567O9': # human countered on side other than left, he loses output('LOL, human, you will lose :-)') return 7 elif bstr == 'X23O56789': # human countered on left side, he loses output('LOL, human, you will lose :-)') return 3 ### MACHINE'S THIRD MOVE # If human countered at adjacent corner, machine defense alone will beat him, no code necessary here elif bstr == 'XO3O56X89' or bstr == 'X23O5OX89' or bstr == 'X23O56XO9': # human countered on side other than left, he loses output('LOL, human, you will lose :-)') return 5 elif bstr == 'XOXO56789': # human countered on left side, he loses output('LOL, human, you will lose :-)') return 5 elif bstr == 'X23O56X8O': # human countered on left side, he loses return 3 elif bstr == 'OXX456789': # human countered with corner return 7 else: diaprint('dia calling best_guess()') return best_guess(board) elif board['other']['xplayer'] == 'human': if bstr == 'X23456789' or bstr == '12X456789' or bstr == '123456X89' or bstr == '12345678X': return 5 elif bstr == '1234X6789': return 1 elif bstr == '1X3456789' or bstr == '123X56789' or bstr == '12345X789' or bstr == '1234567X9': return 5 elif bstr == 'X234O678X' or bstr == '12X4O6X89': return 2 else: diaprint('dia calling best_guess()') return best_guess(board) else: abort(['Invalid xplayer found in scan_and_exploit()']) pass def is_valid_human_move(board, choice): try: choice = int(choice) except ValueError: return False if not choice in range(1,10): return False slot = board['squares'][choice]['slot'] if slot == 'X' or slot == 'O': return False else: return True def machine_moves_now(board): diaprint('Dia machine_move, level is {}.'.format(str(board['other']['level']))) if all_filled_up(board): return(board) elif board['other']['winner'] != 'nobody': return(board) print_board(board) nearwins = near_wins(board) symbol = machines_symbol(board) ### GLOAT IF HUMAN COUNTERS DIAGANAL XOX WITH A CORNER st = board2string(board) if st == 'X2O4O678X' or st == 'X234O6O8X': output('SORRY human, your last move doomed you to loss!') ### MACHINE WINS IF IT CAN if len(nearwins[symbol]) > 0: machinemove = nearwins[symbol][0] ### MACHINE BLOCKS IF IT MUST elif len(nearwins[opposite_symbol(symbol)]) > 0: machinemove = nearwins[opposite_symbol(symbol)][0] ### ELSE LOOK FOR PATTERNS AND EXPLOIT else: machinemove = scan_and_exploit(board) diaprint('dia scan_and_exploit returned {}'.format(str(machinemove))) board['squares'][machinemove]['slot'] = symbol winner = deduce_winner(board) board['other']['winner'] = winner if winner == 'nobody': board['other']['level'] += 1 board = let_human_move(board) diaprint('dia huxtable machine') board['other']['level'] -= 1 return board def let_human_move(board): diaprint('Dia human_move, level is {}.'.format(str(board['other']['level']))) if all_filled_up(board): return(board) elif board['other']['winner'] != 'nobody': return(board) while True: output('Human: you are {}'.format(humans_symbol(board))) print_board(board) output('Human: pick a square...\n') choice = sys.stdin.readline() if is_valid_human_move(board, choice): break else: output('BAD CHOICE HUMAN, TRY AGAIN!!!') ## BY NOW YOU HAVE A VALID CHOICE choice = int(choice) board['squares'][choice]['slot'] = humans_symbol(board) winner = deduce_winner(board) board['other']['winner'] = winner if winner == 'nobody': board['other']['level'] += 1 board = machine_moves_now(board) board['other']['level'] -= 1 diaprint('dia huxtable machine') return board def one_game(): for i in range(1,10): output('##################################################################') board = makeboard() board = human_choose_xy(board) output('Winner is {}'.format(deduce_winner(board))) if board['other']['xplayer'] == 'machine': board = machine_starting_move(board) board = let_human_move(board) else: board = let_human_move(board) diaprint('dia1') output(board) diaprint('dia2') winner = board['other']['winner'] print_board(board) output('The winner is {}!!!!'.format(winner)) if humans_symbol(board) == winner: sympathy = 'CONGRATULATIONS' outcome = 'WON' elif winner == 'nobody': sympathy = 'MEH' outcome = 'TIED (cats game)' else: sympathy = 'SO SORRY' outcome = 'LOST' output('{}, human, you {}.'.format(sympathy, outcome)) for ss in range(1,2): one_game() output('\n\n')