Create Border Patterns
00:00 Creating Border Patterns. Each square can have between zero and four sides painted along the main compass directions: north, south, east, and west. When you do the math, you’ll find out that there are sixteen combinations of unique side patterns in total, ranging from an empty square to one with all four sides painted.
00:24 There’s one empty square, four squares with only one side, six squares with two sides, four squares with three sides, and one square with all four sides.
00:35 Now you can build almost any maze pattern with these tiles by connecting them like a jigsaw puzzle. To compactly represent a border pattern around a square with just a single number, you should consider using a bit field.
00:49 This will map each of the four sides to a specific binary digit. Extracting information from such a number usually requires the use of bitwise operators, which aren’t very common in Python. However, in this course, you’ll use a handy shortcut from the standard library, which hides the details. Because there are four sides, you must allocate four bits that you can individually turn on or off depending on whether or not there’s a border on that side.
01:17 When you convert the corresponding bit string into a decimal number, you’ll get the unambiguous representation of one of the border patterns, as seen on-screen. As you can see, each border pattern is represented with a decimal number ranging from zero to fifteen.
01:33 You can check which sides are included in the border by looking at the binary representation of the number and reading the corresponding bits. If a bit is set to one, then you know that you should paint that side of the border.
01:45 The bit count, or the number of ones in the bit string, reflects the number of sides in the border. Additionally, it carries some useful information about the type of square. For example, you can recognize a dead end by detecting precisely three sides of the border, leaving only one end to connect with other squares.
02:03 Fewer than two sides may indicate a potential intersection of paths in the maze if the square isn’t a wall or part of the exterior. A T-shaped intersection will have one side, while a four-way intersection will have none.
02:18 Detecting a corner is a bit more tricky because it requires you to compare the border against one of the four specific patterns, even though they all have exactly two sides.
02:28 This helps eliminate the other two border patterns that also have two sides but aren’t corners. Their sides run in parallel instead of meeting at a corner.
02:40
So now that you know what makes a square border, how do you define it in Python? You’ll use the Enum
data type again, but with a slight twist.
02:48
This time around, you’ll extend the enum.IntFlag
base class, which is an even more specific enumeration type that implements the bit field logic. You’ll give more common names to your enum members instead of using the compass directions.
03:05
Add the code seen on-screen to the border
module.
03:16
You override the value of Border.EMPTY
with 0
to indicate the absence of border sides. This class resembles the Role
enumeration that you defined earlier, but IntFlag
allows you to do much more with its members.
03:29
Rather than being a mutually exclusive choice of one member, the Border
enumeration lets you combine any number of its members to create a composite value. For example, to define a closed border, you’d combine all four sides using the bitwise OR
operator (|
) like this.
04:04
The .name
and .value
attributes of the resulting border are calculated dynamically based on the combination of its sides. Note that the order of the individual sides doesn’t matter when you define a composite bit field. Underneath, it’s a numeric value resulting from turning on the specific bits.
04:24
Python will always show a consistent text representation of that value, determined by the order of your members in the class definition. To compare your border to another border pattern, you can use the identity test operator (is
).
04:45
Alternatively, you could use the equality test operator (==
) to directly compare your border against a known numeric value.
04:58
Additionally, you can check if the border contains a given side by using the membership test operator (in
). Comparing an instance of enum.IntFlag
to a number is possible because the flag is a subclass of the built-in integer type, just like enum.IntEnum
, which you used before.
05:22 Next, add a few convenience properties to the enumeration so that you can detect corners, dead ends, and intersections.
05:33
All three properties return a Boolean value. In the .corner
property, you use the membership test operator (in
) to check if an instance of the Border
enumeration—indicated by self
—is one of the predefined corners.
05:54
The other two properties rely on the integer’s .bit_count()
method, which returns the number of ones in a binary representation of your border.
06:22
With the Role
and Border
building blocks in place, you can now tie them together on a higher abstraction level. And in the next section, you’ll implement the Square
class.
Become a Member to join the conversation.