0

I am currently trying to develop a puzzle. All I got is my application with my playing-field and gaming-pieces. Next step is to click on one of my gaming-piece to select it and be able to move it with the arrow-keys (furthermore I want to them only to move, if the next step - which will be 100 pixels - does not contain any other gaming-piece).

The problem I'm currently running into: Using addMouseListener() on my main JPanel and then using getSource() only returns my playing-field (called View in my code) but I need it to return the desired gaming-piece (such as topLeft). I already tried casting getSource() to Piece but this does not work (Cannot cast View to Piece).

So, I need to find a way to add a mouse listener which returns the gaming-piece that was clicked on so that I can change the location and check for any collision with any other gaming-piece. Thanks in advance!

Edited code thanks to @camickr.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Puzzle {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(Puzzle::new);
    }

    private final static int[] SHAPE_X = { -100, 100, 100, 0, 0, -100 };
    private final static int[] SHAPE_Y = { -100, -100, 0, 0, 100, 100 };

    public Puzzle() {
        JFrame frame = new JFrame("Puzzle");
        frame.setSize(400, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        View view = new View();
        frame.setContentPane(view);
        view.addMouseListener(new MouseAdapterMod(view));

        Shape polyShape = new Polygon(SHAPE_X, SHAPE_Y, SHAPE_X.length);
        Piece topLeft = new Piece(Color.YELLOW, polyShape,   0, 100, 100);
        view.pieces.add(topLeft);

        Piece topRight = new Piece(Color.CYAN, polyShape, 90, 300, 100);
        view.pieces.add(topRight);

        Piece bottomRight = new Piece(Color.GREEN,  polyShape, 180, 300, 300);
        view.pieces.add(bottomRight);

        Piece bottomLeft = new Piece(Color.RED,  polyShape, 270, 100, 300);
        view.pieces.add(bottomLeft);

        Piece square = new Piece(Color.ORANGE, new Rectangle(200, 200), 0, 100, 100);
        view.pieces.add(square);

        frame.setVisible(true);
    }

}

class View extends JPanel {

    final List<Piece> pieces = new ArrayList<>();

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D gc = (Graphics2D) g;
        for (Piece piece : pieces) {
            piece.draw(gc);
        }

    }
}

class Piece {
    final Color color;
    final Shape shape;
    final int angle;

    int x;
    int y;

    Piece(Color color, Shape shape, int angle, int x, int y) {
        this.color = color;
        this.shape = shape;
        this.angle = angle;
        this.x = x;
        this.y = y;
    }

    void draw(Graphics2D gc) {
        AffineTransform tf = gc.getTransform();
        gc.translate(x, y);
        gc.rotate(Math.toRadians(angle));
        gc.setColor(color);
        gc.fill(shape);
        gc.setTransform(tf);
    }

    Shape getShape() {
    return shape;
    }
}

class MouseAdapterMod extends MouseAdapter {

    final View view;

    public MouseAdapterMod(View view) {
    this.view = view;
    }

    @Override
    public void mousePressed(MouseEvent e) {
    for(Piece piece : view.pieces) {
        if(piece.getShape().contains(e.getX(), e.getY())) {
        System.out.println("yes");
        }
    }
    }
}
Nordic88
  • 129
  • 1
  • 12
  • You may want to use the `getX()` and `getY()` from `MouseEvent` to find which `Piece` has been selected. – Arnaud Mar 05 '19 at 13:12
  • @Arnaud by using `getComponentAt()` or any similiar method of `View`? I also tried that, only returns the `View` in general too. – Nordic88 Mar 05 '19 at 13:15

2 Answers2

1

So, I need to find a way to add a mouse listener which returns the gaming-piece that was clicked

You use the getX() and getY() methods from the MouseEvent.

Then you iterate through your "pieces" ArrayList and invoke the contains(... method on the Shape contained in each Piece to see if the mouse point is contained in the piece.

So you will also need to add a getShape(...) method to your "Piece" class so you can access the Shape of each Piece.

Edit:

So your basic logic might be something like:

//Shape polyShape = new Polygon(SHAPE_X, SHAPE_Y, SHAPE_X.length);
//Piece topLeft = new Piece(Color.YELLOW, polyShape,   0, 100, 100);
Polygon topLeftPolygon = new Polygon(SHAPE_X, SHAPE_Y, SHAPE_X.length);
topLeftPolygon.translate(100, 100);
//topLeftPolygon = ShapeUtils.rotate(...); // do rotation when required
Piece topLeft = new Piece(Color.YELLOW, topLeftPolygon);

Then the painting code in the draw(..) method is just:

gc.setColor(color);
gc.fill(shape);

No need for the transform or the translate.

Edit 2:

So use the Shape:

//topLeftPolygon = ShapeUtils.rotate(...); // do rotation when required
//Piece topLeft = new Piece(Color.YELLOW, topLeftPolygon);
Shape topLeftShape = ShapeUtils.rotate(...); // do rotation when required
Piece topLeft = new Piece(Color.YELLOW, topLeftShape);

This currently matches your Piece class which expects a Shape object anyway. Please think about the concept being suggested and don't assume posted code is perfect since it obviously hasn't been tested.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thank, you, that partly worked. The only problem I'm having now: Not all parts of the piece apparently contains the x- and y-coordinates, even though I click directly in the middle of the shape (see my edited post for my code and try to click the piece in the bottom-left corner, you'll see, it will not work). – Nordic88 Mar 05 '19 at 15:53
  • 1
    @Nordic88, The problem is that you alter the Shape when you paint it by doing the translation and rotation so you are comparing apples to oranges. What you need to do is translate and rotate the Shape before adding it to your Piece. Then you just simply paint the Shape. The `Polygon` class supports a `translate(...)` method. You can then check out `ShapeUtils` found in [Playing With Shapes](https://tips4java.wordpress.com/2013/05/13/playing-with-shapes/). It suppports a `rotate(...)` method. – camickr Mar 05 '19 at 16:51
  • thank you, but I don't really get what you mean. So I should subclass `Polygon` in my `Piece` and do the drawing there and then paint the Piece in `View`? – Nordic88 Mar 05 '19 at 17:01
  • 1
    The problem is you have a single Polygon. So when you invoke its `contains(...)` method you are always comparing the same Polygon. Then when you paint the Polygon you translate the x/y location and rotate it so the Polygon appears at a different location on the panel. Instead, you need to create individual Polygons. Each Polygon will be translated/rotated when you create it. Then you just paint the Polygon as is, there is no need for the translation/rotation logic in the painting code. – camickr Mar 05 '19 at 18:16
  • Thanks! That works perfectly, but rotating is still a problem since `ShapeUtils#rotate` returns a shape so I cannot use something like `topLeftPolygon = ShapeUtils.rotate(...)` and casting it to a `Polygon` also does not work. I'd rather use rotating instead of changing each x/y coordination to archive the rotated polygon. – Nordic88 Mar 05 '19 at 18:38
  • @Nordic88 `I'd rather use rotating instead of changing each x/y coordination` - why? 1) How do you expect to translate the mouse point to match the x/y points of the painted Shape. 2) So you want to translate/rotate the Shape every time the component needs to be repainted, instead of doing it once when you create the Shape? – camickr Mar 05 '19 at 19:27
  • I decided to not rotate the origin x/y location. Thanks for your help, really appreciated it! Helped me alot. I thought I could move the piece in `KeyEvent.keyPressed` by using `translate(...)` but nothing happens. Could not figure out why. – Nordic88 Mar 05 '19 at 19:47
  • Addition: Using `translate()` and adding my cell-height (100) to the y-location of `Polygon#getBounds#getY` will move it like 300 pixel down and 300 pixel left instead of just 100 pixel down. – Nordic88 Mar 05 '19 at 20:20
1

I see you have used my answer from this question, as the basis of your puzzle game, instead of continuing with your own code. Understand, I gave you a Minimal, Complete, Verifiable Example for drawing shapes on a JPanel. I never said it was suitable to use directly; if fact, by making the example "minimal" it may have caused extending the code to support things like hit-testing to be more difficult. But since you have chosen to proceed with my code as the basis...

Your hit-test issue comes from the fact that the mouse clicks are in the view's coordinate system, but the shapes are being translated and rotated to various other positions for drawing. You can solve this by reversing the transformation, translating the mouse position into the shape's coordinate system, and then using Shape.contains(Point2D).

class Piece {

    // ... omitted for brevity ...

    public boolean hitTest(Point point) {
        AffineTransform tf = new AffineTransform();
        tf.translate(x, y);
        tf.rotate(Math.toRadians(angle));
        try {
            Point2D pnt = tf.inverseTransform(point, null);
            return shape.contains(pnt);
        } catch (NoninvertibleTransformException e) {
            return false;
        }
    }
}

Then, simply loop over the each piece, and ask it if it is under the mouse's location.

class View extends JPanel {

    // ... omitted for brevity ...

    Piece hitTest(MouseEvent e) {
        Point pnt = e.getPoint();
        for (Piece piece : pieces) {
            if (piece.hitTest(pnt))
                return piece;
        }
        return null;
    }
}

Note that I'm returning the Piece here, not the Shape. Since 4 out of the 5 pieces use the same polyShape shape, that isn't very useful.

The hitTest() could be shorten using Java8 streams:

    Piece hitTest(MouseEvent e) {
        final Point pnt = e.getPoint();
        return pieces.stream()
                     .filter(piece -> piece.hitTest(pnt))
                     .findFirst()
                     .orElse(null);
    }

If the pieces can overlap, the one that appears to be on top is the last one drawn. findFirst() will return the bottom-most piece drawn at the given mouse point, not the top most. The following change will correct the behaviour:

    Piece hitTest(MouseEvent e) {
        final Point pnt = e.getPoint();
        return pieces.stream()
                     .filter(piece -> piece.hitTest(pnt))
                     .reduce((first, second) -> second)
                     .orElse(null);
    }
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44