1 Problem Description: Building a Flexible Voting System
1.1 Assignment Options Based on Desired Course Grade
2 Detailed Problems and Class Names
2.1 Step 1: Record three choices per voter (Core and Full)
2.2 Step 2: Add Exceptions to Report Malformed Votes (Core and Full)
2.3 Step 3: Support Methods to Compute the Winner (Core and Full)
2.4 Step 4: Include Appropriate Access Modifiers (Core and Full)
2.5 Step 5: Encapsulate Recorded Votes (Full Only)
2.6 Step 6: Separate the User Interface from the Voting Data (Full Only)
3 Testing Expectations (Core and Full)
3.1 Things To Consider About Testing
4 Program Design Expectations (Core and Full)
5 What to Turn In

Homework 5: Building a Voting Machine

Due Tuesday December 8 at 11:59 pm via InstructAssist. Submit to the area that matches which version of the assignment you did (see below).

If you are planning to go on to 3000- or 4000-level CS classes, this would be a good time to switch over to Eclipse if you’ve been using DrJava all term.

1 Problem Description: Building a Flexible Voting System

You are building software to conduct an election. In this election, voters will state their top three choices (rather than a single choice). There are many different ways to determine winners in such preference-based elections. Unfortunately, the people conducting the election haven’t yet decided how they will count the votes. Therefore, they want you to build a voting system that will record votes, but will also let them experiment with different ways to determine the winner.

This starter file gives you a very rudimentary voting program: it presents a login screen that asks a voter to enter the name to vote for, stores that choice, and counts votes for a given candidate. Your job is to

Your solution to this homework should NOT include the countVotes method from the starter file or the current votes variable of type LinkedList<String>. You will still have a votes variable (or something like it), but with a different type that can handle three ranked choices per voter.

If you want to test your Java skills, spend a little time thinking about how you might do this before reading how we break this assignment into steps below.

1.1 Assignment Options Based on Desired Course Grade

This assignment can be done at two "levels", which differ in the program design criteria that you try to meet (which in turn affects the course grade you can earn).

The core option is designed for those who are struggling in the course and are more concerned about passing the course than with getting a high grade. If you are in this group, we’d rather see you master the basics than do a poorly-quality job on more material. Focusing in this way may also help you prepare for the programming exam.

You cannot get an A in the course without attempting the full level. This does not mean that you need a particular score on the assignment to get an A, but we need to see you making a good faith attempt at encapsulation and interface organization as part of earning an A.

The subsection headings in the following assignment details clearly mark which details are required for each of full and core.

2 Detailed Problems and Class Names

Warning: Do this assignment in stages, making sure that your program runs properly after each stage. If you try to make all of the changes at once, you’ll spend far longer than needed on this assignment. Build up to your solution step by step. You can get much more credit for fully working early steps than for having most of the steps only partly working.

Update 12/4: Here is a file that you can use to check your work for autograding. It isn’t a Main.java, but rather a file that you can include alongside your other Java files once you have all of the methods and exceptions in place. You will get compilation errors when including this file if you are missing exceptions, methods, throws statements, and other details that autograding will depend on. We posted this instead of a Main.java so as not to interfere with your work on the early steps (when the AutogradeCheck file would not compile).

2.1 Step 1: Record three choices per voter (Core and Full)

Edit the starter code to store the number of first, second, and third choice votes for each candidate on a ballot. Before reading on, think about what data structure(s) you might use for this information. After you’ve come up with your proposal on paper, feel free to look at (and use, or not) our suggested data structure.

Once you have your data structure, add the following two methods:

Use Java’s built-in .equals method to check whether names are the same (our solution will do the same). This will treat names with different capitalization as different (such as "kathi" and "Kathi"). Our tests against your code will not try to trick you with this—we will use distinct names for all candidates and attempted votes to be on the safe side.

Update 12/6: The .contains method uses .equals, so using .contains is fine to check whether a name is in a list.

You do not need any special handling around empty strings passed as names. Our tests will not use empty strings as names and our solution will treat the empty string as a name like any other. Similarly, you may assume that null will not be given as a name.

2.2 Step 2: Add Exceptions to Report Malformed Votes (Core and Full)

A vote is only valid if the voter enters three different names, each of which is a candidate on the ballot. Modify your processVote method to check these criteria, using exceptions (named below) to report problems and require the voter to vote again if their vote is not valid.

Create Exception subclasses named DuplicateVotesException and UnknownCandidateException. The constructor for each method should take a String for the name of the candidate that was voted for multiple times or not on the ballot, respectively. If multiple names were not on the ballot, report the first one (in order of the arguments to processVote).

For purposes of autograding, we need to agree on which of these two exceptions will be thrown if both problems arise on the same call to processVote. Check/throw the UnknownCandidateException before the DuplicateVotesException if both apply; our reference solution and test cases will do/assume the same.

2.3 Step 3: Support Methods to Compute the Winner (Core and Full)

Next, add the following two methods for counting votes. Each returns the name of the winning candidate. In case of a tie, return the name of one candidate with the highest score (any candidate with the highest score would be a valid answer):

Update 12/5: You may assume that these methods will only be called after at least one valid vote has been processed. While normally it would be better to just throw an exception if there were no valid votes, we’re using the assumption to reduce the impact of changes to the assignment mid-stream. Our tests of your code will respect this assumption.

You should delete the countVotes method from the starter file once you have these methods in place. We put countVotes in the starter file only to give you an initial understanding of the voting system.

2.4 Step 4: Include Appropriate Access Modifiers (Core and Full)

Put an appropriate access modifier (public, private, or protected) on each of your fields and methods.

2.5 Step 5: Encapsulate Recorded Votes (Full Only)

Now that you have the ability to record votes, encapsulate the data structure that you are using to capture the voting data. You don’t need to be able to customize the specific data structure from outside the ElectionData class (as we did when we passed different data structures into the Banking constructor). However, someone should be able to change the data structure for cast votes simply by changing the name of the class you instantiate within the ElectionData class.

Update 12/4: The phrase "don’t need to customize the data structure from outside" means you don’t have to create an interface when you encapsulate the data. You normally would (and you certainly can if you want), but we won’t be grading for that. It suffices for you to replace the current LinkedList<String> type on votes with a new type. [end update]

In particular, leave the constructor for your ElectionData class as taking no arguments (so it will work with autograding).

Leave the ballots as a LinkedList<String>. You do not need to encapsulate those.

2.6 Step 6: Separate the User Interface from the Voting Data (Full Only)

The starter file put the entire voting system—ballot information, votes, and input-output methods—into a single class. Separate the system into (at least) two classes: VotingMachine for the input/output portion, and ElectionData for the ballot and votes information. The VotingMachine class should have a variable that holds an object of the ElectionData class.

In particular, the four key methods for this assignment—addCandidate, processVote, findWinnerMostFirstVotes, and findWinnerMostPointsshould end up in your ElectionData class and be public. The autograder will look for those methods in that class.

You do not need to add methods to VotingMachine that report winners (since voters would not be the ones computing the election results); the existing screen method serves as the interface to processVote.

3 Testing Expectations (Core and Full)

Think of your test cases as mock elections: each will set up a ballot, cast some votes, and determine the winner by one of the two voting methods. Here is an example of one way to set up a test case for this assignment: we write a method to populate a ballot and cast votes, then write a checkExpect to test the outcomes.

  // method to set up a ballot and cast votes

  ElectionData Setup1 () {

    ElectionData ED = new ElectionData();

  

    // put candidates on the ballot

    ED.addCandidate("gompei");

    ED.addCandidate("husky");

    ED.addCandidate("ziggy");

    // cast votes

    try {

      ED.processVote("gompei", "husky", "ziggy");

      ED.processVote("gompei", "ziggy", "husky");

      ED.processVote("husky", "gompei", "ziggy");

    } catch (Exception e) {}

    return(ED);

  }

  

  // now run a test on a specific election

  boolean testMostFirstWinner (Tester t) {

    return t.checkExpect(Setup1().findWinnerMostFirstVotes(),

                         "gompei");

  }

For an election/test that would raise an exception, you might put the processVote calls in the test method directly – that’s also fine. This example just tries to give you an idea of how to get started.

Note that tests, ours and yours, should run directly through the ElectionData methods, not through the screen-based interface.

Your Examples class should contain up to 12 (twelve) tests. You can run multiple tests on the same ballot and/or election, or you can create different elections. Running each test should require calling only the required methods (addCandidate, processVote, findWinnerMostFirstVotes, and findWinnerMostPoints) and operations that are built into Java. Calling any other methods from your ElectionData or VotingMachine classes will break autograding.

3.1 Things To Consider About Testing

Here are some reminders of criteria that have cost students testing points so far this term:

We will run your tests/elections against both our correct solution and several broken solutions. Each broken solution will have an error in at least one of the four required methods. We will look to see how thorough your elections were in detecting the broken solutions.

4 Program Design Expectations (Core and Full)

In grading this assignment for program design, we will be looking for various good OO coding practices as we have discussed them this term. This includes:

5 What to Turn In

Submit a single zip file that contains all .java files for this assignment. Please check that: