CPSC 124 Introduction to Programming Spring 2024

Final Project
Connect Four

Muddiest Points Due: Thu 4/25 at 10pm
Project Due: Wed 5/8 at 4:30pm (no extensions!)


Introduction

The programming projects are an opportunity for you to work on a larger program, where there are more pieces to bring together. Unlike the labs, you have several weeks to complete this project — but this project is larger than the lab programs, so start early! This project is meant as a final capstone — it utilizes many of the elements introduced in the course (arrays, classes and objects, and inheritance as well as more basic elements like variables, loops, and conditionals).

Collaboration

The final project and final exam assess what you, individually, have mastered. You may get help only from Teaching Fellows and in office hours for this project — you may not discuss with, help, or get help from other people (students in the class or others).

As with all assignments, you may not look for solutions online or elsewhere or copy solutions or code written by others. To avoid problems, it is strongly recommended that you do not use the Internet as a resource at all — use the course materials (textbook, slides and examples posted on the schedule page, other resources linked directly from the course webpages) first, and then office hours and/or Teaching Fellows for additional help.


Setup

Remember that you will need to compile and run your program using jfxc and jfx, respectively, instead of javac and java.


Overview and Requirements

In this project, you will be writing a version of the game Connect Four. In the game, players take turns dropping discs into a vertical grid; the first player to get four discs in a row (horizontally, vertically, or diagonally) wins. Your version will support human vs. human, human vs. computer, and computer vs. computer play.

To see how your program should work, try out the demo:

  jfx -jar /classes/cs124/demos/connectfour.jar

Once the game starts, computer players play automatically. For human players, a colored disc is shown at the top of the window. Use the arrow keys (left and right) to move the disc in order to select a column, and press the spacebar to drop the disc in the selected column.

When the game ends, the winning player's win-loss-tie record is displayed and the user is prompted to play the game again. When the user no longer wants to play, the win records of both players are displayed.

Functionality

Your program should work like the demo. In particular:

You are free to customize the program's graphics (colors, fonts, appearance, etc) as long as you satisfy the requirements listed above.

Program Structure

In addition to the functionality listed above, your program must have the structure outlined below. This is an outline of the structure, not a 100% complete design — the main classes, instance variables, and methods are specified but you may need to add parameters to the methods listed and/or add instance variables, getters and setters, private helper methods, or additional constructors. If you want to change this design in ways other than those listed in the previous sentence, discuss your plans with me! You will not receive full credit if you make substantive changes without getting approval for those changes, even if the functionality is complete.

GameBoard

You should have a class called GameBoard to represent the game board. This class should contain all of the information and necessary functionality related to the contents of the game board:

In addition, GameBoard has the following elements to support displaying the board:

The board's primary job is to keep track of which player has claimed each square. That's what the 2D array is for — it should store a reference to the player object for the player whose disc is in that spot or null if the spot is empty.

Only draw and highlightColumn involve drawing — reset and dropPiece only update the array and do not redraw the board after.

getWinner is somewhat complicated because it needs to detect which player has won, and because there are many ways for a player to win. You can simplify it by writing several private helper methods which check for a win in a row, column, right diagonal, or left diagonal. Sit down with some examples to think about how you might detect a win in, say, a row — once you get the idea for a row, figuring out how to detect a win in a column or on a diagonal will be much easier. Note: when you compare different spots on the board, you'll want to check if the player object referenced by one spot is the same player object referenced by another. Is this check done with .equals() or ==?

highlightColumn is used during the human player's turn to mark the currently-selected column while the player is making a selection. This should just draw some indicator (the demo draws a disc above the column) indicating that the specified column is the current one.

Players

The game should support both human and computer players. Both kinds of players have some things in common:

The difference between the two types of players is how a move is decided on. Computer players randomly pick an open column. (One strategy for this is to keep picking random columns until an open one is found.) Human players move the currently-selected column using the arrow keys, then press the space bar to choose the current column. Thus, the human player's getNextMove will need to have a loop which keeps reading and processing user input until a column is chosen. Use the board's highlightColumn method to indicate the currently-selected column. Note that for both kinds of players, the desired column is returned — getNextMove does not actually make the move.

You should make use of inheritance: create a class Player for the things common to both kinds of players and then create subclasses ComputerPlayer and HumanPlayer to do the things specific to one type of player. All three classes should have a constructor which takes the player's name and color as parameters. Make appropriate decisions about where instance variables and methods are declared, and what (if anything) is abstract.

Main Program

The main program carries out the game — it should create the board and players and then carry out the game (alternating turns between the players). Note that much of the game's functionality is done by methods in the board and players, so the main program largely just needs to call the right things at the right time.

Note that this is a march-through-main type of program like Minesweeper and most of the other programs you've written — there's no animation like Pong and PacMan.

Important: Make use of polymorphism so that the only difference between the human vs. human, human vs. computer, and computer vs. computer options is in the initial setup (asking the user if each player is human or computer, getting the human player's name, and constructing the player objects). The code for the game itself (taking turns, handling the end-of-game stuff) should not be repeated for each of the variations.

Error Checking and Robustness

You should perform reasonable error-checking on all user input. That means, for example, if you ask the user to enter 'h' or 'c' to specify the type of player, you should re-prompt the user and re-read the input until one of those values is entered.

You should also make sure that your program doesn't crash with an exception, and you should make sure that a human player can't move the current position off the edge of the board or drop a piece in a full column.

Programming Style

As with labs, you should follow the standard conventions for indentation, line breaks, naming, and comments. Using autoformat will take care of most of the formatting considerations, though you still need to put in blank lines yourself in order to visually group related statements together and separate statements dealing with separate tasks. Remember to include enough in your variable names to identify what they are for and to distinguish similar variables each other, and to comment your variable declarations if there is additional relevant information not apparent from the variable name.

Javadoc-style comments are expected for each class and method. The need for comments inside of methods/subroutines to describe or explain chunks of code is greatly reduced with an object-oriented design, but they are still appropriate for longer method bodies or when there's something particularly non-apparent about a chunk of code.

Your code should be reasonably compact and elegant, meaning that your program is not significantly longer or more complex than it needs to be. Three important components of this are using loops when loops are appropriate (achieving repetition by copying-and-pasting code is not elegant!), using subroutines/private helper methods to organize and to avoid repeated code, and using polymorphism to avoid duplicating otherwise identical chunks of code.

Extra Credit

There are lots of possibilities for extra credit — start early so you have time to try some of them! Some possibilities include:

The tags "small", "medium", and "large" are meant to give some idea of the level of effort involved. (More involved options and more elegant solutions will earn more extra credit. (The chief inelegances to be concerned about are unnecessarily complex solutions, and unnecessary repeated code.) A maximum of 15 points (out of 100 for the project) of extra credit can be earned. If you plan to tackle a lot of extra credit or have an idea for something not listed, come discuss your plans with me.


Exercises

Work through these in order. Note that there is not a specific deadline for the pseudocode, but you should take advantage of the opportunity for feedback to make sure you are on the right track. (And allow 24 hours for said feedback — don't wait until the last minute!)

Muddiest Points

Class Design

Stepwise Refinement and Pseudocode

Finally, Connect Four

This should now largely be a matter of translating your pseudocode and filling in the bodies of constructors and methods in your classes.

Practice incremental development, and remember that you don't have to implement an entire class at once. A first goal might be to get the game board displayed on the screen. That involves implementing GameBoard's constructor and draw methods, as well as any methods of Player that are used in those. Add code in the main program to create and draw a game board.

To move beyond an empty game board, you'll need a way to add pieces to the game board — implement dropPiece and enough of HumanPlayer or ComputerPlayer to be able to create player objects and get their colors. Add code in the main program to create two players with different colors and call dropPiece a couple of times to add some pieces to the board.

Then start to build up the main turn-taking. First implement getNextMove for one of the kinds of players (the computer player is simpler) and add code in the main program to do one turn. Next, add a loop to take multiple turns — start with a set number such as 5, then add the code needed to determine if the game is over (do the tie case first) and change the loop to take turns until the game is over. If you are using computer players, you'll need a delay so you can see what is happening — have the user press a key after each computer player turn.

From there, keep building up bit by bit — check for a win, implement getNextMove for human players, track the number of wins, display the end-of-game and done-playing messages, add the player type selection at the beginning, ...


Technical Details

Main Program / Program Structure

The provided main program ConnectFour.java has the same structure as Minesweeper — all of the "main program" code you write will go into draw, under the comment

  // *** main program code goes here!

User Input

Your program will need to repeatedly get input specifying the (human) player's next move. This works just like in Minesweeper — the provided ConnectFour code contains a definition for getKeyCode() which you can call when the program needs to read a key press from the user, and check the returned value against KeyCode.LEFT, KeyCode.RIGHT, and KeyCode.SPACE to determine which key was pressed. Note that if you need to use getKeyCode() outside of ConnectFour, you'll need to write ConnectFour.getKeyCode() instead and will need to add

import javafx.scene.input.*;

at the very beginning of the class where you are using getKeyCode().

In addition, you'll need to read the player information at the start of the game and the user's response to the "play again?" question. This can be done as usual by printing a prompt to the terminal and using Scanner to read the input.

Drawing

You will need to include the following at the very beginning (before the class declaration) in any class that does drawing:

import javafx.scene.paint.*;
import javafx.scene.canvas.*;

Handin and Grading

Don't forget to hand in your work when you are done!

As with labs, grading will be based on both functionality and style. The expectations for both are given in the "Overview and Specifications" section above.