CPSC 124 | Introduction to Programming | Spring 2024 |
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).
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.
Remember that you will need to compile and run your program using jfxc and jfx, respectively, instead of javac and java.
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.
Your program should work like the demo. In particular:
When the program starts, the user should be asked if each player is human or computer. For human players, the user should also be asked for the player's name. Computer players can be assigned a name by the program, but make sure that each computer player receives a different name if there are multiple computer players.
The board should be displayed graphically. The display can be as plain or as fancy as you want as long as it is clear where the discs are, which player's discs are which, and which column is the current column when a human player is taking a turn.
Human players should know when it is their turn. The demo does this by displaying the assigned colors before the game starts. Another option is to display a message on the game board with the name of the player whose turn it is.
The game should follow the normal rules of Connect Four: the board should have 6 rows and 7 columns, discs should fall as low in the column as possible when dropped, players cannot play into an already-full column, the first player to get four in a row wins, and the game ends in a tie if the board is filled without a winner.
Human players select the column to play in by using the arrow keys to move the currently-selected column, and then pressing the space bar to drop the disc in that column. (The currently-selected column should be indicated in some way.) Computer players randomly choose a non-full column.
When the game ends, a congratulatory message naming the winner or a "game tied" message should be displayed on the game board. In addition, the winner's win-loss-tie record should be displayed on the game board.
The user should be prompted to play again when the game ends. (This prompt can be displayed in the terminal window.) If the user wants to play again, a new game should be started with the same players. If not, the program should print the number of wins each player has accumulated and then end. (This just means that draw will end; the program itself won't actually end until the window displaying the board is closed. That's OK.)
You are free to customize the program's graphics (colors, fonts, appearance, etc) as long as you satisfy the requirements listed above.
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.
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.
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.
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.
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.
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.
There are lots of possibilities for extra credit — start early so you have time to try some of them! Some possibilities include:
[small] Add additional statistics, such as the length of the current winner's winning streak and the longest winning streak for each player. Display this information somewhere.
[small] Expand the game so it can be played on a grid of any size e.g. 7x8, 7x9, 7x10. The user should be prompted for the size of the grid at the start of the program.
[small-medium] Improve the game graphics. The amount of extra credit will depend on how much effort you put into this — more significant or sophisticated changes to the look of the game will earn more extra credit; minor changes (such as choosing different colors) will not earn any. You can also "animate" elements like the dropping of a piece and/or the computer's selection of a column using a loop to repeat the thing being animated. Include the following, where pause is replaced by a number of milliseconds, in the loop to slow things down a bit — a longer pause means a slower animation.
try { Thread.sleep(pause); } catch ( InterruptedException e ) {}
[small-large] Create one or more additional subclasses of Player which implement smarter computer players. Allow the user to choose the level of opponent at the beginning of the game. The amount of extra credit will depend on the sophistication of the computer's strategy.
[medium/large] Implement one or more variations of the game, such as the Pop Out and Pop 10 variations described in the Wikipedia article or the rotating board of Gravity Wins. Allow the user to choose which variation to play at the beginning of the game.
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.
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!)
Read through the whole handout and run the demo to see how your program should work.
Send an email with one or two questions about things you find confusing or unclear, something you aren't sure how to do, what you anticipate to be the most difficult part or the part you are most apprehensive of, etc — in short, aspects of the assignment that you find to be the muddiest. Try to be as specific as possible in identifying what the tricky points are.
Familiarize yourself with the program structure outlined above — make sure you understand what each class is for and what each method does.
Write skeletons for each class. Write the class declaration (header) and Javadoc comment for each class, then outline what you'll need in each class. You can start with note-to-yourself comments (e.g. pasting instructions from the handout) about what goes into each class or go straight to writing declarations for instance variables, constructor and method headers, and the associated comments. Make decisions about what is abstract or not, and for non-abstract methods that return something, either return a hardcoded value that you'll replace later or comment out the method definition for now.
Create a new text file named pseudocode.txt in your final directory. Put your name in the first line of the file!
Write pseudocode for the main program. Keep in mind the functionality handled by the other classes — the main program largely coordinates things.
Write pseudocode or outline how you are going to accomplish the task for each of the methods of GameBoard.
Write pseudocode or outline how you are going to accomplish the task for getNextMove for both human and computer players.
Email the pseudocode you've developed.
Implement Connect Four as described in the "Overview and Requirements" section above.
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, ...
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!
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.
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.*;
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.