Опубликован: 12.07.2013 | Доступ: свободный | Студентов: 733 / 20 | Длительность: 37:41:00
Лекция 10:

Tic Tac Toe

< Лекция 9 || Лекция 10: 12345 || Лекция 11 >

Duplicating the Board Data

62. def getBoardCopy(board):
63.     # Make a duplicate of the board list and return it the duplicate.
64.     dupeBoard = [] 
66.      for i in board:
67.         dupeBoard.append(i) 
69.            return dupeBoard

The getBoardCopy() function is here so that we can easily make a copy of a given 10-string list that represents a Tic Tac Toe board in our game. There are times that we will want our AI algorithm to make temporary modifications to a temporary copy of the board without changing the original board. In that case, we call this function to make a copy of the board's list. The actual new list is created on line 64, with the blank list brackets [].

Line 64 actually creates a brand new list and stores a reference to it in dupeBoard. But the list stored in dupeBoard is just an empty list. The for loop will go through the board parameter, appending a copy of the string values in the original board to our duplicate board. Finally, after the loop, we will return the dupeBoard variable's reference to the duplicate board. So you can see how the getBoardCopy() function is building up a copy of the original board and returning a reference to this new board, and not the original one.

Checking if a Space on the Board is Free

71. def isSpaceFree(board, move):
72.     # Return true if the passed move is free on the passed board.
73.     return board[move] == ' '

This is a simple function that, given a Tic Tac Toe board and a possible move, will return if that move is available or not. Remember that free spaces on our board lists are marked as a single space string.

Letting the Player Enter Their Move

75. def getPlayerMove(board):
76.     # Let the player type in his move.
77.     move = ' '
78.     while move not in '1 2 3 4 5 6 7 8 9'.split() or not isSpaceFree(board, int(move)):
79.         print('What is your next move? (1-9)')
80.         move = input()
81.     return int(move)

The getPlayerMove() function asks the player to enter the number for the space they wish to move. The function makes sure that they enter a space that is a valid space (an integer 1 through 9). It also checks that the space that is not already taken, given the Tic Tac Toe board passed to the function in the board parameter.

The two lines of code inside the while loop simply ask the player to enter a number from 1 to 9. The loop's condition will keep looping, that is, it will keep asking the player for a space, as long as the condition is True. The condition is True if either of the expressions on the left or right side of the or keyword is True.

The expression on the left side checks if the move that the player entered is equal to '1', '2', '3', and so on up to '9' by creating a list with these strings (with the split() method) and checking if move is in this list. '1 2 3 4 5 6 7 8 9'.split() evaluates to be the same as ['1', '2', '3', '4', '5', '6', '7', '8', '9'], but it easier to type.

The expression on the right side checks if the move that the player entered is a free space on the board. It checks this by calling the isSpaceFree() function we just wrote. Remember that isSpaceFree() will return True if the move we pass is available on the board. Note that isSpaceFree() expects an integer for move, so we use the int() function to evaluate an integer form of move.

We add the not operators to both sides so that the condition will be True when both of these requirements are unfulfilled. This will cause the loop to ask the player again and again until they enter a proper move.

Finally, on line 81, we will return the integer form of whatever move the player entered. Remember that input() returns a string, so we will want to use the int() function to evaluate the string as an integer.

Short-Circuit Evaluation

You may have noticed there is a possible problem in our getPlayerMove() function. What if the player typed in 'X' or some other non-integer string? The move not in '1 2 3 4 5 6 7 8 9'.split() expression on the left side of or would return False as expected, and then we would evaluate the expression on the right side of the or operator. But when we pass 'X' (which would be the value in move) to the int() function, int('X') would give us an error. It gives us this error because the int() function can only take strings of number characters, like '9' or '42', not strings like 'X'

As an example of this kind of error, try entering this into the shell:

>>> int('42')
>>> int('X')
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module> int('X') ValueError: invalid literal for int() with base 10: 'X'

But when you play our Tic Tac Toe game and try entering 'X' in for your move, this error doesn't happen. The reason is because the while loop's condition is being short-circuited.

What short-circuiting means is that because the expression on the left side of the or keyword (move not in '1 2 3 4 5 6 7 8 9'.split()) evaluates to True, the Python interpreter knows that the entire expression will evaluate to True. It doesn't matter if the expression on the right side of the or keyword evaluates to True or False, because only one value on the side of the or operator needs to be True.

Think about it: The expression True or False evaluates to True and the expression True or True also evaluates to True. If the value on the left side is True, it doesn't matter what the value is on the right side. So Python stops checking the rest of the expression and doesn't even bother evaluating the not isSpaceFree(board, int (move)) part. This means the int() and the isSpaceFree() functions are never called.

This works out well for us, because if the expression on the right side is True then move is not a string in number form. That would cause int() to give us an error. The only times move not in '1 2 3 4 5 6 7 8 9'.split() evaluates to False are when move is not a single-digit string. In that case, the call to int() would not give us an error.

An Example of Short-Circuit Evaluation

Here's a short program that gives a good example of short-circuiting. Open a new file in the IDLE editor and type in this program, save it as truefalsefizz.py, then press F5 to run it.

Don't add the numbers down the left side of the program, those just appear in this book to make the program's explanation easier to understand. The function calls in bold are the function calls that are evaluated.


This code can be downloaded from http://inventwithpython.com/truefalsefizz.py

If you get errors after typing this code in, compare it to the book's code with the online

diff tool at http://inventwithpython.com/diff or email the author at


1. def TrueFizz(message):
2.     print(message)
3.     return True 
5. def FalseFizz(message):
6.     print(message)
7.     return False 
9. if FalseFizz('Cats') or TrueFizz('Dogs'): 
10.   print('Step 1') 
12. if TrueFizz('Hello') or TrueFizz('Goodbye'):
13.     print('Step 2') 
15. if TrueFizz('Spam') and TrueFizz('Cheese'):
16.     print('Step 3') 
18. if FalseFizz('Red') and TrueFizz('Blue'):
19.     print('Step 4')

When you run this program, you can see the output (the letters on the left side have been added to make the output's explanation easier to understand):

A. Cats
B. Dogs
C. Step 1
D. Hello
E. Step 2
F. Spam
G. Cheese 
H. Step 3 
I. Red

This small program has two functions: TrueFizz() and FalseFizz(). TrueFizz () will display a message and return the value True, while FalseFizz() will display a message and return the value False. This will help us determine when these functions are being called, or when these functions are being skipped due to short-circuiting.

The First if Statement (Cats and Dogs)

The first if statement on line 9 in our small program will first evaluate TrueFizz(). We know this happens because Cats is printed to the screen (on line A in the output). The entire expression could still be True if the expression to the right of the or keyword is True. So the call TrueFizz('Dogs') one line 9 is evaluated, Dogs is printed to the screen (on line B in the output) and True is returned. On line 9, the if statement's condition evaluates to False or True, which in turn evaluates to True. Step 1 is then printed to the screen. No short-circuiting took place for this expression's evaluation.

The Second if Statement (Hello and Goodbye)

The second if statement on line 12 also has short-circuiting. This is because when we call TrueFizz('Hello') on line 12, it prints Hello (see line D in the output) and returns True. Because it doesn't matter what is on the right side of the or keyword, the Python interpreter doesn't call TrueFizz('Goodbye'). You can tell it is not called because Goodbye is not printed to the screen. The if statement's condition is True, so Step 2 is printed to the screen on line E.

The Third if Statement (Spam and Cheese)

The third if statement on line 15 does not have short-circuiting. The call to TrueFizz ('Spam') returns True, but we do not know if the entire condition is True or False because of the and operator. So Python will call TrueFizz('Cheese'), which prints Cheese and returns True. The if statement's condition is evaluated to True and True, which in turn evaluates to True. Because the condition is True, Step 3 is printed to the screen on line H.

The Fourth if Statement (Red and Blue)

The fourth if statement on line 18 does have short-circuiting. The FalseFizz ('Red') call prints Red on line I in the output and returns False. Because the left side of the and keyword is False, it does not matter if the right side is True or False, the condition will evaluate to False anyway. So TrueFizz('Blue') is not called and Blue does not appear on the screen. Because the if statement's condition evaluated to False, Step 4 is also not printed to the screen.

Short-circuiting can happen for any expression that includes the Boolean operators and and or. It is important to remember that this can happen; otherwise you may find that some function calls in the expression are never called and you will not understand why.

How the Code Works: Lines 83 to 94

Choosing a Move from a List of Moves
83. def chooseRandomMoveFromList(board, movesList):
84.     # Returns a valid move from the passed list on the passed board.
85.     # Returns None if there is no valid move.
86.     possibleMoves = []
87.      for i in movesList:
88.          if isSpaceFree(board, i):
89.             possibleMoves.append(i)

The chooseRandomMoveFromList() function will be of use to us when we are implementing the code for our AI. The first parameter board is the 10-string list that represents a Tic Tac Toe board. The second parameter movesList is a list of integers that represent possible moves. For example, if movesList is [1, 3, 7, 9], that means we should return the number for one of the corner spaces on the board.

The chooseRandomMoveFromList() function will then choose one of those moves from the possibleMoves list. It also makes sure that the move that it chooses is not already taken. To do this, we create a blank list and assign it to possibleMoves. The for loop will go through the list of moves passed to this function in movesList. If that move is available (which we figure out with a call to isSpaceFree()), then we add it to possibleMoves with the append() method.

91.      if len(possibleMoves) != 0:
92.         return random.choice(possibleMoves)
93.     else:
94.                       return None

At this point, the possibleMoves list has all of the moves that were in movesList that are also free spaces on the board represented by board. If the list is not empty, then there is at least one possible move that can be made on the board.

This list might be empty. For example, if movesList was [1, 3, 7, 9] but the board represented by the board parameter had all the corner spaces already taken, the possibleMoves list would have been empty.

If possibleMoves is empty, then len(possibleMoves) will evaluate to 0 and the code in the else-block will execute. Notice that it returns something called None.

The None Value

None is a special value that you can assign to a variable. The None value represents the lack of a value. None is the only value of the data type NoneType. (Just like the boolean data type has only two values, the NoneType data type has only one value, None.) It can be very useful to use the None value when you have not set a variables value yet. For example, say you had a variable named quizAnswer which holds the user's answer to some True-False pop quiz question. You could set quizAnswer to None if the user skipped the question or did not answer it. Using None would be better because if you set it to True or False before assigning the value of the user's answer, it may look like the user gave an answer the question even though they didn't.

Calls to functions that do not return anything (that is, they exit by reaching the end of the function and not from a return statement) will evaluate to None. The None value is written without quotes and with a capital "N" and lowercase "one".

< Лекция 9 || Лекция 10: 12345 || Лекция 11 >
Андрей Дубинин
Андрей Дубинин
Россия, Калининград, МГУТУ, 2001
Андрей Шевелев
Андрей Шевелев