$30
You are an acorn, heir to the Honourable Furious Forest Throne. Your beauty shines like no other, reflecting the colourful solar rays into the eyes of all spectators. But instead of your usual coat of beautiful acorn shell, you find yourself covered in ash. Not only that, but you've fallen great depths from the heights of the towering trees of the Honourable Furious Forest that once cared for you.
You shed a tear of loss. The horrid memories of the Fire Nation's invasion flash before you as you relive the moments your hometown, the Honourable Furious Forest, was burnt to ashes. Your friends, the koalas and kangaroos, your family, the Honourable Furious Forest members, all burnt to a crisp.
Mustering up your motivation for revenge, you, the acorn, heir to the Honourable Furious Forest Throne, stumble forward and find yourself in a maze. You observe walls of fire, helicopter search lights and teleporting pads within.
You cry out and slam your little acorn fist on the greyed Honourable Furious Forest floor of dried up leaves. You swear upon your Father's name, Lord Scarlet Oak of the Honourable Furious Forest, that you will conquer this maze and restore the Honourable Furious Forest back to its former glory of rainbow and sunshine.
Cells
Cell character
Meaning
A
Player cell (stands for Acorn)
' '
Air cell (space bar)
X
Starting cell
Y
Ending/Goal cell
*
Wall cells
1, 2, 3, 4,
5, 6, 7, 8,
9
Teleport cells. These numbers will come in pairs. On stepping onto the cell, you enter the cell '1', you teleport to the other '1'. Values greater than 9 will not be given. Note: 0 is not a valid teleport pad!
W
A water bucket cell. On stepping onto the cell, the player gains a water bucket.
F
A fire obstacle that you cannot pass unless you have a water bucket.
Configuration
There will be one txt file which contains an ASCII representation of the maze. The maze may have more than one viable solution. The symbols correspond to the cell characters outlined above. All letters shall be in upper case.
Example configuration file:
Commands
Command
Meaning
w
Move up
a
Move left
s
Move down
d
Move right
e
Wait a turn
q
Quit the game
If the user enters an invalid move, print Please enter a valid move (w, a, s, d, e, q). . The game should then continue to ask for the next move.
These commands are case-insensitive!
See the sample outputs below for usage!
Implementation details
Your program will be written in Python 3. The only in-built module methods and attributes you are allowed to use are: sys.argv sys.exit() os.system("clear") <--- (More on this later) You may not import any other modules.
To help you, a scaffold of a suggested implementation structure is provided. Some test cases require certain features of your code and cannot be modified.
Things you CANNOT modify (these are tested):
File
Function/Attribute
Why it must not be modified
game_parser.py
parse()
parse(lines) must take in a list of strings
and must return a list of lists of cells. This will be tested.
grid.py
grid_to_string()
grid_to_string(grid, player) must take in a list of list of cells and a player and must return a single string representing the grid & water buckets. This will be tested
cells.py
display
The display attribute will be used to test your grid_to_string() function.
player.py
display
Same as above.
player.py
num_water_buckets
The num_water_buckets attribute will be used to test your grid_to_string() function.
player.py
row
Specifies their row in the grid.
player.py
col
Specifies their col in the grid.
You may modify any other files to your liking. The rest of the scaffold is to help you with some basic structure! You may, of course, import your own modules...! You are also encouraged to write helper functions appropriately.
Note: No file other than run.py and solver.py should print to the screen! Only run.py should print to the screen while playing the game. This is good programming practice, so get used to it early! If needed, you may use it sparingly to debug your program!
game.py
It is recommended you write your game engine here. By writing a Game class, you can easily run and delete game instances for your solver. The game engine should hold all the relevant data regarding the game's state. This includes the moves made, the player's position, the cells, etc.
You should call read_lines() which uses the parse() function to parse the lines in the file. Your read_lines() function should return the grid as well. If you decide to do this differently however, that is also ok!
cells.py
It is recommended that you write your cells here. All cells must have a display attribute. We recommend your cells also have a step() method. The step() method should take in a Game object (defined above) and make modifications to the game depending on the cell.
If you are a strong programmer and know how to use inheritance, you may use it. Using inheritance will not advantage you much however, and the assignment is completely doable without it! We will not teach it in this course, so it is up to you if you want to use it!
Notable cells:
Wall Cell
If a user steps onto a Wall cell, the user should be pushed back to their original cell and the message You walked into a wall. Oof! should be returned. The game should not record illegal user moves onto walls or out of bounds!
Water Cell
If a user steps onto a Water cell, it should increment the player's water bucket count and return the message Thank the Honourable Furious Forest, you've found a bucket of water! . It should then behave like an Air block.
Fire Cell
If a user steps onto a Fire cell, two things can happen:
If the user has at least one water bucket, it should return the message With your strong
acorn arms, you throw a water bucket at the fire. You acorn roll your way
through the extinguished flames! and reduce the player's water bucket count by one. The fire block should then behave like an Air block.
If the user does not have a water bucket, it should return the message You step into the fires and watch your dreams disappear :(. and end the game.
Teleport Cell
If a user steps onto a Teleport cell, it should teleport the user to the destination and return the message Whoosh! The magical gates break Physics as we know it and opens a wormhole through space and time. .
If a user waits (by inputting 'e' ) on a teleport cell after stepping on one, they will be teleported again.
If the Acorn is currently on a teleport pad and walk into a wall, when the Acorn is pushed back, it does not re-trigger the teleport pad.
player.py
It is recommend that you write your Player class here. It must contain the attribute num_water_buckets . The Player class should also contain a method called move(...) which
will receive a move command and move the player.
The Player class must have a row and col attribute which represents their location on the grid. The grid_to_string() function must use these attributes when drawing the player.
If a Player tries to leave the grid (say there was a hole in the perimeter), it should act as if the player walked into a wall.
game_parser.py
It is recommended that you write your parser functions here. A parser is a module which reads an input and processes it into something useful.
You may assume that the input grid given in the configuration file is a rectangle (i.e. each row has the same number of entries). You may also assume that the border of the maze given in the configuration file will be surrounded with wall cells (except the starting and ending cells)!
Your parse() function will be tested. It should receive a list of strings. The parse() function must be able to handle \n characters at the end of each string in the input list. There are certain error cases it must be able to handle. In this specific order:
If the configuration file contains an unknown letter (including teleport pad '0'), raise a
ValueError with message Bad letter in configuration file: <letter>. . If there are multiple unknown letters, you only need to output a single unknown letter in the message. You do not need to find them all and concatenate the output!
If the configuration file does not contain exactly one X , raise a ValueError with message Expected 1 starting position, got <number>. , where number is the occurrence of X .
If the configuration file does not contain exactly one Y , raise a ValueError with message Expected 1 ending position, got <number>. , where number is the occurrence of Y .
If the teleport pads do not come in exact pairs, raise a ValueError with message
Teleport pad <number> does not have an exclusively matching pad. , where number is the number of the teleport pad. If there are multiple pairs of non-matching pads, you only need to output a single pad number in the message. You do not need to find them all and concatenate the output!
The function read_lines() will not be tested. You will need to use this to prepare your data to pass into the parse() function. If the file doesn't exist, print <filename> does not exist! and exit gracefully.
The order of function calls in this file should look like:
Call read_lines()
Call parse(lines)
Return grid
Return grid
grid.py
It is recommended that you write helper functions for the grid here.
The grid_to_string() function must be implemented in this file as it is tested. It should take in a grid (list of lists of Cells ) and a Player . It should return the grid and the number of water buckets the player has as a single string.
Note: Your returned string should include \n characters.
Example contents of the string:
run.py
This file is the entry-point to your game program. It will be run given a single command line argument that represents the filename of the configuration file.
If no command line arguments are provided, print Usage: python3 run.py <filename> [play] and exit gracefully.
run.py should create a game object, handle user inputs, and handle messages from the game
object to display to the user.
You may import os and use os.system("clear") to clear the screen after each user input. This creates a much better user experience. See the attached demonstration video to see it in action. Ensure that this mode is only activated if the user provides the optional play command line argument. This screen clearing functionality is not tested and you may skip this.
solver.py - Solver
This file is the entry-point to your solver program. Your solver should have two modes, Depth First Search (DFS) and Breadth First Search (BFS). These are two maze solving algorithms and the pseudocode will be provided in the corresponding lab sheet. Your solver should take in two command line arguments that represents the filename of the configuration file and the mode (DFS or BFS).
If no command line argument is provided, print Usage: python3 solver.py <filename> <mode> and exit gracefully.
It should print out the number of moves made as well as the path. There may be more than one path at the shortest path length. These are all accepted solutions. You only need to find and output one such path. The output shall follow the format:
If there are no solutions:
Some tips:
You should only need to change one line to move between DFS and BFS modes!
Truly understand how these algorithms work. Their advantages, shortcomings, appropriateness, etc.
Avoid recursion as it is not very efficient and your solves may time out!
You may want to prohibit your solver from "waiting" more than three turns consecutively.
You may ask your tutor for assistance in the solver component regarding your algorithm design. Not your code!
Ed test cases will test both DFS and BFS modes. While the test cases will be as lenient as possible in terms of run-time, If your solver times out, you need to rethink how you approach the problem!
The DFS and BFS modes will be given different configuration files as they shine in different scenarios.
Your BFS solver must produce an optimal path. There may be multiple optimal paths and these will all be considered correct! (what do you think 'optimal' here means?)
Your DFS solver only needs to produce a valid path since there are many paths it can take.
A summary of the file relationships are as follows:
Game Finish
When the player finishes the game successfully by reaching the ending/goal cell, print:
When the player finishes the game unsuccessfully, print: