This repository contains the skeleton code for the Minesweeper Alpha project assigned to the students in the Spring 2022 CSCI 1302 classes at the University of Georgia.
Please read the entirety of this file before beginning your project.
There are different deadline options for this project. Students who
perform their final submission via the submit
command before the date/times listed
below automatically receive the associated Submission-Based (SB) extra credit.
The late penalty does not start applying until after the final date listed.
- WED 2022-02-09 (Feb 9th) @ 11:55 PM EST (
+20
SB Extra Credit) - FRI 2022-02-11 (Feb 11th) @ 11:55 PM EST (
+10
SB Extra Credit) - SUN 2022-02-13 (Feb 13th) @ 11:55 PM EST (
+0
SB Extra Credit)
Seriously. Read this entire file before starting.
- Academic Honesty
- Course-Specific Learning Outcomes
- Updates
- Suggested Checklist
- Project Description
- Minesweeper Oracle
- Project Requirements & Grading
- Suggestions
- How to Download the Project
- Submission Instructions
- Appendix
You agree to the Academic Honesty policy as outlined in the course syllabus. In accordance with this notice, I must caution you not to fork this repository on GitHub if you have an account. Doing so will more than likely make your copy of the project publicly visible. Please follow the instructions contained in the How to Download the Project section below in order to do your development on Odin. Furthermore, you must adhere to the copyright notice and licensing information at the bottom of this document.
- LO1.a: Navigate and modify files, directories, and permissions in a multi-user Unix-like environment.
- LO1.b: (Partial) Execute, redirect, pipe, and manage programs/processes in a multi-user Unix-like environment.
- LO1.c: Create and modify text files and source code using a powerful terminal-based text editor such as Emacs or Vi.
- LO1.d: (Partial) Use shell commands to compile new and existing software solutions that are organized into multi-level packages and have external dependencies.
- LO2.b: (Partial) Define, throw, and propagate exceptions appropriately in a software solution.
- LO3.a: Create and update source code that adheres to established style guidelines.
- LO3.b: (Partial) Create class, interface, method, and inline documentation that satisfies a set of requirements.
- LO7.c: (Partial) Use common abstract data types and structures, including lists, queues, arrays, and stacks in solving typical problems.
Updates will be posted in this section.
If there is an update and you have already cloned the project to Odin,
then you can update your copy of the project using the $ git pull
command while inside of your project directory. In many cases, this just
updates your copy of the README.md
file; however, it is possible for
an update to affect other files as well. If other files are affected, then
they will be mentioned in the update summary.
To help you with planning out this project, here are some suggested steps you can take that your instructors believe will help you complete the project more easily. Some of the items in this checklist may not make sense until you have read the entire project description, including the appendices. These steps are suggesions and, therefore, do not constitute an exhaustive list of steps that you may need to take to complete the project.
Preparation: (Finish before Monday, Jan. 31st)
- Start reading the project description.
- Read through the entire project description, including the appendices, and write down questions as you go.
- Read it again! This time, you may be able to answer some of your own questions.
Planning: (Finish before Tuesday, Feb. 1st)
- Play the Minesweeper game using the provided oracle. This will help you see how to run the program and allow you to see expected input / output for a variety of scenarios.
- Plan how you will represent the Minesweeper board. The internal representation should not contain all of the characters that the user sees when the board is printed (e.g., you don't need to include vertical bars or index numbers in your array since they can be printed as you loop over your array). One idea of how to approach this is given under suggestions.
- Unsure of your chosen board representation? Feel free to come by office hours to discuss!
- Write out test cases. Make sure to consider edge cases.
Documenting: (Finish before Friday, Feb. 4th)
- Type out the method signatures (not the full methods - just the signatures) for each method listed in the functional requirements
- Add proper Javadoc comments to all methods and both classes. Don't implement the methods yet. This will give you an opportunity to think about each method a little bit more before you start writing the code. Also, these comments serve as a convenient reference while writing the code. Remember, if you can't describe, in detail, what each method does, you can't implement it.
Implementing: (Finish before Monday, Feb. 7th)
- Start by declaring the instance variables of the
MinesweeperGame
class based on your chosen board representation. - For testing and debugging purposes, it's a good idea to implement the
printMineField
method early in the implementation phase. - Create seed files with various board dimensions. Test that your
printMineField
method works with each. - Implement
printWelcome
,printWin
,printLoss
using the banners given later in this document. - Implement the main game loop and the commands one at a time, testing each as you go.
- Double check all output formatting and error conditions.
- Implement the
isWon
method with the proper win conditions. - Implement any remaining methods
Testing (Finish before Tuesday, Feb. 8th)
- Create seed files for testing. Be sure to vary the dimensions of the board, the number of mines and the mine locations. Also be sure to include invalid configurations.
- Create input and output files for each seed file. Run your code as described later in this document. In the testing phase, try to avoid entering commands manually while playing the game. The commands should come from the input files at this point.
Review (Finish before Wednesday, Feb. 9th)
- Do one final pass through the project document making sure you didn't miss anything.
- Run your code through your test cases one last time.
- Submit your code to Odin.
This first project is meant to ensure that you are able to apply and extend your prerequisite knowledge as well as introduce you to developing and testing a Java application in a Linux environment (i.e., the Odin development server). You should be familiar with the majority of aspects of this project through your introductory programming course and the work so far from 1302. However, you may also be asked to do things that you have never been given explicit directions for before. This is just a part of software development. Sometimes you need to research how to solve a problem in order to implement a solution. That being said, the material included in this document should hopefully answer the majority of your questions.
Your goal is to develop a non-recursive, non-GUI (GUI = Graphical User Interface) version of the game called Minesweeper. The code for this game will be organized in such a way that the recursive elements of Minesweeper can be added at a later point in time, if desired. It will also be organized so that you can add a GUI to it later as well. Interestingly, the organization of some of the classes in this project will also introduce you to some elementary aspects of game programming.
If you are not familiar with Minesweeper as a game, we recommend playing a free, online version of the game. Just make sure to ignore the recursive elements of the game that uncovers more than one cell in a single click (more below). Our version will only do one cell at a time. After you've familiarized yourself with the game, play the oracle a few times to see the expected 1302 implementation. You should also consult the Wikipedia entry for the game, ignoring any mentions of recursion. Once you're familiar with the basic gameplay, then continue reading this project description.
In a traditional game of Minesweeper, when the player "reveals" a square that does not contain a mine, two things happen:
-
A number representing the number of mines in the (up to) eight adjacent squares is placed in the revealed square; and
-
If the number of adjacent mines is zero, then game goes ahead and "reveals" all of the (up to) eight adjacent squares.
The second part mentioned above can cause one reveal made by the user to result in multiple reveals in the minefield. This behavior is what the literature is referring to when it talks about recursion in Mineweeper. Your game should not support this behavior. If the user reveals one square, then, at most, one square is revealed in the minefield.
In your Minesweeper, the player is initially presented with a grid of undifferentiated squares. Some of those squares contain hidden mines. The size of the grid, the number of mines, and the individual mine locations are set in advance by a seed file (more on that later) that the user specifies as a command-line argument to your program. The ratio of the number of mines to the grid size is often used as a measure of an individual game's difficulty. The grid size can also be represented in terms of the number of rows and columns in the grid. In this project description, we may refer to the grid or to the minefield. Both of these terms mean the same thing. Furthermore, we will use the term square to denote a location in the minefield, even in situations where a location may be visually rectangular instead of perfectly square.
The game is played in rounds. During each round, the player is presented with the grid, the number of rounds completed so far, as well as a prompt. The player has the option to do 6 different things, each of which is briefly listed below and explained in great detail in later sections:
- Reveal a square on the grid.
- Mark a square as potentially containing a mine.
- Mark a square as definitely containing a mine.
- Lift the fog of war (cheat code).
- Display help information.
- Quit the game.
When the player reveals a square of the grid, different things can happen:
-
If the revealed square contains a mine, then the player loses the game.
-
If the revealed square does not contain a mine, then a digit is instead displayed in the square, indicating how many adjacent squares contain mines. Typically, there are 8 squares adjacent to any given square, unless the square lies on an edge or corner of the grid. The player uses this information to deduce the contents of other squares, and may perform any of the first three options in the list presented above.
-
When the player marks a square as potentially containing a mine, a
?
is displayed in the square. This provides the user with a way to note those places that they believe may contain a mine but are not sure enough to mark as definitely containing a mine. -
When the player marks a square as definitely containing a mine, a flag, denoted by the character
F
, is displayed in the square.
To simplify the game mechanics, the player may mark, guess, or reveal any square in the grid, even squares that have already been marked or revealed. In other words, the player may issue a command to mark, guess, or reveal a square, regardless of its current state. The logic for determining what happens to the square is always the same. For example, if a square has been revealed and the user marks it as definitely containing a mine then a round is consumed and the square should be marked. The user would then have to reveal this square again later. This may not be consistent with how you've played Minesweeper in the past but it will make it easier to code. We will leave it up to the user to be smart about how they play!
The game is won only when both of the following conditions are met:
- All squares containing a mine are marked as definitely containing a mine; and
- All squares not containing a mine are revealed.
At the end of the game, the player is presented with a score. Let rows
, cols
,
and rounds
denote the number of rows in the grid, columns in the grid, and
number of rounds completed, respectively. A round is defined as one successful
iteration of the main game loop. Therefore, only valid commands result in a round
being consumed. To be clear, rounds is not quite the same as the number of commands
entered (some may be invalid); however, it should be less than or equal to that number.
The player's score is calculated as follows:
score = 100.0 * rows * cols / rounds;
A score of 100
would denote a perfect game. In this version of Mineweeper, it should
not be possible for the player to win the game in less than (rows * cols)
-many rounds
(take a second to convince yourself of this fact).
Therefore, any game in which the player exceeds that many rounds would result in a score
that is less than 100
. When displaying the score, the number should always be printed
with two digits following the decimal point.
When the game begins, the following welcome banner should be displayed to the player once and only once:
_
/\/\ (F)_ __ ___ _____ _____ ___ _ __ ___ _ __
/ \| | '_ \ / _ \/ __\ \ /\ / / _ \/ _ \ '_ \ / _ \ '__|
/ /\/\ \ | | | | __/\__ \\ V V / __/ __/ |_) | __/ |
\/ \/_|_| |_|\___||___/ \_/\_/ \___|\___| .__/ \___|_|
ALPHA EDITION |_| v2022.sp
Take care when printing this message out to the screen. Depending on how you implement this
part, you may need to escape some of the characters in order for them to show up correctly.
A copy of this welcome banner is contained in this README.md
file and in
resources/welcome.txt
.
In this Minesweeper game, the initial game configuration is loaded from a seed file; the player provides the path to a seed file when as a command-line argument to the program. Two pieces of of information that are read from the seed file are the number of rows and the number of columns which together specify the grid size (i.e., the size of the minefield).
The number of rows and the number of columns need not be the same. Rows and columns
are indexed starting at 0
. Therefore, in a 10
-by-10
(rows-by-columns),
the first row is indexed as 0
and the last row is indexed as 9
(similarly for columns).
In a 5
-by-8
game, the row indices are from 0
to 4
, while the column indices
are from 0
to 7
, respectively.
Let's assume we are playing a 5
-by-5
game of Minesweeper. When the game
starts, the interface should look like this:
Rounds Completed: 0
0 | | | | | |
1 | | | | | |
2 | | | | | |
3 | | | | | |
4 | | | | | |
0 1 2 3 4
minesweeper-alpha:
Let's assume we are playing a 10
-by-10
game of Minesweeper. When the game
starts, the interface should look like this:
Rounds Completed: 0
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha:
Please note that the in either example, the first, third, and second-to-last lines are blank
(the lines before and after "Rounds Completed" and the line before the prompt).
All other lines, except the last line containing the prompt, start with one blank space.
The line containing the prompt contains an extra space after the :
so that when the user types in a command, the text does not touch the
:
. Multiple output examples are provided in the Appendix
of this project description for your convenience.
The possible commands that can be entered into the game's prompt as well as their syntax are listed in the subsections below. Commands with leading or trailing whitespace are to be interpreted as if there were no leading or trailing whitespace. For example, the following two examples should be interpreted the same:
minesweeper-alpha: help
minesweeper-alpha: help
Although it's hard to see in the example above, trailing whitespace should
also be ignored. That is, if the user types
one or more times before
pressing the RET
(return) key, then those extra whitespaces should be
ignored.
The different parts of a command are known as tokens. The help
command, for example, only has one token. Other commands, such as the
mark
(seen below) have more than one token because other
pieces of information are needed in order to interpret the command. As a quick
example (which will be explored in more depth below), the player can
mark the square at coordinate (0,0) using mark
as follows:
minesweeper-alpha: mark 0 0
In the above example, you can see that the mark
command has three
tokens. A command with more than one token is still considered syntactically
correct if there is more than one white space between tokens. For example, the
following four examples should be interpreted the same:
minesweeper-alpha: mark 0 0
minesweeper-alpha: mark 0 0
minesweeper-alpha: mark 0 0
minesweeper-alpha: mark 0 0
As a reminder, trailing whitespace is ignored.
All valid game commands are entered on a single line. Implementers should always
use the nextLine()
method of their one and only standard input Scanner
object to retrieve an entire line of input for a command as a String
. Once
an entire line is retrieved, it can be parsed using various methods; however,
implementers may find it useful to construct a new Scanner
object using
the line as its source so that they can scan over the individual tokens.
To put this into perspective, taking the "make a Scanner
from the line"
approach would make it so you can handle all four examples at the end
of the last sub-section with one set of code.
Here's some example code:
// Assume you have have a Scanner object that reads from System.in called stdIn
String fullCommand = stdIn.nextLine(); // reads the full command from the user (Ex: command may contain "reveal 1 3")
// Create a new Scanner to parse the tokens from the given command
Scanner commandScan = new Scanner(fullCommand); // Neat! A new use of Scanner. :)
// Now, we can call our regular Scanner methods to get each part of the assigned command
String command = commandScan.next(); // command would contain "reveal" from if given the command above.
// Continue to call additional Scanner methods (nextInt(), etc.) to parse out the other tokens from the full command.
In the sections below, we describe the syntax format that each command must adhere to in order to be considered correct. Unknown commands and commands that are known but syntactically incorrect are considered invalid. Information about displaying errors related to invalid commands is contained in a later section in this document.
Please do not confuse this syntax with regular expressions, a topic that will not be covered in this course. You are NOT meant to put this weird looking syntax into any code. It is purely meant to convey to you, the reader, what is and what is not valid input for a given command.
In a syntax format string, one or more non-new-line white space is represented
as a -
. Command tokens are enclosed in []
braces. If the
contents of a token are surrounded by ""
marks, then that token can
only take on that literal value. If more than one literal value is accepted for
a token, then the quoted literals are separated by /
. If the
contents of a token are surrounded by ()
marks, then that token can
only take on a value of the type expressed in parentheses. Note: the literal
values are case-sensitive. So, "ReVeal" is not the same as "reveal".
In order to reveal a square, the reveal
or r
command
is used. The syntax format for this command is as follows: -["reveal"/"r"]-[(int)]-[(int)]-
.
The second and third tokens indicate the row and column indices, respectively,
of the square to be revealed.
Let's go back to our 10
-by-10
example. Suppose that we secretly know that there is
a mine in squares (1,1) and (1,3). Now suppose that the player wants to reveal
square (1, 2). Here is an example of what that might look like.
Rounds Completed: 0
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha: r 1 2
Rounds Completed: 1
0 | | | | | | | | | | |
1 | | | 2 | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha:
After the player correctly entered the command r 1 2
, the state of
the game updates (e.g., number of rounds completed, the grid, etc.), and the
next round happens. Since there was no mine in square (1,2), the player does not
lose the game. Also, since the total number of mines in the 8 cells directly
adjacent to square (1,2) is 2, the number 2 is now placed in that cell.
If the player reveals a square containing a mine, then the following message should be displayed and the program should exit gracefully (as defined near the end of this section):
Oh no... You revealed a mine!
__ _ __ _ _ __ ___ ___ _____ _____ _ __
/ _` |/ _` | '_ ` _ \ / _ \ / _ \ \ / / _ \ '__|
| (_| | (_| | | | | | | __/ | (_) \ V / __/ |
\__, |\__,_|_| |_| |_|\___| \___/ \_/ \___|_|
|___/
Yeah, that's old school ASCII art. Please note that the first and last lines are
blank. Also note that the second line (containing "oh no...") begins with a single
white space. A copy of this game over text, excluding the first and last blank
lines, is contained in resources/gameover.txt
.
Graceful Exit: When we say that a program should exit gracefully, we mean that
the exit status code used in the call to
System.exit
is 0
(i.e., zero).
-
If a graceful exit is expected and your program exits for any reason with an exit status other than
0
(e.g., if your game crashes), then some points will be deducted from your grade. -
Immediately after any program terminates and returns to the terminal shell, a user can inspect what exit code was used by executing the following command:
$ echo $?
Note that using
echo $?
a second time would show the exit status of the firstecho
command; you would need to rerun your program and cause it to exit in order to check the exit status again.
In order to mark a square as definitely containing a mine, the
mark
or m
command is used. The syntax format for this
command is as follows: -["mark"/"m"]-[(int)]-[(int)]-
.
The second and third tokens indicate the row and column indices, respectively,
of the square to be revealed.
Let's go back to our 10
-by-10
example. Suppose that the player wants to mark
square (1, 2). Here is an example of what that might look like.
Rounds Completed: 0
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha: m 1 2
Rounds Completed: 1
0 | | | | | | | | | | |
1 | | | F | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha:
After the player correctly entered the command m 1 2
, the state of
the game updates (e.g., number of rounds completed, the grid, etc.), and the
next round happens.
In order to mark a square as potentially containing a mine, the
guess
or g
command is used. The syntax format for this
command is as follows: -["guess"/"g"]-[(int)]-[(int)]-
.
The second and third tokens indicate the row and column indices, respectively,
of the square to be revealed.
Let's go back to our 10
-by-10
example. Suppose that the player wants to guess
square (1, 2). Here is an example of what that might look like.
Rounds Completed: 0
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha: g 1 2
Rounds Completed: 1
0 | | | | | | | | | | |
1 | | | ? | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha:
After the player correctly entered the command g 1 2
, the state of
the game updates (e.g., number of rounds completed, the grid, etc.), and the
next round happens.
This command removes, for the next round only, what is often
referred to as the, "fog of war." All squares containing mines, whether unrevealed,
marked, or guessed, will be displayed with less-than and greater-than symbols on
either side of the square's center (as opposed to white space). Using the
nofog
command does use up a round.
In order to issue this command, the nofog
command is used.
The syntax format for this command is as follows: -["nofog"]-
.
Let's go back to our 10
-by-10
example. Suppose that in this example, there
are only two mines in the entire board which are located in squares (1,1) and (1,3).
If the player marked square (1,1) during the
first round and then used the nofog
command during the second
round, then here is an example of what that scenario might look like:
Rounds Completed: 2
0 | | | | | | | | | | |
1 | |<F>| |< >| | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha:
NOTE: This command should not be listed when the help
command
is used. Think of it as a cheat code! It should also be useful for debugging.
In order to show the help menu, the help
or h
command
is used. The syntax format for this command is as follows: -["help"/"h"]-
.
Let's go back to our 10
-by-10
example. Suppose that the player wants to display
the help menu. Here is an example of what that might look like.
Rounds Completed: 0
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha: h
Commands Available...
- Reveal: r/reveal row col
- Mark: m/mark row col
- Guess: g/guess row col
- Help: h/help
- Quit: q/quit
Rounds Completed: 1
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha:
After the player correctly entered the command h
, the state of
the game updates (e.g., number of rounds completed, the grid, etc.), the
help menu is displayed, and the next round happens.
Note: the help
command does use up a round.
In order to quit the game, the quit
or q
command
is used. The syntax format for this command is as follows: -["quit"/"q"]-
.
Let's go back to our 10
-by-10
example. Suppose that the player wants to quit the
game. Here is an example of what that might look like.
Rounds Completed: 0
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
minesweeper-alpha: q
Quitting the game...
Bye!
After the player correctly entered the command q
, the game
displayed the goodbye message and the program exited gracefully
(as defined elsewhere in this document).
When the player wins the game, the following message should be displayed to the player and the game should exit gracefully (as defined elsewhere in this document):
░░░░░░░░░▄░░░░░░░░░░░░░░▄░░░░ "So Doge"
░░░░░░░░▌▒█░░░░░░░░░░░▄▀▒▌░░░
░░░░░░░░▌▒▒█░░░░░░░░▄▀▒▒▒▐░░░ "Such Score"
░░░░░░░▐▄▀▒▒▀▀▀▀▄▄▄▀▒▒▒▒▒▐░░░
░░░░░▄▄▀▒░▒▒▒▒▒▒▒▒▒█▒▒▄█▒▐░░░ "Much Minesweeping"
░░░▄▀▒▒▒░░░▒▒▒░░░▒▒▒▀██▀▒▌░░░
░░▐▒▒▒▄▄▒▒▒▒░░░▒▒▒▒▒▒▒▀▄▒▒▌░░ "Wow"
░░▌░░▌█▀▒▒▒▒▒▄▀█▄▒▒▒▒▒▒▒█▒▐░░
░▐░░░▒▒▒▒▒▒▒▒▌██▀▒▒░░░▒▒▒▀▄▌░
░▌░▒▄██▄▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▌░
▀▒▀▐▄█▄█▌▄░▀▒▒░░░░░░░░░░▒▒▒▐░
▐▒▒▐▀▐▀▒░▄▄▒▄▒▒▒▒▒▒░▒░▒░▒▒▒▒▌
▐▒▒▒▀▀▄▄▒▒▒▄▒▒▒▒▒▒▒▒░▒░▒░▒▒▐░
░▌▒▒▒▒▒▒▀▀▀▒▒▒▒▒▒░▒░▒░▒░▒▒▒▌░
░▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▒▄▒▒▐░░
░░▀▄▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▄▒▒▒▒▌░░
░░░░▀▄▒▒▒▒▒▒▒▒▒▒▄▄▄▀▒▒▒▒▄▀░░░ CONGRATULATIONS!
░░░░░░▀▄▄▄▄▄▄▀▀▀▒▒▒▒▒▄▄▀░░░░░ YOU HAVE WON!
░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▀▀░░░░░░░░ SCORE: 82.30
Note that the first and last lines are blank and that the beginning of the
other lines contain a single white space. You should replace the score in the
output with the actual calculated score (mentioned above). A copy of this game won
text, excluding the first and last blank lines as well as the score value, is
contained in resources/gamewon.txt
.
The conditions for winning are outlined earlier in this document, here.
Each game is setup using seed files. Seed files have the following format:
-
The first two tokens are two integers (separated by white-space) indicating the number of
rows
andcols
, respectively, for the size of the mine board. -
The third token is an integer indicating
numMines
, i.e., the number of mines to be placed on the mine board. -
Subsequent pairs of tokens are integers (separated by white space) indicating the location of each mine.
NOTE: Please refer to the "Seed File Malformed Error" description in the "Displaying Errors" section later in this document for constraints related to the tokens in a seed file.
NOTE: In Java, the term white-space refers to one or more characters in a sequence that
each satisfy the conditions outlined in Character.isWhitespace
.
You do not need to check these conditions specifically nor use this method
if you use the built-in tokenizing provided by the Scanner
class.
NOTE: It is acceptable for white-space to occur both at the beginning and end of a seed file.
The following seed files are valid and contain the same information:
10 10 2 0 0 1 1
10 10
2
0 0
1 1
10 10 2
0 0 1 1
An example seed file is present in the project materials. In order to run your program with the seed file, you should be able to use the following command (actual seed filename may differ):
$ java -cp bin cs1302.game.MinesweeperDriver tests/seed1.txt
Note: The command you use to run your file from your main project directory
(the directory containing src
and bin
) should exactly match the command above
if you are passing in a seed file called seed1.txt
containined in a tests
directory
located directly within your main project directory.
To read the file, let us assume that we have access to the seed file's path
via a String
variable called seedPath
and that we will use the following classes:
Most of you have used the Scanner
class to read keyboard input from standard
input. Here, we will use it to read from a text file. This is accomplished using
something similar to the following code snippet:
try {
File configFile = new File(seedPath);
Scanner configScanner = new Scanner(configFile);
// use Scanner here as usual
} catch (FileNotFoundException e) {
// handle the exception here
// and perhaps do the following for testing/debugging only:
// System.err.println(e);
// e.printStackTrace();
// and don't forget to:
// print any error messages described earlier and exit appropriately
} // try
You may need to import
FileNotFoundException
(or use its fully qualified name) if adapting the code snippet above.
All error messages should be printed to standard error (System.err
)
with one blank line preceeding the line with the error message.
In some cases, an error message should cause the program
to terminate. In such cases, an integer will be specificied that
you are required to use the specific exit status number in your call to
System.exit
.
If you let errorMessage
denote an error message and exitStatus
denote
an exit status, then here is some code that exactly illustrates what needs
to happen for error messages that exit the program:
System.err.println();
System.err.println(errorMessage);
System.exit(exitStatus);
If an error does not exit the program, then the last line in the code above should be omitted.
Here is a list of the different errors that can occur in the program:
-
Invalid Usage Error: If your program encounters any number of command-line arguments other than one (i.e., if
args.length != 1
), then the error message and exit status in the table near the end of this section should be used. -
Seed File Not Found Error: If your program is not able to read the seed file that is specified by the user via a command-line argument due to a
FileNotFoundException
, then the error message and exit status in the table near the end of this section should be used.Note that
<message>
(including the angle brackets) in the error message text should be replaced with theString
returned by the exception object'sgetMessage()
method. -
- a token is expected but is not found;
- a token is not of the expected type (e.g., it's expected to be an
int
but it's not); - the token for
rows
is less than5
or greater than10
; - the token for
cols
is less than5
or greater than10
; - the token for
numMines
is less than1
or greater than(rows * cols) - 1
; or - the location of a mine is not in bounds.
Note that
<message>
(including the angle brackets) in the error message text should be replaced with some descriptiveString
. If the error arises due to some exception, then you may use theString
returned by the exception object'sgetMessage()
method; otherwise, you may use some short, single lineString
of your choosing that describes the problem. -
Invalid Command Error: While the game is running, if a command entered by the player is invalid or not recognized, then the error message in the table near the end of this section should be used. For this kind of error, the program should NOT exit, and a round should NOT be consumed (i.e., your counter for the number of rounds should not increase). After the error message is displayed, the round is essentially restarted and the number of rounds, the grid, and the prompt should be displayed again.
Note that
<message>
(including the angle brackets) in the error message text should be replaced with some descriptiveString
. If the error arrises due to some exception, then you may use theString
returned by the exception object'sgetMessage()
method; otherwise, you may use some short, single lineString
of your choosing that describes the problem.
Here is the table that summarizes the different error messages and exit status codes:
Error | Error Message | Exit Status |
---|---|---|
Invalid Usage Error | Usage: MinesweeperDriver SEED_FILE_PATH |
1 |
Seed File Not Found Error | Seed File Not Found Error: <message> |
2 |
Seed File Malformed Error | Seed File Malformed Error: <message> |
3 |
Invalid Command Error: | Invalid Command: <message> |
NA |
If at any time while you are writing your Minesweeper program you find yourself wondering "How should my program respond if _____ happens", you can consult the provided Minesweeper oracle. The oracle is an executable-only (no code provided) version of the instructors' solution to the Minesweeper project and is available on Odin. To run the oracle, you need to provide the same command-line arguments that you would to your version of Minesweeper. You only need to change the start of the command. Here is the exact syntax:
$ minesweeper-oracle cs1302.game.MinesweeperDriver some/path/to/seed.txt < optional/path/to/input.txt
where some/path/to/seed.txt
and optional/path/to/input.txt
are replaced with paths to real seed
and input files, respectively.
If you find a bug in the oracle or believe there to be an inconsistency between what the oracle says and what this document says, please let your instructors know in a Piazza post. We have only given students access to the oracle once before so we may still have to iron out a few kinks.
This assignment is worth 100 points. The lowest possible grade is 0, and the highest possible grade without extra credit is 100.
A functional requirement is added to your point total if satisfied. There will be no partial credit for any of the requirements that simply require the presence of a method related a particular functionality. The actual functionality is tested using test cases.
-
cs1302.game.MinesweeperGame
Class: Instances of this class represent a game of Minesweeper Alpha. You need to implement all of the methods listed below. Unless stated otherwise, each method is assumed to have public visibility. Instance variables may be added, as needed, to keep track of object state.-
MinesweeperGame(Scanner stdIn, String seedPath)
: In this constructor, you should intialize some of your instance variables and setup the game using the information in the seed file referred to byseedPath
.-
Keep in mind that this constructor will be called from your
Driver
class, so that's where the iniitial values ofstdIn
andseedPath
come from. You should refer to the instructions forDriver
for more information before you make any assumptions. -
You will probably want to create a separate method for reading the seed file.
-
Take care that you assign
stdIn
to an instance variable and that you use that instance variable whenever you need to read from standard input.WARNING: Other than the initial assignment to your
stdIn
-related instance variable in the constructor, you should not reassign that instance variable elsewhere in yourMinesweeperGame
class; if you do, then you risk getting a zero on your project per one of the requirements.SUGGESTION: We said that you should assign
stdIn
to an instance variable, but it's also perfectly okay for you to assign it to an instance constant. This will prevent you from reassigning it on accident. In Java, you can leave an instance constant uninitialized on the line where it's declared so long as you initialize it exactly once inside your constructor. Here is the declaration (notice we didn't initialize it here):private final Scanner stdIn; // declare instance constant
Here is what a line in the constructor might look like:
this.stdIn = stdIn; // initialize instance constant
If you follow this suggestion, then you can call
stdIn.nextLine()
orthis.stdIn.nextLine()
in your class's instance methods whenever you want to get a line of user input (e.g., for a command).
-
-
void printWelcome()
: This method should print the welcome banner to standard output, as described earlier in this document. -
void printMineField()
: This method should print the current contents of the mine field to standard output, as described earlier in this document. -
void promptUser()
: This method should print the game prompt to standard output and interpret user input from standard input, as described earlier in this document. Based on the command received, this method should delegate (i.e., call other methods) to handle the work. -
boolean isWon()
: This method should returntrue
if, and only if, all the conditions are met to win the game as defined earlier in this document. -
void printWin()
andvoid printLoss()
: These methods should print the win and game over emssages to standard output, respectively, as described earler in this document. -
void play()
: This method should provide the main game loop by invoking other instance methods, as needed.
NOTE: Please see the Suggestions section of this document before writing the code to implement these methods.
NOTE: You are encouraged to implement other methods, as needed, to help with readability, code reuse, etc. In some cases, you may need to add other methods to meet the style requirement for method length.
-
-
cs1302.game.MinesweeperDriver
Class: This class should only contain themain
method:-
void main(String[] args)
: This public, static method should do the following :-
Create your one and only standard input
Scanner
object. While your program may have as manyScanner
objects as is needed, it may only have exactly oneScanner
object for standard input per program execution. Yourmain
method should include the following code:Scanner stdIn = new Scanner(System.in);
You must not include
new Scanner(System.in)
anywhere else in your project or you risk getting a grade of zero for the project. -
Handle command-line arguments. Exactly one command-line argument is expected, and it represents the path to a seed file. You should assign that argument to a local
String
variable calledseedPath
so that it can be used in the call to yourMinesweeperGame
constructor in step 3. -
Instatiate the
MinesweeperGame
object call itsplay()
method. Use thestdIn
andseedPath
variables from the first two steps when you create the object. Once the object is created, call itsplay()
method.
-
-
-
(60 points) Test Cases: The bulk of this project will be graded based on multiple test cases. A single test case can be described by three things:
Some example test cases are provided with the project description; they are described in the Test Case Examples section of the Appendix. Since the actual test cases that will be used to grade your project may be a superset of the ones provided, you should take care to read and understand how to test your program using the example test cases. If your program does not execute with a command-line argument and input/output redirection as described in the appendix (and perhaps elsewhere), then it will automatically be assigned a grade of zero, regardless of any code contained in the submission. No exceptions will be made for this.
To be absolutely clear, you must make sure your program runs as described in the Test Case Examples section of the Appendix; the graders will not adjust the commands when running your program to accomodate a different set of command-line arguments. If there is a package-related issue, however, then the graders may make some minor adjustments for a relatively small grade penalty.
A non-functional requirement is subtracted from your point total if not satisfied. In order to emphasize the importance of these requirements, non-compliance results in the full point amount being subtracted from your point total. That is, they are all or nothing (no partial credit).
-
(10 or 100 points) Project Directory Structure: The location of the default package for the source code should be a direct subdirectory of
cs1302-minesweeper-alpha
calledsrc
. When the project is compiled, the-d
option should be used withjavac
to make the default package for compiled code a direct subdirectory ofcs1302-minesweeper-alpha
calledbin
.If you follow this structure, then you would type the following to compile your code, assuming you are in the top-level project directory
cs1302-minesweeper-alpha
:$ javac -cp bin -d bin src/cs1302/game/MinesweeperGame.java $ javac -cp bin -d bin src/cs1302/game/MinesweeperDriver.java
The class path may be omitted in the first command if there are no other dependencies. Remember, when you compile
.java
files individually, there might be dependencies between the files. In such cases, the order in which you compile the code and whether or not you specify the class path matters.NOTE: If your grader needs to modify your directory structure or any of your filenames to compile your code, then the 10 point version of this penalty will apply. If, however, your grader is unable to compile your code, then the 100 point version of this penalty applies. Graders are instructed not to modify source code in an attempt to to make a submission compile.
Any additional classes that you create should be located in or under the
cs1302.game
package. If other.java
files are present, then they will be compiled individually by the graders and added to the class path, as needed, when compiling other files. -
(100 points) Development Environment: This project must be implemented in Java 17, and it must compile and run correctly on Odin using the specific version of Java 17 that is setup according to the instructions provided by your instructor. Graders are instructed not to modify source code in an attempt to to make a submission compile.
-
(100 points) One Scanner for Standard Input: Only one
Scanner
object forSystem.in
(i.e., for standard input) should be created. You may createScanner
objects for other input sources as needed. Please note that if you create a newScanner
object at the beginning of a method or loop, then more than one object will be created if the method is called more than once or if the loop iterates more than once. -
(0 points) [RECOMMENDED] No Static Variables: Use of static variables is not appropriate for this assignment. However, static constants are perfectly fine.
-
(20 points) Code Style Guidelines: You should be consistent with the style aspect of your code in order to promote readability. Every
.java
file that you include as part of your submission for this project must be in valid style as defined in the CS1302 Code Style Guide. All of the individual code style guidelines listed in that document are part of this single non-functional requirement. Like the other non-functional requirements, this requirement is all or nothing.NOTE: The CS1302 Code Style Guide includes instructions on how to use the
check1302
program to check your code for compliance on Odin. -
In-line Documentation (10 points): Code blocks should be adequately documented using in-line comments. With in-line comments, you should explain tricky, large, complicated, or confusing blocks of code. This is especially necessary whenever a block of code is not immediately understood by a reader (e.g., yourself or the grader). You might also include information that someone reading your code would need to know but not someone using it (that is more appropriate for a Javadoc comment). A good heuristic for this: if you can imagine that, after six months, you might not be able to tell in under a few seconds what a code block is doing, then then you probably need to write some in-line comments.
This project will be a lot easier if you structure your code properly. There is not a single correct way to do this, but here are some ideas for support methods that I think will make things easier. These are just suggestions. If you choose to use these, then you will need to implement them yourself.
/**
* Returns the number of mines adjacent to the specified
* square in the grid.
*
* @param row the row index of the square
* @param col the column index of the square
* @return the number of adjacent mines
*/
private int getNumAdjMines(int row, int col) { }
The method above (as well as some other methods) can be implemented a lot more easily if you have an easy way to determine if a square is in bounds. Here is a suggestion for a method that does just that.
/**
* Indicates whether or not the square is in the game grid.
*
* @param row the row index of the square
* @param col the column index of the square
* @return true if the square is in the game grid; false otherwise
*/
private boolean isInBounds(int row, int col) { }
Also, it might be easier to use two different arrays (of the same size) in order to keep track of the game grid. One of the arrays could be a two-dimensional boolean array that indicates mine locations. The other array could be a two-dimensional char or String array that holds the blanks, numbers, and other characters for each square.
On Odin, execute the following terminal command in order to download the project files into sub-directory within your present working directory:
$ git clone --depth 1 https://github.com/cs1302uga/cs1302-minesweeper-alpha.git
This should create a directory called cs1302-minesweeper-alpha
in
your present working directory that contains the project files.
If any updates to the project files are announced by your instructor, you can merge those changes into your copy by changing into your project's directory on Odin and issuing the following terminal command:
$ git pull
If you have any problems with any of these procedures, then please contact your instructor.
You will submit your project via Odin. Before you submit, make sure that your
project files are located in a directory called cs1302-minesweeper-alpha
-- if
you followed the instructions provided earlier in this document to download
the project, then your directory name should good. To submit, change into the
parent of your project directory and execute the following command:
$ check1302 cs1302-minesweeper-alpha
If there are style guide violations, then fix them and retest your code! Once you have no style guide violations and your code compiles, you can submit using the following command:
$ submit cs1302-minesweeper-alpha csci-1302
If you have any problems submitting your project then please email your instructor as soon as possible. However, emailing him about something like this the day or night the project is due is probably not the best idea.
The examples
directory contains an example of
a blank grid for every possible combination of rows
and cols
supported by this game.
What is described in this section is exactly the method that the graders will use to test your program, with the exception that the graders have access to more test cases. Therefore, you should make every effor to test your program following these instruction.
The tests
directory contains some example test cases.
Each test case has a number (e.g., 01
) and can be described by five things:
- a path to a seed file;
- a path to a file with user input (StdIn);
- a path to a file with expected standard output (StdOut) given 1 and 2;
- a path to a file with expected standard error (StdErr) given 1 and 2; and
- a path to a file with expected combined output (Combined, i.e., standard output and standard error) given 1 and 2.
# | Seed File | StdIn | StdOut | StdErr | Combined |
---|---|---|---|---|---|
tc01 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
tc02 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
tc03 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
tc04 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
tc05 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
tc06 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
tc07 |
.seed.txt |
.in.txt |
.out.txt |
.err.txt |
.combined.txt |
You are encouraged to add your own test cases; however, we will not use your test cases when grading your project.
When a regular user plays the game, they specify the seed file as a command-line argument, e.g.,
$ java -cp bin cs1302.game.MinesweeperDriver some/path/to/seed.txt
In this scenario, the user enters their commands into standard input and the game prints its output to standard output.
When the grader wants to check your game, they will not manually
type in commands into standard input. Instead, they will use the shell
to redirect standard input to a file that contains user input. From the
program's perspective, it stil thinks it's reading from standard input.
It's just that standard input now refers to an actual file on disk
instead of keyboard input. This is accomplished using the shell
input redirection operator <
or pipe |
. For example, the grader
might type the following to run the first test case provided
in the tests
:
$ java -cp bin cs1302.game.MinesweeperDriver tests/tc01.seed.txt < tests/tc01.in.txt
Here is what each part of that command means:
run with test/tc01.seed.txt
+--------------------------------------------------------------+
$ java -cp bin cs1302.game.MinesweeperDriver tests/tc01.seed.txt < tests/tc01.in.txt
+-------------------+
redirect standard input to tests/tc01.in.txt
In this example, the shell forces the program to interpret standard input
as the file tests/tc01.in.txt
. Instead of halting for user input, any method
calls to your program's Scanner
object for System.in
return immediately
with a token from the file. Once the program has stopped producing output,
the grader then compares that output to tests/tc01.out.txt
, tests/tc01.err.txt
, and
tests/tc01.combined.txt
to see ensure that everything appears as it should for
that test case.
You can run the same tests against the oracle implementation. Here is an example:
$ minesweeper-oracle cs1302.game.MinesweeperDriver tests/tc01.seed.txt < tests/tc01.in.txt
If you want to save the standard output, standard error, and combined output of your
program, then you can utilize output redirection as follows (replace filename
with
some appropriate name, as needed):
Type | Example |
---|---|
Standard Output | > filename.out.txt |
Standard Error | 2> filename.err.txt |
Combined Output | &> filename.combined.txt |
You can combine input and output redirection, but take care not to overwrite the
output files in the test
directory.
Copyright © Michael E. Cotterell and the University of Georgia. This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License to students and the public. The content and opinions expressed on this Web page do not necessarily reflect the views of nor are they endorsed by the University of Georgia or the University System of Georgia.