From brand guide: https://flsouthern.mediavalet.com/portals/fsc-brand-guidelines
CSC 3280: Data Structures
(Spring 2025)

Syllabus

LMS

Teachers


Assignments


Other Pages

Project 0:
Pairing Up


Assigned: Mon Dec 30 2024
Due: 11:59:00 PM on Tue Jan 21 2025
Team Size: 1
Language: Java
Out of: 100 points


In this project you'll make a data structure representing an ordered pair that holds two values. This will be a basic exercise in getters and setters. After you create the data structure, you'll use it to play a combinatorial game based on that structure: Wythoff's Nim.

Part 0, 0 points: Create a file, Pair.java. Add a Javadoc header to your Pair.java file:

/**
 * Pair.java models an ordered pair of values.  (You can add more description yourself here.)
 * 
 * @author  <Your Names Here>
 */
This comment tells me who deserves credit for this assignment. In this first project, you'll be working on your own, but in future team projects, I will look at this tag to see who your other teammates are.

Part 1, 5 points: Your file should use two generic types, the types of the two objects you'll be storing. When I first did this project I used FirstType and SecondType. You are welcome to use whatever variable names for the types you want, as long as they are written in PascalCase. Common generic type names include: T, E, R, S. I expect many people will use F and S for this project. (I will continue to use FirstType and SecondType throughout.) Once you choose those, your class header should look something like this:

public class Pair<FirstType, SecondType> {
This tells Java that you're going to use those two generic type variables listed in between the angle brackets. This should go directly beneath your header comment, e.g.:
 */
public class Pair<FirstType, SecondType> {
Your class should have two fields, one of each type. (I used first and second, like this.)
    //the first element in this pair
    private FirstType first;
Since these fields are private, they should have a normal comment instead of a JavaDoc comment. Add a constructor that takes two objects, one of each generic type, and assigns them to your fields, creating a new Pair object. Here's what the header for your constructor should look like:
    /**
     * Class constructor.
     */
    public Pair(FirstType a, SecondType b) {
The body of the constructor should be relatively simple: just two assignment statements. Once that's done, other code (e.g. code you put in Main.java, hint hint) will be able to use that constructor like this:
//creates a new pair, (5, "monkey")
Pair<Integer, String> two = new Pair<>(5, "monkey");
I can't test that this part works perfectly, so you might get the points for it, but still need to come back and fix it. You can test this a little bit by adding the following main method to your class:
    public static void main(String[] args) {
        Pair<Integer, String> two = new Pair<>(5, "monkey");
        Pair<String, String> strings = new Pair<>("hi", "there");
        Pair<String, Integer> twoMore = new Pair("Yo", 3);
    }
Then try compiling and running this code.

Part 2, 5 points: Write a toString method that returns a string containing the contents of the pair. The signature for the toString method should always look the same. I often cheat and use nearly the same docstring for the toString method in all of my classes:

    /**
     * Returns a String version of this.
     * 
     * @return A String representation of this Pair.
     */
    public String toString() {
Implement the method so that it returns a String that contains the string representation of both fields. E.g., using our example before:
Pair<Integer, String> two = new Pair<>(5, "monkey");
System.out.println(two);
Should create output similar to this:
(5, monkey)
You can also test this using asserts with the following code:
Pair<Integer, String> two = new Pair<>(5, "monkey");
String s = two.toString();
assert s.contains("5");
assert s.contains("monkey");
To just run that single test, replace the code in your main method with that. Replacing the code is an ugly solution, however. It would be best to incorporate both tests so you can run them all whenever you make changes. Combining them together into one test would look like this:
    public static void main(String[] args) {
        Pair<Integer, String> two = new Pair<>(5, "monkey");
        Pair<String, String> strings = new Pair<>("hi", "there");
        Pair<String, Integer> twoMore = new Pair("Yo", 3);
        String s = two.toString();
        assert s.contains("5");
        assert s.contains("monkey");
    }
If you include assert statements in your code, you'll want it to actually enforce them. To do that, after compiling, run with the -ea flag:
$ javac *.java
$ java -ea Pair
If you don't include that flag, then it won't throw an exception if the assert is false. Regarding organizing your tests, from now on, I'll just give the new code and let you incorporate it however you wish. Warning: I don't recommend getting rid of your tests! Do not blame me if you don't catch an issue because you deleted the tests. Also, when I'm grading your code, my tests will always be stronger than yours! I highly encourage that you add more tests to be even more certain your code works! Don't be afraid to ask for tips on how to write stronger tests.

Part 3, 10 points: Write appropriate getFirst and getSecond methods. These should be very simple and should return the appropriate field. For example, getFirst should work like this:

Pair<Integer, String> two = new Pair<>(5, "monkey");
System.out.println(two.getFirst());
outputs:
5
Here is some testing code:
Pair<Integer, String> two = new Pair<>(5, "monkey");
assert two.getFirst().equals(5);
assert two.getSecond().equals("monkey");
Pair<String, String> strings = new Pair<>("hi", "there");
assert strings.getFirst().equals("hi");
assert strings.getSecond().equals("there");

Part 4, 10 points: Now it's time for the setters. Write the setFirst and setSecond methods. These should take one parameter (each) and make the appropriate change to the object's field. Here is some testing code:

Pair<Integer, String> two = new Pair<>(5, "monkey");
two.setFirst(19);
assert two.getFirst().equals(19);
assert two.getSecond().equals("monkey");
two.setSecond("capuchin");
assert two.getSecond().equals("capuchin");

Part 5, 10 points: Write the equals method. You may want two of these. At least one should take an Object, not a Pair. The reason you want two is because you need one that takes a single Object as the parameter in order to override the Object class's equals method and you may want a separate one that takes a parameter of the same type as this to make things easier. A common way to do that is something like this, that provides a template for many classes: (Note: I'm dropping a layer of indentation in my examples, but you should still indent properly.)

/**
 * Checks whether two objects are equal.
 * @return Whether this is equivalent to obj.
 */ 
public boolean equals(Object obj) {
    try {
        Pair<FirstType, SecondType> other = (Pair<FirstType, SecondType>) obj;
        return this.equals(other);
    } catch (ClassCastException e) {
        return false;
    }
}

/**
 * Returns whether this equals another Pair.
 */
public boolean equals(Pair<FirstType, SecondType> other) {
    //write your code to check whether this equals other here...
}
Here's some testing code:
Pair<Integer, String> two = new Pair<>(5, "monkey");
assert two.equals(two);
Pair<String, String> strings = new Pair<>("hi", "there");
assert !two.equals(strings);
assert !strings.equals(two);
Pair<Integer, String> twoClone = new Pair<>(5, "monkey");
assert two.equals(twoClone);
assert twoClone.equals(two);
Pair<Integer, String> notTwo = new Pair<>(6, "monkey");
assert !notTwo.equals(two);
assert !two.equals(notTwo);
notTwo = new Pair<>(5, "mankey");
assert !notTwo.equals(two);
assert !two.equals(notTwo);
Notice how many cases I'm testing there!

Part 6, 10 points: Write a getReverse method that returns the backwards version of the pair. (That means that the first element comes second and the second one comes first.) I want you to figure out the method signature for this one, so the first thing you should think about is: what should the return type of this method be? Here's some testing code:

Pair<String, String> strings = new Pair<>("hi", "there");
Pair<String, String> strings2 = new Pair<>("there", "hi");
assert strings.getReverse().equals(strings2);
assert strings2.getReverse().equals(strings);

Pair<Integer, String> two = new Pair<>(5, "monkey");
Pair<String, Integer> backTwo = new Pair<>("monkey", 5);
assert two.getReverse().equals(backTwo);
assert backTwo.getReverse().equals(two);

Part 7, 0 points: You've completed the part of the project where you create the data structure. The rest of the project is spent working with playing an abstract board game (more specifically, a combinatorial game) that uses the data structure heavily. For pairs, that game is Wythoff's Nim. In this game there are two piles of sticks, represented by a pair of integers. Each turn a player can do one of the following moves:

  • Take any number of sticks from the first pile.
  • Take any number of sticks from the second pile.
  • Take the same number of sticks from both piles. (Both piles need to have at least that number of sticks.)
You win by taking the last stick. A sample game could proceed as:
  • Start with the pair (6, 2).
  • The left player takes three sticks from the first pile. Now the game is: (3, 2)
  • The right player takes one stick from both piles. Now the game is: (2, 1).
  • The left player takes one stick from the second pile. Now the game is: (2, 0).
  • The right player wins by taking both sticks from the first pile. There are no more sticks, so left loses.

Part 8, 0 points: Let's test your code out during actual game play. To compile and run the code locally, you'll need these:

Part 9, 0 points: These files are code that handles playing the games. CombinatorialGame is the abstract super class for all the different games. Player is the abstract super class for players of games. The Referee is a concrete (non-abstract) class that plays games between two players. It presents a player with the current game, and waits for that player to respond with a move. Once that move is returned, the ref checks that it's a legal move and then hands that new game to the other player. It waits for this player to respond, checks that the response is legal, and continues on. If the response is ever illegal, the referee forfeits that game for that player. PositionFactory is the abstract super class for objects that create game states. Referees use these factories to generate the starting position in a match. RandomPlayer is a concrete player that chooses a random legal move. To run this code, download all these files and put them in the same folder as your Pair.java. Feel free to look through any of the code and ask me any questions about things in them!

Part 10, 0 points: I coded up rules for Wythoff's Nim in WythoffsNim.java. If you want to compile the code yourself, you can download that file to test with your code. If you have questions about how to create positions, I can show you that.

Part 11, 20 points: Now it's time to create your player for Wythoff's Nim. If you take a close look at the Player.java file, you'll see there are two methods you can override: getMove, which takes a position and returns a new resulting position, and toString. First add the signature for the class:

public class WythoffsNimPlayer extends Player<WythoffsNim> {
Then add a toString method to return a name for your player. Then add the getMove:
public WythoffsNim getMove(WythoffsNim position, int playerId) {
    Pair<Integer, Integer> piles = position.getPiles();
    //add your code to create a new pair or modify piles
    ...
    return new WythoffsNim(piles); //you can use another Pair object instead of piles
}
Write some code to make a legal move. There are some restrictions:
  • Your player must always make legal moves and not forfeit any games.
  • Your player cannot use any randomness.
  • Your player cannot call the getOptions method of the game object.
You can create a competition to test your player against a random player by using code like this inside a method (e.g. your player's main method):
int maxPileSize = 10;
PositionFactory<WythoffsNim> factory = new WythoffsNim.PositionBuilder(maxPileSize);
Player<WythoffsNim> me = new WythoffsNimPlayer();
Player<WythoffsNim> random = new RandomPlayer<WythoffsNim>();
Referee<WythoffsNim> ref = new Referee<>(me, random, factory);
ref.call();

ref.gauntlet(10000);

Part 12, 30 points: Modify your strategy so that your player does well against my random player. You will earn points depending on how often you win in my tests. Warning: I won't tell you the parameters of those tests. I recommend chatting with other teams about the factory parameters they're using and going with the hardest ones. Important: do not copy code from any outside sources, but:

  • It's okay to search online for strategies and info about this game, as long as you're not getting code (in any programming language) or pseudocode.
  • It's okay to talk to other people about strategies so long as you're not sharing code.
  • It's okay to challenge me to a game if that would help!
Here's how many points you earn based on how your player does when I test it:
  • Win ≥10%: 10 points
  • Win ≥50%: 20 points
  • Win ≥88%: 30 points
  • Win ≥95%: 35 points
  • Win ≥98%: 40 points

Submitting your Project:

The files I'm looking for in this project are:

  • Pair.java
  • WythoffsNimPlayer.java
Please make sure to remove scaffolding from your code so that it doesn't print a ton when executed. Your team name is your username if you're working alone or your members' last names with "And" between them if you're working with a teammate. (For example, alone my teamname would be "kburke", but when working with Stacey Stock it would be "burkeAndStock".) Put completed working files you have in a folder named <YourTeamName>Project0, then zip the folder and upload the resulting .zip file to the assignment's canvas page so I can grade it. (E.g., for me: kburkeProject0.zip or burkeAndStockProject0.zip.) If you do it before the deadline and want me to run it through my tester to see how it does against the random players, send me a message and I'll do it. If you upload it after the deadline, please send me a message so I can grade it ASAP. Please please please name the folder correctly, then zip it; don't rename the zip file, or you'll have to resubmit and may incur a lateness penalty.