Wednesday, August 5, 2009

tic tac toe


My son and his cousins have occasionally expressed an interest in programming (they are 2*13 + 15). But what they mean is, they want to make a game like, say, Halo. And when I explain that GUIs take a bit of doing, not to mention AI, they go back to reading Harry Potter or whatever.

I did play with Scratch. Check it out, it's fun. But I think it gets awkward after a while.

I thought, so OK, let's do a game with the command-line for IO. And I adapted my tic-tac-toe program to this goal. It is short and simple. Rather than read the code, however, I urge you to make your own version.

Here is the printout:

.  .  .      1  2  3
. . . 4 5 6
. . . 7 8 9

Please pick a valid move,
or q(quit) or r(reset) -> 1
You picked move = 1

P . . 1 2 3
. . . 4 5 6
. . . 7 8 9

Computer picked move = 7

P . . 1 2 3
. . . 4 5 6
C . . 7 8 9

Please pick a valid move,
or q(quit) or r(reset) -> 9
You picked move = 9

P . . 1 2 3
. . . 4 5 6
C . P 7 8 9

player has a winning move in [1, 5, 9]
returning 4
Computer picked move = 5

P . . 1 2 3
. C . 4 5 6
C . P 7 8 9

Please pick a valid move,
or q(quit) or r(reset) -> 3
You picked move = 3

P . P 1 2 3
. C . 4 5 6
C . P 7 8 9

player has a winning move in [1, 2, 3]
returning 1
Computer picked move = 2

P C P 1 2 3
. C . 4 5 6
C . P 7 8 9

Please pick a valid move,
or q(quit) or r(reset) -> 6
You picked move = 6

P C P 1 2 3
. C P 4 5 6
C . P 7 8 9

You won! :)

Want to play again?
Please pick a valid move,
or q(quit) or r(reset) -> q


Since I grafted the new IO on top of the game logic, I think that it would benefit from a redesign but I haven't done that yet. Here is the code (128 lines, with blanks). For the future: make the logic cleaner. Also, recognize when a game must be drawn, and bail.


import sys, random, time

rows = [ [0,1,2], [3,4,5], [6,7,8] ]
cols = [ [0,3,6], [1,4,7], [2,5,8] ]
diags = [ [0,4,8], [2,4,6] ]
corners = [0,2,6,8]
all = rows + cols + diags

def printBoard(bL):
print
for i,e in enumerate(bL):
if i and not i % 3:
if i == 3: print ' 1 2 3'
elif i == 6: print ' 4 5 6'
if e: print e + ' ',
else: print '. ',
print ' 7 8 9'
print

def openCorners(bL):
rL = list()
for i in corners:
if not bL[i]: rL.append(i)
return rL

#------------------------------------------

def askPlayer(bL):
played = [i+1 for i,s in enumerate(bL) if s]
nogood = played

p = 'Please pick a valid move,\n'
p += 'or q(quit) or r(reset) -> '
s = raw_input(p)

while True:
if not s or s == '\n':
print '\nNo input'
print 'Sorry that move is no good :(\n'
s = raw_input(p)
continue
if s[0] in ['q','Q']: sys.exit()
if s[0] in ['r']: runloop()
try:
i = int(s)
except ValueError:
print 'Sorry that move is no good :(\n'
s = raw_input(p)
continue
if i in nogood:
print 'Sorry that move is no good :(\n'
s = raw_input(p)
continue
if not s in nogood: break
print "You picked move = ", s
return s

#==========================================

def testWinner(bL): # also draw
if bL.count(None) == 0:
print 'Draw'
return 'N'
for line in all:
values = [bL[i] for i in line]
if values.count('P') == 3:
print 'You won! :)'
return 'P'
elif values.count('C') == 3:
print 'I win. Sucks to be you :)'
return 'C'
return None

def whoCanWin(bL,who='C'):
rL = list()
for line in all:
vL = [bL[i] for i in line]
if vL.count(None) == 1:
if vL.count(who) == 2:
rL.append(line)
return rL

#------------------------------------------

def pickComputerMove(bL):
L = whoCanWin(bL,who='C') # computer
if L:
for i in random.choice(L):
if not bL[i]: return i
L = whoCanWin(bL,who='P') # player
if L:
print "Player has a winning move in",
print [i+1 for i in L[0]]
for i in L[0]:
if not bL[i]:
print "returning ",i
return i

if not bL[4] and random.random() < 0.8:
return 4 # "usually" pick open center
L = openCorners(bL)
if L: return random.choice(L)
return random.choice(
[i for i,e in enumerate(bL) if not e])

def computersTurn(bL):
time.sleep(0.5) # I'm thinking, OK?
i = pickComputerMove(bL)
print "Computer picked move = ", i+1
bL[i] = 'C'

#------------------------------------------

def runloop():
bL = [None] * 9
printBoard(bL)
while True:
s = askPlayer(bL)
bL[int(s)-1] = 'P'
printBoard(bL)
if testWinner(bL): break
computersTurn(bL)
if testWinner(bL): break
printBoard(bL)

print '\nWant to play again?'
runloop()

runloop()