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

Represent the Maze With Objects

00:00 Represent the Maze Using an Object-Oriented Approach. At this point, you know what kind of maze you’ll be solving and have a good idea of the best data structure to represent it with.

00:14 In this part of the course, you’ll use a top-down approach to conceptually decompose the maze into a set of basic elements. One by one, you’ll implement those elements as objects and combine them to represent the complete maze. For the purposes of this course, you can think of the maze as a rectangular grid of uniform squares arranged in rows and columns.

00:37 Each square has the same width and height and a piece of border around it. Squares can additionally play specific roles in the maze to make it more interesting. For example, some of them can represent obstacles such as walls or enemies, and others can represent rewards.

00:55 To avoid tight coupling between your building blocks, which could manifest itself through the circular import error mentioned earlier on, you are going to put the individual classes in separate files.

01:07 Go ahead and create the four Python module placeholders seen on-screen in the models package.

01:16 Each empty file corresponds to one of the building blocks you’ve identified. Over the remaining sections, you’ll fill them in with necessary code. You’ll begin by defining the available roles of a square in the maze.

01:31 Most squares won’t have a particular role in the maze. However, at least two of them must be marked as entrance and exit respectively. They’ll tell the pathfinding algorithm where to start and finish its journey.

01:44 Some other squares can be marked as obstacles, such as walls, enemies, or the exterior, which you can’t cross. Finally, a few squares can contain rewards, such as bonus points or power-ups that may influence the path.

02:00 Here’s the same maze that you saw earlier, but with a few of its squares annotated so you can get a better idea of their different roles.

02:11 There are six unique roles you can assign to the squares in any given maze: enemy, entrance, exit, exterior, reward, and wall. A role is optional, so the square doesn’t have any by default.

02:26 On the other hand, when a square already has a role, then it can’t have another role at the same time. For instance, you can’t place a reward and an enemy on the same square.

02:36 Note that this is just an arbitrary constraint on the problem to make it easier to solve, which doesn’t hold true for all mazes in general. The most appropriate data type for representing square roles in Python, which can enforce these rules, is an Enum.

02:53 It lets you choose at most one role from a fixed set of mutually exclusive values.

03:00 Open the role module in your project and define the class as seen on-screen.

03:11 The class extends the enum.IntEnum base class from the standard library, which provides special semantics for its instances. There are currently only six such instances, which have unique identities called members of the enumeration.

03:26 You usually write their names in uppercase to indicate they behave like constants, but unlike regular constants, they share a common namespace. By calling, you give each member the next numeric value in turn.

03:44 At this point, it doesn’t make much difference whether you extend enum.IntEnum or the more basic enum.Enum type. However, the benefit of using the former will become apparent in part two of this course, where you’ll start implementing a custom file format to save your mazes on disk in binary form.

04:04 Because enum.IntEnum is also a subclass of int, as the name implies, you can treat your Role members as numbers.

04:16 Even though regular enumerations defined with enum.Enum would have identical numeric values, they don’t support operators associated with maths expressions, such as the plus operator (+).

04:30 You’ll use this feature to combine roles with other information about the square.

04:38 Remember that a role is optional. Therefore, you could use a special null value to indicate the lack of a role in a given square. In Python, that null value is the built-in None.

04:50 However, empty values can be problematic because they force you to add a conditional branch to your code that will handle the missing value whenever you want to access an attribute. If you forget to check for a None value, then you may get an error. Fortunately, in the object-oriented world, there’s a convenient design pattern called the null object pattern, which can help you avoid this issue. In short, the pattern instructs you to stop using None in favor of dedicated null objects representing the missing value of the associated types. Generally, each attribute type should have its own null object that can implement the desired interface with some default behavior, such as a no-op.

05:31 To follow this pattern in your Role enumeration, specify an additional member that will serve as a null object.

05:44 Because starts enumerating your members from one and then picks up the value of the previous member, the default values may not always be desirable.

05:54 If you’d like to change them, then you can explicitly set an arbitrary value for some members, such as using zero for the null object’s value, as this feels more appropriate than one.

06:07 Now, you can assign a Role instance to all of the squares, even if some of them don’t play an inherent role in the maze. That way, you can treat the squares in uniform manner, which will greatly simplify the code written in the future. So far, you defined roles for the squares in the maze.

06:25 Its next building block is the border around each square, which will let you give them a visual appearance and sense of location. You’ll be looking at creating these border patterns in the next section of the course.

Become a Member to join the conversation.