Player
class, and could pit them against one another or against the "human" player.
Brendan Bell and Lucas Takiff
Bonacic Bonacic and Rohan Crossland
Asa Bonner and Jacob Endrina
Liam Bradley and Ella Slattery
Lucas Calaff and Mireia Pujol
Jinwoo Choi and Gabriel Guinn
Brett Garon and Stephen Rice
Mackenzie Leibee and Grey Roppolo
Noah McCullough and Devan Meyer
Cian Monaghan and Noah Sprenger
Spencer Racca-Gwozdzik, Noah Zimmer, and Ethan Spence
Board
and Player
classes and you'll write the rest.
The Board
class represents a Tic-Tac-Toe board. It stores information about the symbols on the board, and contains methods for filling board spaces with an X
or O
, detecting when the board's full, and determining whether anyone's won the game. Its toString
method returns a representation of the board, with .
representing open spaces. The Player
class models a Tic-Tac-Toe player, though not a very interesting one — it asks the human user where it should move rather than trying to figure out a move on its own. Player
objects are assigned a symbol (X
or O
) when they're created, and have a makeMove
method that, when passed a Board
instance, fills in an open position with their symbol after getting input from the user. One could play a game by creating a Board
instance and a pair of Player
instances, and having the players take turns making moves on the board.
Your task for this assignment is to create several subclasses of the Player
class that make moves automatically: RandomPlayer
, CornerPlayer
, and NextOpenPlayer
. (These classes are all described in the online documentation.)
The interactions below show the creation of a Board
object, and invocation of several of its methods. The fillPosition
method takes a zero-based column and row number, and the symbol to put in the specified position. (Note that there are no players involved in the interactions below. It's just highlighting some methods in the Board
class.)
> Board b = new Board(); > System.out.println(b); . . . . . . . . . > b.fillPosition(1, 2, Board.O); > System.out.println(b); . O . . . . . . . > b.isOpen(1,2) false (boolean) > b.boardFilled() false (boolean)
The following interactions show a pair of Player
instances making moves on the board instance, b
, from above. The Player
constructor takes a constant from the Board
class describing which symbol they're playing, and a name as a string. The user inputs are in blue:
> Player x = new Player(Board.X, "Kasparov"); > Player o = new Player(Board.O, "Karpov"); > x.makeMove(b); The current board: . O . . . . . . . Kasparov, enter col: 2 Kasparov, enter row: 0 > o.makeMove(b); The current board: . O . . . . . . X Karpov, enter col: 0 Karpov, enter row: 1 > System.out.println(b); . O . O . . . . X
Board
and Player
classes, and starting a project in either BlueJ or Eclipse with them. You don't need to know anything about how these classes work (just which methods they support, which you can get from the documentation), and you should not modify them.
Player
class has a makeMove
method that gets advice from the user. It displays the current game board, then asks the user to enter the column and row for the position on the board that they'd like to fill. That's no fun. Define a NextOpenPlayer
class that makes moves without getting help from the outside world. Your class should extend Player
and override the makeMove
method with a new version that selects the first open space it comes across, starting in the upper-left corner and working left to right. If no open spaces are found on the board, return without making a move. The interactions below show a NextOpenPlayer
object in action. (Well, they show it cheating — it makes three moves in a row after an initial "O" is placed on the board, but that's what we get if we call fillPosition
three times in a row as player O
.)
> Board b = new Board(); > b.fillPosition(1, 2, Board.O); > System.out.println(b); . O . . . . . . . > NextOpenPlayer x = new NextOpenPlayer(Board.X, "Spassky"); > x.makeMove(b); > System.out.println(b); X O . . . . . . . > x.makeMove(b); > System.out.println(b); X O X . . . . . . > x.makeMove(b); > System.out.println(b); X O X X . . . . . > x.toString() "Spassky" (String)
Player
again to create a RandomPlayer
class. Objects of this type select from among the open positions on the board randomly. (You can generate random row and column values until you find a position that's open.) If the board is already full, your method should return without modifying the board. Here's a couple of random players in action, though you'll obviously get different results:
> Board b = new Board(); > RandomPlayer x = new RandomPlayer(Board.X, "I'm X"); > RandomPlayer o = new RandomPlayer(Board.O, "I'm O"); > x.makeMove(b); > o.makeMove(b); > System.out.println(b); . . O . X . . . . > x.makeMove(b); > o.makeMove(b); > System.out.println(b); . . O X X . . O . > x.makeMove(b); > o.makeMove(b); > System.out.println(b); . O O X X X . O . > b.getWinner() == Board.X true (boolean)
CornerPlayer
that extends RandomPlayer
. (No, that's not a typo — it extends RandomPlayer
and not Player
.) Objects of type CornerPlayer
look first to see if any of the corners are open, and fill a corner if they find one. The order in which the corners are inspected isn't important. If none of the corners are available, it should select its move randomly from among the open positions. For full credit, your new makeMove
method should not generate any random numbers. Instead, it should invoke makeMove
from the superclass (RandomPlayer
) if no corners are open.
> Board b = new Board(); > CornerPlayer x = new CornerPlayer(Board.X, "Corner X"); > CornerPlayer o = new CornerPlayer(Board.O, "Corner O"); > x.makeMove(b); > o.makeMove(b); > System.out.println(b); X . . . . . O . . > x.makeMove(b); > o.makeMove(b); > System.out.println(b); X . X . . . O . O > x.makeMove(b); > o.makeMove(b); > System.out.println(b); X O X . . X O . O
You can pit competing strategies against each other by creating instances of two different player classes and having them alternate turns on a board. (It would look like the interactions above, but with a CornerPlayer
and a RandomPlayer
, for example.) It's a bit of a pain to organize this all manually, but we'll write something for the next assignment that automates the process.
Tournament
class. In it, define a playGame
method that takes two player instances and plays a single round of Tic-Tac-Toe between them. You should treat the first argument to playGame
as the player using X and the second as O. (You'll want to use the setSymbol
method to ensure that the players are using the proper symbols too.) Your method should print the output from the winner's celebrate
method and the loser's mourn
method, then return the winning player (or null
if the game ends in a draw).
CornerPlayer
does. I'll run a tournament consisting of all of the "extra" classes submitted by the on-time deadline, and award even more extra points to the winner. If you're looking for additional challenges, you might consider having the player classes throw exceptions if they're asked to make a move on a full board. You could also write a main method in the Tournament
class that plays games between instances of RandomPlayer and NextOpenPlayer until there's a winner, then pits the winner of that match against a CornerPlayer to see who wins.
@param
and @return
directives) above each method. When you're convinced it's ready to go, zip up your project folder and submit it via the Canvas submission tool for Assignment #1. (To zip on a Windows machine, right-click on the project folder, select "Send to" off of the menu, and then select "Compressed (zipped) folder" off of the submenu. On a Mac, control-click on the folder and select "Compress folder_name". Regardless of platform, this produces a compressed folder whose name ends in .zip
— upload the .zip
file via Canvas.) Only one member of the team should submit the project, but make sure all names are in the comments at the top of each class.