Starting from:

$34.99

csse1001 Assignment 3-Maze Runner Once Again Solution


CSSE1001/CSSE7030
1 Introduction
In assignment 2 you implemented a game of MazeRunner with a text-based interface. The Apple MVC design pattern used in this game allows modelling logic and view classes to be modified fairly independently. In this assignment, you’ll exchange the text-based interface of MazeRunner with a more sophisticated tkinter-based Graphical User Interface (GUI).
The game view initially contains 3 components:
• A level view on which the tiles and entities are drawn using either coloured shapes or images;
• An inventory view, which appears to the right of the level view and allows users to apply collected items (other than coins) by left-clicking those items in their inventory; and
• A stats view, which shows the player’s stats, as well as the number coins they have collected.
As opposed to assignment 2, where users controlled the game by inputting text at a prompt, in assignment 3 users control the game through key-presses and mouse clicks. An example of the final game is shown in Figure 1.
You can find the tkinter documentation on effbot and New Mexico Tech .
2 Tips and hints
Except where specified, minor differences in the look (e.g. colours, fonts, etc.) of the GUI are acceptable. Except where specified, you are only required to do enough error handling such that

Figure 1: Example fully functional game at the end of Task 2.
regular game play does not cause your program to crash or error. If an attempt at a feature causes your program to crash or behave in a way that testing other functionality becomes difficult without your marker modifying your code, comment it out before submitting your assignment. If your solution contains code that prevents it from being run, you will receive a mark of 0.
You must only make use of libraries listed in Appendix A. You must not import anything that is not on this list; doing so will result in a deduction of up to 100% of your mark.
Task 1 requires you to implement a functional GUI-based version of MazeRunner. At the end of this task your game should look like Figure 2. A heading label should appear at the top of the window at all times. Below this, the three components should appear as described in Section 1 and as depicted below.

Figure 2: Game at end of task 1.
Certain events should cause behaviour as per Table 1.
Event Behaviour
Key Press: ‘w’ Player attempts to move up one square.
Key Press: ‘a’ Player attempts to move left one square.
Key Press: ‘s’ Player attempts to move down one square.
Key Press: ‘d’ Player attempts to move right one square.
Left click on an item in inven-
tory One item of the kind clicked should be applied to the player and removed from the inventory. If no instances of the item remain in the inventory, the label showing the item should be removed from view, and all other item labels below it should move up to fill the space.
Table 1: Events and their corresponding behaviours.
In task 1, tiles are represented by coloured rectangles and entities are represented by coloured circles. You must also annotate the circles of entities with their id (as per Fig. 2). The colours representing each tile and entity can be found in constants.py. You must use the colours specified in constants.py.
When the player wins or loses the game they should be informed of the outcome via a messagebox.
The messagebox must display the text of WIN MESSAGE or LOSS MESSAGE in constants.py. For task 1, there is no required behaviour after the messagebox is closed; the program can terminate or remain open. However, whatever behaviour you choose must be reasonable (i.e. closing the messagebox should not cause your program to crash, or an error to show).
To complete this task you will need to implement various view classes, as well as a graphical controller class which extends the existing MazeRunner controller class. While it is not necessary in order to complete task 1, you are permitted to add additional modelling classes if they improve your solution.
The following sub-sections outline the required structure for your code. You will benefit from writing these classes in parallel, but you should still test individual methods as you write them.
3.1 Provided Code
The following code is provided in a2 solution.py and a3 support.py for you to use when implementing your solution.
3.1.1 Model classes
3.1.2 AbstractGrid
• init (self, master: Union[tk.Tk, tk.Frame], dimensions: tuple[int, int], size: tuple[int, int], **kwargs) -> None:
Sets up a new AbstractGrid in the master frame. dimensions is the initial number of rows and number of columns, and size is the width in pixels, and the height in pixels. **kwargs is used to allow AbstractGrid to support any named arguments supported by tk.Canvas.
• set dimensions(self, dimensions: tuple[int, int]) -> None: Sets the dimensions of the grid to dimensions.
• get bbox(self, position: tuple[int, int]) -> tuple[int, int, int, int]: Returns the bounding box for the (row, col) position, in the form (x min, y min, x max, y max).
• get midpoint(self, position: tuple[int, int]) -> tuple[int, int]:
Gets the graphics coordinates for the center of the cell at the given (row, col) position.
• annotate position(self, position: tuple[int, int], text: str) -> None: Annotates the cell at the given (row, col) position with the provided text.
• clear(self) -> None: Clears the canvas.
3.2 View classes
You must implement the view classes for the level map, inventory, and player stats. Because the level map and player stats can both be represented by grids, you are required to use the AbstractGrid class to factor out the shared functionality. This section outlines the classes and methods you are required to write. While you do not need to, you are permitted to add additional methods or classes provided they improve your solution.
3.2.1 LevelView
LevelView is a view class which inherits from AbstractGrid and displays the maze (tiles) along with the entities. Tiles are drawn on the map using coloured rectangles at their (row, column) postitions, and entities are drawn over the tiles using coloured, annotated ovals at their (row, column) positions (as per Fig. 2). The colours representing each tile and entity can be found in constants.py. You must use these colours.
Your program should work for reasonable map sizes. You must not assume that the number of rows will always be equal to the number of columns. You must use the create oval, create rectangle, and create text methods for tk.Canvas to achieve this task. The LevelView class should be instantiated as LevelView(master, dimensions, size, **kwargs) where **kwargs should be passed to the super class as with AbstractGrid. The width and height of the maze shown in the level (and consequently, the level view) are given in constants.py. While it is recommended to create your own helper methods in this class, the only method you are required to create is:
• draw(self, tiles: list[list[Tile]], items: dict[tuple[int, int], Item], player pos: tuple[int, int]) -> None:
Clears and redraws the entire level (maze and entities).
3.2.2 StatsView
StatsView is a view class which inherits from AbstractGrid and displays the player’s stats (HP, health, thirst), along with the number of coins collected (as per Fig. 2). A StatsView always has four columns and two rows. The first row contains the headings for the types of stats and the second row contains the values for the stat in that column. You are required to implement the following methods in this class:
• init (self, master: Union[tk.Tk, tk.Frame], width: int, **kwargs) -> None: Sets up a new StatsView in the master frame with the given width. The height of the StatsView can be found in constants.py. The background colour should be set to THEME COLOUR from constants.py via the **kwargs.
• draw stats(self, player stats: tuple[int, int, int]) -> None:
Draws the player’s stats (hp, hunger, thirst).
• draw coins(self, num coins: int) -> None: Draws the number of coins.
3.2.3 InventoryView
InventoryView is a view class which inherits from tk.Frame, and displays the items the player has in their inventory via tk.Labels, under a title label. This class also provides a mechanism through which the user can apply items. You must implement the following methods in this class:
• init (self, master: Union[tk.Tk, tk.Frame], **kwargs) -> None: Creates a new InventoryView within master.
• set click callback(self, callback: Callable[[str], None]) -> None:
Sets the function to be called when an item is clicked. The provided callback function should take one argument; the string name of the item.
• clear(self) -> None: Clears all child widgets from this InventoryView.
• draw item(self, name: str, num: int, colour: str) -> None:
Creates and binds (if a callback exists) a single tk.Label in the InventoryView frame. name is the name of the item, num is the quantity currently in the users inventory, and colour is the background colour for this item label (determined by the type of item).
• draw inventory(self, inventory: Inventory) -> None:
Draws any non-coin inventory items with their quantities and binds the callback for each, if a click callback has been set. Hint: loop over each item in the inventory and call draw item for each to create and bind the item label.
3.2.4 GraphicalInterface
GraphicalInterface inherits from UserInterface and must implement the methods described by UserInterface. The GraphicalInterface manages the overall view (i.e. the title banner and the three major widgets), and enables event handling. You must implement the following methods:
• create interface(self, dimensions: tuple[int, int]) -> None:
Creates the components (level view, inventory view, and stats view) in the master frame for this interface. dimensions represent the (row, column) dimensions of the maze in the current level.
• clear all(self) -> None: Clears each of the three major components (do not delete the component instances, just clear them).
• set maze dimensions(self, dimensions: tuple[int, int]) -> None: Updates the dimensions of the maze in the level to dimensions.
• bind keypress(self, command: Callable[[tk.Event], None]) -> None:
Binds the given command to the general keypress event. The command should be a function which takes in the keypress event, and performs different actions depending on what character was pressed. Any character other than ‘w’, ‘a’, ‘s’, or ‘d’ should be ignored (including all uppercase letters).
• set inventory callback(self, callback: Callable[[str], None]) -> None:
Sets the function to be called when an item is clicked in the inventory view to be callback. The callback function will need to be constructed in the controller class (see Section 3.3), and must take one argument (the string name of an Item), and apply the first instance of that item in the inventory to the player. This function should then remove that item from the player’s inventory.
• draw inventory(self, inventory: Inventory) -> None:
Draws any non-coin inventory items with their quantities and binds the callback for each, if a click callback has been set.
• draw(self, maze: Maze, items: dict[tuple[int, int], Item], player position: tuple[int, int], inventory: Inventory, player stats: tuple[int, int, int]) -> None: Must implement the draw method as per the docstring in the UserInterface class. This should just involve clearing the three major components and redrawing them with the new state (provided as arguments to this method).
• draw inventory(self, inventory: Inventory) -> None:
Must implement the draw inventory method as per the docstring in the UserInterface class. Note: this method will need to draw both the non-coin items on the inventory view (see public draw inventory method), and also draw the coins on the stats view.
• draw level(self, maze: Maze, items: dict[tuple[int, int], Item], player position: tuple[int, int]) -> None:
Must implement the draw level method as per the docstring in the UserInterface class.
• draw player stats(self, player stats: tuple[int, int, int]) -> None:
Must implement the draw player stats method as per the docstring in the UserInterface class.
3.3 Controller class
3.3.1 GraphicalMazeRunner
GraphicalMazeRunner should inherit from MazeRunner and overwrite / add methods where required in order to enable the game to use a GraphicalInterface instead of a TextInterface, and to be based on events generated by the user (keypresses and mouse clicks), rather than based on user input to the shell. In particular, you will need to implement the following methods:
• init (self, game file: str, root: tk.Tk) -> None: Creates a new GraphicalMazeRunner game, with the view inside the given root widget.
• handle keypress(self, e: tk.Event) -> None: Handles a keypress. If the key pressed was one of ‘w’, ‘a’, ‘s’, or ‘d’ a move is attempted. If the player wins or loses the game with this move, they are informed of their result via a messagebox.
• apply item(self, item name: str) -> None: Attempts to apply an item with the given name to the player.
• play(self) -> None: Called to cause gameplay to occur. This method should first create the widgets on the GraphicalInterface, bind the keypress handler, set the inventory callback, and then redraw to allow the game to begin.
3.4 play game(root: tk.Tk) function
The play game function should be fairly short. You should:
1. Construct the controller instance using the file in the GAME FILE constant and the root tk.Tk parameter.
2. Cause gameplay to commence.
3. Ensure the root window stays opening listening for events (using mainloop).
3.5 main function
The main function should:
1. Construct the root tk.Tk instance.
2. Call the play game function passing in the newly created root tk.Tk instance.
4 Task 2: Images, Buttons, and File Menu
Task 2 requires you to add additional features to enhance the games look and functionality. Figure 1 gives an example of the game at the end of task 2. Another example is shown below in Figure 3. Note: Your task 1 functionality must still be testable. When your program is run with the TASK constant in constants.py set to 1, the game should display only task 1 features. When your program is run with the TASK constant set to 2, the game should display all attempted task 2 features. There should be no task 2 features visible when running the game in task 1 mode.
As an advanced task, the structure of your code is largely up to you. However, if you complete this task, you will be marked on how well-designed your code is. Some aspects of design are compulsory (e.g. implementing the subclasses described in subsections below). However, the methods and internal design of these classes are not prescribed. When designing your solution, you should consider ways to effectively utilize the inheritance structure to avoid duplicating code.
4.1 Images
Create a new view class, ImageLevelView, that extends your existing LevelView class. This class should behave similarly to the existing LevelView class, except that images should be used to display the tiles and entities, rather than rectangles and ovals (see the provided images folder). When your assignment is run with the TASK constant in constants.py set to 2, the ImageLevelView should be displayed. When it is run with the TASK constant in constants.py set to 1, the basic LevelView from task 1 should be displayed.
Just as the rectangles and ovals needed to change size depending on the dimensions of the level maze in task 1, in task 2, the images must resize appropriately. In order to do this, you will need to install Pillow , and then import ImageTk and Image from PIL.

Figure 3: Another example fully functional game at the end of Task 2.
4.2 ControlsFrame
Add a new class ControlsFrame which inherits from tk.Frame, and displays two buttons (restart and new game), as well as a timer of how long the current game has been going for. The ControlsFrame instance should be displayed at the bottom of the interface, just below the StatsView. The details of the three components on this widget are as follows:
• Restart button: when this button is clicked, the current game should be restarted, i.e. the user should return to the start of the first level, their move count and stats should reset, and the game timer should return to 0.
• New game button: when this button is clicked, a tk.TopLevel window should appear, prompting the user to enter a relative path to a new game file. If the user enters a valid game file, the game should restart using this game file. If they enter an invalid game file, they should be informed that the game file was not valid via a messagebox. Once the user has acknowledged the messagebox, the toplevel window should close and the user should not be automatically reprompted.
• The game timer should display the number of minutes and seconds that have elapsed since the current game began.
4.3 File Menu

Figure 4: Example file menu on Mac.
Option Behaviour
Save game Prompt the user for the location to save their file (using an appropriate method of your choosing) and save all necessary information to replicate the current state of the game.
Load game Prompt the user for the location of the file to load a game from and load the game described in that file.
Restart game Restart the current game, including game timer.
Quit Prompt the player via messagebox to ask whether they are sure they would like to quit. If no, do nothing. If yes, quit the game (window should close and program should terminate).
Table 2: File menu options.
5 Postgraduate Task: Shop and shop-exclusive items
Note: functionality for this task should only be present when the TASK constant is set to 3. None of the postgraduate features should be present in task 1 or 2 mode.
For this task, you’ll create a shop where the user can spend their collected coins to buy items, and integrate this shop into your assignment via a new Button on the ControlsFrame. Note that this button should not be present in your task 2 functionality. Additionally, you’ll extend the model to add a item, that will be exclusively available in the shop, candy.
5.1 Basic Shop
Create a basic shop interface with the four items along with their prices:
Apple $1 Water $1 Honey $2 Potion $2

(a) Basic shop front before purchasing anything. (b) Basic shop front after purchasing two items.
Figure 5: Basic shop.
The store front should also have a ‘Done’ button, which closes the shop when clicked. The interface should look as shown in Figure 5a. Note: you cannot create more than one tk.Tk instance. Instead, you should set up the store front as a tk.Toplevel.
When the image of an item is left clicked, if the player can afford the item (has enough coins), then the price should be subtracted from the player’s coins, and an instance of the item should be added to the player’s inventory. An example of this is shown in Figure 5b, which shows the interface from Figure 5a immediately after left clicking water and honey once each.
5.2 Candy
For this task, you will extend the existing model to add a new item that the player can buy from the store. At the end of this task, your store front should look like Figure 6.
You must implement a new Food subclass called Candy. A candy should restore the player’s hunger to 0 when applied, but also reduce their health by 2. Add the Candy item to the store front, with a price of $3.

Figure 6: Shop front after adding shop-exclusive items.
6 Assessment and Marking Criteria
This assignment assesses course learning objectives:
1. apply program constructs such as variables, selection, iteration and sub-routines,
2. apply basic object-oriented concepts such as classes, instances and methods,
3. read and analyse code written by others,
4. analyse a problem and design an algorithmic solution to the problem,
5. read and analyse a design and be able to translate the design into a working program, and
6. apply techniques for testing and debugging, and
7. design and implement simple GUIs.
6.1 Marking Breakdown
functionality mark
final style mark = style mark ×
(
100, if postgraduate
75, otherwise
6.2 Functionality Marking
Feature Undergradute Postgraduate
Task 1 45 45
Window title 3 3
Game grid 20 20
Inventory 10 10
Stats view 10 10
Game end events 2 2
Task 2 30 30
Images 10 10
File menu 10 10
Controls 10 10
Task 3 0 25
Basic shop 0 15
Candy 0 10
Total 75 100
6.3 Style Marking
The key consideration in marking your code style is whether the code is easy to understand. There are several aspects of code style that contribute to how easy it is to understand code. In this assignment, your code style will be assessed against the following criteria.
• Readability
– Program Structure: Layout of code makes it easier to read and follow its logic. This includes using whitespace to highlight blocks of logic.
– Identifier Names: Variable, constant, function, class and method names clearly describe what they represent in the program’s logic. Do not use Hungarian Notation for identifiers.
• Documentation
– Inline Comments: All significant blocks of code should have a comment to explain how the logic works. For a small method or function, the logic should usually be clear from the code and docstring. For long or complex methods or functions, each logical block should have an in-line comment describing its logic.
– Informative Docstrings: Every class, method and function should have a docstring that summarises its purpose. This includes describing parameters and return values so that others can understand how to use the method or function correctly.
• Code Design
– Single Instance of Logic: Blocks of code should not be duplicated in your program. Any code that needs to be used multiple times should be implemented as a method or function.
– Control Structures: Logic is structured simply and clearly through good use of control structures (e.g. loops and conditional statements).
• Object-Oriented Program Structure
– Model View Controller: The GUI’s view and control logic is clearly separated from the model. Model information stored in the controller and passed to the view when required.
– Abstraction: Public interfaces of classes are simple and reusable. Enabling modular and reusable components which abstract GUI details..
– Encapsulation: Classes are designed as independent modules with state and behaviour. Methods only directly access the state of the object on which they were invoked. Methods never update the state of another object.
– Inheritance: Subclasses extend the behaviour of their superclass without re-implementing behaviour, or breaking the superclass behaviour or design. Abstract classes have been used to effectively group shared behaviour amongst subclasses.
7 Assignment Submission
Your assignment must be submitted as a3.py via the assignment three submission link on Gradescope. You should not submit any other files (e.g. maps, images, etc.). You do not need to resubmit a2 solution.py or any other supplied files.
8 Appendices
8.1 Appendix A: Permitted libraries.
You will need the following libraries to form a working solution:
1. tkinter and any of its submodules: You will need to import tkinter itself, as well as some of its submodules. For example, to use a messagebox, you will need to explicitly import the messagebox submodule from tkinter. If you do not explicitly import the submodules you are using, your solution will likely work in IDLE, but nowhere else.
2. PIL
1. math
2. typing
Use of any other libraries (including in-built libraries) is not permitted and will be penalized by a deduction of up to 100% of the Assignment 3 grade.

More products