org.vectomatic.svg.edu.client.puzzle.Puzzle.java Source code

Java tutorial

Introduction

Here is the source code for org.vectomatic.svg.edu.client.puzzle.Puzzle.java

Source

/**********************************************
 * Copyright (C) 2010 Lukas Laag
 * This file is part of lib-gwt-svg-edu.
 * 
 * libgwtsvg-edu is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * libgwtsvg-edu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with libgwtsvg-edu.  If not, see http://www.gnu.org/licenses/
 **********************************************/
package org.vectomatic.svg.edu.client.puzzle;

import java.util.ArrayList;
import java.util.List;

import org.vectomatic.dom.svg.OMNode;
import org.vectomatic.dom.svg.OMSVGClipPathElement;
import org.vectomatic.dom.svg.OMSVGDefsElement;
import org.vectomatic.dom.svg.OMSVGDocument;
import org.vectomatic.dom.svg.OMSVGGElement;
import org.vectomatic.dom.svg.OMSVGMatrix;
import org.vectomatic.dom.svg.OMSVGPathElement;
import org.vectomatic.dom.svg.OMSVGPathSegList;
import org.vectomatic.dom.svg.OMSVGPoint;
import org.vectomatic.dom.svg.OMSVGRect;
import org.vectomatic.dom.svg.OMSVGRectElement;
import org.vectomatic.dom.svg.OMSVGSVGElement;
import org.vectomatic.dom.svg.OMSVGTransform;
import org.vectomatic.dom.svg.OMSVGUseElement;
import org.vectomatic.dom.svg.utils.OMSVGParser;
import org.vectomatic.dom.svg.utils.SVGConstants;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Window;

/**
 * Main class of the puzzle game
 * @author laaglu
 */
public class Puzzle implements MouseDownHandler, MouseMoveHandler, MouseUpHandler {
    /**
     * Class to represent a drag and drop source/target
     */
    private class Target {
        /**
         * The piece contained by this target (if any)
         */
        private Piece piece;
        /**
         * The shadow to display when the drag source enters this
         * target (only for the assembly zone)
         */
        private OMSVGUseElement shadow;
        /**
         * The target matrix to which this target belongs
         */
        private TargetMatrix matrix;
        /**
         * Coordinates of the target in the target matrix
         */
        private int u, v;

        public Target(TargetMatrix targetMatrix, int u, int v) {
            this.matrix = targetMatrix;
            this.u = u;
            this.v = v;
        }

        public Piece getPiece() {
            return piece;
        }

        void setPiece(Piece piece) {
            this.piece = piece;
            if (piece != null) {
                OMSVGUseElement geometry = piece.geometry;
                if (geometry != null) {
                    setPosition(getPosition());
                }
            }
        }

        void setShadow(OMSVGUseElement shadow) {
            this.shadow = shadow;
            if (shadow != null) {
                setSelected(false, shadow);
            }
        }

        void setSelected(boolean selected, OMSVGUseElement shadow) {
            OMSVGPoint p = getPosition();
            shadow.getX().getBaseVal().setValue(p.getX());
            shadow.getY().getBaseVal().setValue(p.getY());
            shadow.setClassNameBaseVal(selected ? matrix.selectedShadowClass : matrix.shadowClass);
        }

        public OMSVGPoint getPosition() {
            return rootSvg.createSVGPoint(matrix.x + u * matrix.w, matrix.y + v * matrix.h);
        }

        public void setPosition(OMSVGPoint p) {
            OMSVGUseElement geometry = piece.geometry;
            if (geometry != null) {
                geometry.getX().getBaseVal().setValue(p.getX());
                geometry.getY().getBaseVal().setValue(p.getY());
            }
        }

        @Override
        public String toString() {
            StringBuilder buffer = new StringBuilder();
            buffer.append(matrix.id);
            buffer.append(".T<");
            buffer.append(u);
            buffer.append(", ");
            buffer.append(v);
            buffer.append(">[");
            buffer.append(piece != null ? piece.toString() : "null");
            buffer.append(",");
            buffer.append(
                    shadow != null ? (shadow.getClassName().getBaseVal().equals(matrix.selectedShadowClass)) : "?");
            buffer.append("]");
            return buffer.toString();
        }
    }

    /**
     * Class to group together drag and drop targets
     */
    class TargetMatrix {
        /**
         * An id (to print in the debugger)
         */
        private String id;
        /**
         * The matrix of targets
         */
        private Target[][] targets;
        /**
         * A rectangle used to compute the layout of the targets
         */
        private float x, y, w, h;
        /**
         * The CSS class of a target 
         * (when not selected in the drag and drop operation)
         */
        private String shadowClass;
        /**
         * The CSS class of a target 
         * (when selected in the drag and drop operation)
         */
        private String selectedShadowClass;

        TargetMatrix(String id, int colCount, int rowCount, float x, float y, float w, float h, String shadowClass,
                String selectedShadowClass) {
            targets = new Target[colCount][];
            this.id = id;
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
            this.shadowClass = shadowClass;
            this.selectedShadowClass = selectedShadowClass;
            for (int i = 0; i < colCount; i++) {
                targets[i] = new Target[rowCount];
                for (int j = 0; j < rowCount; j++) {
                    targets[i][j] = new Target(this, i, j);
                }
            }
        }

        public void setPiece(Piece piece, int col, int row) {
            targets[col][row].setPiece(piece);
        }

        public Piece getPiece(int col, int row) {
            return getTarget(col, row).getPiece();
        }

        public Target getTarget(int col, int row) {
            return targets[col][row];
        }

        public Target getTarget(MouseEvent<? extends EventHandler> e) {
            OMSVGPoint p = getCoordinates(e).substract(rootSvg.createSVGPoint(x, y))
                    .product(rootSvg.createSVGPoint(1f / w, 1f / h)).floor();
            return p.getX() >= 0 && p.getX() < colCount && p.getY() >= 0 && p.getY() < rowCount
                    ? targets[(int) p.getX()][(int) p.getY()]
                    : null;
        }
    }

    /**
     * Class to represent a puzzle piece
     */
    static class Piece {
        /**
         * Piece coordinates in the assembled puzzle
         */
        int x, y;
        /**
         * Piece connector to other pieces
         */
        Connector north, south, east, west;
        /**
         * The piece geometry
         */
        OMSVGUseElement geometry;
        /**
         * The piece shadow used during drag and drop operations
         */
        OMSVGUseElement shadow;

        Piece(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public String getId() {
            return x + "-" + y;
        }

        @Override
        public String toString() {
            return getId();
        }
    }

    /**
     * Class to represent a directed connector
     * between two pieces
     */
    static class Connector {
        /**
         * The source piece
         */
        Piece src;
        /**
         * The destination piece
         */
        Piece dest;

        Connector(Piece src, Piece dest) {
            if (Random.nextInt() % 2 == 0) {
                this.src = src;
                this.dest = dest;
            } else {
                this.src = dest;
                this.dest = src;
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append(src);
            builder.append(">>");
            builder.append(dest);
            return builder.toString();
        }
    }

    static enum ConnectorShape {
        SQUARE {
            @Override
            void makeVConnector(float connectorWidth, float connectorHeight, float pieceWidth, int direction,
                    int connectorDirection, OMSVGPathElement piecePath, OMSVGPathSegList segs) {
                segs.appendItem(piecePath
                        .createSVGPathSegLinetoHorizontalAbs(0.5f * (pieceWidth - direction * connectorWidth)));
                segs.appendItem(piecePath
                        .createSVGPathSegLinetoVerticalRel(connectorDirection * direction * connectorHeight));
                segs.appendItem(piecePath.createSVGPathSegLinetoHorizontalRel(direction * connectorWidth));
                segs.appendItem(piecePath
                        .createSVGPathSegLinetoVerticalRel(-connectorDirection * direction * connectorHeight));
            }

            @Override
            void makeHConnector(float connectorWidth, float connectorHeight, float pieceHeight, int direction,
                    int connectorDirection, OMSVGPathElement piecePath, OMSVGPathSegList segs) {
                segs.appendItem(piecePath
                        .createSVGPathSegLinetoVerticalAbs(0.5f * (pieceHeight - direction * connectorHeight)));
                segs.appendItem(piecePath
                        .createSVGPathSegLinetoHorizontalRel(connectorDirection * direction * connectorWidth));
                segs.appendItem(piecePath.createSVGPathSegLinetoVerticalRel(direction * connectorHeight));
                segs.appendItem(piecePath
                        .createSVGPathSegLinetoHorizontalRel(-connectorDirection * direction * connectorWidth));
            }
        },
        SPLINE {
            @Override
            void makeVConnector(float connectorWidth, float connectorHeight, float pieceWidth, int direction,
                    int connectorDirection, OMSVGPathElement piecePath, OMSVGPathSegList segs) {
                float x1 = 0.5f * (pieceWidth - 0.5f * direction * connectorWidth);
                float kx = 0.5f * K * direction * connectorWidth;
                float ky = 0.5f * K * connectorDirection * direction * connectorHeight;
                float w2 = 0.5f * direction * connectorWidth;
                float w4 = 0.25f * direction * connectorWidth;
                float h2 = 0.5f * connectorDirection * direction * connectorHeight;
                segs.appendItem(piecePath.createSVGPathSegLinetoHorizontalAbs(x1));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(-w4, -h2, 0, -ky, -w4, ky - h2));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(w2, -h2, 0, -ky, w2 - kx, -h2));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(w2, h2, kx, 0, w2, h2 - ky));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(-w4, h2, 0, ky, -w4, h2 - ky));
            }

            @Override
            void makeHConnector(float connectorWidth, float connectorHeight, float pieceHeight, int direction,
                    int connectorDirection, OMSVGPathElement piecePath, OMSVGPathSegList segs) {
                float y1 = 0.5f * (pieceHeight - 0.5f * direction * connectorHeight);
                float kx = 0.5f * K * connectorDirection * direction * connectorWidth;
                float ky = 0.5f * K * direction * connectorHeight;
                float h2 = 0.5f * direction * connectorHeight;
                float h4 = 0.25f * direction * connectorHeight;
                float w2 = 0.5f * connectorDirection * direction * connectorWidth;
                segs.appendItem(piecePath.createSVGPathSegLinetoVerticalAbs(y1));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(-w2, -h4, -kx, 0, kx - w2, -h4));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(-w2, h2, -kx, 0, -w2, h2 - ky));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(w2, h2, 0, ky, w2 - kx, h2));
                segs.appendItem(piecePath.createSVGPathSegCurvetoCubicRel(w2, -h4, kx, 0, w2 - kx, -h4));
            }
        },
        NONE;
        void makeVConnector(float connectorWidth, float connectorHeight, float pieceWidth, int direction,
                int connectorDirection, OMSVGPathElement piecePath, OMSVGPathSegList segs) {

        }

        void makeHConnector(float connectorWidth, float connectorHeight, float pieceHeight, int direction,
                int connectorDirection, OMSVGPathElement piecePath, OMSVGPathSegList segs) {
        }
    }

    static PuzzleCss style = PuzzleBundle.INSTANCE.getCss();
    private static final String ID_PIECE = "piece";
    private static final String ID_PIECE_CLIP = "piecec";
    private static final String ID_PIECE_PATH = "piecep";
    private static final String ID_IMAGE = "puzzle";
    /**
     * Best tangent size to emulate circle with spline
     */
    private static final float K = ((float) Math.sqrt(2) - 1) * 4 / 3;
    /**
     * Size of the connector as a percentage of the piece size
     */
    private static final float CONNECTOR_PCT = 0.15f;
    /**
     * Size of the border as a percentage of the puzzle size
     */
    private static final float ASSEMBLY_BORDER_SIZE_PCT = 0.075f;
    /**
     * Size of the border corner radius as a percentage of the puzzle size
     */
    private static final float ASSEMBLY_BORDER_CORNER_PCT = 0.025f;
    /**
     * Size of the margin separating the assemblyZone from the
     * pieceZone as a percentage of the puzzle size
     */
    private static final float MARGIN_PCT = 0.04f;

    /**
     * The list of all puzzle pieces
     */
    private List<Piece> pieceList;
    /**
     * The root element of the SVG DOM hierarchy
     */
    OMSVGSVGElement rootSvg;
    /**
     * The tile zone
     */
    private TargetMatrix tileZone;
    /**
     * The assembly zone
     */
    private TargetMatrix assemblyZone;
    /**
     * The number of pieces per column
     */
    private int colCount;
    /**
     * The number of pieces per row
     */
    private int rowCount;
    /**
     * The dimension of a piece
     */
    float pieceWidth, pieceHeight;
    /**
     * The dimension of a piece connector
     */
    float connectorWidth, connectorHeight;
    /**
     * The dimension of a tile in the tile zone
     */
    float tileWidth, tileHeight;
    /**
     * The coordinates of the upper left corner of
     * the tile zone
     */
    float tileZoneX, tileZoneY;
    /**
     * The coordinates of the upper left corner of the
     * puzzle in the assembly zone
     */
    float puzzleX, puzzleY;
    /**
     * True if a drag and drop session is taking place
     */
    boolean dragging;
    /**
     * Distance from the drag point to the piece upper
     * left corner
     */
    OMSVGPoint d;
    /**
     * Drag and drop source
     */
    Target srcTarget;
    /**
     * Drag and drop target
     */
    Target destTarget;

    public Puzzle(OMSVGSVGElement srcSvg, int colCount, int rowCount) {
        this.colCount = colCount;
        this.rowCount = rowCount;

        // Create the puzzle geometry
        OMSVGDocument document = OMSVGParser.currentDocument();
        rootSvg = document.createSVGSVGElement();
        rootSvg.addMouseDownHandler(this);
        rootSvg.addMouseMoveHandler(this);
        rootSvg.addMouseUpHandler(this);
        OMSVGDefsElement defs = document.createSVGDefsElement();
        rootSvg.appendChild(defs);

        // Compute basic metrics
        OMSVGRect viewBox = srcSvg.getViewBox().getBaseVal();
        float width = viewBox.getWidth();
        float height = viewBox.getHeight();
        pieceWidth = width / colCount;
        pieceHeight = height / rowCount;
        connectorWidth = CONNECTOR_PCT * pieceWidth;
        connectorHeight = CONNECTOR_PCT * pieceHeight;
        float borderWidth = ASSEMBLY_BORDER_SIZE_PCT * width;
        float borderHeight = ASSEMBLY_BORDER_SIZE_PCT * height;
        float borderCornerWidth = ASSEMBLY_BORDER_CORNER_PCT * width;
        float borderCornerHeight = ASSEMBLY_BORDER_CORNER_PCT * height;
        tileWidth = (CONNECTOR_PCT * 2 + 1.01f) * pieceWidth;
        tileHeight = (CONNECTOR_PCT * 2 + 1.01f) * pieceHeight;
        float tileZoneWidth = colCount * tileWidth;
        float tileZoneHeight = rowCount * tileHeight;
        float assemblyZoneWidth = borderWidth * 2 + width;
        float assemblyZoneHeight = borderHeight * 2 + height;
        float assemblyZoneX = 0f;
        float assemblyZoneY = 0.5f * (tileZoneHeight - assemblyZoneHeight);
        tileZoneX = assemblyZoneWidth + MARGIN_PCT * width;
        tileZoneY = 0f;
        puzzleX = assemblyZoneX + borderWidth;
        puzzleY = assemblyZoneY + borderHeight;
        float totalWidth = tileZoneX + tileZoneWidth;
        float totalHeight = tileZoneHeight;
        rootSvg.setViewBox(0, 0, totalWidth, totalHeight);
        tileZone = new TargetMatrix("tiles", colCount, rowCount, tileZoneX + connectorWidth,
                tileZoneY + connectorHeight, tileWidth, tileHeight, style.tileShadow(), style.tileShadowSelected());
        assemblyZone = new TargetMatrix("assembly", colCount, rowCount, puzzleX, puzzleY, pieceWidth, pieceHeight,
                style.assemblyShadow(), style.assemblyShadowSelected());

        // Create the puzzle pieces
        pieceList = new ArrayList<Piece>();
        for (int i = 0; i < colCount; i++) {
            for (int j = 0; j < rowCount; j++) {
                Piece piece = new Piece(i, j);
                tileZone.setPiece(piece, i, j);
                pieceList.add(piece);
            }
        }

        // Create the connectors between the pieces
        for (int i = 0; i < colCount - 1; i++) {
            for (int j = 0; j < rowCount; j++) {
                Connector c = new Connector(tileZone.getPiece(i, j), tileZone.getPiece(i + 1, j));
                tileZone.getPiece(i, j).east = c;
                tileZone.getPiece(i + 1, j).west = c;
            }
        }
        for (int i = 0; i < colCount; i++) {
            for (int j = 0; j < rowCount - 1; j++) {
                Connector c = new Connector(tileZone.getPiece(i, j), tileZone.getPiece(i, j + 1));
                tileZone.getPiece(i, j).south = c;
                tileZone.getPiece(i, j + 1).north = c;
            }
        }
        //
        // Create the puzzle game layout:
        // + at the left, the assemblyGroup (where the player assembles the pieces).
        //   The assemblyGroup itself consists in a assemblyBorder, and an
        //   assemblyZone (which contains the assembled pieces). Optionally and
        //   assemblyHint can be displayed (show the contour of the pieces)
        // + at the right, the tileZone (where the pieces are randomly positioned
        //   on a grid of tiles)
        // <g id="assemblyGroup">
        //  <rect id="assemblyBorder" rx="" ry="">
        //  <rect id="assemblyContent1"/>
        //  <g id="assemblyHint">
        //   <use xlink:href="#piecepX-Y"/>
        //  </g>
        //  <rect id="assemblyContent2"/>
        // <g>
        // </g>
        OMSVGGElement assemblyGroup = document.createSVGGElement();

        OMSVGRectElement assemblyBorder = document.createSVGRectElement(assemblyZoneX, assemblyZoneY,
                assemblyZoneWidth, assemblyZoneHeight, borderCornerWidth, borderCornerHeight);
        assemblyBorder.setClassNameBaseVal(style.assemblyBorder());
        OMSVGRectElement assemblyContent1 = document.createSVGRectElement(puzzleX, puzzleY, width, height, 0, 0);
        assemblyContent1.setClassNameBaseVal(style.assemblyContent1());
        OMSVGGElement assemblyShadows = document.createSVGGElement();
        OMSVGRectElement assemblyContent2 = document.createSVGRectElement(puzzleX, puzzleY, width, height, 0, 0);
        assemblyContent2.setClassNameBaseVal(style.assemblyContent2());
        assemblyGroup.appendChild(assemblyBorder);
        assemblyGroup.appendChild(assemblyContent1);
        assemblyGroup.appendChild(assemblyShadows);
        assemblyGroup.appendChild(assemblyContent2);
        rootSvg.appendChild(assemblyGroup);
        OMSVGGElement tileShadows = document.createSVGGElement();
        rootSvg.appendChild(tileShadows);

        // Copy the source SVG in a dedicated group inside
        // the defs
        OMSVGGElement imgGroup = document.createSVGGElement();
        imgGroup.setId(ID_IMAGE);
        for (OMNode node : srcSvg.getChildNodes()) {
            imgGroup.appendChild(node.cloneNode(true));
        }
        defs.appendChild(imgGroup);

        ConnectorShape connectorShape = ConnectorShape.SPLINE;
        String connectorParam = Window.Location.getParameter("connector");
        if (connectorParam != null) {
            try {
                connectorShape = ConnectorShape.valueOf(ConnectorShape.class, connectorParam.toUpperCase());
            } catch (Throwable e) {
                GWT.log("Cannot parse connector=" + connectorParam, e);
            }
        }

        for (int i = 0; i < colCount; i++) {
            for (int j = 0; j < rowCount; j++) {

                // Create the piece definition geometry
                // Each piece definition has the following structure
                // <g id="pieceX-Y">
                //  <clipPath id="piececX-Y">
                //   <path id="piecepX-Y" d="..."/> 
                //  </clipPath>
                //  <use x="0" y="0" xlink:href="#piecepX-Y"/>
                //  <g style="clip-path:url(#piececX-Y)">
                //   <g transform="translate(-X,-Y)">
                //    <use x="0" y="0" xlink:href="#puzzle"/>
                //   </g>
                //  </g>
                //  <use x="0" y="0" xlink:href="#piecepX-Y"/>
                // </g>
                Piece piece = tileZone.getPiece(i, j);

                OMSVGGElement pieceDef = document.createSVGGElement();
                String idPiece = ID_PIECE + piece.getId();
                pieceDef.setId(idPiece);

                String idPieceClip = ID_PIECE_CLIP + piece.getId();
                OMSVGClipPathElement pieceClipDef = document.createSVGClipPathElement();
                pieceClipDef.setId(idPieceClip);

                String idPiecePath = ID_PIECE_PATH + piece.getId();
                OMSVGPathElement piecePath = document.createSVGPathElement();
                piecePath.setId(idPiecePath);
                OMSVGPathSegList segs = piecePath.getPathSegList();
                segs.appendItem(piecePath.createSVGPathSegMovetoAbs(0f, 0f));
                if (piece.north != null) {
                    connectorShape.makeVConnector(connectorWidth, connectorHeight, pieceWidth, 1,
                            (piece.north.src == piece) ? 1 : -1, piecePath, segs);
                }
                segs.appendItem(piecePath.createSVGPathSegLinetoHorizontalAbs(pieceWidth));
                if (piece.east != null) {
                    connectorShape.makeHConnector(connectorWidth, connectorHeight, pieceHeight, 1,
                            (piece.east.src == piece) ? 1 : -1, piecePath, segs);
                }
                segs.appendItem(piecePath.createSVGPathSegLinetoVerticalAbs(pieceHeight));
                if (piece.south != null) {
                    connectorShape.makeVConnector(connectorWidth, connectorHeight, pieceWidth, -1,
                            (piece.south.src == piece) ? 1 : -1, piecePath, segs);
                }
                segs.appendItem(piecePath.createSVGPathSegLinetoHorizontalAbs(0));
                if (piece.west != null) {
                    connectorShape.makeHConnector(connectorWidth, connectorHeight, pieceHeight, -1,
                            (piece.west.src == piece) ? 1 : -1, piecePath, segs);
                }
                segs.appendItem(piecePath.createSVGPathSegClosePath());

                OMSVGGElement pieceClipPath = document.createSVGGElement();
                pieceClipPath.getStyle().setSVGProperty(SVGConstants.CSS_CLIP_PATH_PROPERTY,
                        "url(#" + idPieceClip + ")");

                OMSVGGElement pieceTransform = document.createSVGGElement();
                OMSVGTransform xform = rootSvg.createSVGTransform();
                xform.setTranslate(viewBox.getX() - i * pieceWidth, viewBox.getY() - j * pieceHeight);
                pieceTransform.getTransform().getBaseVal().appendItem(xform);

                OMSVGUseElement pieceContent = document.createSVGUseElement();
                pieceContent.getX().getBaseVal().setValue(viewBox.getX());
                pieceContent.getY().getBaseVal().setValue(viewBox.getY());
                pieceContent.getHref().setBaseVal("#" + idPiecePath);
                pieceContent.setClassNameBaseVal(style.pieceContent());

                OMSVGUseElement imgUse = document.createSVGUseElement();
                imgUse.getX().getBaseVal().setValue(viewBox.getX());
                imgUse.getY().getBaseVal().setValue(viewBox.getY());
                imgUse.getHref().setBaseVal("#" + ID_IMAGE);

                OMSVGUseElement pieceBorder = document.createSVGUseElement();
                pieceBorder.getX().getBaseVal().setValue(viewBox.getX());
                pieceBorder.getY().getBaseVal().setValue(viewBox.getY());
                pieceBorder.getHref().setBaseVal("#" + idPiecePath);
                pieceBorder.setClassNameBaseVal(style.pieceBorder());

                pieceDef.appendChild(pieceClipDef);
                pieceClipDef.appendChild(piecePath);
                pieceDef.appendChild(pieceContent);
                pieceDef.appendChild(pieceClipPath);
                pieceClipPath.appendChild(pieceTransform);
                pieceTransform.appendChild(imgUse);
                pieceDef.appendChild(pieceBorder);
                defs.appendChild(pieceDef);

                // Create the hints
                OMSVGUseElement tileShadow = document.createSVGUseElement();
                tileShadow.getHref().setBaseVal("#" + idPiecePath);
                tileShadow.setClassNameBaseVal(style.tileShadow());
                piece.shadow = tileShadow;
                tileShadows.appendChild(tileShadow);
                OMSVGUseElement assemblyShadow = document.createSVGUseElement();
                Target assemblyTarget = assemblyZone.getTarget(i, j);
                assemblyShadow.getHref().setBaseVal("#" + idPiecePath);
                assemblyShadows.appendChild(assemblyShadow);
                assemblyTarget.setShadow(assemblyShadow);
                assemblyShadows.appendChild(assemblyShadow);

                // Create the piece
                // <use x="130" y="260" xlink:href="#pieceX-Y"/>
                OMSVGUseElement geometry = document.createSVGUseElement();
                geometry.setClassNameBaseVal(style.piece());
                geometry.getHref().setBaseVal("#" + idPiece);
                rootSvg.appendChild(geometry);
                piece.geometry = geometry;
            }
        }
    }

    public OMSVGSVGElement getSvgElement() {
        return rootSvg;
    }

    public void shuffle() {
        List<Piece> pieceList = new ArrayList<Piece>(this.pieceList);
        // Shuffle the pieces
        int pieceCount = colCount * rowCount;
        for (int i = 0; i < colCount; i++) {
            for (int j = 0; j < rowCount; j++) {
                assemblyZone.setPiece(null, i, j);
                tileZone.setPiece(pieceList.remove(Random.nextInt(pieceCount--)), i, j);
            }
        }
    }

    @Override
    public void onMouseDown(MouseDownEvent event) {
        if (!dragging) {
            srcTarget = getTarget(event);
            if (srcTarget != null) {
                Piece piece = srcTarget.getPiece();
                if (piece != null) {
                    dragging = true;
                    d = getCoordinates(event).substract(srcTarget.getPosition());
                    // Move the DOM node to the end of the tree so that it is drawn after
                    // all other nodes
                    rootSvg.removeChild(piece.geometry);
                    rootSvg.appendChild(piece.geometry);
                }
                event.preventDefault();
                event.stopPropagation();
            }
        } else {
            onMouseUp_(event);
        }
    }

    @Override
    public void onMouseMove(MouseMoveEvent event) {
        if (dragging) {
            if (destTarget != null) {
                destTarget.setSelected(false,
                        destTarget.shadow != null ? destTarget.shadow : srcTarget.piece.shadow);
            }
            Target target = getTarget(event);
            //         GWT.log("target = " + target);
            if (target != null && (target.piece == null || target == srcTarget)) {
                destTarget = target;
                destTarget.setSelected(true,
                        destTarget.shadow != null ? destTarget.shadow : srcTarget.piece.shadow);
            } else {
                destTarget = null;
            }
            srcTarget.setPosition(getCoordinates(event).substract(d));
            event.preventDefault();
            event.stopPropagation();
        }
    }

    @Override
    public void onMouseUp(MouseUpEvent event) {
        onMouseUp_(event);
    }

    private void onMouseUp_(MouseEvent<? extends EventHandler> event) {
        if (dragging) {
            if (destTarget == null) {
                destTarget = srcTarget;
            } else {
                if (destTarget != null) {
                    destTarget.setSelected(false,
                            destTarget.shadow != null ? destTarget.shadow : srcTarget.piece.shadow);
                    destTarget.setPiece(srcTarget.getPiece());
                    if (srcTarget != destTarget) {
                        srcTarget.setPiece(null);
                    }
                    if (isGameOver()) {
                        Window.alert(PuzzleConstants.INSTANCE.congratulations());
                    }
                } else {
                    srcTarget.setPosition(srcTarget.getPosition());
                }
                destTarget = null;
                dragging = false;
            }
        }
        event.preventDefault();
        event.stopPropagation();
    }

    public boolean isGameOver() {
        for (int i = 0; i < colCount; i++) {
            for (int j = 0; j < rowCount; j++) {
                Piece piece = assemblyZone.getPiece(i, j);
                if (piece == null || piece.x != i || piece.y != j) {
                    return false;
                }
            }
        }
        return true;
    }

    public OMSVGPoint getCoordinates(MouseEvent<? extends EventHandler> e) {
        OMSVGPoint p = rootSvg.createSVGPoint(e.getClientX(), e.getClientY());
        OMSVGMatrix m = rootSvg.getScreenCTM().inverse();
        return p.matrixTransform(m);
    }

    public Target getTarget(MouseEvent<? extends EventHandler> e) {
        Target target = tileZone.getTarget(e);
        if (target == null) {
            target = assemblyZone.getTarget(e);
        }
        return target;
    }
}