I am working on a project that creates a simple text-based adventure game. It's a little complicated, but basically there are about eight rooms in the game, each with a certain item. You can only move in certain directions depending on the room, and the goal is to get to the last room. I have a class called Item, a class called Room, and then the main Game class.
Anyway, I'm having some trouble with my checkForItem and drop methods at this point. I have to check my itemsHeld ArrayList for a certain item, but whenever I type in "item" it says it can't find the symbol item. Similar issues arise with my drop method. Any suggestions? Here is the (very lengthy) code.
import java.util.ArrayList;
public class Game
{
private ArrayList<Item> itemsHeld = new ArrayList<Item>();
private Room planetHarvest;
private Room planetReach;
private Room alphaHalo;
private Room newMombasa;
private Room deltaHalo;
private Room voi;
private Room theArk;
private Room forerunnerShieldWorld;
private Item DMR;
private Item Launcher;
private Item Shotgun;
private Item Health;
private Item Key;
private Room currentLocation;
private String msg;
public Game(){
createRooms();
ArrayList<Item> itemsHeld = new ArrayList<Item>(0);
currentLocation = planetHarvest;
}
private void createRooms(){
DMR = new Item("DMR", "The Designated Marksman Rifle fires a medium-range shot that defeats one enemy at a time.",
10, false);
Launcher = new Item("Launcher", "The rocket launcher fires a long-range rocket that defeats two enemies at a time.",
30, false);
Shotgun = new Item("Shotgun", "The shotgun fires a short-range buckshot blast that defeats one enemy at a time.",
10, false);
Health = new Item("Health", "The health pack heals you if you've been attacked.", 10, true);
Key = new Item("Key", "The Key is the secret to saving the galaxy.", 10000, false);
planetHarvest = new Room("a farming colony at the edge of human-controlled space. Though usually quiet, it has now been disturbed by an invasion from extraterrestrials who call themselves the Covenant. The Covenant will not let you escape without a fight.", DMR);
planetReach = new Room("twenty-seven years in the future. The Covenant has invaded every human colony world and bombarded each one with plasma, reducing the surface to molten glass. Planet Reach is the only human colony that remains. The aliens know their campaign to exterminate humanity is almost over and they show it in their ferocity on the battlefield.", Launcher);
alphaHalo = new Room("a strange, alien, planet-sized ring following your escape from the doomed planet Reach. The inside of the mysterious non-Covenant hoop structure has continents, oceans, ecosystems, breathable atmosphere – and two relentless foes. The Covenant has arrived; so has the Flood, a terrifying alien parasite that will eat both you and the Covenant. On top of that, you learn that this Halo is not just a habitat but also a weapon that will destroy all life in the galaxy if you don’t stop it.", Key);
newMombasa = new Room("an African city on Earth, humanity's last safe haven in the universe. A Covenant invasion force has landed. You’ll need to fend them off and follow them to wherever they’re headed next.", Health);
deltaHalo = new Room("where the Covenant and the Flood are already waiting. The same rules from Alpha Halo apply here, but now you learn that there are a total of eleven Halos and that the only way to stop them all is to shut them down from a structure outside the galaxy called the Ark. Meanwhile, the Covenant is still attacking Earth.", Key);
voi = new Room("The Flood has arrived and is infecting humans and aliens; meanwhile, the Covenant is in civil war and some of the aliens are helping you. Following your escape from Delta Halo, all of the Halos in the galaxy have been put on standby, so if you don’t find the Ark soon then it’s the end of life as we know it.", Shotgun);
theArk = new Room("a massive installation many times bigger than even a Halo. Shutting down the Ark will mean saving the galaxy. However, most of the Covenant and the Flood are still after you. Finish the fight.", Key);
forerunnerShieldWorld = new Room("an artificial planet made by a super-advanced alien civilization that disappeared 100,000 years ago – the makers of the Halos and the Ark. The war against the Covenant is finally over, the Flood has been defeated, and the galaxy is safe. The mysteries of the Forerunners, however, are just beginning to be unraveled.");
planetHarvest.addNeighbor("Slipspace Forward", planetReach);
planetReach.addNeighbor("Slipspace Out", alphaHalo);
alphaHalo.addNeighbor("Slipspace Back", planetReach);
alphaHalo.addNeighbor("Slipspace Forward", newMombasa);
newMombasa.addNeighbor("Slipspace Out", deltaHalo);
newMombasa.addNeighbor("Forward", voi);
deltaHalo.addNeighbor("Slipspace Back", newMombasa);
voi.addNeighbor("Slipspace Out", theArk);
voi.addNeighbor("Back", newMombasa);
theArk.addNeighbor("Slipspace Unknown", forerunnerShieldWorld);
}
private void setWelcomeMessage(){
msg = "You are John-117, an augmented soldier fighting for Earth and its colonies in the year 2552. You battle the Covenant, an alien religious alliance, and the Flood, an alien parasite that infects both humans and Covenant and turns them into zombies. Your battles take you to planetary colonies like Harvest and Reach, ancient alien ring-worlds called Halos, futuristic cities in Africa, and the Halos’ control station called the Ark, located outside the galaxy. The goal of the game is to defeat all enemies and make it to the safety of the Forerunner Shield World.";
}
public String getMessage(){
return msg;
}
public void help(){
msg = "Every place you enter is crawling with Covenent and/or Flood. You'll have to neutralize them before they neutralize you. As a Reclaimer, you're the only one who can use Keys on a Halo or Ark. Some places involve more than one mission.";
}
public void look(){
msg = currentLocation.getLongDescription();
}
public void move (String direction){
Room nextRoom = currentLocation.getNeighbor(direction);
if (nextRoom == null){
msg = "You can't go there.";
}else{
currentLocation = nextRoom;
msg = currentLocation.getLongDescription();
}
}
public void list(){
if(itemsHeld != null){
msg += itemsHeld;
}else{
msg = "You are not holding anything.";
}
}
public boolean gameOver(){
if(currentLocation == forerunnerShieldWorld){
msg = "You defeated the Covenant and survived the war. You won!";
}
return false;
}
public void pickup(){
if(currentLocation.hasItem()){
if(currentLocation.getItem().getWeight() < 50){
msg = "You are now holding the" + currentLocation.getItem().getName();
itemsHeld.add(currentLocation.getItem());
currentLocation.removeItem();
}else{
msg = "This item is too heavy to pick up.";
}
}else{
msg = "There is no item to pick up.";
}
}
private Item checkForItem (String name){
if(itemsHeld.contains(name)){
return ();
}else{
return null;
}
}
public void drop (String item){
if(itemsHeld.contains(item)){
itemsHeld.remove(item);
currentLocation.addItem;
}
}
}
Item is the type in this case. Not the variable name, which is what you want. You have several variables of type Item, but no variables named item.
Also, you have no field named itemsHeld. In your constructor you create a local variable with that name, but a variable by that name on the class is not defined.
Add
private ArrayList<Item> itemsHeld
to your field declarations, and in the constructor do
itemsHeld = new ArrayList<Item>(0);
to initialize it.
Or just do
private List<Item> itemsHeld = new ArrayList<Item>(0);
in your field declarations...
if(!PlayerLocation.hasItem()){
msg = "The player does not have that Item";
}
if(PlayerLocation.hasItem()){
for(Item lookfor: ItemsHeld){
String result = lookfor.getName();
if(result.contains(PlayerLocation.getItem().getName())){
msg = "The room already has an item " +
PlayerLocation.getItem().getName() +
".";
}
}
}
else {
for(Item lookfor: ItemsHeld){
String result = lookfor.getName();
if(result.contains(name)){
dropped = lookfor;
ItemsHeld.remove(lookfor);
PlayerLocation.addItem(dropped);
msg = "the player has successfully" +
"dropped the item in the room";
}
}
}
Idk if we have the same java teacher, but I was googling around
and browsing the interwebs for some answers and came accross your question.
Mine method works for droping the item; however, I'm degubbing my code for if the array list is empty, and has no items for it.
Related
I am trying to make an AI to play simple Snake game following this article. With my current solution snake is able to come up to score of around 35 points. For some reason it will handle the walls correctly but would almost every time die by hitting its own tail.
I am not able to pinpoint my mistake, but I assume its the way I use fit function on the neural network.
for (int i = 0; i < EPOCHS; i++) {
epsilon = 0.9;
board.initGame();
State state = board.getState();
while (state.isInGame()) {
// Get next action to perform
final Action action = epsilonGreedyAction(state);
// Play action and get reward
final double reward = board.getRewardForAction(state, action);
// Get next state
final State nextState = board.getState();
// Update neural network
update(state, action, reward, nextState);
// Apply next state
state = nextState;
}
}
private Action epsilonGreedyAction(final State state) {
epsilon -=0.001;
double random = SnakeUtil.getRandom();
if (random < epsilon) {
randomActions++;
return Action.randomAction(state);
}
return SnakeUtil.getMaxQAction(state, Q_TABLE);
}
getMaxQAction will return any action that is in the Q_TABLE for this state that has had high reward up until now (max reward).
Q_TABLE is updated also in the update method
private void update(final State state, final Action action, final double reward, final State nextState) {
MaxQ maxQ = SnakeUtil.getMaxQ(nextState, Q_TABLE);
double targetReward = reward + (0.90 * maxQ.getReward());
SnakeUtil.updateQTable(state, action, targetReward, Q_TABLE);
net.fit(buildObservation(state).getData(), Nd4j.create(fromAction(action)));
}
private double[][] fromAction(final Action action) {
return new double[][] {
{
action.equals(Action.UP) ? 1 : 0,
action.equals(Action.RIGHT) ? 1 : 0,
action.equals(Action.DOWN) ? 1 : 0,
action.equals(Action.LEFT) ? 1 : 0
}
};
}
private Observation buildObservation(final State state) {
return new Observation(Nd4j.create(new boolean[][]{
{
state.isCanGoUp(),
state.isCanGoRight(),
state.isCanGoDown(),
state.isCanGoLeft(),
state.isFoodUp(),
state.isFoodUpRight(),
state.isFoodRight(),
state.isFoodDownRight(),
state.isFoodDown(),
state.isFoodDownLeft(),
state.isFoodLeft(),
state.isFoodUpLeft()
}
}));
}
My question is if the fit method is receiving correct parameters. If yes then my problem must be somwhere else.
In addition reward is calculated in a way that every time when snake steps in direction of the food its rewarded with 1, otherwise it's punished with -1.5. If it eats the food it's rewarded with 30.
Any hints and suggestions are welcome.
I have a game template which has two classes as its levels.
The game has a player, enemies, treasure chests, and walls.
When I run the program, it loads Level 1, successfully. I have designed the code so if a certain condition is met, Level 2 must load. Now, Level 2 loads. But certain elements from Level 1 remain on screen, ignoring certain elements from Level 2.
To be exact:
From Level 2, it loads the player's initial coordinates, the treasure chests, and the final destination, ignoring the enemies and the walls.
It brings with itself from Level 1 to Level 2: the walls, the treasure chests, and the enemies. The latter two are in a frozen state. They don't function.
//Instance variables:
private int lvlCount = 1;
// Game() method
public Game(Stage stage) {
this.score = new SimpleIntegerProperty(0);
this.mobs = new ArrayList<>();
this.hitBoxes = new ArrayList<>();
this.treasure = new ArrayList<>();
this.newLevel = new ArrayList<>();
this.enemyCount = 0;
bg();
loadLevel(1);
living();
stuff();
mob2();
finalText();
stage.setOnShown(e -> this.run());
}
//The switch for loading the Levels:
void loadLevel(int in) {
switch (in) {
case 1:
this.level = new Level1();
break;
case 2:
this.level = new Level2(); // placeholder for second level
break;
}
hitBoxes.clear(); // clear the list of hitboxes
mobs.clear(); // remove any existing old mobs
this.getChildren().addAll(hitBoxes); // add all the hitboxes for the walls to the scene
background.setImage(level.getImage()); // get the background image
hitBoxes.addAll(level.getWalls()); // get all the wall hitboxes
enemyCount = level.getEnemyCount(); // get the enemy count from the level
chestCount = level.treasureCount(); // get the treasure count from the level
this.initTreasure(chestCount); // add the treasure chests for the level
this.initMobs(enemyCount); // initialize our mobs
lvlCount = level.lvlCount();
this.initLevel(lvlCount);
}
//The collision check:
private void collisionCheck() {if (r.getFill().equals(Color.WHITE)){lvlCount--;}
//Main Game loop method:
private void play() {
AnimationTimer gameLoop = new AnimationTimer() {
public void handle(long arg0) {
for (int i = 0; i < mobs.size(); i++) {
MOB m = mobs.get(i);
if (m instanceof Enemy) {
((Enemy) m).moveCheck(arg0);
}
}
collisionCheck();
if (lvlCount == 0){
loadLevel(2);} //The condition that loads Level 2
gameLoop.start();
}
Please help me to figure out how I can load Level 2, successfully.
Please note that it does not produce any errors in the console. So, I am having a very hard time figuring out where the problem lies.
I only pasted the code that I thought is necessary to achieve my goal. Here you can see the full code, for veiwing purposes only, no download necessary:
http://paste.mooc.fi/955c6d30
Thank you very much in advance.
I am trying to add a user submitted question to a String[] in a separate activity.
Im about two weeks in on learning app development with android and any help would be greatly appreciated.User submission page
how do I add the "enter text here" to the public String[] questions = new String []
what I have so far is
--Submission Page--
EditText userText;
EditText drunkOrKid;
private RadioGroup radioGroup;
private RadioButton radioButton;
private Button btnDisplay;
public void onRadioButtonClicked(View view) {
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
RadioButton radioDerek = (RadioButton) findViewById(R.id.radioDrunk);
RadioButton radioStephen = (RadioButton) findViewById(R.id.radioKid);
// Is the current Radio Button checked?
boolean checked = ((RadioButton) view).isChecked();
switch (view.getId()) {
case R.id.radioDrunk:
if (checked)
drunkOrKid.setText("true");
break;
case R.id.radioKid:
if (checked)
drunkOrKid.setText("false");
break;
}
}
-- QUESTION storage page --
public class questions extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_questions);
}
public String[] questions = new String [] {
"I went to go see an animated film in a movie theater. I screamed warnings to the main character for the duration of the movie, because I feared if I didn't she wouldn't realize the danger she was in.",
"I raced a friend for shotgun (front seat) of the car and they slammed open the door on my head and split my forehead open",
"Late at night, I got up and ran around screaming about how I needed the \"sticky notes\" and then proceeded to vomit.",
"Chugged orange cough syrup because I thought it was OJ",
"I peed in the middle of the high way while no cars were passing by.",
"I dropped four cookies into a grand piano at a college recital.",
"I broke into a safe because I wasn't in a mindset where I realized it was illegal. I then told the people whose safe it was \"I broke into your safe!\"",
"Shot myself in the hand with an arrow. Didn’t notice until I looked down at my hand. Yanked it out, ran inside yelling about there was a hole in my hand.",
"At a friend's house. Ended up naked under the table, crying, because I didn't want to go home.",
"I woke up in the dead of night, walked downstairs, out the back door, and peed on the swingset in the backyard",
"Thought about something that was supposed to be kept a secret during a conversation. Didn't actually tell anyone. But began panicking over the fact that I might have. Eventually got so stressed I had to leave the conversation altogether, leaving my peers very confused.",
"I ran down the street in just my underwear. I ran around for about 30 minutes before someone caught me and took me back to my house.",
"Took off my pants and got naked in the middle of a bunch of people. Didn't realise I'd done anything wrong until I was *told* so.",
"I went to a brand new shopping center in my town, i decided to fuck around and play in the fountain, where i proceeded to fall in.",
"Went into a store to buy something after being to the super market, but tried to pay with cookies instead of cash",
"Decided I could most certainly jump over 3 of my friends on the stairs, fell, broke a bone and cried until I was carried to the car",
"got attached to ball of string and cried for 3 hours straight after leaving it behind",
"Nearly started a house fire by spraying aerosol deodorant at the flame of a candle to see what it would do and paper was directly behind it",
"Nearly suffocated while hanging by the collar of my t-shirt from a high fence. Afterwards claimed to look like a frog for the whole situation.",
"After a football(soccer) game where I was the keeper and couldn’t keep any goals I fell asleep on a small wooden bench in a locker room filled with kids changing clothes while there was loud hardcore music playing.",
"Was with my brother one night and he claimed I hit him in the head with a 2x4 and when I was asked why I hit him with a 2x4 I said I didn’t hit him with a 2x4... I hit him with a 2x6",
};
public boolean[] answers = new boolean[]{
true, false, true, false, true, false, false, false, true, false, true, false, false, false, true, false, false, true, false, true, false,
};
}
]
Your string array already has a set size. Why not use Collections instead? Say, an ArrayList? you can add new objects to the List.
I would recommend changing array to a list or array list and make it static in that class.
ArrayList<String> questions = new ArrayList<>();
questions.add("");
questions.add("");
...
Once you define an array, you cant add more values to it so you need a more collections oriented object. If you make it static in that class, and some static functions for manipulation. You can add new values, or get the current values from anywhere in your app.
If you are just using that class for storing arrays of data you dont need all that activity stuff.
Is it possible to change an image in the imageView depending on the outcome of the text inside a textView.
For example if I have:
public void pourfive(View v){
TextView statusupdate = (TextView) findViewById(R.id.txt_statusupdate);
TextView fivecup = (TextView) findViewById(R.id.txt_fivecup);
TextView threecup = (TextView) findViewById(R.id.txt_threecup);
if (fivecup.getText().toString().equals("0")){
threecup.setText("0");
statusupdate.setText("You have no water in the 5 litre cup to pour!");
}
else if (threecup.getText().toString().equals("0") && (fivecup.getText().toString().equals("5"))){
fivecup.setText("2");
threecup.setText("3");
statusupdate.setText("You poured 3 litres into the 3 litre cup!");
}
In this instance I have a cup that can contain 1,2,3,4,5 different levels of coffee. If i wanted to have an image change to represent each one of these different levels. I think it might start something like this:
public void cupLevel(View v){
TextView threecup = (TextView) findViewById(R.id.txt_threecup);
threecup.getText();
}
I then think I would need some if and if else statements depending on the threecup number and some way to call the relevant image but I'm not sure how to call the required image.
Any help would be greatly appreciated thanks.
There are many ways you could approach this.
One of the simplest to understand if you're just starting out learning to program, would be to have a bunch of images for different levels of the cup. For example:
// Sets the image for the three cup, based on the fill level supplied
public void setThreeCupImage(int fillLevel) {
// make an int array containing the relevant images.
int[] threeCupLevels = {R.drawable.threecup_level_1,
R.drawable.threecup_level_2,
R.drawable.threecup_level_3};
// get reference to an ImageView you've put in you layout xml.
ImageView threeCupImage = (ImageView)findViewById(R.id.image_threecup);
// set the ImageView to display the required image from the array.
threeCupImage.setImageResource(threeCupLevels[fillLevel]);
}
Some tips regarding how you've written your other code...
Hard coding mathematical results is not a good idea as you have done here:
else if (threecup.getText().toString().equals("0") && (fivecup.getText().toString().equals("5"))){
fivecup.setText("2");
threecup.setText("3");
This is inflexible and quickly becomes bulky and complicated in larger problems. Try and make you code more general. Computers are good at maths! Let them do the maths for you!
For example, think about what the main variables are in this problem. They are:
The sizes of the cups
The levels they are filled to
A more general way to code your problem may be to first define a Cup class:
public class Cup {
// public fields of the class
public final int cupSize; // this is final because a cup does not change size.
public int cupLevel; // this is NOT final because level can vary
// class constructor
public Cup(int size, int level) {
cupSize = size;
cupLevel = level;
}
}
Then you can create cups and change their contents as needed:
// create a new cup with a size of 5 and fill level of 0 (empty)
Cup fiveCup = new Cup(5, 0)
// create a new cup with a size of 3 and fill level of 3 (full)
Cup threeCup = new Cup(3, 3)
// change the level of fiveCup
fiveCup.cupLevel = 2;
You could then write a method for pouring from one cup to another which can handle cups of any size or fill level:
public void pourCup(Cup source, Cup target) {
int spaceInTargetCup = target.cupSize - target.cupLevel;
if (source.cupLevel <= spaceInTargetCup) {
// there's room to pour all the contents of the source cup into the target cup
target.cupLevel += source.cupLevel; // add the contents of source to the target
source.cupLevel = 0; // the source is now empty
} else {
// there's not enough room to pour all the contents of the source in
source.cupLevel -= spaceInTargetCup; // empty as much as you can from the source
target.cupLevel = target.cupSize; // the target is now full
}
}
To pour the contents of threeCup into fiveCup you would simply call:
pourCup(threeCup, fiveCup);
...and to pour the contents of fiveCup into threeCup would obviously be:
pourCup(fiveCup, threeCup);
You can easily modify the method to let the user know what is going on with statements like:
statusupdate.setText("There is room to pour " + spaceInTargetCup + " litres into the " +
target.cupSize + " litre target cup");
i seek if somebody could help me out with my room searching algorithm
I'm trying to implement a backtracking algorhitm for maze solving. I'm stuck in the place where i should memorize the rooms which i have already visited.
The maze is made up from rooms and each room has 4 sides - north, east, south and west. Each room is linked to next room by making a door to desired side, i.e room1.createNorth(roomName) which creates a new room up north and a new room has southern door to link back to the first as you can see in my Room class.
Here is my chopped room class, which represents each room in a maze. I removed south, west and east directions which are identical to my methods which deal with northern side.
public class Room {
private String name;
private Room north;
private Room east;
private Room west;
private Room south;
private boolean isExit = false;
private Maze maze;
/**
* #return name room
*/
public String getName() {
return this.name;
}
/**
* Sets room name
*
* #param name
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets northern room if any
*
* #return pointer to northern room if any, otherwise <code>null</code>
*/
public Room getNorth() {
return this.north;
}
/**
* Sets the door to the next room to the north in that room and in the other
* room sets southern door as connecting back to that room
*
* #param otherRoom
*/
public void setNorth(Room otherRoom) {
this.north = otherRoom;
otherRoom.south = this;
}
/**
* creates a new room to the north and connects back to this room
*
* #param name
* of the room
* #return created room
*/
public Room createNorth(String name) {
Room otherRoom = null;
// create new room in that direction ONLY if there is no room yet
if (this.getNorth() == null) { // get northern direction, if it's null,
// then it's okay to create there
otherRoom = new Room(); // create!
this.setNorth(otherRoom); // set the door
otherRoom.setName(name); // set the name
} else { // there is a room in that direction, so don't create a new
// room and drop a warning
System.out.println("There is already a room in northern direction");
}
return otherRoom;
}
/**
* Asdf
*
* #return maze
*/
public Maze getMaze() {
return this.maze;
}
/**
* Set maze
*
* #param maze
*/
public void setMaze(Maze maze) {
this.maze = maze;
}
/**
* #param roomName path to this room must be found
*/
public void findPathTo(String roomName) {
Room soughtRoom = this.getMaze().getEntry();
while (!(soughtRoom.getName().equalsIgnoreCase(roomName))) {
// here should be also a method such as setRoomAsVisited()
if (this.getWest() != null) {
soughtRoom = this.getWest();
this.getMaze().getPaths().push(soughtRoom);
}
else if (this.getNorth() != null) {
soughtRoom = this.getNorth();
this.getMaze().getPaths().push(soughtRoom);
}
else if (this.getEast() != null) {
soughtRoom = this.getEast();
this.getMaze().getPaths().push(soughtRoom);
}
else if (this.getSouth() != null) {
soughtRoom = this.getSouth();
this.getMaze().getPaths().push(soughtRoom);
}
else {
if (this.getMaze().getPaths().isEmpty()) {
break; // no more path for backtracking, exit (no solution found)
}
// dead end, go back!
soughtRoom = this.getMaze().getPaths().pop();
}
System.out.println(this.getMaze().getPaths().toString());
}
}
#Override
public String toString() {
return "Room name is " + this.getName();
}
}
Maze looks like this: http://i.stack.imgur.com/2KePs.jpg where S is the start point
My Maze class
public class Maze {
Room room;
/**
* helper collection path stack for findPathTo() method
*/
private Stack<Room> paths = new Stack<Room>();
/**
* #return path for exit
*/
public Stack<Room> getPaths() {
return this.paths;
}
/**
* Singleton method for first room in the maze which is entry room
*
* #return room if no room is created then creates new, otherwise returns
* already created room
*/
public Room getEntry() {
if (this.room == null) {
this.room = new Room();
return this.room;
}
return this.room;
}
}
Here is my main class
public class Main {
public static void main(String[] args) {
Maze maze = new Maze();
maze.getEntry().setName("A4"); // set first room's name A4
// labyrinth creation
maze.getEntry().createEast("B4").createNorth("B3").createWest("A3");
maze.getEntry().getEast().getNorth().createNorth("B2").createWest("A2")
.createNorth("A1");
maze.getEntry().getEast().getNorth().getNorth().createNorth("B1");
maze.getEntry().getEast().getNorth().getNorth().createEast("C2")
.createNorth("C1").createEast("D1");
maze.getEntry().getEast().createEast("C4").createEast("D4");
maze.getEntry().getEast().getEast().createNorth("C3").createEast("D3")
.createNorth("D2").setExit(true);
System.out.println("=====Test findPathTo method======");
maze.getEntry().setMaze(maze); // set maze instance to our entrance room
maze.getEntry().findPathTo("B4");
System.out.println("=====End of testing findPathTo method======");
}
}
The problem is in my findPathTo(roomName) method, which finds the route to the room.
If i enter the room D4 then my algorithm moves only once to east to "B4" room from "A4" and there it just loops infinitely and the stack just grows with room "B4" only. Why doesn't it move ahead for example to next room "B3" or "C4" ?
EDIT: here is the working code
public void findPathTo(String roomName) {
Room soughtRoom = this.getMaze().getEntry();
while (!(soughtRoom.getName().equalsIgnoreCase(roomName))) {
if (soughtRoom.getWest() != null && soughtRoom.getWest().isVisited != true) {
this.getMaze().getPaths().push(soughtRoom);
soughtRoom = soughtRoom.getWest();
soughtRoom.isVisited = true;
}
else if (soughtRoom.getNorth() != null && soughtRoom.getNorth().isVisited != true) {
this.getMaze().getPaths().push(soughtRoom);
soughtRoom = soughtRoom.getNorth();
soughtRoom.isVisited = true;
}
else if (soughtRoom.getEast() != null && soughtRoom.getEast().isVisited != true) {
this.getMaze().getPaths().push(soughtRoom);
soughtRoom = soughtRoom.getEast();
soughtRoom.isVisited = true;
}
else if (soughtRoom.getSouth() != null && soughtRoom.getSouth().isVisited != true) {
this.getMaze().getPaths().push(soughtRoom);
soughtRoom = soughtRoom.getSouth();
soughtRoom.isVisited = true;
}
else {
if (this.getMaze().getPaths().isEmpty()) {
System.out.println("No solutions found :(");
break; // no more path for backtracking, exit (no solution found)
}
// dead end, go back!
soughtRoom = this.getMaze().getPaths().pop();
}
System.out.println("Path rooms: " + this.getMaze().getPaths().toString());
}
}
There are several ways of doing this.
One would be to keep a "isVisited" boolean flag in each room object that you set/unset as your tracking and backtracking proceeds. This means taht you can only single-thread-search your maze (this may or may not matter).
Another would be to keep a list of visited rooms that you compare to (advantage here is that it should be relatively easy to just "push" a new room onto the list and have it popped automatically if you use the call-stack to pass this list).
You could also use a per-search hashtable of room to an isVisible, this allows for (possibly) faster lookups than searching a list, allows for multi-threading (as in "more than one thread can search the maze", not "more than one thread can participate in the same search").
There's also probably a few things I haven't thought of, but all three of these should be pretty straight-forward to implement.
A quick and highly unoptimized approach:
For each visited room, store the possible directions (make an enum Direction and a Set<Direction> for example) and remove the one you came from as well as the direction you took from the previous room. Thus, moving from A4 to B4 you'd remove EAST from A4 and WEST from B4. If you have to track back, just unwind the stack until you find a room with an unvisited direction (the list of possible directions is not empty) and try the next one. If the stack becomes empty at this point, you tried all possibilities without finding the target room.
As I said this is highly unoptimized, but it should work.
Some comments:
Your code is missing some functions you are using. There is no getPaths() method in your maze class. If you post code online, try to make sure it is easily compilable and testable. In your case I would have to guess, that getPaths() returns some kind of stack on which you try to store possible paths to be explored, but there is no way to be sure, what it actually does.
Also try to simplify the code before posting. Your maze is rather complicated and one would have to draw it, to see if your constructed maze matches the one on the picture. Try if you can reproduce the problem with a much simpler maze (2 or 3 rooms maybe). Also simplifying will often give you many hints as to where the problem might be. While you are simplifying there will be a point at which the problem will suddenly disappear. This tells you a lot about the actual bug.
Some Ideas on what might be wrong with your Code:
At each direction you set soughtRoom to the one in that direction. I am assuming soughtRoom is the room your search is currently at. Then you push that room on the stack. However for a backtracking you need to store at each branch all the information that brings you back to a previous state. If you first set the current room and then push it, the information from the previous state is lost. Try it the other way round and see what happens.