# GameInterface.py # Hyung-Joon Kim, CSE, University of Washington import Othello from re import * def instruct(): return "--------------------------- INSTRUCTION ---------------------------\n"+\ "1. You can play by typing the command like:\n\n 'Place a disc at c,3' or"+\ " 'Play at c 3' or simply 'c 3'\n\n2. You can also set up display"+\ " options by typing the command like:\n\n 'Show my move' or possible"+\ " options will be searching statistics,\n time, maxply, or coefficients"+\ " of evaluation function.\n\n3. You can keep conversation with me, too!\n"+\ "--------------------------------------------------------------------\n" ROW = 'z' COL = '-1' GAME_STARTED = False BLACK_FORFEIT, WHITE_FORFEIT = False, False WHITE_TURN = False def start(): global GAME_STARTED print "*************************************************" print "** **" print "** Welcome to Blue MoJo's Othello World. **" print "** **" print "** Programmed by Hyung-Joon Kim **" print "** University of Washington **" print "** zoony23@u.washington.edu **" print "** **" print "*************************************************" print Othello.introduce()+"\nWhen you want to start a game, tell me so. "+\ "I'm already excited about our game.\nAnyway, how are you doing today?\n" while True: the_input = raw_input('You say >> ').lower() if match('bye', the_input): print "Have a nice day!" return resp = respond(the_input) if match('START-GAME', resp) and not GAME_STARTED: GAME_STARTED = True print instruct() print "Let's start a game!\n" playGame() GAME_STARTED = False print "If you want to play one more game, type 'play'."+\ " Otherwise, type 'bye' to exit the program." else: print Othello.nickname()+" says >> "+resp def playGame(): global ROW, COL, BLACK_FORTEIT, WHITE_FORTEIT, WHITE_TURN Othello.initSettings() board = Othello.INIT_BOARD Othello.printBoard(board) # Game play main loop while True: currentBoard = Othello.copyBoard(board) WHITE_TURN = False ###### Check the board is full if Othello.getFreeCells(board) == []: print Othello.reportResult(board); return ###### Check if Black can make a legal move. If not, pass its turn. legal_cells = Othello.showLegalCells(board) if legal_cells == None: BLACK_FORFEIT = True WHITE_TURN = True print "########################################################################" print "### Black's turn is forfeited since black can't make any legal move. ###" print "########################################################################" else: BLACK_FORFEIT = False print "Available cells to move: "+legal_cells ###### Check the end of game - no one can make a legal move. if BLACK_FORFEIT and WHITE_FORFEIT: print Othello.reportResult(board) return ###### Black makes a move if not BLACK_FORFEIT: ROW = 'z'; COL = '-1' the_input = raw_input('Play or say something >> ').lower() if the_input in ['bye','exit','stop','quit']: print "White wins due to your withdrawal from the game." return elif 'show' in the_input and 'board' in the_input: Othello.printBoard(currentBoard) continue process(remove_punctuation(the_input)) if ROW != 'z' and COL != '-1': board = Othello.userMove(board, ROW, int(COL)) if board == []: print "Illegal Move. You must place a disc in the position"+\ " where it flanks my discs." board = Othello.copyBoard(currentBoard) continue else: if Othello.SHOW_MOVE: print " #### You placed a disc at "+"("+ROW.upper()+","+COL+") ####" # Update the board Othello.printBoard(board) # White(A.I player) gets turn ONLY IF Black made a move. # So, while a human make commands or conversation instead of # a move, the main loop will continue without changing the turn WHITE_TURN = True ####### Check if White can make a legal move. If not, pass its turn. if WHITE_TURN and Othello.checkForfeit(board, 'W'): WHITE_FORFEIT = True print "########################################################################" print "### White's turn is forfeited since white can't make any legal move. ###" print "########################################################################" else: WHITE_FORFEIT = False ###### White makes a move. if WHITE_TURN and not WHITE_FORFEIT: # Returning result is in form of [new board, list of stats, string comment] result = Othello.aiMove(board); aiBoard = result[0] if Othello.SHOW_MOVE: where = Othello.justPlayed(board, aiBoard) print " ##### I placed a disc at "+"("+where[0]+","+str(where[1])+") #####" # Update the board board = aiBoard; Othello.printBoard(board) if Othello.SHOW_MAXPLY: print result[1][0] if Othello.SHOW_SEARCH_STATS: print result[1][1] if Othello.SHOW_SEARCH_TIME: print result[1][2] print "\n"+Othello.nickname()+" says >> "+result[2] resp = respond(the_input) if resp != 'SILENCE': print Othello.nickname()+" says >> "+respond(the_input) ############################################################################# ## Natural-Language Interface ############################################################################# # Some regular expressions used to parse the user sentences: index_command_pattern = compile(r"^([a-h0-7])\s+([a-h0-7])*$", IGNORECASE) play_command_pattern = \ compile(r"^(place|put|leave|release|drop)\s+(a|the|my)\s+(disc|piece|stone)\s"+\ "(at|on|in|into|to)\s+([a-h0-7])\s+([a-h0-7])*$", IGNORECASE) short_play_command_pattern = compile(r"^(play)\s+(at|on|in|into|to)\s+([a-h0-7])\s+([a-h0-7])*$", IGNORECASE) set_command_pattern = compile(r"^(set)\s+(a|the)\s+(depth|maxply|level)\s+(to)\s+([1-9][0-9]*)*$", IGNORECASE) def process(info) : 'Handles the user sentence, matching and responding.' result_match_object = index_command_pattern.match(info) if result_match_object != None : items = result_match_object.groups() assignIndex(items[0], items[1]) print "Input: "+str(items) print "I understand your command." return result_match_object = play_command_pattern.match(info) if result_match_object != None : items = result_match_object.groups() assignIndex(items[4], items[5]) print "Input: "+ str(items) print "I understand your command." return result_match_object = short_play_command_pattern.match(info) if result_match_object != None : items = result_match_object.groups() assignIndex(items[2], items[3]) print "Input: "+str(items) print "I understand your command." return result_match_object = set_command_pattern.match(info) if result_match_object != None : items = result_match_object.groups() print str(items) if int(items[4]) > 10: print "Maxply is allowed up to 10." else: Othello.setMaxPly(int(items[4])) print "Input: "+str(items) print "I understand your command." return result_match_object = optionCommand(info) if result_match_object != None : print result_match_object print "I understand your command." return print "I do not understand your command. You entered: "+info def assignIndex(x, y): global ROW, COL if x in ['a','b','c','d','e','f','g','h']: ROW = x; COL = y else: ROW = y; COL = x def optionCommand(wordlist): wordlist = split(' ', wordlist) if wordlist[0] in ['show','display']: if 'time' in wordlist[1:]: Othello.showTime(True) return "Show time option is turned on." else: return anyOption(wordlist[1:]) MOVE_OPTION = ['move','movement','play'] STATS_OPTION = ['stats','statistics','results'] MAXPLY_OPTION = ['maxply', 'depth'] EVAL_FUNC_OPTION = ['coefficient','function','evaluation','weight'] def anyOption(wordlist): for word in MOVE_OPTION: if word in wordlist: Othello.showMove(True) return "Show move option is turned on." for word in STATS_OPTION: if word in wordlist: Othello.showStats(True) return "Show stats option is turned on." for word in MAXPLY_OPTION: if word in wordlist: Othello.showMaxPly(True) return "Show maxPly option is turned on." for word in EVAL_FUNC_OPTION: if word in wordlist: for a in Othello.WEIGHT: print a; print return "Show evaluation coefficients option is turned on." return None NUM_TURN = 0 def respond(the_input): global NUM_TURN, GAME_STARTED NUM_TURN += 1 wordlist = split(' ', remove_punctuation(the_input)) wordlist[0]=wordlist[0].lower() mapped_wordlist = you_me_map(wordlist) mapped_wordlist[0]= mapped_wordlist[0].capitalize() if wordlist[0]=='': return "Please say something." if start_game(wordlist) and not GAME_STARTED: return "START-GAME" # Check if any positive/negative adjectives are included if any_word(wordlist, POS_WORDS): return rand_resp() # Random response if any_word(wordlist, NEG_WORDS): return alt_resp(1) # Cycling alternative response #1 # Check if the agent got a question regarding the topic word if (wpred(wordlist[0]) or wordlist[0] == 'what') and find_topic_word(wordlist) != False: return "Hmmm, it's a secret. I'll tell you after playing a game." # Check if the agent got a question if wpred(wordlist[0]): return alt_resp(2) # Cycling alternative response #2 if wordlist[0:2] == ['i','have']: return "How long have you had " + stringify(mapped_wordlist[2:]) + '?' if 'because' in wordlist: return "Yeah, but that might not be the reason." if 'yes' in wordlist: return "Are you sure? Tell me the reason." if wordlist[0:2] == ['you','are']: return "Oh yeah, I am " + stringify(mapped_wordlist[2:]) + '.' if verbp(wordlist[0]): return "I will " + stringify(mapped_wordlist).lower() +\ " after finishing our game." if wordlist[0:3] == ['do','you','think'] and wordlist[3].lower() == 'i': return "You bet! " + stringify(mapped_wordlist[3:]) + '.' if wordlist[0:2]==['can','you'] or wordlist[0:2]==['could','you']: return "Absolutely, I " + wordlist[0] + ' ' +\ stringify(mapped_wordlist[2:]) + '.' # Memory feature : detect the presence of any word in the given topic # and remember the word for the latter conversation if find_topic_word(wordlist) != False: return alt_resp(3) # Cycling alternative response #3 if 'no' in wordlist: return "Why not?" if 'maybe' in wordlist: return "Tell me yes or no! I'm not smart enough, man." # Memory feature : in case none of the above production rules fires, # the word which is mentioned earlier will be used to continue # the conversation. At least 5 turns of dialog should be passed # in order for the topic word to be used again. if len(MENTIONED) > 0 and (NUM_TURN)%6 == 1: return "By the way, you mentioned your " + MENTIONED + " earlier." +\ " Tell me more about that." if ('you' in mapped_wordlist or 'You' in mapped_wordlist) and\ not '?' in mapped_wordlist and len(wordlist) < 8: return stringify(mapped_wordlist) + '? ' +\ alt_resp(4) # Cycling alternative response #4 if not GAME_STARTED: return punt() return 'SILENCE' START_COMMAND = ['start','play','begin'] def start_game(wordlist): for word in START_COMMAND: if word in wordlist: return True return False # Topic words which characterize the agent TOPIC_WORDLIST = ['A.I.', 'player', 'program', 'artificial', 'Othello', 'game', 'human', 'computer'] MENTIONED = '' # The function which enables the memory feature def find_topic_word(wordlist): '''Find the first occurence of a topic word in the input words. Remember which word is found by assigning it to the global variable 'MENTIONED'. Return the word if such a word was found, otherwise False.''' for word in TOPIC_WORDLIST: if word in wordlist: global MENTIONED MENTIONED = word return word return False # List of alternative responses alt_resps1 = [] alt_resps1.append("What's wrong?") alt_resps1.append("Tell me more if you don't mind.") alt_resps1.append("Don't be so negative. You're okay.") alt_resps2 = [] alt_resps2.append("Do you think I can answer that?") alt_resps2.append("I don't know. You tell me, now.") alt_resps2.append("Who in the word can answer that?") alt_resps3 = [] alt_resps3.append("I love to play a game with human.") alt_resps3.append("I promise I\'m a dumb A.I. player, really.") alt_resps3.append("If the program crash during the game, don\'t blame me, but"+\ " H.J. Kim who programmed this.") alt_resps4 = [] alt_resps4.append("I\'m confused. Tell me more.") alt_resps4.append("Please go on.") alt_resps4.append("I see. Just tell me more.") # List of random responses rand_resps = [] rand_resps.append("Good to hear that.") rand_resps.append("Good, so what do you want to know from me?") rand_resps.append("Good, are you excited about our game?") rand_resps.append("Don't lie to me. Tell me the truth.") rand_resps.append("Hey man, be honest.") # Index variables for cycling alternative responses RES_NUM1 = 0 RES_NUM2 = 0 RES_NUM3 = 0 RES_NUM4 = 0 # Return a cycling alternative response def alt_resp(which): global RES_NUM1 global RES_NUM2 global RES_NUM3 global RES_NUM4 if which == 1: RES_NUM1 += 1 if RES_NUM1 == 3: # all alt. resp. used, so reset the cycling index RES_NUM1 = 0 return alt_resps1[RES_NUM1] elif which == 2: RES_NUM2 += 1 if RES_NUM2 == 3: RES_NUM2 = 0 return alt_resps2[RES_NUM2] elif which == 3: RES_NUM3 += 1 if RES_NUM3 == 3: RES_NUM3 = 0 return alt_resps3[RES_NUM3] else : RES_NUM4 += 1 if RES_NUM4 == 3: RES_NUM4 = 0 return alt_resps4[RES_NUM4] from random import * # Return a random response def rand_resp(): return rand_resps[randint(1,100)%len(rand_resps)] import string def stringify(wordlist): 'Create a string from wordlist, but with spaces between words.' return string.join(wordlist) punctuation_pattern = compile(r"\,|\.|\?|\!|\;|\:") def remove_punctuation(text): 'Returns a string without any punctuation.' return sub(punctuation_pattern,'', text) def wpred(w): 'Returns True if w is one of the question words.' return (w in ['when','why','where','how','who']) def dpred(w): 'Returns True if w is an auxiliary verb.' return (w in ['do','can','should','would']) PUNTS = ['Keep going. I\'m listeing.', 'By the way, are you good at playing Othello?', 'Why don\'t we start a game?', 'Let\'t play a game!', 'Have you ever played a game with an A.I. player like me?', 'If you beat me, you must be a pretty good player.'] punt_count = 0 def punt(): 'Returns one from a list of default responses.' global punt_count punt_count += 1 return PUNTS[punt_count % 6] CASE_MAP = {'i':'you', 'I':'you', 'me':'you','you':'me', 'my':'your','your':'my', 'yours':'mine','mine':'yours','am':'are'} def you_me(w): 'Changes a word from 1st to 2nd person or vice-versa.' try: result = CASE_MAP[w] except KeyError: result = w return result def you_me_map(wordlist): 'Applies YOU-ME to a whole sentence or phrase.' return map(you_me, wordlist) def verbp(w): 'Returns True if w is one of these known verbs.' return (w in ['go', 'have', 'be', 'try', 'eat', 'take', 'help', 'make', 'get', 'jump', 'write', 'type', 'fill', 'put', 'turn', 'compute', 'think', 'drink', 'blink', 'crash', 'crunch', 'add']) # Lists of adjectives which expresse the feeling POS_WORDS = ['excellent','awesome','great','good','fine','nice'] NEG_WORDS = ['terrible', 'horrible', 'awful', 'bad'] def any_word(w, wordlist): '''Return True if any of words in w is found in wordlist. Otherwise, return False.''' for word in w: if word in wordlist: return True return False start() # Start the game