Discussion 5: Object-Oriented Programming
In today’s discussion, you’ll practice some of the principles of object-oriented design and development. Object-oriented programming is a powerful tool for structuring code, which enables readability, maintainability, and extensibility. You’ll get a start on assignment A5, a simple text-based battle game (think a super pared-down version of D&D combat, if you’re familiar with that). This discussion activity will get you familiar with some of the structure of the assignment code and have you implement the main combat loop; the assignment will focus on extending and adding new functionality to the activity you’re completing today.
Learning Outcomes
- Draw memory diagrams that visualize the state of code involving instance method calls.
- Compare and contrast static types and dynamic types.
- Explain the benefits of leveraging polymorphism in object-oriented code.
- Describe the principle of dynamic dispatch and the compile-time reference rule.
- Identify the invariant of a class and write code that maintains this invariant and/or asserts that it is satisfied.
Reminder: Discussion Guidelines
The work that you complete in discussion serves as a formative assessment tool; it offers the opportunity to assess your understanding of the material and for our course staff to get a “pulse” on how things are going, so we can make adjustments in future classes. Your grade in discussion is based on your attendance, active participation, and completion of that day’s activity. More information about the grading and expectations can be found in the syllabus.
Since discussion activities are not graded for correctness, we do not restrict what resources you may use to complete them, which include notes, books, unrestricted conversations with other students, internet searches, and the use of large language models or other generative AI. We advise you to be pragmatic about your use of these resources and think critically about whether they are enhancing your learning. Discussion activities are intended to serve as “strength training” for programming tasks we will expect on assignments and exams (and that you will encounter in future courses and careers), and their main benefit comes from thinking critically to “puzzle” them out.
Background
In A5, you’ll be implementing a text-based combat simulator for a simple tabletop role-playing game. Imagine you and some friends are playing D&D. If you’ve never done this, don’t worry; the details of the actual game are not important (and will be vastly oversimplified for our purposes anyway), and we’ll tell you everything you need to know. Essentially, you and your friends form an adventure party; you are the players in this situation. Your objective is to defeat the monsters you encounter in combat, subject to some system of rules.
As we start to translate these mechanics into code, we’ll think about which objects will serve as useful abstractions in our game. We’ll want a GameEngine class that is responsible for managing the progress of the game. Its behaviors include processing user input, maintaining the state of the players and monsters in the current battle, and managing turn order and advancement. We’ll also likely want Player and Monster classes to model these types of characters.
Since many actions are shared by both players and monsters (which we’ll discuss more below), it will make sense to extract these common behaviors into an Actor interface that is implemented by both the Player and Monster classes.
Soon, you’ll see that the subtype polymorphism enabled by this Actor interface will greatly simplify the GameEngine code to manage the game loop. Both Players and Monsters (which implement the Actor interface) take some actions on their turn. For now, let’s assume the following about the actions that Players and Monsters can take:
- On its turn, a
Monsterselects a random livingPlayerandattack()s thisPlayer. The damage dealt is random and dependent on theMonster’s power level. - On their turn, a
Playerselects oneMonstertoattack(). The damage dealt is random and dependent on thePlayer’s power level. - When a
Playeror aMonsterisattack()ed, they have the opportunity todefend(), which can succeed or fail. If their defense succeeds, they will not take damage; if it fails, they will take damage from the successfulattack(). - All
Actors start with a certain number of health points. As soon as theirhealth()reaches 0, they “die”, so they are no longer able to take actions.
Take a look at the Javadoc pages for our game’s classes. Deciding on the complete specifications included in these Javadoc pages would be an important checkpoint along the development process that would take place just before dividing up the coding tasks among the programming team. A bit earlier on in the process, we may use tools like UML diagrams (see Section 4) to help visualize the behaviors and connections between the game’s classes before settling on the complete specifications.
Written Tasks
gameSnippet() method:
|
|
|
|
simulate() method:
|
|
|
|
What is the static type of the variable actor1? What is the dynamic type of the object that it references?
When line 6 executes, which method body do we enter? How does Java figure this out?
Draw a memory diagram that depicts the state of the program immediately after entering the takeTurn() method on line 6 (i.e., before any of its local variables have been initialized). You can fill in any valid value for each Actor’s health and power.
The simulate() call frame should be the lowest one depicted on the runtime stack (since we don’t know how we got into this method). To simplify your diagram a bit, you can visualize the GameEngine as an empty rounded rectangle (omitting its fields). Draw all fields of any other objects.
You’ll need to skim the starter code to correctly visualize the fields and call frames, but don’t worry about reading it too closely before you finish your drawing.
Programming Tasks
For the rest of this discussion, you’ll add additional functionality to the GameEngine class. Before you start coding, open the GameEngine class and read the documentation of its fields to understand its class invariant. As an implementer of the GameEngine class, it will be your responsibility to restore this class invariant at the end of every (non-private) method that you write.
processMonsterDeath() and processPlayerDeath()
Complete the definitions of the processMonsterDeath() (TODO 1) and processPlayerDeath() (TODO 2) methods to conform to their specifications. Where do you think that these methods would be called within the other classes? Take a look at the rest of the code to check if you were correct.
The Main Game Loop
Implement the main game loop method in GameEngine.java according to its specification (TODOs 3-6).
|
|
|
|
Think carefully about how to do this part so that, if we were to add new types of Players (which you’ll do in the assignment), we would not have to add additional cases/logic to the implementation of this function. This is possible, and showcases polymorphism and dynamic dispatch; this is really neat, since this modular design allows us to easily support new features.
Submission
At the end of class, your group’s primary author should create a PDF with your written answers to Exercises 1 and 2 and upload this to the “Discussion 5” Gradescope assignment, tagging all other members of the group onto the submission.
FYI: UML Class Diagrams
UML (Unified Modeling Language) is a set of guidelines that offers developers a standard diagrammatic language for visualizing the structure of their code. While the specifics of UML fall beyond the scope of 2110, this discussion offers a nice opportunity to expose you to one type of UML diagram, the class diagram. In a class diagram, each type (i.e., class or interface) is represented by a rectangle, and arrows connect related classes (for example, dashed triangular-headed arrows represent implementing an interface, just like in our type hierarchies). Within the rectangle, there are two sections below the type name. The upper section lists the type’s state (or fields), and the lower section lists the type’s novel behaviors (i.e., instance methods not declared in a supertype). We use prefixes on these entries to mark their visibility: ‘+’ indicates public visibility and - indicates private (or protected) visibility. A (simplified… UML can get very intricate) class diagram for the discussion code is shown below. For simplicity, we’ve omitted some GameEngine fields and methods from the visualization.