Assignment 7: Third Time’s a Charm – Three Trios, part 3
Due dates:
Implementation: Friday, Nov 22 at 8:59pm
Self-evaluation: RELEASED Saturday, Nov 23 at 09:05pm, DUE Sunday, Nov 24 11:59pm
1 Purpose
In this portion of the project, you will be building a controller for your game, listeners for various features interfaces to connect the components, and ultimately building a playable, complete game. Read through the entirety of this assignment before diving into coding, as the suggested designs here may influence how you structure your work.
2 Preparation: cleaning up prior code
You should likely have received design feedback about your model and/or view implementations. Incorporate that feedback, as well as complete any missing functionality from the prior assignments.
In the previous assignment, you implemented selecting grid cells via mouse-clicks, and players could indicate what card they wanted to play by click on the card in their hand. Nothing in particular needed to happen in response to those choices yet, but in this assignment we need to connect those choices to the controller. If you have not done so already, design a features interface describing those player actions, and enhance your view with the ability to add listeners for those actions. (Again, review Lecture 13: GUI Basics for more details on this approach.)
3 Thinking about control-flow
As discussed in class, GUI controllers are asynchronous: they respond to events that may arrive at any time. However, the game-play of Three Trios is synchronous: like Solo Red or TicTacToe, the rules of the game enforce turn-based play. These two facts are in conflict with each other, so we need to design a way to reconcile them.
Think about the strategies you implemented on the previous assignment, or for
TicTacToe in class: ultimately, they were simple functions from
State of the game -> Choice of move
. This approach works well for
synchronous games, since another player strategy cannot move while the
first one is computing its result. So conceivably, the model could call
the player strategies directly, to get their choice of move. But this has two
design problems. First, it gives the model too much controller-like power, by
bypassing the views and controllers and talking to players directly. Second,
and equally importantly, we want to be able to have games with any mix of human
and machine players: humans take a while to choose their next move, while
machines compute much more quickly. In particular, there is no obvious way to
implement “a human strategy” as a function, since while that function
is executing, the GUI is unresponsive to input —
3.1 Wait, how many controllers?
In single-player games, it makes sense to have a single controller. In single-user applications, likewise it makes sense to have a single controller. But in multi-player games, where players can act independently, it makes more sense for each player to have their own controller dedicated to that player. Think of each controller as interacting with the rest of the system on behalf of that one player.
3.2 Listening and reacting to events
Making your controllers work will require thinking carefully about features interfaces. You have already considered what high-level events the view can publish, that the controller should listen for and respond to. These likely consisted of at least two events: player choosing a card to play, or player choosing a position to play the chosen card to. We needed these high-level events because they could happen at any time, so the controller had to wait for them to occur before it could respond.
Now think about what notifications the model can publish, that would be of interest to its players. At minimum, the notification that “it’s your turn” could come at any time, since the other player could be arbitrarily slow in choosing a move. (There may be other notifications you think the model can send out as well.) Accordingly, design a features interface for your model, and augment it with the ability to add listeners for those events too.
Your controller should now subscribe itself as a listener for both features interfaces: since it needs to coordinate between the model and the view, it needs to know when the view has actively chosen something and when the model notifies that the current player has changed.
A subtle note: If the model is responsible for notifying players
when it is their turn, then you need to be very careful that the very
first player gets notified. This requires that all the players (or the
players’ controllers) are registered as listeners before the first
“it’s your turn!” notification is sent. This implies that you should have a
startGame
method on your model, to ensure that there is a clear
distinction between “setting everything up” and “actually playing the game”.
4 Designing players
There are clearly two implementations of players needed: human players and machine players. Machine players need a strategy in order to compute their next move; human players wait for the human to interact with the view to choose their next move. By abstracting out “what can a player do?” as a player-actions interface, we have a very clean opportunity to reconcile human and machine players:
In the design so far, your views are capable of emitting player-action events, and your controller registers itself as a listener for those events.
A machine player can also emit those player-action events! If the controller registers itself as a listener for those events, then it can control machine players as well as human ones.
This implies that the human-player implementation also must be capable of emitting such events...but in practice it never will, since the relevant events for that player will be emitted by the view for that player.
5 Designing the controller
The controller implementation will need to take in a player implementation (either machine or human), and a view showing the game from that player’s perspective.
The controller will need to register itself as a listener of player actions coming from both the view and the player that it’s controlling —
and again, in practice either the view or the player will emit those actions, not both. The controller must ensure that its player can only take action when its that player’s turn. This means the other player waiting should not be able to place cards on the grid or select cards at all. Accordingly, the controller must register itself as a listener for events coming from the model, so that it can be informed when the active player changes.
The controller must prevent a player from selecting cards in their opponent’s hand at all times.
The controller must ensure that the player has selected a card from their hand to play before they choose a cell to play to. Once both have occurred and both are valid, then the player can make the move and pass over/switch over to the other player.
The controller should ensure that its view stays up to date, even if the player is a machine and doesn’t interact with the view directly. Reread the discussion specifically about
toggleColor
in Lecture 13: GUI Basics, as the design concerns there might be similarly applicable here.Your model is likely capable of throwing exceptions when a given move is invalid. Your controller and/or your view must somehow handle these exceptions and make them visible to the user; it is not acceptable for them to silently fail and leave a warning in the console. For example, you can use the
showMessageDialog
method to display the error message to the player:Finally, when the game is over, the game must display who won and the winning score. This must be visible to the user much like an error message is.
You may find it helpful to set the title of the views to their respective players and whether they are playing out their turn or waiting for their turn.
6 Running your game
6.1 Showing two views
In the previous assignment, we built a placeholder main()
method that
simply created a view for the model. Now, we need to create two views,
one for each player —
package cs3500.threetrios;
public final class ThreeTrios {
public static void main(String[] args) {
YourModel model = ...create an example model...
YourView viewPlayer1 = new YourView(model);
YourView viewPlayer2 = new YourView(model);
YourPlayer player1 = new YourHumanPlayer(model);
YourPlayer player2 = new YourHumanPlayer(model);
YourController controller1 = new YourController(model, player1, viewPlayer1);
YourController controller2 = new YourController(model, player2, viewPlayer2);
model.startGame();
}
}
(It is entirely possible that when your program starts, you only see one window
appear. That may be an illusion because the two JFrame
s overlap each
other completely; try moving the windows around to ensure they’re not covering
each other, before assuming that there truly only is one window appearing...)
6.2 Configuring the game
As with Solo Red, we would like to be able to configure our Three Trios game with different combinations of players.
Your command-line here does not have to be particularly elaborate: at minimum,
it should expect two string arguments describing each of the players and their
strategies, for example, "human"
, "strategy1"
,
"strategy2"
, and "strategy3"
. Document whatever command-line
arguments you choose. Augment the scaffold code above for main()
to
take into account the command-line and cleanly configure the players accordingly.
Review instructions from Solo Red on configuring IntelliJ to run your program with command-line arguments
7 What to do
Attempt an initial design for the player-action and model-status interfaces. (They should be fairly small interfaces, with very few methods.) Make sure you have clearly documented the purposes of those interfaces and their methods, to guide you in the next implementation stages. Implement the ability for your views to add player-action listeners, and for your model to add model-status listeners.
Design a controller that takes in a model, which player they are working for, and a view for that player. It should register itself as a listener for both features interfaces, and should mediate between the view and the model on behalf of that player.
Update your
main()
method as described above.Update your README file to include explanations of all the new classes you’ve designed. Be sure to include a “Changes for part 3” section documenting what you changed from your initial design or from the previous assignment.
8 What to submit
Submit all your source and test files so far
Submit your updated README file
Submit the four screenshots demonstrating your view works as intended
Submit a plain-text file named
strategy-transcript.txt
containing a transcript from your mocked model of your simplest strategy choosing a move for Red on the starting board configuration of the 3x3 board with no holes.Submit a JAR file (with extension
.jar
) that can run your program. (See the instructions in Assignment 6 for how to create a JAR.)
9 Grading standards
For this assignment, you will be graded on
the design of your view and strategy interfaces, in terms of clarity, flexibility, and how plausibly they will support needed functionality;
how well you justify any changes made to your model;
the forward thinking in your design, in terms of its flexibility, use of abstraction, etc.;
the correctness and style of your implementation, and;
the comprehensiveness and correctness of your test coverage.
10 Submission
Please submit your homework to https://handins.ccs.neu.edu/ by the above deadline. Then be sure to complete your self evaluation by its due date.