import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* A graphical interface driver for MineSweeper.
* Takes advantage of the same (implicit) interface provided by the MineSweeper class (As do all drivers).
* Supports the same command line options as the textual driver (See {@link #printHelp() printHelp()}).
* @author Nick Neely
*/
public class PlayMinesGui
{
private static final String HELP_SHORT_ARGUMENT = "-h";
private static final String HELP_LONG_ARGUMENT = "--help";
private static final String DEBUG_SHORT_ARGUMENT = "-d";
private static final String DEBUG_LONG_ARGUMENT = "--debug";
private static final int NORMAL_EXIT_CODE = 0;
private static final int ERROR_EXIT_CODE = 1;
// These should have been public in MineSweeper.java
private static final char BLANK_TILE = ' ';
private static final char CLOSED_TILE = 'X';
private static final char FLAGGED_TILE = 'F';
private static final char MINE_TILE = '*';
private static final int COMMAND_OPEN = 0;
private static final int COMMAND_FLAG = 1;
private static final int COMMAND_UNFLAG = 2;
private static final String GAME_WINDOW_TITLE = "CS61B MineSweeper";
private static final int INITIAL_WINDOW_WIDTH = 600;
private static final int INITIAL_WINDOW_HEIGHT = 600;
private static final int INITIAL_WINDOW_X = 0;
private static final int INITIAL_WINDOW_Y = 0;
private static final Color CLOSED_TILE_BACKGROUND_COLOR = Color.black;
private static final Color CLOSED_TILE_FOREGROUND_COLOR = Color.white;
private static final Color OPEN_TILE_BACKGROUND_COLOR = Color.white;
private static final Color OPEN_TILE_FOREGROUND_COLOR = Color.black;
private static final Color FLAGGED_TILE_BACKGROUND_COLOR = Color.blue;
private static final Color FLAGGED_TILE_FOREGROUND_COLOR = Color.white;
private static final Color MINE_TILE_BACKGROUND_COLOR = Color.red;
private static final Color MINE_TILE_FOREGROUND_COLOR = Color.white;
private static final Color GRID_COLOR = Color.gray;
private static final int CLUE_OFFSET_X = 10;
private static final int CLUE_OFFSET_Y = 20;
private static final int COLUMN_LABEL_TILE_INDEX = -1;
private static final int ROW_LABEL_TILE_INDEX = -1;
// Set default values here
private boolean debugEnabled = false;
private int rowCount = 9;
private boolean rowCountAlreadySet = false;
private int columnCount = 9;
private MineSweeper game;
public static void main(String[] arguments)
{
PlayMinesGui instance = new PlayMinesGui(arguments);
}
/**
* @param arguments Accepts command line arguments (Passed from the constructor).
*/
public PlayMinesGui(String[] arguments)
{
processCommandLineArguments(arguments);
game = new MineSweeper(rowCount, columnCount);
JFrame gameWindow = new JFrame(GAME_WINDOW_TITLE);
gameWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MineSweeperGui gui = new MineSweeperGui();
gameWindow.setContentPane(gui);
gameWindow.setSize(INITIAL_WINDOW_WIDTH, INITIAL_WINDOW_HEIGHT);
gameWindow.setLocation(INITIAL_WINDOW_X, INITIAL_WINDOW_Y);
gameWindow.setVisible(true);
// Game window takes control from here, until it is closed.
}
/**
* Processes the command line arguments, setting member fields with the results.
* @param arguments The command line arguments.
*/
private void processCommandLineArguments(String[] arguments)
{
int argumentCount = arguments.length;
for (int currentArgumentIndex = 0;
currentArgumentIndex < argumentCount;
currentArgumentIndex ++)
{
String currentArgument = arguments[currentArgumentIndex];
if (currentArgument.equals(HELP_SHORT_ARGUMENT) ||
currentArgument.equals(HELP_LONG_ARGUMENT))
{
printHelp();
System.exit(NORMAL_EXIT_CODE);
}
else if (currentArgument.equals(DEBUG_SHORT_ARGUMENT) ||
currentArgument.equals(DEBUG_SHORT_ARGUMENT))
{
debugEnabled = true;
}
else
{
try
{
int dimensionSpecification;
// If this throws an Exception (because the argument is
// a malformed number), then control will jump right
// to the catch block and skip setting the dimension.
dimensionSpecification = Integer.parseInt(currentArgument);
if (rowCountAlreadySet)
{
columnCount = dimensionSpecification;
}
else
{
rowCount = dimensionSpecification;
rowCountAlreadySet = true;
}
}
catch (Exception e)
{
// Do nothing
}
}
}
}
/**
* Prints command line usage information.
*/
private void printHelp()
{
//TODO
System.out.println("PlayMinesGui - A graphical interface to MineSweeper.");
System.out.println("Usage: java PlayMinesGui [options] rows columns");
System.out.println(" In short, this class has the same interface as PlayMines.");
System.out.println(" -h, --help - Print this message.");
System.out.println(" -d, --debug - Turn on debug.");
}
/**
* The main graphics pane used for displaying the MineSweeper board.
*/
private class MineSweeperGui extends JComponent
{
// There will be this many grid spacings between the edge of the window
// (on all sides) and the playing board.
private static final int WINDOW_PADDING_WEIGHT = 1;
private double boundarySpacingX, boundarySpacingY;
public MineSweeperGui()
{
this.setOpaque(true);
this.setDoubleBuffered(true);
GuiMouseListener mouse = new GuiMouseListener();
this.addMouseListener(mouse);
}
/**
* Our custom paintComponent().
* Called automatically each time the window needs to be repainted.
*/
public void paintComponent(Graphics surface)
{
calculateBoundarySpacing();
paintBackground(surface);
paintTiles(surface);
paintTileIndexLabels(surface);
paintGrid(surface);
}
/**
* Calculates the double precision horizontal (x) and vertical (y) spacing
* between tile boundaries.
*/
private void calculateBoundarySpacing()
{
boundarySpacingX = (((double)this.getWidth()) /
((double)(columnCount + (2 * WINDOW_PADDING_WEIGHT))));
boundarySpacingY = (((double)this.getHeight()) /
((double)(rowCount + (2 * WINDOW_PADDING_WEIGHT))));
}
/**
* Calculates the x coordinate of the tile boundary (AKA grid line) with
* the given number/index.
* @param boundaryNumber Generally meant to be a number from 0 to the number
* of columns, but will work with all values.
* In general, the left boundary of a tile has the same number/index as that tile.
* @return The x coordinate of the specified boundary.
*/
private int calculateBoundaryX(int boundaryNumber)
{
return ((int)
(((double)(boundaryNumber + WINDOW_PADDING_WEIGHT)) *
boundarySpacingX));
}
/**
* Calculates the y coordinate of the tile boundary (AKA grid line) with
* the given number/index.
* @param boundaryNumber Generally meant to be a number from 0 to the number
* of rows, but will work with all values.
* In general, the top boundary of a tile has the same number/index as that tile.
* @return The y coordinate of the specified boundary.
*/
private int calculateBoundaryY(int boundaryNumber)
{
return ((int)
(((double)(boundaryNumber + WINDOW_PADDING_WEIGHT)) *
boundarySpacingY));
}
/**
* Calculates the lesser x coordinate for the tile with the given number/index.
* Useful for determining the start coordinate for drawing the tile rectangle.
* @param tileNumber Generally meant to be a horizontal tile index, but
* will work with all values.
* @return The lesser x coordinate for the specified tile.
*/
private int calculateTileMinX(int tileNumber)
{
return (calculateBoundaryX(tileNumber) + 1);
}
/**
* Calculates the lesser y coordinate for the tile with the given number/index.
* Useful for determining the start coordinate for drawing the tile rectangle.
* @param tileNumber Generally meant to be a vertical tile index, but
* will work with all values.
* @return The lesser y coordinate for the specified tile.
*/
private int calculateTileMinY(int tileNumber)
{
return (calculateBoundaryY(tileNumber) + 1);
}
/**
* Calculates the width of the tile with the given number/index.
* Useful when drawing the tile rectangle.
* @param tileNumber Will work with all values.
* @return The width of the specified tile.
*/
private int calculateTileWidth(int tileNumber)
{
return (calculateBoundaryX(tileNumber + 1) -
calculateTileMinX(tileNumber));
}
/**
* Calculates the height of the tile with the given number/index.
* Useful when drawing the tile rectangle.
* @param tileNumber Will work with all values.
* @return The height of the specified tile.
*/
private int calculateTileHeight(int tileNumber)
{
return (calculateBoundaryY(tileNumber + 1) -
calculateTileMinY(tileNumber));
}
/**
* Indicates tile column index that contains the given x coordinate.
* @param x The raw x coordinate to be translated.
* @return The horizontal tile index.
*/
private int mapXCoordinateToTileColumn(int x)
{
return (((int)(((double)x) / boundarySpacingX)) - 1);
}
/**
* Indicates tile row index that contains the given y coordinate.
* @param x The raw y coordinate to be translated.
* @return The vertical tile index.
*/
private int mapYCoordinateToTileRow(int y)
{
return (((int)(((double)y) / boundarySpacingY)) - 1);
}
/**
* Paints the background of the graphics pane.
*/
private void paintBackground(Graphics surface)
{
surface.setColor(Color.white);
surface.fillRect(0, 0, this.getWidth(), this.getHeight());
}
/**
* Paints the tiles, including debug information (if enabled), of the game board.
*/
private void paintTiles(Graphics surface)
{
for (int currentTileRow = 0;
currentTileRow < rowCount;
currentTileRow ++)
{
for (int currentTileColumn = 0;
currentTileColumn < columnCount;
currentTileColumn ++)
{
int currentTileMinX =
calculateTileMinX(currentTileColumn);
int currentTileWidth =
calculateTileWidth(currentTileColumn);
int currentTileMinY =
calculateTileMinY(currentTileRow);
int currentTileHeight =
calculateTileHeight(currentTileRow);
char tileType = game.getBoard(currentTileRow, currentTileColumn);
// It might have been possible to rearrange the following block
// (and even save some lines in the process). I opted, instead,
// for this organization because it allows for future flexibility
// and is more clear, I think.
// The text drawing code (For drawing the clues) could use some
// improvement; Perhaps some font size information and variable
// font sizes could be used?
if (tileType == CLOSED_TILE)
{
surface.setColor(CLOSED_TILE_BACKGROUND_COLOR);
surface.fillRect(currentTileMinX, currentTileMinY,
currentTileWidth, currentTileHeight);
if (debugEnabled)
{
String tileClue =
Integer.toString(
game.getMines(currentTileRow, currentTileColumn));
surface.setColor(CLOSED_TILE_FOREGROUND_COLOR);
surface.drawString(tileClue,
currentTileMinX + CLUE_OFFSET_X,
currentTileMinY + CLUE_OFFSET_Y);
}
}
else if (tileType == BLANK_TILE)
{
surface.setColor(Color.white);
surface.fillRect(currentTileMinX, currentTileMinY,
currentTileWidth, currentTileHeight);
}
else if (tileType == FLAGGED_TILE)
{
surface.setColor(FLAGGED_TILE_BACKGROUND_COLOR);
surface.fillRect(currentTileMinX, currentTileMinY,
currentTileWidth, currentTileHeight);
if (debugEnabled)
{
String tileClue =
Integer.toString(
game.getMines(currentTileRow, currentTileColumn));
surface.setColor(FLAGGED_TILE_FOREGROUND_COLOR);
surface.drawString(tileClue,
currentTileMinX + CLUE_OFFSET_X,
currentTileMinY + CLUE_OFFSET_Y);
}
}
else if (tileType == MINE_TILE)
{
surface.setColor(MINE_TILE_BACKGROUND_COLOR);
surface.fillRect(currentTileMinX, currentTileMinY,
currentTileWidth, currentTileHeight);
if (debugEnabled)
{
String tileClue =
Integer.toString(
game.getMines(currentTileRow, currentTileColumn));
surface.setColor(MINE_TILE_FOREGROUND_COLOR);
surface.drawString(tileClue,
currentTileMinX + CLUE_OFFSET_X,
currentTileMinY + CLUE_OFFSET_Y);
}
}
// For drawing the clues (1-8) in open tiles.
else
{
String tileClue = new String(new char[] {tileType});
surface.setColor(OPEN_TILE_FOREGROUND_COLOR);
surface.drawString(tileClue,
currentTileMinX + CLUE_OFFSET_X,
currentTileMinY + CLUE_OFFSET_Y);
}
}
}
}
/**
* Paints tile row and column indices.
*/
private void paintTileIndexLabels(Graphics surface)
{
int columnLabelMinY = calculateTileMinY(COLUMN_LABEL_TILE_INDEX);
for (int currentColumnIndex = 0;
currentColumnIndex < columnCount;
currentColumnIndex ++)
{
int currentColumnLabelMinX = calculateTileMinX(currentColumnIndex);
String currentColumnIndexLabel = Integer.toString(currentColumnIndex);
surface.setColor(OPEN_TILE_FOREGROUND_COLOR);
surface.drawString(currentColumnIndexLabel,
currentColumnLabelMinX + CLUE_OFFSET_X,
columnLabelMinY + CLUE_OFFSET_Y);
}
int rowLabelMinX = calculateTileMinX(ROW_LABEL_TILE_INDEX);
for (int currentRowIndex = 0;
currentRowIndex < rowCount;
currentRowIndex ++)
{
int currentRowLabelMinY = calculateTileMinY(currentRowIndex);
String currentRowIndexLabel = Integer.toString(currentRowIndex);
surface.setColor(OPEN_TILE_FOREGROUND_COLOR);
surface.drawString(currentRowIndexLabel,
rowLabelMinX + CLUE_OFFSET_X,
currentRowLabelMinY + CLUE_OFFSET_Y);
}
}
/**
* Paints the gameboard grid.
*/
private void paintGrid(Graphics surface)
{
int minBoundaryX = calculateBoundaryX(0);
int maxBoundaryX = calculateBoundaryX(columnCount);
int minBoundaryY = calculateBoundaryY(0);
int maxBoundaryY = calculateBoundaryY(rowCount);
for (int currentXBoundaryNumber = 0;
currentXBoundaryNumber <= columnCount;
currentXBoundaryNumber ++)
{
int currentBoundaryX = calculateBoundaryX(currentXBoundaryNumber);
surface.setColor(GRID_COLOR);
surface.drawLine(currentBoundaryX, minBoundaryY,
currentBoundaryX, maxBoundaryY);
}
for (int currentYBoundaryNumber = 0;
currentYBoundaryNumber <= columnCount;
currentYBoundaryNumber ++)
{
int currentBoundaryY = calculateBoundaryY(currentYBoundaryNumber);
surface.setColor(GRID_COLOR);
surface.drawLine(minBoundaryX, currentBoundaryY,
maxBoundaryX, currentBoundaryY);
}
}
/**
* Our custom mouse handler.
*/
private class GuiMouseListener implements MouseListener
{
public void mouseClicked(MouseEvent event)
{
processMouseInput(event);
}
public void mousePressed(MouseEvent inputEvent) { }
public void mouseReleased(MouseEvent inputEvent) { }
public void mouseEntered(MouseEvent inputEvent) { }
public void mouseExited(MouseEvent inputEvent) { }
}
/*
* Deals with mouse input. For now, it only handles mouse clicks.
* @param event The MouseEvent generated on the mouse action.
*/
private void processMouseInput(MouseEvent event)
{
if (game.gameRunning())
{
int x = event.getX();
int y = event.getY();
int button = event.getButton();
int tileRow = mapYCoordinateToTileRow(y);
int tileColumn = mapXCoordinateToTileColumn(x);
if (button == MouseEvent.BUTTON1)
{
game.markTile(tileRow, tileColumn, COMMAND_OPEN);
}
else if (button == MouseEvent.BUTTON3)
{
if (event.isControlDown())
{
game.markTile(tileRow, tileColumn, COMMAND_UNFLAG);
}
else
{
game.markTile(tileRow, tileColumn, COMMAND_FLAG);
}
}
this.repaint();
// This chunk stolen from PlayMines.java (Written by Sourav)
if (debugEnabled)
{
System.out.println("Tiles array:");
System.out.println(game.toStringTiles());
System.out.println("Mines array:");
System.out.println(game.toStringMines());
}
System.out.println(game.toStringBoard());
}
else
{
if (game.didWin())
{
System.out.println("You Won! Congratulations!");
}
else
{
System.out.println("Sorry, better luck next time");
}
// End of plagiarism
}
}
}
}