Let the Computer Pick a Random Move
00:00 Let the Computer Pick a Random Move. First, define an abstract player, which will be the base class for concrete players to extend.
00:26
An abstract class is one that you can’t instantiate because its objects wouldn’t stand on their own. Its only purpose is to provide the skeleton for concrete subclasses. You can mark a class as abstract in Python by setting its metaclass to abc.ABCMeta
, or extending the abc.ABC
ancestor.
00:48
Using the metaclass
argument instead of extending the base class is slightly more flexible, as it doesn’t affect your inheritance hierarchy.
00:56
This is less important in languages like Python, which support multiple inheritance, but as a rule of thumb, you should favor composition over inheritance wherever possible. The player gets assigned a Mark
instance that they’ll be using during the game.
01:12 The player also exposes a public method to make a move given a certain game state.
02:10
Notice how the public .make_move()
method defines a universal algorithm for making a move, but the individual step of getting the move is delegated to an abstract method, which you must implement in concrete subclasses.
02:23 Such a design is known as the template method pattern in object-oriented programming.
02:31
Making a move entails checking if it’s the given player’s turn and whether the move exists. The .get_move()
method returns None
to indicate that no more moves are possible, and the abstract Player
class uses the walrus operator (:=
) to simplify the calling code.
02:46 To make the game feel more natural, you can introduce a short delay for the computer player to wait before choosing their move. Otherwise, the computer would make its move instantly, unlike a human player. You can define another, slightly more specific abstract base class to represent computer players.
03:25
ComputerPlayer
extends Player
by adding an additional member, .delay_seconds
, to its instances, which is equal by default to 250 milliseconds.
03:44
It also implements the .get_move()
method to simulate a certain wait time and then calls another abstract method specific to computer players.
04:04 Having an abstract computer player data type enforces a uniform interface, which you can conveniently satisfy with a few lines of code. For example, you can implement a computer player picking moves at random in the way seen on-screen.
04:46
You use random.choice()
to pick a random element from a list of possible moves. If there are no more moves in the given game state, then you’ll get an IndexError
because of an empty list, so you catch it and return None
instead. You now have two abstract base classes, Player
and ComputerPlayer
, as well as one concrete RandomComputerPlayer
, which you’ll be able to use in your games.
05:10 The only remaining element of the equation before you can put those classes into action is the abstract renderer, which you’ll define next. Giving the tic-tac-toe grid a visual form is entirely up to the front end, so you’ll only define an abstract interface in your library.
05:48 This could have been implemented as a regular function because the renderer exposes only a single operation while getting the whole state through an argument.
05:56 However, concrete subclasses may need to maintain an additional state such as the application’s window, so having a class may come in handy at some point.
06:08 You now have the tic-tac-toe library with a robust domain model. An engine encapsulating the game rules, a mechanism to simulate moves, and even a concrete computer player.
06:20 In the next section of the course, you’ll combine all the pieces together and build a game front end, letting you finally see some action.
Become a Member to join the conversation.