Course:CPSC312-2023/Pokemon Battle
Authors: Angela, Michael, Justin
What is the problem?
Pokemon (or Pocket Monsters; originally JP: ポケットモンスター) is a popular role-playing game featuring a character that wanders the world capturing creatures that form their team and battling other Trainers and their assembled teams. In this project, we attempt to use Haskell to simulate a single Pokemon battle, allowing for a player to take on a computer opponent and try to defeat its Pokemon by reducing its health to 0 before their own. The game is expected to run within the command line using the interactive GHCi terminal, where the player will be able to provide turn-based input to execute their desired actions with their Pokemon character against the computer opponent.
Some original battle mechanics that in the interest of simplicity we do not implement include
- Status effect moves, and status effects in general (Sleep, Paralysis, Burn, Poison, etc.)
- Type advantages (unless time permits)
- Attack order precedence (e.g. "Quick Attack" guarantee, successful "Protect")
- Pokemon stats outside health (ATK, DEF, SPD, SP. ATK, SP. DEF, etc.)
What is the something extra?
As part of the battling mechanism of the computer opponent in the game, we would like to try and add a basic heuristic which helps the computer decide what action may be more beneficial to perform based on certain circumstances (for example, the computer may choose to heal more often than attack when its Pokemon's health is below a particular threshold, or may consider offense more often if the player's health appears low enough to "press the attack").
Depending on how much time we are able to have after implementing core mechanics, may look into implementing different options for actions true to the base Pokemon franchise games (access to bag or items, wider moveset range, etc.)
Otherwise, simply implementing additional mechanics true to the actual game is deemed "extra".
What did we learn from doing this?
Through the course of developing this project, we learned a great deal about Haskell syntactically, including aspects like
- making use of modules and how to import them; exporting selected constants and functions to make them "public" for other modules to use
- IO programming and IO Monad using do-expressions and extracting return values from our IO functions to be used elsewhere
- currying of parameters (reduce parameter lists in definitions)
- making use of the Maybe class (Nothing / Just <val>)
- data types, data constructors, and using record syntax to access attributes of those "objects" or instances
Functional programming and Haskell particularly is relatively good for making games. In particular, due to how functional programming works, where we don't have loops implemented we see recursion. This made making menu dialogs simpler to implement, as things like invalid input or input that did not correspond to a valid choice an easy function call back to the top of the current function so that things would be re-displayed. Using record syntax, Haskell is able to emulate a primitive form of object orientation, alongside named fields within some instance created through a data constructor. We also saw how functional programming does well with mutual recursion between the player's turn and the computer's turn, as we can just call the function related to them without having to reference a source external to any specific player or entity, which facilitated the turn-based mechanic relatively easily.
Because of the nature of our project being a Pokemon game, a significant portion of the implementation involves a form of object; for example, a Pokemon creature is best described as an object that contains attributes like health and a name, as well as a certain set of moves. Similarly, a Move may contain a power level, or a healing item may contain the numerical amount of health it restores. Especially for a Pokemon, not having object-orientation made using a Pokemon object somewhat more tedious and inflexible.
With functional programming, we tend to see that we have to pass in the Pokemon object as a parameter to the function, as we do not have methods that can be called intrinsically within an object. This occurs frequently within the Pokemon.hs file where many of the "getters" or functions that act on Pokemon must require a parameter containing that object before it can be used, instead of simply returning fields called from an object. We also see that because of how objects are constructed in Haskell using data constructors and pattern matching, this makes some things even more difficult as we have to then provide an implementation or case to approach Pokemon created through different data constructors that returned a Pokemon type. For example, where in object-oriented programming we might see a `getMoves( )` method simply return a field created as a simple list of Moves from the Pokemon, in functional programming we must provide a case that takes in a Pokemon object created using any of the (Pokemon1, Pokemon2, Pokemon3, Pokemon4) constructors, due to each being created with an independent pattern. Furthermore, because a Move object cannot be referenced simply by field access as in OOP, we must then provide `getMoveNames( )` and `getMoveDamages( )` to extract those values from the Moves separately (using a list comprehension to maintain the list structure).
Links to Code
See https://github.com/MichaelTC5525/Pokemon-Battle for all source code created for this project.
This is meant to be run as a command-line user-interactive program. Ensure you have downloaded Haskell and open an interactive GHCi command line within a terminal program on your computer.
Simply run "ghci" in the terminal, load the program using " :load main", and then "play" to start.
Note that most actions require you wrap your desired input within quotation marks (""), else it be considered empty input.