import whrandom, string # Logic game # From a program by Judith Haris, John Swets, and Wallace Feurzeig # Reference: The Secret Guide to Computers, by Russ Walter, 18th ed 1993. # Written in Python by A.M. Kuchling (amk@magnet.com) # For each letter, we need the various characteristics: # (curves, loose ends, obliques, horizontals, verticals). # There should really be a sample character set for the user to look # at; otherwise, there are ambiguities. For example, does B have # horizontals? Does D? How about P and R? # There's a bug lurking in this data! Can you catch it? # (See the bottom of the program for the answer.) letter_stats={'a': (0, 2, 2, 1, 0), 'b':(2, 0, 0, 3, 1), 'c': (1, 2, 0, 0, 0), 'd':(1, 0, 0, 0, 1), 'e': (0, 3, 0, 3, 1), 'f':(0, 3, 0, 2, 1), 'g': (1, 2, 0, 1, 1), 'h':(0, 4, 0, 1, 2), 'i': (0, 2, 0, 0, 1), 'j':(1, 2, 0, 0, 1), 'k': (0, 4, 2, 0, 1), 'l':(0, 2, 0, 1, 1), 'm': (0, 2, 2, 0, 2), 'n':(0, 2, 1, 0, 2), 'o': (1, 0, 0, 0, 0), 'p':(1, 1, 0, 2, 1), 'q': (1, 2, 1, 0, 0), 'r':(1, 2, 1, 0, 1), 's': (1, 2, 0, 0, 0), 't':(0, 3, 0, 1, 1), 'u': (1, 2, 0, 0, 2), 'v':(0, 2, 2, 0, 0), 'w': (0, 2, 4, 0, 0), 'x':(0, 4, 2, 0, 0), 'y': (0, 3, 2, 0, 1), 'z':(0, 2, 1, 2, 0)} # We'll define constants for the various statistics; each constant is # equal to the position of the statistic in the tuples in #letter_stats. CURVES=0 ; LOOSE_ENDS=1 ; OBLIQUES=2 ; HORIZONTALS=3 ; VERTICALS=4 # This dictionary is used to map questions to corresponding # statistics. Note that different keys can map to the same value; # for example, 'obliques' and 'diagonals' both map to the OBLIQUES constant. questions={'curves':CURVES, 'looseends':LOOSE_ENDS, 'obliques':OBLIQUES, 'diagonals':OBLIQUES, 'horizontals':HORIZONTALS, 'verticals':VERTICALS} # Play a single game def play_once(): # Choose a random number between 0 and 26, inclusive. choice=26*whrandom.random() # Convert the numeric choice to a letter: 0->a, 1->b, etc. choice=chr(ord('a')+choice) #choice=raw_input("What should I choose?") # (for debugging) # We'll track how many possibilities the user still has available. # Start with all of the letters. possibilities=string.lower("ABCDEFGHIJKLMNOPQRSTUVWXYZ") # We'll also track which questions have been asked, and chide the # user when he repeats a question. asked=[] # Loop forever; the play_once() function will exit by hitting a # 'return' statement inside the loop. while (1): try: #print possibilities # (for debugging) # Get input from the user query=raw_input('Next? ') # Convert the input to lowercase query=string.lower(query) # Remove all non-letter characters query=filter(lambda x: x in string.lowercase, query) # Remove whitespace query=string.strip(query) except (EOFError, KeyboardInterrupt): # End-Of-File : the user print '\nOK; give up if you like.' return if len(query)==1: # The query is one character long, so it's a guess if query not in possibilities: print ("Wrong! That guess is inconsistent " "with the information you've been given.\n" "I think you made that guess just to see " "what I would say.") elif len(possibilities)>1: print "You don't have enough information yet." # Temporarily remove the user's guess from # possibilities, and pick a random letter. temp=filter(lambda x, query=query: x!=query, possibilities) r=int(whrandom.random()*len(temp)) print "How do you know it isn't", temp[r]+',', print "for example?" else: # query is in possibilities, and # len(possibilities)==1, so the user is right. print "Yes, you've done it. Good work!" ; return elif questions.has_key(query): # Get the field of the letter_stats tuple to compare. field=questions[query] # Determine the answer for the computer's letter result=letter_stats[choice][field] original_length=len(possibilities) # Exclude possibilities that don't match those of the # mystery letter. # filter(func, sequence) calls func() on each element in # the sequence, and returns a new sequence object # containing only elements for which func() returned true. # For strings, each character is an element. Instead of # defining a formal function, a lambda is used to create # an anonymous function (one without a name). # Various other things required by the function are set # as default arguments, so they're accessible inside the # scope of the anonymous function. possibilities=filter(lambda letter, letter_stats=letter_stats, field=field, result=result: letter_stats[letter][field]==result, possibilities) new_length=len(possibilities) if field in asked: print "You asked me that already." print "The answer is the same as before:", else: asked.append(field) # Note that this question was asked. print str(result)+'.' if (original_length==new_length): print 'That was a wasted question; it did not exclude any possibilities.' elif (new_length") # The solution to the bug-hunt is below... # It's not a bug that the Python interpreter can catch; instead, it's # a specification bug: # # 'C' and 'S' both have the same stats: 1 curve, 2 loose ends, # and no obliques, horizontals, or verticals. If either C or S is # chosen as the computer's letter, the user can never get the right # answer, because he/she can't narrow down the possibilities to just # one! To fix this, you'd have to add another statistic, like # number of intersections or number of closed loops. However, the # statistic would have to be *different* for 'C' and 'S', and neither # of those two suggestions qualify. Can you think of a property to # distinguish between the two letters?