Lab Exercise 5 (Berry Interesting Pokémon Battle)
Overview
For this lab exercise, there is a berry interesting Pokémon battle. Instead of using moves, Pokémon use berries!
The battle will be between a gym challenger and a gym leader. Each trainer has a team that consists of 1 main Pokémon and 1 support Pokémon. All four Pokémon are on the battlefield at the same time (in other words, this is a double battle).
Each Pokémon has a name, an HP stat, and (possibly) a bag of berries.
A berry can either heal or damage a Pokémon. Berries may either be eaten by a Pokémon or thrown to a different Pokémon.
A Pokémon that carries berries stores them in their berry bag. Note that it is possible for a Pokémon to not carry a berry bag.
Tasks
For this Lab Exercise, there is a Berry class, which is meant to represent a berry. It is defined like this:
@dataclass
class Berry:
name: str
hp_delta: int
When a berry is eaten by or thrown at a Pokémon, the hp_delta is applied to the affected Pokémon's HP. Note that hp_delta may be negative.
There is also a BerryBag type alias, defined like this:
type BerryBag = list[Berry]
In battle.py, there is an implementation of the following classes:
Pokemon
Fields:
name(str)hp(int)__berry_bag(BerryBag | None)- For Pokémon that do not carry a berry bag, their
__berry_bagproperty will be represented asNone.
- For Pokémon that do not carry a berry bag, their
Methods:
__init__get_berry_bag(property)gain_hpis_alivethrowjumble
Behaviorally,
get_berry_bagreturns the Pokémon's berry bag.gain_hp(delta)modifies the Pokémon's HP.throw(target)removes a berry from the bag and applies its effect to target.jumble()randomly rearranges the berries in the bag.
MainPokemon
The main Pokémon is the team's ace. As the ace, the main Pokémon must always carry a berry bag (it should never be None).
The MainPokemon class should inherit from the Pokemon class; you must override any methods that need to be overridden.
GreedyPokemon
A greedy Pokémon is, well, greedy. They don't want other Pokémon to touch their berries.
The GreedyPokemon class should inherit from the MainPokemon class. This Pokémon bag should not be modified; as such:
- The return types of some existing methods might need to be updated.
throwshould not do anything if called.
SupportPokemon
The support Pokémon assists the main Pokémon by interfering with the enemy team. They do so by stealing berries.
The SupportPokemon class should inherit from the Pokemon class; you must add the following method:
eat_from_berry_bag_of(self, target)
This should eat a berry from the enemy team's main Pokémon's bag.
SuperSupportPokemon
The gym leader has a super support Pokémon. Unlike a normal support Pokémon, it can eat up to two berries at once.
The SuperSupportPokemon class should inherit from the SupportPokemon class; you must override any methods that need to be overridden.
PokemonTeam
This should be an abstract base class.
Fields:
m(MainPokemon)s(SupportPokemon)
Abstract methods:
get_main()get_support()
ChallengerTeam
This should inherit from PokemonTeam.
m and s should be a MainPokemon and a SupportPokemon, respectively.
LeaderTeam
This should inherit from PokemonTeam.
m and s should be a MainPokemon and a SuperSupportPokemon, respectively.
Game implementation
(Checkpoint Task 1) Implement a Game class that facilitates a match between two different trainers. If test_game.py is ran, the output should look similar to logs.txt.
This implementation is not required to adhere to the MVC architecture.
You are not allowed to modify any existing parts of battle.py.
[Update]
- See updated
logs.txtfile below. - Add
from __future__ import annotationsto the top ofbattle.py. - The battle ends if all Pokémon in one team faint (i.e., have \(\le 0\) HP). A Pokémon that has fainted can't do anything.
- The battle should last for at most a certain number of turns (player 1 -> player 2 -> player 1 counts as 3 turns); if both teams are still up after all turns, no one wins.
- The battle should run randomly. At each turn, the player for that turn chooses a random Pokémon (that hasn't fainted), and that Pokémon will choose a random action.
- You may refer to the
take_actionmethod of thePokemonclass.
- You may refer to the
LSP bug hunting
- (Checkpoint Task 2) Demonstrate one way in which
GreedyPokemon's invariant could be violated. - Find as many violations of the Liskov Substitution Principle (LSP) as you can.
- Rewrite the code so that these violations are avoided.
Printer adapter
To print out the members of a team, there is a TeamPrinter class that works like this:
class TeamPrinter:
def print_team(self, l: GeneralTeam) -> None:
for p in l.list_pokemon():
print(p.name, end=" -- ")
print()
Use the adapter pattern to allow a TeamPrinter to print out the members of a PokemonTeam.
You will have to define GeneralTeam yourself.
Scoring
- [20 ❤️] Checkpoint tasks 1 & 2
- checked during lab time
- [40 🔴] LSP violations
- explain in
lsp.pdf
- explain in
- [40 🔴] LSP fixes
- implement in
lsp.py, explain inlsp.pdf
- implement in
- [20 🔴] adapter for
TeamPrinter- put in
adapter.py
- put in
Submission
Note that this lab exercise is by pairs. You may pair up with anyone; make sure you form your pair in GitHub Classroom.
GitHub Classroom link: https://classroom.github.com/a/_BuTseaa
- Should initially contain
battle.py,test_game.py, andlogs.txt
Deadline: March 13 (F), 11:12 pm
Appendix
Updated logs.txt file
1
Dratini 25 [Berry(name='Oran Berry', hp_delta=10), Berry(name='Cheri Berry', hp_delta=5)]
Charmander 15 None
Pikachu 30 [Berry(name='Sitrus Berry', hp_delta=15)]
Magikarp 10 None
Charmander eat from Magikarp
2
Dratini 25 [Berry(name='Oran Berry', hp_delta=10), Berry(name='Cheri Berry', hp_delta=5)]
Charmander 15 None
Pikachu 30 [Berry(name='Sitrus Berry', hp_delta=15)]
Magikarp 10 None
Magikarp throw at Dratini
3
Dratini 25 [Berry(name='Oran Berry', hp_delta=10), Berry(name='Cheri Berry', hp_delta=5)]
Charmander 15 None
Pikachu 30 [Berry(name='Sitrus Berry', hp_delta=15)]
Magikarp 10 None
Charmander eat from Magikarp
4
Dratini 25 [Berry(name='Oran Berry', hp_delta=10), Berry(name='Cheri Berry', hp_delta=5)]
Charmander 15 None
Pikachu 30 [Berry(name='Sitrus Berry', hp_delta=15)]
Magikarp 10 None
Pikachu throw at Charmander
5
Dratini 25 [Berry(name='Oran Berry', hp_delta=10), Berry(name='Cheri Berry', hp_delta=5)]
Pikachu 30
Magikarp 10 None
Dratini throw at Magikarp
6
Dratini 25 [Berry(name='Oran Berry', hp_delta=10)]
Pikachu 30
Magikarp 5 None
Magikarp throw at Dratini
7
Dratini 25 [Berry(name='Oran Berry', hp_delta=10)]
Pikachu 30
Magikarp 5 None
Dratini throw at Pikachu
8
Dratini 25
Pikachu 20
Magikarp 5 None
Magikarp throw at Dratini
9
Dratini 25
Pikachu 20
Magikarp 5 None
Dratini throw at Pikachu
10
Dratini 25
Pikachu 20
Magikarp 5 None
Pikachu throw at Dratini
No one wins...