CS 261 Assignment #1

Due Friday, February 3rd by 11:59pm
(Not accepted after 2/5)

Introduction

Inheritance is a good thing. Really. It allows you to create new classes quickly by basing them off of other classes, instead of having to write a new class from scratch. And, as you'll see as you complete this assignment, it allows you to enhance an existing class without having to change the original — which is important if the original class does something useful and we want to keep it as is. For this assignment, you'll write parts of a program to play Tic-Tac-Toe. The project is composed of several classes: There's a class to model the game's board, and classes that model players (and playing styles). You'll create new players by basing them off of the existing Player class, and could pit them against one another or against the "human" player.

Teams

You are required to work in pairs on this assignment. (Team assignments are below.) Do not start writing any code until you've met with your partner and discussed the assignment and possible approaches. As on the lab, please be kind in your interactions with your partners! Keep in mind that students in this class have a range of previous programming experience, and that some have been college students for longer than others. We're all in this together, and you have something to learn from your partner, no matter who they are or what their previous experiences have been. Ideally, your team would work together using the same pair programming approach as used on the lab, but however you choose to complete the assignment, I will assume that each member of the team has contributed equally to the project. If that assumption isn't true, please contact me privately. Please respect the privacy and scheduling preferences of your partner when finding times to work! Don't ask for a phone number if your partner isn't comfortable sharing it. When setting up times to work, it's probably best to aim for spaces where folks will be comfortable meeting with relative strangers (e.g. the library, or CS lounge). Please be professional in your conduct!
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

Overview

The class diagram below shows the relationship between the classes in the finished version of the project. I'm providing the 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.

Final Class Diagram

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 

The Assignment

  1. Start by downloading the 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.
  2. The 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)
    
  3. Extend 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)
    
  4. Next, write a third and final player class called 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.

  5. Finally, create a new 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).

Style Guide

Before you submit your assignment, go through the checklist below and make sure your code conforms to the style requirements.

Grading

This assignment will be graded out of a total of 100 points:

Extending the Assignment

None of the three player classes you created are very smart — there's lots of room for improvement. I'll give you 5 extra points if you write your own class that does a better job of guessing than a 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.

Submitting

Before submitting, test each of your methods thoroughly and double check for comments (including @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.


Brad Richards, 2023