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:IconNodeWidgetEach 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 FIRST = 4; int SECOND = 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 < FIRST; i++) { for (int j = 0; j < SECOND; j++) { Image image = Toolkit.getDefaultToolkit().createImage( new FilteredImageSource(source.getSource(), new CropImageFilter(j * width / SECOND, i * height / FIRST, (width / SECOND) + 1, height / FIRST))); PuzzleWidget puzzleWidget = new PuzzleWidget(this, image, generator.nextInt(600), generator.nextInt(300)); layerWidget.addChild(puzzleWidget); PuzzleObject puzzle = new PuzzleObject(j, i); addObject(puzzle, puzzleWidget); } } addChild(layerWidget); } class PuzzleWidget extends ImageWidget { public PuzzleWidget(Scene scene, Image image, int x, int y) { super(scene); setPreferredLocation(new Point(x, y)); setImage(image); getActions().addAction(puzzleMoveAction); } } private class PuzzleMoveProvider implements MoveProvider { @Override public void movementStarted(Widget widget) { } @Override public void movementFinished(Widget widget) { PuzzleObject puzzleObject = (PuzzleObject) findObject(widget); Point newLoc = widget.getPreferredLocation(); int newRow = newLoc.y / 80; int newCol = newLoc.x / 70; if (puzzleObject.getColumn() == newCol && puzzleObject.getRow() == newRow) { correctPuzzlePieces = correctPuzzlePieces + 1; if (correctPuzzlePieces == FIRST * SECOND) { StatusDisplayer.getDefault().setStatusText("Game Over!"); } else { StatusDisplayer.getDefault().setStatusText("Correct! Total: " + correctPuzzlePieces); } //Prevent the piece from being moved after we know it is correct: widget.getActions().removeAction(puzzleMoveAction) ; } else { StatusDisplayer.getDefault().setStatusText("---"); } } @Override public Point getOriginalLocation(Widget widget) { return widget.getPreferredLocation(); } @Override public void setNewLocation(Widget widget, Point location) { widget.setPreferredLocation(location); } } }
A next step is to include the JFugue API, so that when the drop is successful, some happy music is played!


Read More about [Mona Lisa Puzzle (Part 2)...