Quoridor: Playing A Maze Game On The Console With Python
Integration weeks at Marmelab are amazing! Barely arrived, I shook hands of my new coworkers. Then, François told me: "Do you know Quoridor?". I said "No". So he explained me the rules. We played a game. I lost! And he told me that Quoridor will be the game I will work on during my integration period. Every challenge will have its own constraints.
So my first challenge develop a playable Quoridor game in a Terminal using Python. Brutal, but very exciting!
Quoridor in a Nutshell
Quoridor is a board game playable from two to four persons. At the beginning, each player has a pawn placed in the middle of their side. Their goal is to reach the opposite side.
Players play one after the other. Players can choose between moving their pawn, or putting up a fence.
Move You Pawn
You can move your pawn according to a few rules:
- One square at a time in all directions except diagonally ,
- as long as you do not cross a fence ,
- behind another pawn  (jump action),
- in diagonal when a fence is behind your opponent [3, 4],
Put Up a Fence
Fences are two squares long, and you can place them between any squares - except that you cannot add a fence which disallows the pawns to reach their goal.
There is a limit of fences a player can put up: 10 fences for a 2 players game, and 5 fences for 3 or 4 players games.
Before moving on, take a moment to think about how YOU would implement such a game. No straightforward, isn't it?
Throughout the challenges, I learned the processes that marmelab engineers use every day. It's based on agile methodologies and rituals such as planning poker, daily meetings, retrospective, ....
What I appreciate the most is the process of pull requests, and for several reasons.
Each pull request (PR) is reviewed by at least one other developer. Code reviews are important to detect bugs, bad architecture/code, and also to share the knowledge. It is a good opportunity to progress quickly if you follow the recommendations.
The PR title has a prefix which is either [WIP] or [RFR]:
- WIP (work in progress) is the default status. You can add a checklist to explain what you did and what is missing.
- RFR (ready for review) means that the work is over and you need a final review. In general, there are still improvements to do after a code review.
At a glance, you know whether you can review the PR. Convenient!
Adding a screenshot of the feature result enables the reviewer to see what the feature is, and you can have UI reviews as well.
The commit frequency surprised me. Even if the work is not over or at the end of your work day, you push your work to the origin. In that way, you will have feedback quickly. It is a bit disturbing when you learnt to push only when the commit works. But I understood that a remote coworker cannot help you well if you do not show them the code. It is possible to use video conferencing, but it is better to keep track of the work. Moreover, the customers can see what you did during the day.
Learning Python In One Week
I had heard about Python, but I never played with it. So this challenge was a good opportunity to test it, and check if it is as easy as it seems.
I am a java developer. I like fluent APIs. I prefer to read the code as I read a book. Python was a good surprise in that perspective.
Here are some examples.
return Item.PAWN_1 if index == 0 else Item.PAWN_2
- list comprehensions
fences = [[Direction.NO for i in range(FENCE_SIZE)] for j in range(FENCE_SIZE)]
- indentation to delimit blocks
def add_random_fence(fences): center_x = randint(0, FENCE_SIZE - 1) center_y = randint(0, FENCE_SIZE - 1) direction = randint(Direction.HORIZONTALLY, Direction.VERTICALLY) if can_add_fence(fences, center_x, center_y, direction): return Fence(center_x, center_y, direction) return add_random_fence(fences)
I was suprised by the indentation rule. I never thought of using indentation to delimit blocks. Sure, you have fewer markers, but if you add one extra space, your code may not work anymore.
The Challenges I Met
The main files of that project are:
- game.py: the controller
- console.py: the view
- board.py and pawn.py: the model
The game contains the board (the list of fences) and the list of pawns.
A fence is defined with the top left square and a direction (horizontal or vertical)
The main challenges are not how to store the game and the rules, but how to show the game.
Drawing In the Console, One Character at a Time
Displaying a Quoridor board in the console was a long and tedious work. After every feature, the board changed.
On the left is the first display, and on the right the final one. You can see that it changed a bit!
Even if the interface changes, the internal representation is the same. The game gives the console a 2D array, which is a full representation of the squares, the pawns, the fences, and the places where to put up fences. In that way, the console can iterate over this array line after line to print the game.
Moving a Pawn With The Keyboard
The first idea was to use the arrows on the keyboard. I tried to use keyboard but I spent too much time integrating it in my project. So I decided not to listen to the events on the keyboard but to wait for a keystroke. So two actions instead of one. What a pity!
Putting Up a Fence
I did not have enough time to develop that feature. But it is a real challenge to only use the keyboard to add a fence. I thought of asking for the top left square with the direction of the fence like 23v (second column, third line, vertically). The board display should change to show the column and row numbers.
Make It Work, Make It Fast, and Make It Well
Time flies when you only have 4 days and a half. At the end of my challenge, the game was not fully playable.
What I came up with is is a two-player game where players can only move their pawn. Fences are put up randomly at each start of the game. No way to add them manually.
You can have a look at the code in the marmelab/quoridor-python GitHub repository.
"Challenge" is the right word to describe what I felt during this period. I was challenged on what I have done (technical choices, UI, the way I code, ...). I was under pressure. But I am glad about the result I reached and how I reached it.
To sum up what I learned about marmelab work during this week:
- reach a working result,
- reach it quickly,
- and reach it well with high-value code quality.
I am excited to know what will be my next challenge.