Previously, I have created a maze generating code while learning Python. Later I enhanced it with animation and marking special cells. All of it was done in rush, to solve the problem. Now I spent some time improving the quality of the code by encapsulating code into classes, using “private” methods and fields and separating concerns. The exact steps I took can be viewed in the GitHub repository or read here.
I’ll list all commits of the repository, put their descriptions and link for full preview in GitHub.
Table of contents
- (Quick tip – git log)
- First version
- Added visualization
- Extracted StepController
- Extracted FarthestDeadEnd
- FarthestDeadEnd – included getIncreasingRadius
- Extracted CurrentCell
- CurrentCell – added moveForward
- Extracted MazeRules
- MazeRules – added Result
- CurrentCell – added goBack
- CurrentCell – added getDecreasingRadius
- Extracted MazeDrawer
- Refactoring – size_x, size_y as private fields
- Refactoring – Marking private members (1)
- Refactoring – Marking private members (2)
- MazeRules – added getPreviousCell
- MazeRules – added visitedMaze, visitedCell and visitCell
- generateMaze – included mazeDrawer
- Extracted MazeDrawer to a file
- Extracted MazeRules to a file
- Extracted FarthestDeadEnd to a file
- Extracted StepController to a file
- Extracted CurrentCell to a file
- Fixed saving animation frames
- Increased image size
- Fixed FarthestDeadEnd.__getIncreasingRadius: removed variable duplication
- First version
- Classes in Python
- Inner classes and static methods in Python
- Lessons learned
(Quick tip – git log)
I have generated this page using some tricks of git log and WordPress’ Gutenberg editor.
It is possible to use some Markdown syntax when pasting content into the editor. The shortcuts I used were:
E-book: How to set up free WordPress platform for IT blogs
Do you want to set up a typical blog but you don’t know how to start, which plugins to use or how to customize the site? I spent a few weeks in total doing that from scratch – from learning the ecosystem to adjusting the platform to my very specific needs. Now I want to share with my experience and make the start easier for you.
## headercreates a header
- `code` creates a
- [link](https://lukasznojek.com) creates a link
Although the first two work also as you type text in the editor, the last one for some reason doesn’t – only when pasting the text (and only as plain text).
It is possible to customize the format of the output of
git log command – check the placeholders section in git log documentation.
I used a relatively simple format:
git log --pretty=format:"## %s%n[See the diff](https://github.com/lukaszmn/maze-recursive-backtracker/commit/%H)%n%n%b" --reverse >log.txt
What do these options mean?
## %sreturns ## and subject of the commit.
##will convert that line to a header in WordPress
%nadds a new line to separate entries
[See the diff](https://.../%H)creates a link named See the diff with URL that ends with full hash of the commit
%n%nadds another two new lines. A single new line would not create a new paragraph, but would continue the same paragraph from a new line
%badds the body of the comit
--reversestarts with the oldest commits and heads onto the newest
>log.txtsaves the output to the
This command generated the following output (fragment):
## Extracted FarthestDeadEnd [See the diff](https://github.com/lukaszmn/maze-recursive-backtracker/commit/fecc4d453a69c41dfca948c17ab673340618162c) The position of the farthest dead end and the entire
drawDeadEnd()method were extracted to new class
FarthestDeadEnd. ## FarthestDeadEnd - included getIncreasingRadius [See the diff](https://github.com/lukaszmn/maze-recursive-backtracker/commit/e32c467f1aedab0689e744a539a185aa37bede78) The
getIncreasingRadius()method was included into the
FarthestDeadEndclass, because it was used only by it.
I only had to manually unwrap lines and basically it was done.
Now, on to describing the refactoring!
This commit was described in the first post of the series.
This commit was described mostly in the second post of the series.
Lines that were responsible for counting steps and checking step limit were extracted to a
The position of the farthest dead end and the entire
drawDeadEnd() method were extracted to new class
FarthestDeadEnd – included getIncreasingRadius
getIncreasingRadius() method was included into the
FarthestDeadEnd class, because it was used only by it.
Now it’s a little more complicated, as there were no methods that could be just copied. The newly created
CurrentCell class should store location of the cell that’s currently analyzed. It also stores the marker object used for drawing yellow circle.
CurrentCell – added moveForward
else clause was extracted as
moveForward() method of
validCell() consistuted for a new class
MazeRules – added Result
Instead of returning some magic numbers from
getNextCell() method, an object
Result is returned with all information being clear. The code is longer now, but that’s a growth worth the clarity.
CurrentCell – added goBack
It’s time now to extract the first
if clause into the
goBack() method of
CurrentCell – added getDecreasingRadius
getDecreasingRadius() method was used only by
CurrentCell, it made sense to include it in that class.
Most code related to drawing was extracted to
MazeDrawer class. By the way some routines were named to explain what they are doing, e.g.
drawBackgroundAndBorder(). In order to limit graphic operations to one class, it was passed to the constructor of
Refactoring – size_x, size_y as private fields
Some attempt was made to reduce the use of global
size_y variables. And, by the way, private fields
size_y were prefixed with double underscore
Refactoring – Marking private members (1)
Continuing prefixing private members of classes with double underscore
Refactoring – Marking private members (2)
Continuing prefixing private members of classes with double underscore
__. It also enforced adding some getters that would access the now-private members, like
MazeRules – added getPreviousCell
The logic of backtracking was moved into
getPreviousCell() method of
MazeRules – added visitedMaze, visitedCell and visitCell
There were still some loose global variables and methods. Some could be very neatly included into
MazeRules class. This also made a dependency onto that class for other:
FarthestDeadEnd now require a reference to
MazeRules to get the path’s length.
generateMaze – included mazeDrawer
This is the final step to get rid of global
size_y variables. The
MazeDrawer object is now created inside
generateMaze, not outside of it.
Extracted MazeDrawer to a file
This step could have been done earlier, when creating the
MazeDrawer class. It decreases the amount of lines of code in the main file, makes finding classes easier, and decreases imports in each file to only the ones that are necessary.
Extracted MazeRules to a file
Extracted FarthestDeadEnd to a file
Extracted StepController to a file
Extracted CurrentCell to a file
Fixed saving animation frames
As it turned out, after all refactorings the frame saving routine was not working. First of all, it was in incorrect class – now is in
saveFrame() method. It required also access to current step, which was exposed in
get_step() method of
Increased image size
The previous scale of drawing (plot) was good to have code and plot side-by-side in Spyder IDE. The size stopped being enough when I created an animation. It was small and the quality was poor. Here I doubled the size and increased the font’s size.
Fixed FarthestDeadEnd.__getIncreasingRadius: removed variable duplication
One thing was omitted during refactoring – the
distance was passed as argument to the
__getIncreasingRadius() method, and it was also calculated, overwriting that argument. The unnecessary calculation was removed here.
And that’s it as far as the refactoring goes. In the further paragraphs I will list new things I learned during the process of refactoring.
Classes in Python
An example of class in Python:
- The contructor is named
- Every method inside the class must have the first parameter that will refer to the class. Usually it’s named
self, but any name is acceptable. Documentation.
- You cannot list the class’ fields (variables) to have control over what belongs to it. You define them in the place you want to use them – so it’s easy to create the fields in any methods, which will make the code more difficult to maintain. It’s a good idea to declare and initialize all fields in the constructor.
- “Private” fields and methods are by convention prefixed with double underscore
__. This is just an information that member should not be used by other classes, but there is no mechanism that would prevent from calling e.g.
stepController._StepController__max_steps = 4(note the way to access the field from outsisde). Documentation.
- There are no setters and getters special properties in Python, but there is a convention to name them
Inner classes and static methods in Python
- Create the inner class as a class member with keyword
- Instantiate the class from the outer class (
MazeRules) by referring to it with
self.Result. Nothing unexpected here.
- Create a static method with attribute
@staticmethod. There are no magic parameters. I created the
parentparameter, which as you can see in line 4 or 6, passes
self, which allows me to create the
Resultobject by calling
self.Resultfrom the context of
getNextCell()method). It’s a bit complicated, but it works well.