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

Tic Tac Toe

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

Deciding Who Goes First

35. def whoGoesFirst():
36.     # Randomly choose the player who goes first.
37.     if random.randint(0, 1) == 0:
38.         return 'computer'
39.     else:
40.         return 'player' 

The whoGoesFirst() function does a virtual coin flip to determine who goes first, the computer or the player. Instead of flipping an actual coin, this code gets a random number of either 0 or 1 by calling the random.randint() function. If this function call returns a 0, the whoGoesFirst() function returns the string 'computer'. Otherwise, the function returns the string 'player'. The code that calls this function will use the return value to know who will make the first move of the game.

Asking the Player to Play Again

42.  def playAgain() :
43.      # This function returns True if the player wants to play again, otherwise it returns False.
44.      print('Do you want to play again? (yes or no)') 4 5.     return input().lower().startswith('y')

The playAgain() function asks the player if they want to play another game. The function returns True if the player types in 'yes' or 'YES' or 'y' or anything that begins with the letter Y. For any other response, the function returns False. The order of the method calls on line 151 is important. The return value from the call to the input() function is a string that has its lower() method called on it. The lower() method returns another string (the lowercase string) and that string has its startswith() method called on it, passing the argument 'y'.

There is no loop, because we assume that if the user entered anything besides a string that begins with 'y', they want to stop playing. So, we only ask the player once.

Placing a mark on the Board

47. def makeMove(board, letter, move):
48.     board[move] = letter

The makeMove() function is very simple and only one line. The parameters are a list with ten strings named board, one of the player's letters (either 'X' or 'O') named letter, and a place on the board where that player wants to go (which is an integer from 1 to 9) named move.

But wait a second. You might think that this function doesn't do much. It seems to change one of the items in the board list to the value in letter. But because this code is in a function, the board variable will be forgotten when we exit this function and leave the function's scope.

Actually, this is not the case. This is because lists are special when you pass them as arguments to functions. This is because you pass a reference to the list and not the list itself. Let's learn about the difference between lists and list references.

List References

Try entering the following into the shell:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

This makes sense from what we know so far. We assign 42 to the spam variable, and then we copy the value in spam and assign it to the variable cheese. When we later change the value in spam to 100, this doesn't affect the value in cheese. This is because spam and cheese are different variables that store different values.

But lists don't work this way. When you assign a list to a variable with the = sign, you are actually assigning a reference to the list. A reference is a value that points to some bit of data, and a list reference is a value that points to a list. Here is some code that will make this easier to understand. Type this into the shell:

>>> spam = [0, 1, 2, 3, 4, 5]
>>> cheese = spam
>>> cheese[1] = 'Hello!'
>>> spam
[0, 'Hello!', 2, 3, 4, 5]
>>> cheese
[0, 'Hello!', 2, 3, 4, 5]

Notice that the line cheese = spam copies the list reference in spam to cheese, instead of copying the list value itself. This is because the value stored in the spam variable is a list reference, and not the list value itself. This means that the values stored in both spam and cheese refer to the same list. There is only one list because the list was not copied, the reference to the list was copied. So when you modify cheese in the cheese[1] = 'Hello!' line, you are modifying the same list that spam refers to. This is why spam seems to have the same list value that cheese does.

Remember when you first learned about variables, I said that variables were like boxes that contain values. List variables don't actually contain lists at all, they contain references to lists. Here are some pictures that explain what happens in the code you just typed in:

Variables do no store lists, but rather references to lists.

Рис. 10.5. Variables do no store lists, but rather references to lists.

On the first line, the actual list is not contained in the spam variable but a reference to the list. The list itself is not stored in any variable.

Two variables store two references to the same list.

Рис. 10.6. Two variables store two references to the same list.

When you assign the reference in spam to cheese, the cheese variable contains a copy of the reference in spam. Now both cheese and spam refer to the same list.

Changing the list changes all variables with references to that list.

Рис. 10.7. Changing the list changes all variables with references to that list.

When you alter the list that cheese refers to, the list that spam refers to is also changed because they are the same list. If you want spam and cheese to store two different lists, you have to create two different lists instead of copying a reference:

>>> spam = [0, 1, 2, 3, 4, 5] 
>>> cheese = [0, 1, 2, 3, 4, 5]

In the above example, spam and cheese have two different lists stored in them (even though these lists are identical in content). Now if you modify one of the lists, it will not affect the other because spam and cheese have references to two different lists:

>>> spam = [0, 1, 2, 3, 4, 5]
>>> cheese = [0, 1, 2, 3, 4, 5]
>>> cheese[1] = 'Hello!'
>>> spam
[0, 'Hello!', 2, 3, 4, 5]
>>> cheese
[0, 1, 2, 3, 4, 5]

Figure 10.8 shows how the two references point to two different lists:

Two variables each storing references to two different lists.

Рис. 10.8. Two variables each storing references to two different lists.

Using List References in makeMove()

Let's go back to the makeMove() function:

47. def makeMove(board, letter, move):
48.     board[move] = letter

When we pass a list value as the argument for the board parameter, the function's local variable is a copy of the reference, not a copy of the list itself. The letter and move parameters are copies of the string and integer values that we pass. Since they are copies, if we modify letter or move in this function, the original variables we used when we called makeMove() would not be modified. Only the copies would be modified.

But a copy of the reference still refers to the same list that the original reference refers to. So if we make changes to board in this function, the original list is modified. When we exit the makeMove() function, the copy of the reference is forgotten along with the other parameters. But since we were actually changing the original list, those changes remain after we exit the function. This is how the makeMove() function modifies the list that a reference of is passed.

Checking if the Player Has Won

50. def isWinner(bo, le) :
51.     # Given a board and a player's letter, this function returns True if that player has won.
52.     # We use bo instead of board and le instead of letter so we don't have to type as much.
53.     return ((bo[7] == le and bo[8] == le and bo [9] == le) or # across the top
54.      (bo[4] == le and bo [5] == le and bo [6] == le) or # across the middle
55.      (bo[1] == le and bo [2] == le and bo [3] == le) or # across the bottom
56.      (bo[7] == le and bo [4] == le and bo[1] == le) or # down the left side
57.      (bo[8] == le and bo [5] == le and bo[2] == le) or # down the middle
58.      (bo[9] == le and bo[6] == le and bo[3] == le) or # down the right side
59.      (bo[7] == le and bo [5] == le and bo [3] == le) or # diagonal
60.      (bo [9] == le and bo [5] == le and bo[1] == le)) # diagonal

Lines 53 to 60 in the isWinner() function are actually one very long if statement. We use bo and le for the board and letter parameters so that we have less to type in this function. (This is a trick programmers sometimes use to reduce the amount they need to type. Be sure to add a comment though, otherwise you may forget what bo and le are supposed to mean.)

There are eight possible ways to win at Tic Tac Toe. First, have a line across the top, middle, and bottom. Second, have a line down the left, middle, or right. And finally, have either of the two diagonals. Note that each line of the condition checks if the three spaces are equal to the letter provided (combined with the and operator) and we use the or operator to combine the eight different ways to win. This means only one of the eight ways must be true in order for us to say that the player who owns letter in le is the winner.

Let's pretend that le is 'O', and the board looks like this:

   |   |   
 X |   |   
   |   |   
-----------
   |   |   
   | X |   
   |   |   
-----------
   |   |   
 O | O | O 
   |   |   

If the board looks like that, then bo must be equal to [' ', 'O', 'O', 'O', ' ', 'X', ' ', 'X', ' ', ' ']. Here is how the expression after the return keyword on line 53 would evaluate:

Here is the expression as it is in the code:

53.      return ((bo[7] == le and bo[8] == le and bo[9] == le) or
54.      (bo[4] == le and bo[5] == le and bo[6] == le) or
55.      (bo[1] == le and bo[2] == le and bo[3] == le) or
56.      (bo[7] == le and bo[4] == le and bo[1] == le) or
57.      (bo[8] == le and bo[5] == le and bo[2] == le) or
58.      (bo[9] == le and bo[6] == le and bo[3] == le) or
59.      (bo[7] == le and bo[5] == le and bo[3] == le) or
60.      (bo[9] == le and bo[5] == le and bo[1] == le))

First Python will replace the variable bo with the value inside of it:

53.      return (('X' == 'O' and ' ' == 'O' and ' ' == 'O') or 
54.      (' ' == 'O' and 'X' == 'O' and ' ' == 'O') or 
55.      ('O' == 'O' and 'O' == 'O' and 'O' == 'O') or
56.      ('X' == 'O' and ' ' == 'O' and 'O' == 'O') or
57.      (' ' == 'O' and 'X' == 'O' and 'O' == 'O') or
58.      (' ' == 'O' and ' ' == 'O' and 'O' == 'O') or
59.      ('X' == 'O' and 'X' == 'O' and 'O' == 'O') or
60.      (' ' == 'O' and 'X' == 'O' and 'O' == 'O'))

Next, Python will evaluate all those == comparisons inside the parentheses to a boolean value:

53.     return ((False and False and False) or
54.     (False and False and False) or
55.     (True and True and True) or
56.     (False and False and True) or
57.     (False and False and True) or
58.     (False and False and True) or
59.     (False and False and True) or
60.     (False and False and True))

Then the Python interpreter will evaluate all those expressions inside the parentheses:

53.     return ((False) or
54.     (False) or
55.     (True) or
56.     (False) or
57.     (False) or
58.     (False) or
59.     (False) or
60.     (False))

Since now there is only one value inside the parentheses, we can get rid of them:

53.      return (False or
54.      False or
55.      True or
56.      False or
57.      False or
58.      False or
59.      False or
60.      False)

Now we evaluate the expression that is connecter by all those or operators:

53.     return (True)

Once again, we get rid of the parentheses, and we are left with one value:

53.     return True

So given those values for bo and le, the expression would evaluate to True. Remember that the value of le matters. If le is 'O' and X has won the game, the isWinner() would return False.

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