#!/usr/bin/python3 # Copyright (C) 2014 by Steve Litt and Barry Fishman # # 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 errprint(s): print(s) 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 swap_symbol(sym): if sym == 'X': return 'O' elif sym == 'O': return 'X' else: abort(['swap_symbol() was passed illegal symbol {}'.format(sym)]) def display_board(current_symbol, apos, bpos): guide_string = """ 1 2 3 4 5 6 7 8 9 """ matrix_string = """ | | 1 | 2 | 3 | | ------------------------ | | 4 | 5 | 6 | | ------------------------ | | 7 | 8 | 9 | | """ for pos in apos: matrix_string = re.sub(str(pos), current_symbol, matrix_string) for pos in bpos: matrix_string = re.sub(str(pos), swap_symbol(current_symbol), matrix_string) matrix_string = re.sub('[123456789]', ' ', matrix_string) output('\n\n') output(guide_string) output(matrix_string) def human_move(current_symbol, apos, bpos): possible_moves = [] for i in range(0,10): possible_moves.append(True) for pos in apos: possible_moves[pos] = False for pos in bpos: possible_moves[pos] = False possible_moves[0] = False output('Human: you are {}'.format(current_symbol)) output('Human: pick a square...\n') choicestring = sys.stdin.readline().strip() if choicestring in ['1', '2', '3','4', '5', '6', '7', '8', '9']: choice = int(choicestring) else: choice = 0 if not possible_moves[choice]: output('Invalid choice ({}), try again.'.format(str(choicestring))) apos = human_move(current_symbol, apos, bpos) else: apos.append(choice) return sorted(apos) def is_win(pos, wincombos = 'init'): if wincombos == 'init': wincombos = [ [1,2,3], [4,5,6], [7,8,9], [1,4,7], [2,5,8], [3,6,9], [1,5,9], [3,5,7] ] if len(wincombos) == 0: return False; else: wincombo = wincombos.pop() winflag = True for square in wincombo: if not square in pos: winflag = False if winflag: return True return is_win(pos, wincombos) def is_tie(apos, bpos): for i in range(1,10): if not i in apos and not i in bpos: return False return True def is_almost_win(apos, bpos, wincombos = 'init'): if wincombos == 'init': wincombos = [ [1,2,3], [4,5,6], [7,8,9], [1,4,7], [2,5,8], [3,6,9], [1,5,9], [3,5,7] ] opportunity = -101 if len(wincombos) == 0: return 11; wincombo = wincombos.pop() still_eligible = True; for square in wincombo: if square in bpos: still_eligible = False; if still_eligible: hits = 0 for square in wincombo: if square in apos: hits += 1 else: opportunity = square if hits == 3: # already won opportunity = 100 still_eligible = False elif hits > 1: # almost won still_eligible = True else: opportunity = 50 still_eligible = False if opportunity > 0 and opportunity < 10: return opportunity return is_almost_win(apos, bpos, wincombos) def positions2string(apos, bpos): string = '123456789' for square in apos: string = re.sub(str(square), 'A', string) for square in bpos: string = re.sub(str(square), 'B', string) return string def is_available(n, apos, bpos): if n in apos or n in bpos: return False elif n > 0 and n < 10: return i for i in sides: if is_available(i, apos, bpos): return i abort(['FAILURE IN is_available(), dropped through.']) def best_guess(apos, bpos): # CENTER IS BEST if not 5 in apos and not 5 in bpos: return 5 # SECOND BEST IS CORNERS if 1 not in apos and 1 not in bpos: return 1 if 3 not in apos and 3 not in bpos: return 3 if 7 not in apos and 7 not in bpos: return 7 if 9 not in apos and 9 not in bpos: return 9 # SIDES ARE A LAST RESORT if 2 not in apos and 2 not in bpos: return 2 if 4 not in apos and 4 not in bpos: return 4 if 6 not in apos and 6 not in bpos: return 3 if 8 not in apos and 8 not in bpos: return 3 # DROPPING THROUGH IS AN ERROR, THE GAME'S ALREADY WON abort(['ERROR in best_guess: Dropped through, game already won ({}).'.format(str(positions2string(apos, bpos)))]) def scan_and_exploit(apos, bpos): pattern = positions2string(apos, bpos) # MACHINE STARTS, OPENING MOVE if len(apos) == 0 and len(bpos) == 0: apos.append(1) return sorted(apos) # HUMAN STARTED, MACHINE'S FIRST MOVE if len(apos) == 0 and len(bpos) == 1: if 5 in bpos: apos.append(1) else: apos.append(5) return sorted(apos) # MACHINE STARTED, MACHINE'S 2ND MOVE if len(apos) == 1 and len(bpos) == 1: if 5 in bpos: # human did the smart thing apos.append(9) elif 3 in bpos or 7 in bpos: # human countered on adjacent corner output('LOL, human, you will lose :-)') apos.append(9) elif 9 in bpos: # human countered in opposite corner output('LOL, human, you will lose :-)') apos.append(7) elif 4 in bpos: # human countered on left side output('LOL, human, you will lose :-)') apos.append(3) elif 2 in bpos or 6 in bpos or 8 in bpos: # human countered on other side output('LOL, human, you will lose :-)') apos.append(7) else: abort(['Machine start, machine 2nd move, pattern={}, should not have gotten here.'.format(pattern)]) return sorted(apos) # HUMAN STARTED, MACHINE'S 2ND MOVE # NOTE, IT NEVER GOT HERE UNLESS NOT DEFENSIVELY BLOCKED BY machine_move() if len(apos) == 1 and len(bpos) == 2: pattern = positions2string(apos, bpos) if pattern == 'A234B678B': #diagonal with human in center and opposite corner apos.append(3) # all other human at center handled with blocks elif pattern == 'B234A678B': apos.append(2) # diagonal HMH, go to side elif pattern == 'B234AB789': apos.append(3) # diagonal HMH, go to side elif pattern == 'B234A67B9': apos.append(7) # diagonal HMH, go to side elif pattern == '1B3BA6789': apos.append(1) # human occupies 2 adjacent sides, upperleft elif pattern == '1B34AB789': apos.append(3) # human occupies 2 adjacent sides, upperright elif pattern == '1234AB7B9': apos.append(9) # human occupies 2 adjacent sides, lowerright elif pattern == '1B34A67B9': apos.append(7) # human occupies 2 adjacent sides, lowerleft else: abort(['Human start, machine 2nd move, pattern={}, should not have gotten here.'.format(pattern)]) return sorted(apos) # MACHINE STARTED, MACHINE'S 3RD MOVE if len(apos) == 2 and len(bpos) == 2: pattern = positions2string(apos, bpos) if pattern == 'A23B56A8B': # human earlier countered on opposite corner apos.append(3) elif pattern == 'ABAB56789': # human earlier countered on left side apos.append(9) elif pattern == 'AB3B56A89' or pattern == 'A23B5BA89' or pattern == 'A23B56AB9': #human countered on other side apos.append(9) else: abort(['Machine start, machine 3rd move, pattern={}, should not have gotten here.'.format(pattern)]) return sorted(apos) # DROPPED THROUGH, GUESS apos.append(best_guess(apos, bpos)) return sorted(apos) def machine_move(current_symbol, apos, bpos): # WIN IF YOU CAN square = is_almost_win(apos, bpos) if square > 0 and square < 10: apos.append(square) return sorted(apos) # BLOCK IF YOU MUST square = is_almost_win(bpos, apos) # is human ready to win? if square > 0 and square < 10: # if so apos.append(square) # you block return sorted(apos) apos = scan_and_exploit(apos, bpos) return sorted(apos) def get_human_symbol(): output('Human, do you want to go first (x) or second (o)? Type answer please...') choicestring = sys.stdin.readline().strip().upper() if choicestring == 'O' or choicestring == 'X': return(choicestring) print('Human, x and o are your only choices.') return get_human_symbol() def play(current_symbol, apos, bpos, movea, moveb): display_board(current_symbol, apos, bpos) apos_new = movea(current_symbol, apos, bpos) if is_win(apos_new): display_board(current_symbol, apos_new, bpos) return current_symbol elif is_tie(apos_new, bpos): display_board(current_symbol, apos_new, bpos) return 'nobody' else: return play(swap_symbol(current_symbol), bpos, apos_new, moveb, movea) def play_one_game(): for i in range(1,4): print('#############') human_symbol = get_human_symbol() if human_symbol == 'X': winner = play('X', [], [], human_move, machine_move) elif human_symbol == 'O': winner = play('X', [], [], machine_move, human_move) else: abort(['ERROR, human_symbol ({}) is wrong in play_one_game(), aborting.'.format(str(human_symbol))]) output('{} won!'.format(str(winner))) if winner == 'nobody': output('Human: MEH, you tied (cats game).') elif winner == human_symbol: output('CONGRATULATIONS human, you won.') elif winner == swap_symbol(human_symbol): output('SO SORRY human, you lost.') else: output('INTERNAL ERROR, {} won.'.format(winner)) while True: play_one_game()