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:
GameBoard
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 } }
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++; } } } } } }