Java代写:CSE11 Puzzle Game

代写一个带GUI的3x3的拼图游戏。主要考察完整Project的工程能力。

Overview

We have a 3 x 3 puzzle, which gives us 9 puzzle pieces that are movable on the right. They can be dragged anywhere on the canvas, but not off the canvas. On the left, we have a 3 x 3 board. After a puzzle piece is dragged onto its correct location, the puzzle piece is “locked” into the board and is no longer movable.

The reason “locked” is in quotes is because the puzzle piece is actually not locked. We created this illusion by having two different types of Pieces here: PuzzlePiece and BoardPiece. When a PuzzlePiece (movable piece on the right) is dragged onto a BoardPiece (fixed piece on the left), your program will do some verification to see if they match. If so, it’s going to hide the PuzzlePiece and reveal the BoardPiece with the same image as the PuzzlePiece.

So it may seem like the PuzzlePiece is locked in place, but it’s actually a different BoardPiece that’s displayed on the canvas. Now that we know the basic idea/hack behind this game, how do we implement it?

Useful Interfaces

Take advantage of interfaces! We have two different kinds of Piece types here, but they share a lot in common. We’ll implement the Piece interface later in PuzzlePiece and BoardPiece classes.
The Highlightable interface is used to draw borders and highlights around each piece:

1
2
3
4
5
6
import java.awt.Color;

public interface Highlightable {
public abstract void showHighlight( Color color );
public abstract void hideHighlight();
}

The Hideable interface is used to hide and show pieces:

1
2
3
4
public interface Hideable {
public abstract void show();
public abstract void hide();
}

The Piece interface, which extends the two interfaces above and includes a few other methods:

objectdraw.*;
1
2
3
4
5
6
7
8

public interface Piece extends Highlightable, Hideable {
public abstract boolean contains(Location point);
public abstract boolean equals(Object o);
public abstract Location getCenter();
public abstract int getId();
public abstract void move(double dx, double dy);
}

Piece Classes

You will create 2 Piece classes: class BoardPiece and class PuzzlePiece.

Each of these classes will implement the Piece interface. For example:

1
public class BoardPiece implements Piece { ... }

Each Piece class will hold the specifics about that piece:

  • the piece image (a VisibleImage created from passing an Image object to its constructor)
  • ID (integer indexed from 0 to 8 in order of left to right and top to bottom, each ID represents a piece with a distinct image)
  • center (a Location object that represents the center point of this piece)
  • borders/highlights (PuzzlePiece (the movable pieces on the right) has one black FramedRect object right on top of the edge of the image and wraps around it as the border. BoardPiece (the fixed pieces on the left) has two FramedRect objects, one right on top of the edge of the image, and one that’s inset by 1px,so it creates a thicker outline. When the game first starts, BoardPiece should only display the outer highlight as black.)

Thus, the constructor for BoardPiece and PuzzlePiece should be identical. For example:

1
public BoardPiece(Image img, int id, Location loc, DrawingCanvas canvas) { ... }

Each Piece class will also implement the methods from the Piece interface.

  • showHighlight() and hideHighlight() can access and change the visibility of the FramedRect objects that highlight each piece.

  • show() in PuzzlePiece should show the image and the black FramedRect highlight. show() in BoardPiece should just show the image. We will call showHighlight() when we want to display the green highlight to indicate the puzzle piece is in the correct spot on the board (more on this later).

  • hide() in PuzzlePiece should hide the image and the highlight. hide() in BoardPiece does nothing unless you plan to implement the extra credit.

  • contains() in PuzzlePiece determines whether some point is contained in the VisibleImage associated with that piece (Hint: use VisibleImage’s contains() method). contains() method in BoardPiece requires a little more logic. If you are giving this puzzle to someone, you don’t want him/her to solve this so quickly by just dragging the PuzzlePieces close enough to the BoardPieces so they get locked in automatically. If you are the player, you also don’t want to be forced to drag the PuzzlePiece so your cursor/PuzzlePiece falls perfectly within the BoardPiece. So what we are going to do here is add a different check: instead of passing the point where the cursor is, we are going to pass the center of the PuzzlePiece we are dragging to contains(). If the center of the PuzzlePiece is within a 50 by 50 pixels square in the center of the BoardPiece, then we can say “the point is contained” in the BoardPiece. What we are really checking here is whether the center of the PuzzlePiece is contained in the 50 x 50px square in the BoardPiece.

  • equals() checks if two Pieces are equal (for example, does the PuzzlePiece match with the BoardPiece in the board grid?). This is done by checking the unique ID (0 - 8) we assigned to each piece.

  • getId() returns the unique ID of the Piece.

  • getCenter() returns the center of the Piece.

  • move() in BoardPiece should do nothing since BoardPieces are fixed. You would have to add some implementation if you decide to do extra credit. move() in PuzzlePiece is in charge of moving the PuzzlePiece around. There’s one tricky thing about this method… you need to make sure that the PuzzlePieces cannot be dragged off the canvas. More specifically, the center of PuzzlePiece should not be able to move off the canvas.

Part 3: class Puzzle

Now we’ve set up the building blocks for this game, we can start implementing the controller Puzzle class. Class Puzzle should extend WindowController and include main() that starts a new instance of Acme.MainFrame. The canvas size should be 735 by 380 pixels.

Get Images

Nine 100px x 100px puzzle piece images are available for you in ~/../public/PA5-images/. They are numbered from p0 to p8 (e.g. p0.jpg, p1.jpg, …).

In order to load these images into the program, use the getImage() method in class Puzzle (the main GUI controller that implements WindowController). For example:

1
getImage("p0.jpg");

We need to load 9 images into an Image array (array of Image objects). Instead of hardcoding 9 lines of getImage(), we can do this in a loop using some string concatenation. getImage() will return an Image object. You should pass each Image object as an actual argument to each the of Board/PuzzlePiece’s constructor, and inside that Board/PuzzlePiece’s constructor, make a VisibleImage object based on the Image object.

Layout

The main Puzzle class will also layout the BoardPieces and the PuzzlePieces as seen in the earlier screenshots. Again, instead of hardcoding 18 different locations, we are going to use two arrays to store Locations, one for BoardPieces and one for PuzzlePieces. The good news is that the constants and the formulas of calculating these Locations have been worked out for you:

1
2
3
4
5
6
7
private static final int PIECES_PER_COL = 3; // can be changed to expand the puzzle
private static final int PIECES_PER_ROW = 3; // can be changed to expand the puzzle
private static final int PUZZLE_SPACING = 20; // num of px between PuzzlePieces
private static final int PUZZLE_OFFSET = 355; // offset from left side of canvas
private static final int BOARD_MARGIN_X = 25; // left margin of the board
private static final int BOARD_MARGIN_Y = 40; // top margin of the board
private static final int SIDE_LENGTH = 100; // side length of each Piece

BoardPiece with index i is located at

1
x = BOARD_MARGIN_X + SIDE_LENGTH * (i % PIECES_PER_COL)

and

1
y = BOARD_MARGIN_Y + SIDE_LENGTH * (i / PIECES_PER_ROW).

PuzzlePiece with index i is located at

1
x = PUZZLE_OFFSET + PUZZLE_SPACING * (i % PIECES_PER_COL + 1) + SIDE_LENGTH * (i % PIECES_PER_COL)

and

1
y = PUZZLE_SPACING * (i / PIECES_PER_ROW + 1) + SIDE_LENGTH * (i / PIECES_PER_ROW).

These x and y coordinates represent the upper left corners of the VisibleImage in each Piece. You can check the math if you are curious what’s up with all these mods and divides.

Before you place all the Pieces onto the canvas, add a Text object as the winning message “YOU WON!” at x = 355 and y = the Y value of the fourth BoardPiece. It should have font size of 55 in bold and green. Hide the winning message for now, and we will come back to it later (the order of creation matters here because it affects the drawing arrangement).

Now let’s put the Pieces onto the canvas. Placing the BoardPieces is easy. We already have the Image array and the Location array for BoardPieces. We just need another array of the interface type Piece to keep track of the BoardPieces. We will go through the Image array and the Location array and create BoardPieces in order. Each BoardPiece ID should match with its index in the Piece array.

For PuzzlePieces though, we want to pick one of the nine images randomly. We can do so by implementing a method called getRandomPuzzlePiece() that takes in Location and DrawingCanvas. This method will use a RandomIntGenerator that randomly generates an integer between 0 and 8. We will first check if this randomly generated index represents an image that’s already used. If so, we want to get another random index until we find an unused one, because every PuzzlePiece should have a unique image. Once we’ve found an unused index, we can create a new PuzzlePiece. We are going to return this Piece we randomly created and store a reference to it in a Piece array. We’ll do this 9 times to place all the PuzzlePieces. Note that the ID in PuzzlePiece is NOT associated with its random array index but with the unique piece image.

To avoid using magic numbers like 8, you can create a MAX_NUM_OF_PIECES constant. In our case, MAX_NUM_OF_PIECES is 9. When we need to use this as an index, we can do MAX_NUM_OF_PIECES – 1, which takes care of the offset from zero-based indexing.

Hooray! Now you should be able to see all the BoardPieces and PuzzlePieces on your canvas. But we are not quite there yet. Your GUI controller (class Puzzle) needs to handle all the mouse events and the logic of verifying the Pieces. Take a break before you proceed. :)

Dragging PuzzlePiece

By PA5, you should be pretty familiar with onMousePress(), onMouseDrag(), and onMouseRelease(). The logic is still the same. You want to keep a reference to lastPoint and find the difference between lastPoint and current point. You do need to do one additional thing here, and that is keep track of which PuzzlePiece is being grabbed.

You can do so by looping through the Piece array holding all the PuzzlePieces and find which PuzzlePiece contains the point.

Verifying PuzzlePiece

Hmmm… Where should you be checking if the puzzle piece has been placed (not while still dragging) in the correct spot on the board to the left? There’s only one place, and I’ll let you answer it (HINT: it is NOT in onMouseDrag()).

But I’ll walk you through how to verify a puzzle piece has been placed correctly. Remember many words ago, we said if the center of the PuzzlePiece is within a 50 by 50 pixels square in the center of the BoardPiece, we can say the PuzzlePiece is “contained” in the BoardPiece (this check is already done in contains()). Here, the methods you’ve implemented in BoardPiece and PuzzlePiece come in handy. You can use getCenter() to get the center of the grabbed puzzle piece and call contains() on every BoardPiece in a loop. If we were able to find a BoardPiece that contains the center of the grabbed puzzle piece AND they are equal (matching IDs), then we can go ahead and “lock” this Piece. Again, the “locking” is done by hiding the grabbed puzzle piece and showing the BoardPiece as described in the overview. Don’t forget to show green highlights around the BoardPiece to let the pre-schoolers know that they’ve placed the Piece correctly!

Verifying Victory

Almost there… I can hear the sound of victory! How can we check if the puzzle is finished? One easy way to do this is having yet another array. This array will contain booleans that represent whether a Piece has been “locked”. Since the Pieces have unique IDs from 0 to 8, we can just use those as indices and mark them true as they get “locked”. The game is done after every element in this boolean array becomes true. At the moment of victory, you should hide the green highlights around each BoardPiece and display a seamless image composed of 9 pieces. You should also show the winning message.

Running

To compile your Puzzle game, type this into the terminal:

> javac -cp ./Acme.jar:./objectdraw.jar:. Puzzle.java

To run your Puzzle game, type this into the terminal:

> java -cp ./Acme.jar:./objectdraw.jar:. Puzzle

You must have all the files in the pa5 directory including Acme.jar and objectdraw.jar.
Do not have any other .java source files in you pa5 directory other than the ones required for this assignment.

Turnin

To turnin your code, navigate to your home directory and run the following command:

> cse11turnin pa5

You may turn in your programming assignment as many times as you like. The last submission you turn in before the deadline is the one that we will collect.

Verify

To verify a previously turned in assignment,

> cse11verify pa5

If you are unsure your program has been turned in, use the verify command. We will not take any late files you forgot to turn in. Verify will help you check which files you have successfully submitted. It is your responsibility to make sure you properly turned in your assignment.