Mona Lisa Puzzle (Part 2)

The next part of the puzzle is... to let the user know when a dropped piece is correctly placed. So, below, you can see that the most recent drop was good and that the total of correctly dropped pieces is 3:

How to do this? Well, first of all, we need to have access to the logical objects represented by the PuzzleWidgets. After all, currently, as David Kaspar explained to me, there are 37 widgets in the PuzzleScene:

  • 1x LayerWidget
  • 12x PuzzleWidget:IconNodeWidget
  • Each IconNodeWidget has one ImageWidget and LabelWidget

So, there are 1+12+2\*12=37 Widgets in the Scene.

That makes it difficult to work with the widgets that we are actually interested in, i.e., those reprenting the pieces in the puzzle. We can reduce the widget number significantly by working with ImageWidget instead of IconNodeWidget, since we're only interested in displaying an image. Only if we needed an image as well as a label would it have made sense to use an IconNodeWidget.

But then there are still more Widgets than pieces in the puzzle. So, how do we filter out all the widgets except for the ones that we're actually interested in working with? Well, since the PuzzleScene extends ObjectScene, we can do the following, in the innermost for loop, right after adding the PuzzleWidget to the LayerWidget:

PuzzleObject puzzle = new PuzzleObject(j, i);
addObject(puzzle, puzzleWidget);

The ObjectScene provides an API to specify a mapping between a logical object and a Widget in a Scene. Here, the logical object is provided by a class called PuzzleObject, which I defined as follows:

public class PuzzleObject {

    public PuzzleObject(int column, int row) {this.column = column; this.row = row;}

    int column;

    int row;

    public int getColumn() {return column;}

    public void setColumn(int column) {this.column = column;}

    public int getRow() {return row;}

    public void setRow(int row) {this.row = row;}


Now that we have mapped a new Puzzle object to the current Widget, we have a lot of new features available for working with the underlying object. For example, we can call "findWidget(PuzzleObject)" and "findObject(PuzzleWidget)" on the Scene, to get the PuzzleWidget or PuzzleObject that we need to work with.

Now that we can get very easily from underlying domain to Widget, let's create our own MoveAction so that, when the move is finished, we compare the current row and column with the original row and column of the moved piece. The whole code that results is as follows:

class PuzzleScene extends ObjectScene {

    int ROWS = 4;
    int COLUMNS = 3;
    int width;
    int height;
    int correctPuzzlePieces = 0;
    WidgetAction puzzleMoveAction = ActionFactory.createMoveAction(
             ActionFactory.createSnapToGridMoveStrategy(10, 10), new PuzzleMoveProvider());

    public PuzzleScene() {

        LayerWidget layerWidget = new LayerWidget(this);

        ImageIcon mona = ImageUtilities.loadImageIcon("org/mona/puzzle/board/puzzle.jpg", true);
        Image source = mona.getImage();
        width = mona.getIconWidth();
        height = mona.getIconHeight();

        Random generator = new Random();

        for (int i = 0; i < ROWS; i++) {

            for (int j = 0; j < COLUMNS; j++) {

                Image image = Toolkit.getDefaultToolkit().createImage(
                        new FilteredImageSource(source.getSource(),
                        new CropImageFilter(j \* width / COLUMNS, i \* height / ROWS, 
                            (width / COLUMNS) + 1, height / ROWS)));

                PuzzleWidget puzzleWidget = new PuzzleWidget(this, image, 
                             generator.nextInt(600), generator.nextInt(300));


                PuzzleObject puzzle = new PuzzleObject(j, i);

                addObject(puzzle, puzzleWidget);





    class PuzzleWidget extends ImageWidget {

        public PuzzleWidget(Scene scene, Image image, int x, int y) {
            setPreferredLocation(new Point(x, y));


    private class PuzzleMoveProvider implements MoveProvider {

        public void movementStarted(Widget widget) {

        public void movementFinished(Widget widget) {
            PuzzleObject puzzleObject = (PuzzleObject) findObject(widget);
            Point newLoc = widget.getPreferredLocation();
            int newRow = newLoc.y / (HEIGHT/ROWS);
            int newCol = newLoc.x / (WIDTH/COLS);
            if (puzzleObject.getColumn() == newCol && puzzleObject.getRow() == newRow) {
                if (correctPuzzlePieces == ROWS \* COLUMNS) {
                    StatusDisplayer.getDefault().setStatusText("Game Over!");
                } else {
                    StatusDisplayer.getDefault().setStatusText("Correct! Total: " + correctPuzzlePieces);
                //Prevent the piece from being moved after we know it is correct:
            } else {

        public Point getOriginalLocation(Widget widget) {
            return widget.getPreferredLocation();

        public void setNewLocation(Widget widget, Point location) {


A next step is to include the JFugue API, so that when the drop is successful, some happy music is played!


Post a Comment:
  • HTML Syntax: NOT allowed

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.


« April 2014