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@amk.ca)
# 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<original_length/2 or new_length==1):
print "Good question."
else:
print "I don't understand the question."
# Print the instructions
print """This is a guessing game about capital letters.
You can ask various questions about the features of the letter:
curves, loose ends, obliques (or diagonals), horizontals, verticals.
To make a guess, just enter the letter of your choice.
Sample transcript:
Next? curves?
1.
Good question.
Next? c
You don't have enough information yet.
How do you know it isn't s, for example?
Next? horizontals?
0.
Next? s
You don't have enough information yet.
How do you know it isn't c, for example?
"""
# Play a single game
play_once()
raw_input("Press Return>")
# 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?