Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: THEend8_
Hacker Assignment
CSSE1001/CSSE7030
GMT+10 1 Introduction The third assignment will be a culmination of all your existing knowledge of the course content. Your task will be to make a game with a Graphical User Interface (GUI). The game of Hacker is conceptually similar to Space Invaders, the player is fixed vertically while entities progress verti- cally towards the player (See Figure 1). This assignment is split into 3 tasks of increasingly difficulty. As tasks increase, their total marks decrease. Task 3 is for CSSE7030 students only. Student’s are expected to follow the Apple Model-View-Controller (MVC) design pattern. You are not allowed to import any modules Figure 1: A preview of an in-progress game. The Player is represented by a purple square with ‘P’. Other entities are indicated with ‘B’, ‘C’, and ‘D’ squares. other than the following: 1 • tkinter (and all tkinter submodules) • random • Pillow • a3_support Importing any module not listed here will result in a deduction of up to 100% 2 Terminology Destroyable (D) Red entity that players must destroy before it reaches the top row where the player is located. Collectable (C) Blue entity that players must collect. By default, collecting 7 Collectables will cause the Player to win the game. Blocker (B) Grey entity that players cannot shoot through. Shot type There are 2 shot types in the game - destroy and collect. Rules on what can be destroyed and collected can be seen in the Model Class descriptions (Section 5.2). 3 Overview and Game Rules The goal of the game is to collect a specified amount of collectable entities without having a destroyable entity reach the player’s row. If a red destroyable entity reaches the player’s row then the game is over. If any other entity reaches the top row then the game continues as normal. Players mainly interact with the game by moving non-player entities on the grid. It is important to note that the player never moves and remains centred on the grid in the top row. Entities are rotated left and right and wrap around to the other side of the grid if they are moved off an edge (See Figure 2). Every 2 seconds, non-player entities step 1 space closer to the player and a new bottom row containing randomly generated entities will appear. This is known as a ‘step event’. 4 Getting Started Download a3.zip from Blackboard — this archive contains the necessary files to start this as- signment. Some support code has been included to assist with implementing the program. Once extracted, the a3.zip archive will provide the following files/directories: a3.py File to run your program. Write your code in this file. Do not change any provided functions found in this file. Note: You are required to use the methods provided in this file. a3 support.py Constants, functions, and classes you may find helpful for writing the engine. Read the docstrings in order to understand how to use them in your solution. Do not make changes to this file. 2 Figure 2: Before and after of rotating the grid right. Upon rotating right, the blue entity is moved to the other edge as if these edges were connected. images Folder containing images used for Task 2 and 3 functionality. Note: Task 3 is the CSSE7030 task. 5 Task 1 - Basic Model and View The following sub-sections outline the gameplay requirements (Section 5.1), as well as the rec- ommended structure for your code (5.2, 5.3). Remember to test individual methods as you write them. Within this section we outline methods that you must write for the model, and some methods that may be useful to write in the view classes for Task 1. In the view classes, type hints are omitted, as it is up to you to determine what these should be. 5.1 Basic Gameplay User input events should cause behaviour as per Table 1. Event Behaviour Key Press: ‘A’ or ‘a’ All non-player entities rotate left one square. Key Press: ‘D’ or ‘d’ All non-player entities rotate right one square. Key Press: ‘SPACE’ Shoot a destroy shot. Key Press: ‘ENTER’ Shoot a collect shot. Table 1: Task 1 events and their corresponding behaviours. The entities can rotate independently of the step event. That is, the entities can rotate multiple times within the space of a single step event. When the player wins or loses the game they should be informed of the outcome via a messagebox that pops up on the screen. Once this messagebox is closed, the game is ended and the application should terminated. 3 Figure 3: Basic class diagram. 5.2 Model Classes This section outlines classes and functions you need to implement that represent the model of the game. The class diagram below depicts these classes with the basic relationships between them (See Figure 3). • Solid arrows indicate inheritance (i.e. the “is-a” relationship). • Dashed arrows indicate composition (i.e. the “has-a” relationship). 5.2.1 Entity Entity is an abstract class that is used to represent any element that can appear on the game’s grid. • display(self) -> str: Return the character used to represent this entity in a text-based grid. An instance of the abstract Entity class should never be placed in the grid, so this method should only be implemented by subclasses of Entity. To indicate that this method needs to be implemented by subclasses, this method should raise a NotImplementedError. • __repr__(self) -> str}: Return a representation of this entity. This should be inherited by each subclass but should instead display the class name of that subclass rather than always Entity’. >>> e n t i t y = Entity ( ) >>> repr ( e n t i t y ) ’ Ent ity ( ) ’ 5.2.2 Player A subclass of Entity representing a Player within the game. • display(self) -> str: Return the character representing a player: ’P’ 5.2.3 Destroyable A subclass of Entity representing a Destroyable within the game. A destroyable can be destroyed by the player but not collected. • display(self) -> str: Return the character representing a destroyable: ’D’ 4 5.2.4 Collectable A subclass of Entity representing a Collectable within the game. A collectable can be destroyed or collected by the player. • display(self) -> str: Return the character representing a collectable: ’C’ 5.2.5 Blocker A subclass of Entity representing a Blocker within the game. A blocker cannot be destroyed or collected by the player. • display(self) -> str: Return the character representing a blocker: ’B’ 5.2.6 Grid The Grid class is used to represent the 2D grid of entities. The top left position of the grid is indicated by (0, 0). • __init__(self, size: int) -> None: A grid is constructed with a size representing the number of rows (equal to the number of columns) in the grid. Entities should be stored in a dictionary. Initially a grid does not contain any entities. • get_size(self) -> int: Return the size of the grid. • add_entity(self, position: Position, entity: Entity) -> None: Add a given entity into the grid at a specified position. This entity is only added if the position is valid. If an entity already exists at the specified position, this method will replace the current entity at the specified position. • get_entities(self) -> Dict[Position, Entity]: Return the dictionary containing grid entities. Updating the returned dictionary should have no side-effects. • get_entity(self, position: Position) -> Optional[Entity]: Return a entity from the grid at a specific position or None if the position does not have a mapped entity. • remove_entity(self, position: Position) -> None: Remove an entity from the grid at a specified position. • serialise(self) -> Dict[Tuple[int, int], str]: Convert dictionary of Position and Entities into a simplified, serialised dictionary mapping tuples to characters, and return this serialised mapping. Tuples are represented by the x and y coordinates of a Positions and Entities are represented by their ‘display()‘ character. • in_bounds(self, position: Position) -> bool: Return a boolean based on whether the position is valid in terms of the dimensions of the grid. Return True iff: 5 ? x ≥ 0 and x < grid size ? y ≥ 1 and y < grid size • __repr__(self) -> str: Return a representation of this Grid. >>> g r id = Grid (5 ) >>> repr ( g r i d ) ’ Grid (5 ) ’ 5.2.7 Game The Game handles the logic for controlling the actions of the entities within the grid. • __init__(self, size: int) -> None: A game is constructed with a size representing the dimensions of the playing grid. A game should be constructed with at least the following variable: ? Flag representing whether the game is won or lost • get_grid(self) -> Grid: Return the instance of the grid held by the game. • get_player_position(self) -> Position: Return the position of the player in the grid (top row, centre column). This position should be constant. • get_num_collected(self) -> int: Return the total of Collectables acquired. • get_num_destroyed(self) -> int: Return the total of Destroyables removed with a shot. • get_total_shots(self) -> int: Return the total of shots taken. • rotate_grid(self, direction: str) -> None: Rotate the positions of the entities within the grid depending on the direction they are being rotated. Valid directions are specified by the constants found in the a3_support file. Entity positions rotate as seen in Figure 2. – Left rotation moves all entities by an offset of (-1, 0) – Right rotation moves all entities by an offset of (1, 0) • _create_entity(self, display: str) -> Entity: Uses a display character to create an Entity. Raises a NotImplementedError if the character parsed into as the display is not an existing Entity. • generate_entities(self) -> None: (provided) Generate random entities for each step of the game. • step(self) -> None: This method moves all entities on the board by an offset of (0, -1). Once entities have been moved, new entities should be added to the grid (using generate_entities). Entities should not be re-added to the grid if they have moved out of bounds. 6 • fire(self, shot_type: str) -> None: Handles the firing/collecting actions of a player towards an entity within the grid. A shot is fired from the players position and iteratively moves down the grid. shot_type refers to whether a collect or destroy shot has been fired (refer to Entity de- scriptions for how different entities react to being hit by different types). Valid shot_type constants can be found in the a3_support file. • has_won(self) -> bool: Return True if the player has won the game. • has_lost(self) -> bool: Returns True if the game is lost (a Destroyable has reached the top row). 5.3 View Classes You must implement view classes for both a) the game grid and b) the game statistics (left and right sections of Figure 1 respectively). Because these widgets can both be represented by grids of rectangles, you should create an abstract class to factor out the shared functionality. Note: View classes should not be resizable. 5.3.1 AbstractField AbstractField is an abstract view class which inherits from tk.Canvas and provides base func- tionality for other view classes. An AbstractField can be thought of as a grid with a set number of rows and columns, which supports creation of text at specific positions based on row and column. The number of rows may differ from the number of columns, and the cells may be non-square. You must define the constructor for the AbstractField class as: • __init__(self, master, rows, cols, width, height, **kwargs): The parameters rows and cols are the number of rows and columns in the grid, width and height are the width and height of the height of the grid (and hence the width and height of the tk.Canvas, measured in pixels) and **kwargs signifies that any additional named arguments supported by tk.Canvas should also be supported by AbstractField. The following methods may be useful to include in the AbstractField class. • get_bbox(self, position): Returns the bounding box for the position; this is a tuple containing information about the pixel positions of the edges of the shape, in the form (x_min, y_min, x_max, y_max). • pixel to position(self, pixel): Converts the (x, y) pixel position (in graphics units) to a (row, column) position. • get_position_center(self, position): Gets the graphics coordinates for the center of the cell at the given (row, column) position. • annotate_position(self, position, text): Annotates the center of the cell at the given (row, column) position with the provided text. 5.3.2 GameField GameField is a visual representation of the game grid which inherits from AbstractField. Entities are drawn on the map using coloured rectangles at different (row, column) positions. You must annotate the rectangles of all entities with their display() characters (as per Figure 1). You 7 must use the create_rectangle and create_text methods from tk.Canvas to achieve this. The colours representing each entity are found in a3_support. The FIELD_COLOUR constant indicates the background colour of the view. You must define the constructor for the GameField class as: • __init__(self, master, size, width, height, **kwargs): The size parameter is the number of rows (= number of columns) in the grid, width and height are the width and height of the grid (in pixels) and **kwargs signifies that any additional named arguments supported by tk.Canvas should also be supported by GameField. It may be useful to add the following methods to the GameField class: • draw_grid(self, entities): Draws the entities (found in the Grid’s entity dictionary) in the game grid at their given position using a coloured rectangle with superimposed text identifying the entity (this includes the Player entity). • draw_player_area(self): Draws the grey area a player is placed on. This colour is found in a3_support file as the PLAYER_AREA constant. 5.3.3 ScoreBar ScoreBar is a visual representation of shot statistics from the player which inherits from AbstractField. For Task 1, this bar displays the number of collectables acquired and the number of destroyables shot as seen in Figure 1. The ScoreBar’s statistics values must update in real time. You must define the constructor for the ScoreBar class as: • __init__(self, master, rows, **kwargs): rows is the number of rows contained in the ScoreBar canvas. This should match the size of the grid. By default, columns should be set to 2. Relevant support file constants should be used for window sizing and **kwargs signifies that any additional named arguments supported by tk.Canvas should also be supported by ScoreBar. Finally, the background colour of the ScoreBar can be found in a3_support as SCORE_COLOUR. 5.3.4 HackerController HackerController acts as the controller for the Hacker game. The constructor should be defined as: • __init__(self, master, size): The parameter master represents the master window and size represents the number of rows (= number of columns) in the game map. This method should draw the title label (see a3_support for font, colour and size details), ini- tialise the Game model, and instantiate and pack the GameField and ScoreBar. Finally, this method should initiate the game’s ‘step’ The following are suggested methods to include in this class: • handle_keypress(self, event): This method should be called when the user presses any key during the game. It must handle error checking and event calling and execute methods to update both the model and the view accordingly. • draw(self, game): Clears and redraws the view based on the current game state. • handle_rotate(self, direction): Handles rotation of the entities and redrawing the game. It may be easiest for the handle_keypress method to call handle_rotate with the relevant arguments. 8 • handle_fire(self, shot_type): Handles the firing of the specified shot type and redraw- ing of the game. It may be easiest for the handle_keypress method to call handle_fire with the relevant arguments. • step(self): The step method is called every 2 seconds. This method triggers the step method for the game and updates the view accordingly. Note: The .after method for tkinter widgets may be useful when trying to get this method to run every 2 seconds. 6 Task 2 - Images, StatusBar and Filemenu Extensions Task 2 requires you to add additional features to enhance the game’s look and functionality. Fig- ure 4 gives an example of the game at the end of Task 2. Figure 4: Task 2 GUI Note: Your Task 1 functionality must still be testable. When your program is run with the TASK constant in a3_support.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. To separate Task 1 and 2, you must create a new interface class that extends the functionality of the HackerController class named AdvancedHackerController. 9 6.1 Images Create a new view class, ImageGameField, that extends your existing GameField class. This class should behave similarly to GameField, except that images should be used to display each square rather than rectangles (see the provided images folder). The game map should be set up as an ImageGameField in Task 2, but you should still provide a functional GameField class that allows us to test your Task 1 functionality when TASK=1 6.2 StatusBar Add a StatusBar class that inherits from tk.Frame. In this frame, you should include: • A shot counter, displaying how many shots overall the player has made in the current game. • A game timer displaying the number of minutes and seconds the user has been playing the current game. • A ‘Pause/Play’ button, which either pauses or resumes the game. (Button text will toggle on every press) 6.3 File Menu Add a file menu with the options described in Table 2. Note that on Windows this will appear at the top of the game window, whereas on Mac this will appear at the top of your screen. For saving and loading files, you must design an appropriate text (.txt) file format to store information about game state. Reasonable actions by the user (e.g. trying to load a non-game file) should be handled appropriately (e.g. with a pop-up to inform the user that something went wrong). Any error output messages caused by reasonable behaviour in the GUI will be marked down. Option Behaviour New game Start a new Hacker game 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. Quit Prompt the player via a 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. 7 Task 3 - CSSE7030 (Masters) Task 7.1 Model This section outlines the class and functions you need to extend to the original model outline in Task 1. 10 7.1.1 Bomb A subclass of Entity representing a Bomb within the game that player’s can destroy other entities with. A bomb removes all entities within a ‘splash damage’ radius. The SPLASH constant refers to the specific offsets. A bomb can be destroyed but cannot be collected. • display(self) -> str: Return the character representing a bomb: ’B’ 7.1.2 Game The relevant function in Game will need to be extended to accommodate for the Bombs splash damage functionality. This method will need to check for TASK=3 in order to work and the commented section in generate_entities() will need to be uncommented. The Game class will also be extended to allow players to have one extra life. This means players will start with an extra life. 7.2 View The updates to the model seen in the previous section will be visualised as seen in Figure 5. Defining another subclass would be beneficial for the implementation of the extended features. Note: These features should only be displayed when TASK=3. Figure 5: Complete Masters game 11 8 Assessment and Marking Criteria 8.1 Marking Breakdown Your total grade for this assessment piece will be a combination of your functionality and style marks. You are not required to attend an interview for this assignment. 8.2 Functionality Marking Your program’s functionality will be marked out of a total of 50 marks. As in assignment 0, your assignment will be put through a series of tests and your functionality mark will be proportional to the number of tests you pass. You will be given a subset of the functionality tests before the due date for the assignment. Your assignment will be tested on the functionality of gameplay features. The automated tests will play the game and attempt to identify components of the game, how these components operate will then be tested. Well before submission, run the functionality tests to ensure com- ponents of your application can be identified If the autograder is unable to identify components, you will be marked accordingly, even if your assignment is functional. The tests provided prior to submission will help you ensure that all components can be identified by the autograder. You need to perform your own testing of your program to make sure that it meets all spec- ifications given in the assignment. Only relying on the provided tests is likely to result in your program failing in some cases and you losing some functionality marks. Your program must run in the Python interpreter (the IDLE environment). Partial solutions will be marked, but if there are errors in your code that cause the interpreter to fail to execute your program, you will get zero for functionality marks. If there is a part of your code that causes the interpreter to fail, comment out the code so that the remainder can run. Your program must run using the Python 3.9 interpreter. If it runs in another environment (e.g. Python 3.8 or PyCharm) but not in the Python 3.9 interpreter, you will get zero for the functionality mark. 8.3 Style Marking The style of your assignment will be assessed by a tutor. Style will be marked according to the style rubric provided with the assignment. The style mark will also be out of 50. 8.4 Assignment Submission This assignment follows the assignment submission policy as assignment 0. Please refer to the assignment 0 task sheet. You must submit your assignment as a single Python file called a3.py (use this name – all lower case), and nothing else. Your submission will be automatically run to determine the func- tionality mark. If you submit a file with a different name, the tests will fail and you will get zero for functionality. Do not submit the a3 support.py file, or any other files. Do not submit any sort of archive file (e.g. zip, rar, 7z, etc.). 12 8.5 Plagiarism This assignment follows the same plagiarism policy is as per assignment 0. Please refer to the assignment 0 task sheet.