You will be creating the following application: This application represents a sl
ID: 3723434 • Letter: Y
Question
You will be creating the following application:
This application represents a slider puzzle game. There are 15 tiles that can be exchanged horizontally
or vertically with a blank tile in order to shuffle the image pieces around. When the tiles are arranged in
the correct order, they form a complete image and the game is done. The game is timed ... so the faster
you can complete the puzzle, the better your score.
The game must meet all of the following requirements:
• There is a 4 x 4 grid of 16 Buttons that will hold the images for a puzzle. Your interface should
show these buttons as indicated in the screen snapshot above. Each button should be 187 x 187
pixels in size. With a 1 pixel spacing vertically and horizontally between each button. You will
need to set the padding to 0 for each button using setPadding(new Insets(0,0,0,0));
This will allow the image to take the full space in the button. There are 4 sets of images available
for you to use (although you can add your own as well). These are available on the course
website. For each puzzle, there
are 17 images ... one is a thumbnail image which shows the whole image in 187x187 size. The
others are the individual tile images (each 187x187 in size) and are labeled with the format
Name_RC ... where Name is the name of the puzzle (i.e., Pets, Scenery, Lego or Numbers) and R
is the row number and C is the column number for that image (i.e., 0, 1, 2 or 3). Recall that you
set the image for a button as follows:
setGraphic(new ImageView(new Image(getClass().getResourceAsStream(filename))));
where filename is a string representing the name of the file (e.g., "Lego_21"). It may be a good
idea to make sure that you can create the buttons and display all of the images from a puzzle in
order on each button before you go any further on the assignment.
• Your GUI should also show the complete image (i.e., use the available Thumbnail image) as a
Label and underneath it should be a ListView showing (at least) the names of the 4 puzzles. See
snapshot above.
• There should be a
Start/Stop button (shown
as Stop on the snapshot
above) as well as a "Time:"
Label and a TextField that
shows the time that has
elapsed since the puzzle
was started. All
components must be
"nicely" arranged/sized with
reasonably consistent
margins all around.
• Upon window startup, the
buttons should show the
BLANK.png image on
each button, the thumbnail
Label should be enabled
and the Start button should
be DARKGREEN with
WHITE letters indicating
"Start". The time should
be "0:00". See image here.
• When an item is selected from the list, the appropriate image should be shown in the Thumbnail
Label.
• When Start is pressed, the game should begin running. When the game is running, the following
should happen:
o The Thumbnail Label should be disabled (it will look faded as in the first snapshot on this
assignment).
o The Start button should become DARKRED and indicate "Stop" instead of "Start".
o The images on the buttons should be changed to the 16 images for the tiles that make up
the puzzle image. One of these tiles (chosen randomly) should remain as a blank one.
The tiles should be shuffled randomly (see first snapshot) ... more will be mentioned about
this below.
o A Timer should be started. Use a Timeline object (from the javafx.animation package).
Here is how to set up the timer to tick once per second. You should set this up in the
start() method:
updateTimer = new Timeline(new KeyFrame(Duration.millis(1000),
new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
// FILL IN YOUR CODE HERE THAT WILL GET CALLED ONCE PER SEC.
}
}));
updateTimer.setCycleCount(Timeline.INDEFINITE);
To start the timer, you use: updateTimer.play(); This should be done when the Start
button is pressed. When the Stop button is pressed, you should stop the timer by using:
updateTimer.stop();
• At any time that the game is running, the time should be displayed in the TextField, with proper
formatting. That is, it should show the number of minutes that have elapsed, followed by a ':'
character and then the number of seconds that have elapsed since the last minute (always shown
as 2 digits). You should use String.format() here. You'll have to keep track of the number of
seconds that have elapsed since the timer was started.
• The game should stop if the user pressed the Stop button, or when the puzzle has been
completed (more on this is described below). At this time, the timer should be stopped (use:
updateTimer.stop();) . Also, the thumbnail Label should be enabled again. The Stop button
should become DARKGREEN and indicate "Start" instead of "Stop". If the user pressed the
Stop button, then all 16 tile buttons should show the BLANK.png image. If the game ended due
to tile completion, then all 16 tile buttons should be disabled and the blank tile should be replaced
by the puzzle image that was missing from the start (you will want to remember which one was
replaced by the blank image upon starting so that you can show it at this time).
• During the game, when the user clicks on a tile button, you should do the following. Determine
which row & col was clicked on (an example of this was given in chapter 5 of the notes with our
ToggleButtonsApp). Then you should look above, below, left and right of that (row, col)
location to see if the blank tile is there (be careful of boundary cases). If the blank tile is there,
then the image on the blank tile should be swapped with the image of the tile that was clicked on.
Visually, it should appear that the clicked tile slides into the blank position, moving the blank
position into its position. It would be a good idea to create a method called swap(row,col) that
takes the (row,col) of the button that was clicked on, and then attempts to find the blank image
around it and swap it.
• To shuffle the images upon startup, it is good to start with the full 16 images, and then simply call your swap(row, col) method 5000 times or so. This will cause a bunch of tiles to be slid around and should give you a reasonable shuffling. Then replace one of the tiles at random with the blank one. Remember to keep hold of the replaced image so that you can show it at the blank spot when the puzzle is completed.
• To determine when the puzzle is complete, you will need to know when each tile piece is in the correct location. That is, image "xxx_00.png" should be at the top left with image "xxx_01.png" beside it, image "xxx_10.png" below it, etc... It may be easiest to keep three 2D arrays. One for the buttons, one for the images of each button, and one for the coordinate of each button (i.e., the row/col of the image for that button). A coordinate can be stored using a Point2D object which has an x and y coordinate. You can determine when the board is complete by examining each button row by row and col by col (using the double FOR loop) and just check that the coordinate value matches the (row,col) of that button. This will only work if you swap the coordinates along with swapping the images in your swap(row,col) method. You do not have to do things this way, it is just an idea. If you have a simpler way to determine when the board is complete, you can do that instead.
Explanation / Answer
SlidePuzzle.java
Main program
import javax.swing.JFrame;
///////////////////////////////////////////// class SlidePuzzle
class SlidePuzzle {
//============================================= method main
public static void main(String[] args) {
JFrame window = new JFrame("Slide Puzzle");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setContentPane(new SlidePuzzleGUI());
window.pack(); // finalize layout
window.show(); // make window visible
window.setResizable(false);
}//end main
}//endclass SlidePuzzle
Graphical User Interface
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/////////////////////////////////////////////////// class SlidePuzzleGUI
// This class contains all the parts of the GUI interface
class SlidePuzzleGUI extends JPanel {
//=============================================== instance variables
private GraphicsPanel _puzzleGraphics;
private SlidePuzzleModel _puzzleModel = new SlidePuzzleModel();
//end instance variables
//====================================================== constructor
public SlidePuzzleGUI() {
//--- Create a button. Add a listener to it.
JButton newGameButton = new JButton("New Game");
newGameButton.addActionListener(new NewGameAction());
//--- Create control panel
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new FlowLayout());
controlPanel.add(newGameButton);
//--- Create graphics panel
_puzzleGraphics = new GraphicsPanel();
//--- Set the layout and add the components
this.setLayout(new BorderLayout());
this.add(controlPanel, BorderLayout.NORTH);
this.add(_puzzleGraphics, BorderLayout.CENTER);
}//end constructor
//////////////////////////////////////////////// class GraphicsPanel
// This is defined inside the outer class so that
// it can use the outer class instance variables.
class GraphicsPanel extends JPanel implements MouseListener {
private static final int ROWS = 3;
private static final int COLS = 3;
private static final int CELL_SIZE = 80; // Pixels
private Font _biggerFont;
//================================================== constructor
public GraphicsPanel() {
_biggerFont = new Font("SansSerif", Font.BOLD, CELL_SIZE/2);
this.setPreferredSize(
new Dimension(CELL_SIZE * COLS, CELL_SIZE*ROWS));
this.setBackground(Color.black);
this.addMouseListener(this); // Listen own mouse events.
}//end constructor
//=======================================x method paintComponent
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int r=0; r<ROWS; r++) {
for (int c=0; c<COLS; c++) {
int x = c * CELL_SIZE;
int y = r * CELL_SIZE;
String text = _puzzleModel.getFace(r, c);
if (text != null) {
g.setColor(Color.gray);
g.fillRect(x+2, y+2, CELL_SIZE-4, CELL_SIZE-4);
g.setColor(Color.black);
g.setFont(_biggerFont);
g.drawString(text, x+20, y+(3*CELL_SIZE)/4);
}
}
}
}//end paintComponent
//======================================== listener mousePressed
public void mousePressed(MouseEvent e) {
//--- map x,y coordinates into a row and col.
int col = e.getX()/CELL_SIZE;
int row = e.getY()/CELL_SIZE;
if (!_puzzleModel.moveTile(row, col)) {
// moveTile moves tile if legal, else returns false.
Toolkit.getDefaultToolkit().beep();
}
this.repaint(); // Show any updates to model.
}//end mousePressed
//========================================== ignore these events
public void mouseClicked (MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered (MouseEvent e) {}
public void mouseExited (MouseEvent e) {}
}//end class GraphicsPanel
////////////////////////////////////////// inner class NewGameAction
public class NewGameAction implements ActionListener {
public void actionPerformed(ActionEvent e) {
_puzzleModel.reset();
_puzzleGraphics.repaint();
}
}//end inner class NewGameAction
}//end class SlidePuzzleGUI
Logic / Model
/////////////////////////////////////////// class SlidePuzzleModel
class SlidePuzzleModel {
private static final int ROWS = 3;
private static final int COLS = 3;
private Tile[][] _contents; // All tiles.
private Tile _emptyTile; // The empty space.
//================================================= constructor
public SlidePuzzleModel() {
_contents = new Tile[ROWS][COLS];
reset(); // Initialize and shuffle tiles.
}//end constructor
//===================================================== getFace
// Return the string to display at given row, col.
String getFace(int row, int col) {
return _contents[row][col].getFace();
}//end getFace
//======================================================= reset
// Initialize and shuffle the tiles.
public void reset() {
for (int r=0; r<ROWS; r++) {
for (int c=0; c<COLS; c++) {
_contents[r][c] = new Tile(r, c, "" + (r*COLS+c+1));
}
}
//--- Set last tile face to null to mark empty space
_emptyTile = _contents[ROWS-1][COLS-1];
_emptyTile.setFace(null);
//-- Shuffle - Exchange each tile with random tile.
for (int r=0; r<ROWS; r++) {
for (int c=0; c<COLS; c++) {
exchangeTiles(r, c, (int)(Math.random()*ROWS)
, (int)(Math.random()*COLS));
}
}
}//end reset
//==================================================== moveTile
// Move a tile to empty position beside it, if possible.
// Return true if it was moved, false if not legal.
public boolean moveTile(int r, int c) {
//--- It's a legal move if the empty cell is next to it.
return checkEmpty(r, c, -1, 0) || checkEmpty(r, c, 1, 0)
|| checkEmpty(r, c, 0, -1) || checkEmpty(r, c, 0, 1);
}//end moveTile
//================================================== checkEmpty
// Check to see if there is an empty position beside tile.
// Return true and exchange if possible, else return false.
private boolean checkEmpty(int r, int c, int rdelta, int cdelta) {
int rNeighbor = r + rdelta;
int cNeighbor = c + cdelta;
//--- Check to see if this neighbor is on board and is empty.
if (isLegalRowCol(rNeighbor, cNeighbor)
&& _contents[rNeighbor][cNeighbor] == _emptyTile) {
exchangeTiles(r, c, rNeighbor, cNeighbor);
return true;
}
return false;
}//end checkEmpty
//=============================================== isLegalRowCol
// Check for legal row, col
public boolean isLegalRowCol(int r, int c) {
return r>=0 && r<ROWS && c>=0 && c<COLS;
}//end isLegalRowCol
//=============================================== exchangeTiles
// Exchange two tiles.
private void exchangeTiles(int r1, int c1, int r2, int c2) {
Tile temp = _contents[r1][c1];
_contents[r1][c1] = _contents[r2][c2];
_contents[r2][c2] = temp;
}//end exchangeTiles
//=================================================== isGameOver
public boolean isGameOver() {
for (int r=0; r<ROWS; r++) {
for (int c=0; c<ROWS; c++) {
Tile trc = _contents[r][c];
return trc.isInFinalPosition(r, c);
}
}
//--- Falling thru loop means nothing out of place.
return true;
}//end isGameOver
}//end class SlidePuzzleModel
////////////////////////////////////////////////////////// class Tile
// Represents the individual "tiles" that slide in puzzle.
class Tile {
//============================================ instance variables
private int _row; // row of final position
private int _col; // col of final position
private String _face; // string to display
//end instance variables
//==================================================== constructor
public Tile(int row, int col, String face) {
_row = row;
_col = col;
_face = face;
}//end constructor
//======================================================== setFace
public void setFace(String newFace) {
_face = newFace;
}//end getFace
//======================================================== getFace
public String getFace() {
return _face;
}//end getFace
//=============================================== isInFinalPosition
public boolean isInFinalPosition(int r, int c) {
return r==_row && c==_col;
}//end isInFinalPosition
}//end class Tile
Related Questions
drjack9650@gmail.com
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.