This site requires JavaScript, please enable it in your browser!
Greenfoot back
maenstru56
maenstru56 wrote ...

2018/2/5

Mouse dragging and collision

1
2
maenstru56 maenstru56

2018/2/5

#
Hello, I'm trying to make a sliding puzzle game and so far I've been trying to make the collision work between the puzzle pieces and the world border but I can't figure it out. This is what I've done until now, can someone help me edit it so It can detect collision public void Positioning(){ if(Greenfoot.mouseDragged(this)){ MouseInfo mouse = Greenfoot.getMouseInfo(); setLocation(mouse.getX(), mouse.getY()); } if (Greenfoot.mouseDragEnded(this)){ Actor choice = getOneIntersectingObject(Box.class); if (choice != null && getOneIntersectingObject(Mover.class) == null){ setLocation(choice.getX(), choice.getY()); prevX = getX(); prevY = getY(); } else setLocation(prevX, prevY); } } EDIT: This is what I'm trying to do: http://www.bestqualitytoys.com/files/images/thinkfunlarge/5853.jpg
Super_Hippo Super_Hippo

2018/2/5

#
What is the Mover class?
danpost danpost

2018/2/5

#
Please provide your world class codes for needed details. @Hippo, a Mover is just a tile in the sliding puzzle (either the tile class itself or a superclass of it).
danpost danpost

2018/2/5

#
Usage of a link in a first posting has disabled @maenstru56 -- at least until content can be reviewed and cleared by a greenfoot team member.
maenstru56 maenstru56

2018/2/5

#
World class code: import greenfoot.*; public class MyWorld extends World { public MyWorld() { super(1000, 700, 1); genBox(); addObject(new Mover(), 50, 50); addObject(new Mover(), 150, 50); } public void genBox(){ int dx = 50, dy = 50; for(int x = 1; x <= 7; x++){ for(int y = 1; y <= 10; y++){ addObject(new Box(), dx, dy); dx = dx + 100; } dy = dy + 100; dx = 50; } } } Mover actor code (mover is the "piece" of puzzle, the thing I need to move): import greenfoot.*; import java.util.List; import java.util.ArrayList; public class Mover extends Actor { private int prevX = 50, prevY = 50; private boolean isGrabbed; public void act() { Positioning(); } public void Positioning(){ if(Greenfoot.mouseDragged(this)){ MouseInfo mouse = Greenfoot.getMouseInfo(); setLocation(mouse.getX(), mouse.getY()); } if (Greenfoot.mouseDragEnded(this)){ Actor choice = getOneIntersectingObject(Box.class); //Actor choice = getOneTouchedObject(Box.class); //choice.setImage("chosen_box.png"); if (choice != null && getOneIntersectingObject(Mover.class) == null){ setLocation(choice.getX(), choice.getY()); prevX = getX(); prevY = getY(); } else setLocation(prevX, prevY); } } } And finally, the box code (the box is basically the "board" in wich I move the pieces, like a chess board): import greenfoot.*; public class Box extends Actor { public void act() { generate(); } }
danpost danpost

2018/2/5

#
First thing I noticed is that there is no 'generate' method for the Box class -- remove the act method in that class. Next thing I noticed is that you are creating 70 Box objects and only 2 Mover objects. In all standard sliding puzzles using square movers in a rectangular box, the number of movers is always odd -- one less than the number of rows times the number of columns of movers. If you are using the entire area contained by boxes, it looks like you would need 69 movers, which would be a very difficult puzzle. If you used the image the link provided above refers to, then it would be almost an impossible puzzles (with multiple movers having the same apparent image). Since you are apparently filling the entire world with boxes and therefore the only visual objects would be movers, once added, filling all but one location, it would probably be easier to remove the box class and change your world to a (10, 7, 100) one. If your Box object had a visual image, it can be drawn on a white filled image of the same size and that in turn, can be set to the background of the world. Once the above is accomplished, show what you did and ask again about collision (border may not need to be dealt with at all in the end).
maenstru56 maenstru56

2018/2/5

#
First thing, thanks for the help :) Second, I've made the changes you told me to and I now have this code. World class: import greenfoot.*; public class MyWorld extends World { public MyWorld() { super(800, 600, 1); genBox(); genMovers(); } private void genBox(){ int dx = 250, dy = 200; for(int y = 1; y <= 4; y++){ for(int x = 1; x <= 4; x++){ addObject(new Box(), dx, dy); dx = dx + 100; } dy = dy + 100; dx = 250; } } private void genMovers(){ int dx = 250, dy = 200; for(int y = 1; y <= 4; y++){ for(int x = 1; x <= 4; x++){ if(y != 4 || x !=4){ addObject(new Mover(), dx, dy); dx = dx + 100; } } dy = dy + 100; dx = 250; } } } Mover class: import greenfoot.*; public class Mover extends Actor { private int prevX = 250, prevY = 200; public void act() { positioning(); } private void positioning(){ if(Greenfoot.mouseDragged(this)){ MouseInfo mouse = Greenfoot.getMouseInfo(); setLocation(mouse.getX(), mouse.getY()); } if (Greenfoot.mouseDragEnded(this)){ Actor choice = getOneIntersectingObject(Box.class); if (choice != null && getOneIntersectingObject(Mover.class) == null){ setLocation(choice.getX(), choice.getY()); prevX = getX(); prevY = getY(); } else setLocation(prevX, prevY); } } } Box class doesn't have any code in it, I've deleted the act method. I don't want to fill the whole world with boxes and pieces, I also want to add some buttons, a score counter, etc, but I want to get the "gameplay" done first, then the design. What I've sent earlier was more of an experiment, just to test out some things that's why I put a big number of movers. I have 16 boxes and 15 movers now, that's a fair amount and I rearranged them accordingly. Now, about the boxes, they have a transparent 100x100 texture with a border of 1px black, just to separate the pieces with a nice black line. Now, about the interaction between the pieces, or how I referred to it in the last message the "colision", can you help me with that? Also, how can I "cut" the image in pieces so that every mover becomes 1 piece of an image? I know I can do that manually in photoshop, but maybe I can do it using code so I won't have to edit so many images. Again, thanks for all the help.
danpost danpost

2018/2/5

#
First, to cut the image and distribute the portions among the Movers. This should be done in the 'genMovers' method (where you create the movers). First create the full image using the GreenfootImage constructor that uses a String filename. Then scale it (if not already to scale) to the required size (400, 400). Do that much before the 'for' loops. Within the loops, create a new GreenfootImage (call it tile) of size (100, 100) using the appropriate constructor and draw the main image onto it using the (100-100*x, 100-100*y) location. Next, pull the 'new Mover()' out of the 'addObject' parameter and replace it with 'mover' and before that line add 'Actor mover = new Mover();'. Immediately after that line, add 'mover.setImage(tile);'. That should take care of the tile images. You will need some way to determine when the puzzle is completed. What do you have in mind for that? (I did not see anything for that in your current codes).
maenstru56 maenstru56

2018/2/5

#
I made it work and now it looks just good, thanks for the assist. I have some minor bugs though, when I drag the pieces they don't really arrange accordingly sometimes, any idea how I could improve that? I feel like the thing that I did there with the mouse dragging and stuff could be improved a bit. About checking if the puzzle is complete I have 2 ideas, one to remember the position where every piece should be and compare it to the actual one (I don't really know how I'll implement this and there's probably a better way to check) or use some algorithm that solves the puzzle and compares it to the one on the screen (I found A* algorithm but I don't know, it seems a bit too complicated for what I want to do). Soo, if there's no better option I guess I'll use the first one, I hope I'll succeed. If you have a better idea, I'm looking forward to hear.
danpost danpost

2018/2/6

#
First, for checking for a complete puzzle, You need to know when all 15 movers are in their correct positions. So, the important data for each mover are (1) the coordinates of its correct position; and (2) whether at that position or not. (1) requires two int fields to hold the coordinates and (2) requires a boolean field to track the correctness of its location. Any time a mover is moved (or attempted to be moved), the correctness of its position should be checked and that, compared to the last known state of correctness will detect when the state actually changes. That is when the boolean field should be changed. This moment will be used in the next part. To determine completeness, again, all 15 moves must be at their correct locations. So, we need a int counter field to keep track of how many are actually in their correct positions -- or better yet, to count how many are not in their correct positions; that way the counter can start at zero when all movers are initially placed in their correct positions in the world (zero are out of place when puzzle is fixed). It should be obvious that the other fields above go in the class of the movers; but, this field, not so much. Well, only movers will use it -- not just one mover, however, but all of them will use the same field. It can therefore be a class field, also known as a static field, in the class of the mover. So, we have this:
// class field to track how many movers are out of place
public static int numOutOfPlace; // to track number of movers out of place

// instance fields (for each mover)
private boolean outOfPlace; // incorrectness of the location of this mover
private int fixedX, fixedY; // for coordinates of fixed location for this mover

// to initialize fixed location coordinates and counter for out of place movers
protected void addedToWorld(World world)
{
    fixedX = getX(); // save fixed x-coordinate
    fixedY = getY(); // save fixed y-coordinate
    numOutOfPlace = 0; // initialize counter for out of place movers
}

// immediate upon being dragged
boolean poorlyPlaced = getX() != fixedX || getY() != fixedY; // new state
if (poorlyPlaced != outOfPlace) // checked against last known state
{
    outOfPlace = poorlyPlaced; // update state (because it has changed)
    numOutOfPlace += (outOfPlace ? 1 : -1); // update counter
    if (numOutOfPlace == 0) // check for puzzle fixed
    {
        // puzzle is fixed -- whatever here
    }
}
maenstru56 maenstru56

2018/2/6

#
Ok, I just finished with that piece of code, my movers class looks now like this: import greenfoot.*; public class Mover extends Actor { private int prevX = 250, prevY = 200; public static int numOutOfPlace; private boolean outOfPlace; private int fixedX, fixedY; public void act() { positioning(); } protected void addedToWorld(World world){ fixedX = getX(); fixedY = getY(); numOutOfPlace = 0; } private void positioning(){ if(Greenfoot.mouseDragged(this)){ MouseInfo mouse = Greenfoot.getMouseInfo(); setLocation(mouse.getX(), mouse.getY()); } if (Greenfoot.mouseDragEnded(this)){ Actor choice = getOneIntersectingObject(Box.class); if (choice != null && getOneIntersectingObject(Mover.class) == null){ setLocation(choice.getX(), choice.getY()); prevX = getX(); prevY = getY(); } else setLocation(prevX, prevY); boolean poorlyPlaced = getX() != fixedX || getY() != fixedY; if (poorlyPlaced != outOfPlace){ outOfPlace = poorlyPlaced; numOutOfPlace += (outOfPlace ? 1 : -1); if (numOutOfPlace == 0){ System.out.println("WIN!"); } } } } } I tested it and it works just fine, but now what I want to do is randomize the position of the pieces, when you start the game the puzzle should be generated randomly and the positions will change, how can I do that and still know where every piece should be? Thanks!
danpost danpost

2018/2/6

#
You should probably simplify the 'positioning' method and put the actual movement code in a separate method. That way, the states can be tracked both while dragging and while mixing up the puzzle (which will be worked on after this). First thing is you do not need the 'prevX' and 'prevY' fields. The mover should only be moved when it is a valid move and that is something that can be determined prior to moving the mover at all. What I am suggesting is that mouse dragging not move the mover until it the mouse reaches a new valid location for the mover. Something like this:
// mouse dragging
private void positioining(){
    if (Greenfoot.mouseDragged(this)){
        MouseInfo mouse = Greenfoot.getMouseInfo();
        int dx = (mouse.getX()-getX())/50;
        int dy = (mouse.getY()-getY())/50;
        move(dx, dy);
    }
}
    
// makes a move only if a valid one
public void move(int xDir, int yDir){
    // the following ignores the given invalid locations
    // dx*dy != 0  : off-axis locations
    // dx+dy == 0  :  current location
    // dx+dy != 1 && dx+dy != -1  : non-adjacent location
    if (dx*dy != 0 || dx+dx == 0 || (dx+dy != 1 && dx+dy != -1)) return;
    // the following ignores out of bounds locations
    if (getOneObjectAtOffset(dx*100, dy*100, Box.class) == null) return;
    // the following ignores filled locations
    if (getOneObjectAtOffset(dx*100, dy*100, Mover.class) != null) return;
    setLocation(getX()+dx*100, getY()+dy*100);
    boolean poorlyPlaced = getX() != fixedX || getY() != fixedY; 
    if (poorlyPlaced != outOfPlace){
        outOfPlace = poorlyPlaced;
        numOutOfPlace += (outOfPlace ? 1 : -1);
    }
}
I removed the completion check from the code so that it does not show if random mixing at any point inadvertently solves the puzzle. You can easily put the completion checking code in the world act method. With the above, you can now mix the puzzle with something like this (from the world constructor (after adding the movers):
int x=3, y=3; // the empty location when in solved state (should currently be empty)
int[][] ps = new int[4][2]; // for x- and y-coordinates of 4 possible movers to an empty square
for (int i=0; i<100+Greenfoot.getRandomNumber(2); i++)
{
    // list possible movers
    int n=0; // to count listed movers
    if (x > 0) ps[n++] = new int[] { -1, 0 };
    if (x < 3) ps[n++] = new int[] { 1, 0 };
    if (y > 0) ps[n++] = new int[] { 0, -1 };
    if (y < 3) ps[n++] = new int[] { 0, 1 };
    // choose a random mover from list and move it
    int rand = Greenfoot.getRandomNumber(n);
    Mover mover = (Mover)getObjectsAt(250+ps[rand][0]*100, 200+ps[rand][1]*100, Mover.class).get(0);
    mover.move(-ps[rand][0], -ps[rand][1]); // always a valid move
    // track empty location
    x += dx;
    y += dy;
}
Again, the codes given is not tested.
maenstru56 maenstru56

2018/2/6

#
Thanks for the help, I did all the positioning and it works perfectly, just as I wanted :) About the part "You can easily put the completion checking code in the world act method.", I don't quite understand how I should do that, if I put the checking in the world act method how do I pass the numOutOfPlace outside the Mover class? (Maybe it's pretty obvious but I didn't get it). And I have a little problem mixing the puzzle, I get an error when trying to mixing. Here's the error: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:653) at java.util.ArrayList.get(ArrayList.java:429) at MyWorld.genMovers(MyWorld.java:57) -> here is red at MyWorld.<init>(MyWorld.java:9) -> here is red at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at greenfoot.core.Simulation.newInstance(Simulation.java:617) at greenfoot.platforms.ide.WorldHandlerDelegateIDE.lambda$instantiateNewWorld$7(WorldHandlerDelegateIDE.java:430) at greenfoot.core.Simulation.runQueuedTasks(Simulation.java:502) at greenfoot.core.Simulation.maybePause(Simulation.java:305) at greenfoot.core.Simulation.runContent(Simulation.java:218) at greenfoot.core.Simulation.run(Simulation.java:211) The code looks like this:
import greenfoot.*;

public class MyWorld extends World
{
    public MyWorld()
    {
        super(800, 600, 1);
        genBox();
        genMovers();     
    }
    
    private void genBox(){
        int dx = 250, dy = 200;
        
        for(int y = 1; y <= 4; y++){
            for(int x = 1; x <= 4; x++){
                addObject(new Box(), dx, dy);
                dx = dx + 100;
            }
            dy = dy + 100;
            dx = 250;
        }
    }
    
    private void genMovers(){
        int dx = 250, dy = 200;
        
        GreenfootImage image = new GreenfootImage("ger2.png");
        
        for(int y = 1; y <= 4; y++){
            for(int x = 1; x <= 4; x++){
                GreenfootImage tile = new GreenfootImage(100, 100);
                tile.drawImage(image, 100-100*x, 100-100*y);
                
                if(y != 4 || x !=4){
                    Actor mover = new Mover();
                    addObject(mover, dx, dy);
                    mover.setImage(tile);
                    dx = dx + 100;
                }
            }
            dy = dy + 100;
            dx = 250;
        }
        
        int x=3, y=3;
        int[][] ps = new int[4][2];
        for (int i=0; i<100+Greenfoot.getRandomNumber(2); i++){
            int n=0;
            
            if (x > 0) ps[n++] = new int[] { -1, 0 };
            if (x < 3) ps[n++] = new int[] { 1, 0 };
            if (y > 0) ps[n++] = new int[] { 0, -1 };
            if (y < 3) ps[n++] = new int[] { 0, 1 };
            
            int rand = Greenfoot.getRandomNumber(n);
            Mover mover = (Mover)getObjectsAt(250+ps[rand][0]*100, 200+ps[rand][1]*100, Mover.class).get(0);
            mover.move(-ps[rand][0], -ps[rand][1]);
            
            x += dx;
            y += dy;
        }
    }
}
danpost danpost

2018/2/6

#
maenstru56 wrote...
About the part "You can easily put the completion checking code in the world act method.", I don't quite understand how I should do that, if I put the checking in the world act method how do I pass the numOutOfPlace outside the Mover class? (Maybe it's pretty obvious but I didn't get it).
if (Mover.numOutOfPlace == 0)
And I have a little problem mixing the puzzle, I get an error when trying to mixing. Here's the error: << Error Trace Omitted >> The code looks like this: << Code Omitted >>
Alright -- there was a couple of things done wrong in the shuffle code (on my part). First thing was line 57, which should be:
Mover mover = (Mover)getObjectsAt(250+(x+ps[rand][0])*100, 200+(y+ps[rand][1])*100, Mover.class).get(0);
Next lines 60 and 61 should be:
x += ps[rand][0];
y += ps[rand][1];
After testing at this point, I realized that collision checking was not being updated with the shuffling. Therefore, some code will need shuffling (go figure!). In the Mover class, I separated the position checking from the dragging code and separated the absolute moving from the move checking. The final results follow:
private void positioining()
{
    if (Greenfoot.mouseDragged(this))
    {
        MouseInfo mouse = Greenfoot.getMouseInfo();
        int dx = (mouse.getX()-getX())/50;
        int dy = (mouse.getY()-getY())/50;
        checkMove(dx, dy);
    }
}

private void checkMove(int dx, int dy)
{
    if (dx*dy != 0 || dx+dx == 0 || (dx+dy != 1 && dx+dy != -1)) return;
    if (getOneObjectAtOffset(dx*100, dy*100, Box.class) == null) return;
    if (getOneObjectAtOffset(dx*100, dy*100, Mover.class) != null) return;
    move(dx, dy);
}

public void move(int dx, int dy)
{
    setLocation(getX()+dx*100, getY()+dy*100);
    checkPlacement();
}

private void checkPlacement()
{
    boolean poorlyPlaced = getX() != fixedX || getY() != fixedY; 
    if (poorlyPlaced != outOfPlace)
    {
        outOfPlace = poorlyPlaced;
        numOutOfPlace += (outOfPlace ? 1 : -1);
    }
}
maenstru56 maenstru56

2018/2/7

#
danpost wrote...
After testing at this point, I realized that collision checking was not being updated with the shuffling. Therefore, some code will need shuffling (go figure!).
Thanks for the reply, I modified the method used in world class after shuffling, is that what you meant? Whe collision works, the thing is it not detects if the puzzle is complete, I tried modifying a few things but it didn't seem to work. (line 61)
import greenfoot.*;

public class MyWorld extends World
{
    public MyWorld()
    {
        super(800, 600, 1);
        genBox();
        genMovers();
        
        if(Mover.numOutOfPlace == 0)
            showText("WIN!", getWidth()/2, getHeight()/2);
    }
    
    private void genBox(){
        int dx = 250, dy = 200;
        
        for(int y = 1; y <= 4; y++){
            for(int x = 1; x <= 4; x++){
                addObject(new Box(), dx, dy);
                dx = dx + 100;
            }
            dy = dy + 100;
            dx = 250;
        }
    }
    
    private void genMovers(){
        int dx = 250, dy = 200;
        
        GreenfootImage image = new GreenfootImage("ger2.png");
        
        for(int y = 1; y <= 4; y++){
            for(int x = 1; x <= 4; x++){
                GreenfootImage tile = new GreenfootImage(100, 100);
                tile.drawImage(image, 100-100*x, 100-100*y);
                
                if(y != 4 || x !=4){
                    Actor mover = new Mover();
                    addObject(mover, dx, dy);
                    mover.setImage(tile);
                    dx = dx + 100;
                }
            }
            dy = dy + 100;
            dx = 250;
        }
        
        int x=3, y=3;
        int[][] ps = new int[4][2];
        for (int i=0; i<100+Greenfoot.getRandomNumber(2); i++){
            int n=0;
            
            if (x > 0) ps[n++] = new int[] { -1, 0 };
            if (x < 3) ps[n++] = new int[] { 1, 0 };
            if (y > 0) ps[n++] = new int[] { 0, -1 };
            if (y < 3) ps[n++] = new int[] { 0, 1 };
            
            int rand = Greenfoot.getRandomNumber(n);
            Mover mover = (Mover)getObjectsAt(250+(x+ps[rand][0])*100, 200+(y+ps[rand][1])*100, Mover.class).get(0);
            mover.checkMove(-ps[rand][0], -ps[rand][1]); // I changed it from mover.move(-ps[rand][0], -ps[rand][1]); to check if it's a valid move
            
            x += ps[rand][0];
            y += ps[rand][1];
        }
    }
}
There are more replies on the next page.
1
2