Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Represent the Square Grid of Cells

00:00 Represent the Square Grid of Cells. While some people play variants of tic-tac-toe with a different number of players or different sizes of grids, you’ll stick with the most basic and classic rules.

00:12 You may recall that the classic game’s board is represented by a three-by-three grid of cells. Each cell can be empty or marked with either a cross or a naught.

00:22 Because you represent marks with a single character, you can implement the grid using a string of precisely nine characters corresponding to the cells. A cell can be empty, in which case you’ll fill it with the space character, or it can contain the player’s mark.

00:38 In this course, you’ll store the grid in row-major order by concatenating the rows from top to bottom. For example, with this representation, you could express the three gameplays demonstrated before with a string literal seen on-screen.

00:55 To better visualize them, you can create the short function in a REPL session.

01:07 The function takes a string of cells as an argument and prints it out onto the screen in the form of three separate rows carved out with the slice operator from the input string

01:21 While using strings to represent the grid of cells is straightforward, it falls short in terms of validating its shape and content. Other than that, plain strings can’t provide some extra grid-specific properties that you may be interested in.

01:37 For these reasons, you’ll create a new Grid data type on top of a string wrapped in an attribute.

01:53 You define Grid as a frozen data class to make its instances immutable. So once you create a Grid object, you won’t be able to alter its cells.

02:02 This may sound limiting and wasteful at first because you’ll be forced to make many instances of the Grid class instead of reusing just one object.

02:10 However, the benefits of immutable objects, including fault tolerance and improved code readability, far outweigh the cost in modern computers. By default, when you don’t specify any value for the .cells attribute, it will assume a string of exactly nine spaces to reflect an empty grid.

02:28 However, you can still initialize the grid with the wrong value for cells, ultimately crashing the program. You can prevent this by allowing your objects only to exist if they’re in a valid state.

02:39 Otherwise, they won’t be created at all, following the fail-fast and always-valid domain model principles. Data classes take control of object initialization, but they also let you run a post-initialization hook to set derived properties based on the values of other fields, for example. You’ll take advantage of this mechanism to perform cell validation and potentially discard invalid strings before instantiating a Grid object.

03:24 The .__post_init__() method uses a regular expression to check whether the given value of the .cells attribute is exactly nine characters long and contains only the expected characters: "X", "O", or " ".

03:37 There are other ways to validate strings, but regular expressions are very compact and will remain consistent with the future validation rules you’ll add later on.

03:47 If the .cells attribute does not pass the test, then a ValueError is raised.

03:55 Note that the grid is only responsible for validating the syntactical correctness of a string of cells, but it doesn’t understand the higher-level rules of the game.

04:04 You’ll implement the validation of a particular cell combination’s semantics elsewhere once you gain additional context. At this point, you can add a few extra properties to your Grid class, which will become handy when determining the state of the game.

04:34 These three properties return the current number of crosses,

04:45 naughts,

04:53 and empty cells, respectively. Because your data class is immutable, its state will never change, so you can cache the computed property values with the help of the @cached_property decorator from the functools module.

05:06 This will ensure that their code will run at most once no matter how many times you access the properties, for example during validation. On-screen, you can see the Grid class in action showing some of its features.

05:23 You can create an empty Grid or one with a particular cell combination. The grid creation process will check if the grid has too few cells or the wrong characters.

05:44 You can get the count of Xs, Os, or empty cells

05:55 using Python code. You’ve modeled a three-by-three grid of cells, which can contain a particular combination of player’s marks. Now it’s time to model the player’s moves so that artificial intelligence can evaluate and choose the best option, and you’ll be doing that in the next video.

Become a Member to join the conversation.