关键词 > CSSE1001/CSSE7030

CSSE1001/CSSE7030 Slay the Spire Assignment 2 Semester 1, 2023

发布时间:2023-04-20

Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

Slay the Spire

Assignment 2

Semester 1, 2023

CSSE1001/CSSE7030

Due date: 28th April 2023 16:00 GMT+10

1 Introduction

Slay the Spire  is a rogue-like deck building card game in which a player must build a deck of cards, which they use during encounters with monsters.  Details of the original Slay the Spire game can be found here.  In Assignment 2, you will create an object-oriented text-based game inspired by (though heavily simplified and altered from) Slay the Spire1.

You are required to implement a collection of classes and methods as specified in Section 5 of this document. Your program’s output must match the expected output exactly; minor differences in output (such as whitespace or casing) will cause tests to fail, resulting in zero marks for those tests. Any changes to this document will be listed in a changelog at the end of the document.

2 Getting Started

Download a2 .zip from Blackboard — this archive contains the necessary files to start this assignment.  Once extracted, the a2 .zip archive will provide the following files / directories:

a2.py

The game engine. This is the only file you submit and modify. Do not make changes to any other files.

a2_support.py

Support code to assist in more complex parts of the assignment, and to handle randomness.  Note that when implementing random behaviour, you must use the support functions, rather than calling functions from random yourself.

games

A folder containing several text files of playable games.

game_examples

A folder containing example output from running the completed assignment.

3 Gameplay

At the beginning of the game, the user selects a player character; different player characters have different advantanges and disadvantages, such as starting with higher HP or a better deck of cards. The user then selects a game file, which specifies the encounters that they will play. After this, gameplay can begin.

During gameplay, the player users a deck of cards to work through a series of encounters with monsters. Each encounter involves between one and three monsters, which the player must battle in parallel over a series of turns. At the start of each turn the user draws 5 cards at random from their deck into their hand. Each card costs between 0 and 3 energy point to play.  The user may play as many cards as they like from their hand during their turn provided they still have the energy points required to play the requested cards. The user opts to end their turn when they are finished playing cards, at which point the monsters in the encounter each take an action (which may affect the player’s HP or other stats, or the monster’s own stats). When a card is played it is immediately sent to the player’s discard pile. At the end of a turn, all cards in the players hand (regardless of whether they were played that turn) are sent to the discard pile. Cards in the discard pile cannot be drawn until the entire deck has been drawn, at which point the deck is replenished with all the cards from the discard pile.  An encounter ends when either the player has killed all monsters (reduced their HP to 0) or when the monsters have killed the player (reduced the player’s HP to 0). If the player wins an encounter, an encounter win message is printed and the next encounter begins. If no more encounters remain, the game terminates with a game win message, and if the player loses an encounter, the program terminates with a loss message.  See a2_support .py for the relevant messages.  You can find examples of gameplay in the game_examples folder provided in a2 .zip. For more details on the behaviour of main, see Section 5.4.

4 Class overview and relationships

You are required to implement a number of classes, as well as a main function. You should develop the classes first and ensure they all work (at minimum, ensure they pass the Gradescope tests) before beginning work on the main function.  The class diagram in Figure 1 provides an overview of all of these classes, and the basic relationships between them.  The details of these classes and their methods are described in depth in Section 5.

• Hollow-arrowheads indicate inheritance (i.e. the“is-a”relationship).

• Dotted arrows indicates composition  (i.e.  the has-a”relationship).  An arrow marked with 1-1 denotes that each instance of the class at the base of the arrow contains exactly one instance of the class at the head of the arrow. An arrow marked with 1-n denotes that each instance of the class at the base of the arrow may contain many instances of the class at the head of the arrow. E.g. an Encounter instance may contain between 1 and 3 Monster instances, but only one Player instance.

• Green classes are abstract classes.  You should only ever instantiate the blue classes in your program, though you should instantiate the green classes to test them before beginning work on their subclasses.

Figure 1: Basic class relationship diagram for the classes which need to be implemented for this assignment.

5 Implementation

This section outlines the classes, methods, and functions that you are required to implement as part of your assignment. It is recommended that you implement the classes in the order in which they are described. Ensure each class behaves as per the examples (and where possible, the Gradescope tests) before moving on to the next class.

5.1 Cards

Cards are used by the player during encounters to attack monsters, defend, or apply status modifiers.  When implementing this class, it is not necessary that you yet understand the mechanics of how these effects will be applied. You will handle this later in your implementation. Card classes simply provide information about the effects each type of card has; they are not responsible for directly causing these effects.

All instantiable cards inherit from the abstract Card class, and should inheret the default Card behaviour except where specified in the descriptions of each specific type of card.

Card

(“bstr“ct cl“ss)

An abstract class from which all instantiable types of cards inheret. Provides the default card behaviour, which can be inhereted or overwritten by specific types of cards. The __init__ method for all cards do not take any arguments beyond self.

get_damage_amount(self)  ->  int                                                                             (method)

Returns the amount of damage this card does to its target (i.e. the opponent it is played on). By default, the damage done by a card is 0.

get_block(self)  ->  int

(method)

Returns the amount of block this card adds to its user.  By default, the block amount provided by a card is 0.

get_energy_cost(self)  ->  int

Returns the amount of energy this card costs to play. By default, the energy cost should be 1.

get_status_modifiers(self)  ->  dict[str,  int]

(method)

(method)

Returns a dictionary describing each status modifiers applied when this card is played.  By default, no status modifiers are applied; that is, this method should return an empty dictionary in the abstract Card class.

get_name(self)  ->  str

Returns the name of the card. In the Card superclass, this is just the string‘Card’.

get_description(self)  ->  str

Returns a description of the card. In the Card superclass, this is just the stringA card’.

requires_target(self)  -> bool

(method)

(method)

(method)

Returns True if playing this card requires a target, and False if it does not. By default, a card does require a target.

__str__ (self)  ->  str                                                                                                (method)

Returns the string representation for the card, in the format ‘{Card  name}:    {Card  description}’ .

__repr__ (self)  ->  str                                                                                              (method)

Returns the text that would be required to create a new instance of this class identical to self.

Examples


>>>  card  =  Card()

>>>  card .get_damage_amount()

0

>>>  card .get_block()

0

>>>  card .get_energy_cost()

1

>>>  card .get_status_modifiers()

{}



>>>  card .get_name()

'Card'

>>>  card .get_description()

'A  card . '

>>>  card .requires_target()

True

>>>  str(card)

'Card:  A  card . '

>>>  card

Card()


Strike

(class)

Inherits from Card

Strike is a type of Card that deals 6 damage to its target. It costs 1 energy point to play.

Examples


>>>  strike  =  Strike()

>>>  print(strike .get_damage_amount(),  strike .get_block(),  strike .get_energy_cost())

6  0  1

>>>  strike .get_name()

'Strike '

>>>  strike .get_description()

'Deal  6  damage . '

>>>  strike .requires_target()

True

>>>  str(strike)

'Strike:  Deal  6  damage . '

>>>  strike

Strike()


Defend

(class)

Inherits from Card

Defend is a type of Card that adds 5 block to its user. Defend does not require a target. It costs 1 energy point to play.


Examples

>>>  defend  =  Defend()

>>>  print(defend .get_damage_amount(),  defend .get_block(),  defend .get_energy_cost())

0  5  1

>>>  defend .get_name()

'Defend'

>>>  defend .get_description()

'Gain  5  block . '

>>>  defend .requires_target()

False

>>>  str(defend)

'Defend:  Gain  5  block . '

>>>  defend

Defend()


Bash

(class)

Inherits from Card


Bash is a type of Card that adds 5 block to its user and causes 7 damage to its target. It costs 2 to play.

energy points


Examples



>>>  bash  =  Bash()

>>>  print(bash .get_damage_amount(),  bash .get_block(),  bash .get_energy_cost())

7  5  2

>>>  bash .get_name()

'Bash'

>>>  bash .get_description()

'Deal  7  damage .  Gain  5  block . '

>>>  bash .requires_target()

True

>>>  str(bash)

'Bash:  Deal  7  damage .  Gain  5  block . '

>>>  bash

Bash()


Neutralize

(class)


Inherits from Card

Neutralize is a type of card that deals 3 damage to its target.  It also applies status modifiers to its target; namely, it applies 1 weak and 2 vulnerable. Neutralize does not cost any energy points to play.

Examples


>>>  neutralize  =  Neutralize()

>>>  print(neutralize .get_damage_amount(),  neutralize .get_block(),  neutralize .get_energy_cost())

3  0  0

>>>  neutralize .get_status_modifiers()

{ 'weak ' :  1,  'vulnerable ' :  2}

>>>  neutralize .get_name()

'Neutralize '

>>>  neutralize .get_description()

'Deal  3  damage .  Apply  1  weak .  Apply  2  vulnerable . '

>>>  str(neutralize)

'Neutralize:  Deal  3  damage .  Apply  1  weak .  Apply  2  vulnerable . '

>>>  neutralize

Neutralize()


Survivor

(class)


Inherits from Card

Survivor is a type of card that adds 8 block and applies 1 strength to its user.  Survivor does not require a target.

Examples


>>>  survivor  =  Survivor()

>>>  print(survivor .get_damage_amount(),  survivor .get_block(),  survivor .get_energy_cost())

0  8  1

>>>  survivor .get_status_modifiers()

{ 'strength ' :  1}




>>>  survivor .requires_target()

False

>>>  survivor .get_name()

'Survivor '

>>>  survivor .get_description()

'Gain  8  block  and  1  strength . '

>>>  str(survivor)

'Survivor:  Gain  8  block  and  1  strength . '

>>>  survivor

Survivor()


5.2 Entities

Entities in the game include the player and enemies (monsters). All entities have:

• Health points (HP): This starts at the maximum HP for the entity, and may decrease over the course of one or more encounters. An entity is defeated when its HP is reduced to 0.

• Block:  This is the amount of defense the entity has.  When an entity is attacked, damage is applied to the block first.  Only once the block has been reduced to 0 will any remaining damage be caused to the entity’s HP. For example, if an entity with 10 HP and 5 block is attacked with a damage of 8, their block will be reduced to 0 and their HP reduced to 7.

• Strength:  The amount of additional strength this entity has.  When an entity plays a card that causes damage to a target, the damage caused will increase by 1 for each strength point the entity has. Strength does not wear off until the end of an encounter.

• Weak: The number of turns for which this entity is weak. If an entity is weak on a given turn, all cards played by the entity that cause damage will cause 25% less damage.

• Vulnerable: The number of turns for which this entity is vulnerable. If an entity is vulnerable on a turn, damage caused to it will be increased by 50%.

In this assignment you must implement an abstract Entity class, which provides the base entity functionality that all entities inherit.  Except where specified, entities have the default behaviour inherited from the Entity superclass.

Entity

(“bstr“ct cl“ss)

Abstract base class from which all entities inherit.

__init__ (self, max_hp:  int)  ->  None

(method)

Sets up a new entity with the given max_hp.  An entity starts with the maximum amount of HP it can have. Block, strength, weak, and vulnerable all start at 0.

get_hp(self)  -> int

(method)

Returns the current HP for this entity.

get_max_hp(self)  -> int

(method)

Returns the maximum possible HP for this entity.

get_block(self)  -> int

(method)

Returns the amount of block for this entity.

get_strength(self)  -> int

(method)

Returns the amount of strength for this entity.

get_weak(self)  -> int

(method)

Returns the number of turns for which this entity is weak.

get_vulnerable(self)  ->  int     Returns the number of turns for which this

get_name(self)  ->  int

entity is vulnerable.

(method)

(method)

Returns the name of the entity.  The name of an entity is just the name of the most specific class it belongs to.

reduce_hp(self,  amount:  int)  ->  None

(method)

Attacks the entity with a damage of amount.  This involves reducing block until the amount of damage has been done or until block has reduced to zero, in which case the HP is reduced by the remaining amount.  For example, if an entity has 20 HP and 5 block, calling reduce_hp with an amount of 10 would result in 15 HP and 0 block. HP cannot go below 0.

is_defeated(self)  -> bool

(method)

Returns True if the entity is defeated, and False otherwise. An entity is defeated if it has no HP remaining.

add_block(self, amount: int)  -> None

(method)

Adds the given amount to the amount of block this entity has.

add_strength(self, amount: int)  -> None

(method)

Adds the given amount to the amount of strength this entity has.

add_weak(self, amount: int)  -> None

(method)

Adds the given amount to the amount of weak this entity has.

add_vulnerable(self, amount: int)  -> None

(method)

Adds the given amount to the amount of vulnerable this entity has.

new_turn(self)  -> None