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