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

2022/3/11

Hi, need help with Error & logic

PastorI PastorI

2022/3/11

#
Hi, I'm currenly programming a 2048 game and I'm struggling to figure out why I get an error each time 2 blocks Merge "java.lang.IllegalStateException: Actor has been removed from the world." I'm also not sure why when 2 blocks merge, the new merged block ends up in another location. I think when I assign the x & y positions of a new object it may be random: GameSquare:
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.List;

/**
 * Write a description of class GameSquare here.
 * 
 * @author (your name) 
 * @version (a version number or a date)
 */
public class GameSquare extends Actor
{
    //Instance Constants
    
    /* Note: 
     * Since these variables are definied at STATIC they will never change (aka are constant).
     * Normally when we have constant variables like this we name them in ALL CAPS
    */
    //Define some directions to make it easier to reference which way the blocks are moving
    private static final int UP = 0;
    private static final int RIGHT = 1;
    private static final int DOWN = 2;
    private static final int LEFT = 3;
    
    //Define a debugging variable (See video linked in assignment outline)
    private final boolean debug = false;
    
    //Instance Variables    
    private int value;
        
    //Constructor
    public GameSquare()
    {
        this(2);
        displayValue();
    }
    
    public GameSquare(int valueIn)
    {
        this.setValue(valueIn);
        displayValue();
    }
    
    /**
     * Tell the block to move in the given direction until it hits an obstacle
     * 
     * @param The direction in which the block is to move (UP = 0; RIGHT = 1; DOWN = 2; LEFT = 3;
     */
    
    public void move(int direction) 
    {
        //check if can move
        int movable = canMove(direction);

        //if moveable, start a loop
        while (movable > 0)
        {
            //Get current coordinates
            int xPos = getX(); 
            int yPos = getY();
            
            //Change x and y values to the "new" location based on direction
            if(direction == 0){
                yPos -= 1;
            }
            else if(direction == 1){
                xPos += 1;
            }
            else if(direction == 2){
                yPos += 1;
            }
            else if(direction == 3){
                xPos -= 1;
            }    
            
            //If Nothing in the way - move the block
            if (movable == 1) {  
                setLocation(xPos,yPos);
                movable = canMove(direction);
                continue;
                //return; //Why do we continue here, instead of returning?
            }
            //Merge the blocks
            else {
                //Find which block we need to merge with
                if(!getWorld().getObjectsAt(xPos, yPos, GameSquare.class).isEmpty()) {                           // check world for spaces with blocks
                    GameSquare block = (GameSquare)getWorld().getObjectsAt(xPos, yPos, GameSquare.class).get(0); // get objects in the gameSquare world
                    if(merge(block.getValue())) {       //Tell the other block object we wish to merge with it.
                        getWorld().removeObject(block); // remove block object 
                        getWorld().addObject(new GameSquare(getValue()), yPos, xPos);        // set location of new merged object
                        getWorld().removeObject(this);
                        movable = canMove(direction);
                        continue;
                    } else {
                        movable = 0;                    // if block cannot move/merge past other object
                        continue;
                    }
                } else {
                    movable = canMove(direction);       // continue movement if no merging possible 
                    continue;
                }
            }
        }
        
        
        //can't move, so don't move.
        return;
    }

    /**
     * Sets the value of the game square to the value of the input parameter.
     * Will only work if the value is a factor of 2
     * 
     * @param The number to use as the new value
     * @return If the number was set correctly return true, otherwise return false
     */
    
    public boolean setValue(int valueIn)
    {
        if(valueIn % 2 != 0){ //modulo to check if num is a factor of 2
            return false;
        } else {
            value = valueIn;
            return true;
        }
    }
    
    /**
     * Merge with another block and absorb it's value.
     * Will only work if the two blocks are of the same value
     * 
     * @param The value of the block to be added
     * 
     * @return Return true if the merge is successful.
     */
    public boolean merge(int valueIn)
    {
        if(getValue() == valueIn){    // if objects are of equal value
            setValue((getValue() * 2));//multiply the block value by 2
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Returns the current value of the gameSquare
     * 
     * @return The current value (int) of the game square
     */
    public int getValue()
    {
        return this.value;
    }

    /**
     * Checks to see if the block can move
     * 
     * @return int value representing what is in the space to be moved to.  0 -> Path Blocked, 1 -> Empty Space, int>1 value of block in the space to be moved to.
     */
    private int canMove(int direction)
    {
        //Get World
        World world = getWorld(); 
        
        //Get x and y values of current object
        int xPos = getX();
        int yPos = getY();
        
        //Change x and y values to the "new" location based on direction
        if(direction == 0){
            yPos -= 1;
        }
        else if(direction == 1){
            xPos += 1;
        }
        else if(direction == 2){
            yPos += 1;
        }
        else if(direction == 3){
            xPos -= 1;
        }   
        
        //Test for outside border
        if (xPos < 0 || xPos > 3 || yPos<0 || yPos > 3) { // no less than first index to 4 (index 3) for X & Y pos
            return 0;
        }
    
        
        //Check to see if there is a block in the way
        if(!world.getObjectsAt(xPos, yPos, GameSquare.class).isEmpty()) {                           // check world for spaces with blocks
            GameSquare block = (GameSquare)world.getObjectsAt(xPos, yPos, GameSquare.class).get(0);   
            return block.getValue();
        }
        return 1;
    }
    
    /**
     * displayValue - Displays the current value of a block in an image, then sets that image to the block display image
     */    
    private void displayValue() 
    {
        //Create an image consisting of the display value using built in greenfoot commands
        GreenfootImage displayImage;
        displayImage = new GreenfootImage( Integer.toString(value), 20, Color.BLACK, Color.WHITE);
        
        //Add the image as the new image for this object
        setImage(displayImage);
        if(this.value==2048)Greenfoot.stop(); //ends scenario once 2048 is reached
    }
}
GameBoard
import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.List;

/**
 * This is a simple game where you join together boxes of the same value and try to get to 2048
 * 
 * This assignment is to get you familar with workin in an OO environment and 
 * to get you used to accessing and using java documentation.  Below are two
 * useful links that will help you.
 * 
 * Greenfoot Javadoc:  https://www.greenfoot.org/files/javadoc/
 * Java 11 List Javadoc : https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html
 * 
 * I also talk about access control in the video and you might find this page
 * interesting to check out if you want to know more about different java
 * class types : https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html
 * 
 * Extra Videos: See the assignment for some extra video lessons that might help
 * 
 * @author C. Brooks-Prenger, X. XXXXXX
 * @version v1.0 - Template
 */
public class GameBoard extends World
{

    //Instance Constants
    
    /* Note: 
     * Since these variables are definied at STATIC they will never change (aka are constant).
     * Normally when we have constant variables like this we name them in ALL CAPS
    */
    //Define some directions to make it easier to reference which way the blocks are moving
    private static final int UP = 0;
    private static final int RIGHT = 1;
    private static final int DOWN = 2;
    private static final int LEFT = 3;
    
    static int blockCount = 0;
    
    /**
     * Constructor for objects of class MyWorld.
     * 
     */
    public GameBoard()
    {    
    // Create a new world with 600x400 cells with a cell size of 50x50 pixels.
    super(4, 4, 50); 
        
    //populate gameboard with some randomly placed objects
        

        while(blockCount<1) {
            if(placeRandomBlock()){
                blockCount++;
            } else {
                continue;
            }
        }
    }
    
    /**
     * Place a block on a random location on the board
     * 
     * @return Returns true if successful, false if not successful
     */
    public boolean placeRandomBlock()
    {
    //Generate Random Location (Hint: Is there anything to do with random values in the apis?)
        
    GameSquare gs = new GameSquare();           //create a new game space
    int x = (Greenfoot.getRandomNumber(4));     //This method returns a random number between 0 and 3 in the x axis.
    int y = (Greenfoot.getRandomNumber(4));     //Same method for y axis (4 x 4 grid)
        
    //Check to ensure random location is not yet taken, if the spot is free add it to the world
    //(Hint: Is there any way to figure out if an object is somewhere in the world in the apis?)
        
    if(getObjectsAt(x, y, GameSquare.class).isEmpty()) { //The isEmpty() method checks whether a string is empty or not.
            addObject(gs, x, y);                             // adds object 
            return true;
        } else {
            return false;
    }
    
    }
    
    /**
     * Act - Check for key presses and tell each block to move itsself.
     */
    public void act() 
    {
        //Add key press actions here
        String key = Greenfoot.getKey();
        
        //If a key was pressed...do something
        if (key != null) {
            
            //Note: you should disable this, but I wanted to show how you can debug in greenfoot
            System.out.println(key);  
            
            switch(key) {
                case "up": 
                    //Tell the blocks to move up
                    //Start checking from the top, then move downwards

                    for (int i =0; i< getWidth(); i++) {
                        for (int j=0; j<getHeight(); j++) {
                            //Get a list containing all of the GameSquare objects at position (i,j)
                            List blockList = getObjectsAt(i,j,GameSquare.class);
            
                            //Move the block in the direction needed
                            if (blockList.size() == 1) { //Error checking - Might want to deal with the case where this isn't true (ie, something went very wrong)
                                //Create a temporary holding space for a generic object
                                Object tempObject; //Get the first (and only) entry in the list 
                                tempObject = blockList.get(0);
                                //Create a temporary holding space for the gameSquare object
                                GameSquare tempSquare;
                                //Convert it from the generic "Object" to a GameSquare Object
                                tempSquare = (GameSquare)tempObject;  
                                //Then move UP.  
                                tempSquare.move(UP);
                                
                                //The above few lines of code is NOT how I would normally write this.
                                //You could accomplish all of the ablove using the single line of below
                                //It can be a bit confusing when code is all in one line.  Is this considered good form or bad form?
                                //( (GameSquare)( blockList.get(0) )).move(UP);  
                            }
                        }
                    }
                    break;
                //NOTE: The remaining cases are similar to the one above, but not exactly the same
                case "right":
                    //Tell the blocks to move right
                    //Start checking from the right most col, then move left                    

                    for (int i = 3; i >= 0; i--) { // iterate from right
                        for(int j = 0; j < getHeight(); j++) {
                            List blockList = getObjectsAt(i, j, GameSquare.class);
                            if (blockList.size() == 1) {
                                //move RIGHT.
                                ((GameSquare)(blockList.get(0))).move(RIGHT);
                            }
                        }
                    }
                
                    break;

                case "down":
                    //Tell the blocks to move down
                    //Start checking from the bottom, then move up
                    for (int i = 3; i >= 0; i--) {
                        for(int j = 3; j >= 0; j--) {
                            List blockList = getObjectsAt(i, j, GameSquare.class);
                            if (blockList.size() == 1) {
                                // move DOWN


                                ((GameSquare)(blockList.get(0))).move(DOWN);
                            }
                        }
                    }
                    break;

                case "left":
                    //Tell the blocks to move left
                    //Start checking from the left, then move right
                    for (int i = 0; i <= 3; i++) { //iterate through x-axis 
                        for(int j = 0; j < getHeight(); j++) {
                            List blockList = getObjectsAt(i, j, GameSquare.class);
                            if (blockList.size() == 1) {
                                // move LEFT
                                ((GameSquare)(blockList.get(0))).move(LEFT);
                            }
                        }
                    }
                    break;

                }

            //Since placeRandomBlock is not guaranteed to work the first time, repeat the process until it does (Video)
            //This is NOT a great way to do this.  (Hint: Making this part better is a part of level 4)
            int count = 0;
            if(numberOfObjects() < 16) { // numberOfObjects method fetches the number of obj currently in the world
                while (count < 1) {
                    if (placeRandomBlock()) {
                        count++;
                    }
                } 
            }
            
        }
    }
}
Super_Hippo Super_Hippo

2022/3/12

#
The code looks really messy, but the error comes because you remove the actor from the world (line 90 in GameSquare) and then proceed to continue calling getX and other methods on it. Some comments are also simply wrong. For example the comment regarding the keyword static. “static” has nothing to do with being a constant or unable to change. That’s what “final” is doing. Question: Why do you want to remove both GameSquares and add a new one in the same location? You could just remove one and keep the other. After you change the value, you only need to call the displayValue method, so the image with the new number is updated.
You need to login to post a reply.