text adventure/interactive fiction in java

1.8k views Asked by At

I decided to create an account in order to ask a question I cant seem to figure out myself, or by some googling, hopefully I didn't just overlook it.

Essentially I am trying to make a text adventure game in Java, and am having a little trouble seeing how I should relate everything in the idea of objects. I have been successful in using XML stax and sending a file to the program, and using attributes and what not, to make it where the user can enter an integer associated with an option, and see if option requires an "item" or gives them an Item. I however did not take an OOP to this.

I want my new program to people able to take a string of user input in, instead of only an integer, and checking it against an array list if it exists. This is closer to the classic MUDs most may be familiar with.

I want to design it in a modular way, so I can slowly add on ideas, and more complexity to go along, so I don't want a "well it works so lets leave it alone" approach either.

Currently I simply want something close to this:

A Room object, which would have: an ID, Description, and interact-able a Choice object (this one im not sure on) I thought about making an object to hold each rooms possible choices, both for exit, and for interact-ables

if so, the room object may need a Choice Object.

I've thought it over, tried some code, thought it over again, and every time, I keep ending up hard coding more than I feel I should, and making tons more variables than I feel are necessary, which makes me feel like i'm missing something crucial in my thinking.

I also want these rooms to be created through an inputted file, not generated in the code (so essentially the code is a story reader/crafter for any type, not one)

I have also been attempting this too long, and my solutions are becoming worse, but below was my most recent attempt at a rough Idea:

a GameManager class that takes the userInput and checks it some, before passing it along. I havent passed any data because im not sure of the approach. also im not used to regex, so some of that may also be wrong, if it is, maybe point it out, but that is not my focus

import java.util.Scanner;

public class GameManager {

private static final String EXIT_PHRASE = "exit";
public static void main(String[] args) {

    Scanner userInput = new Scanner(System.in);
        String userStringVal = "";
        while(!userStringVal.equals(EXIT_PHRASE)){
            userStringVal= userInput.nextLine();
            if(checkKeywords(userStringVal)){
                System.out.println("matches keyword");
            }
            else System.out.println("didnt match a keyword");
        }
    userInput.close();
}

public static boolean checkKeywords(String string){
    boolean isKeyword = false;
    string.toLowerCase();
    if(string.matches("travel.*") || string.matches("search.*")){
        System.out.println("passed first check");
        String substring = string.substring(6);
        if(matchDirection(substring)){
        isKeyword = true;   
        }
    }

    return isKeyword;
}

public static boolean matchDirection(String string){
    boolean hasDirection = false;
if(string.matches(".*\\bnorth|south|east|west|northeast|northwest|southeast|       southwest|up|down")){
        hasDirection = true;
}
    return hasDirection;
}
}

The Room object I thought about as such:

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

public class Room {

private String roomDescription = "";
private int roomID=0;
private int northExit=0;
private int southExit=0;
private int eastExit=0;
private int westExit=0;
private int northeastExit=0;
private int northwestExit=0;
private int southeastExit=0;
private int southwestExit=0;
private int upExit=0;
private int downExit=0;
private String[] interactables = new String[10];
private Options options = new Options();

public Room(XMLStreamReader reader) throws XMLStreamException{
    setAttValues(reader);
    setRoomDescription(reader);
    setUpOptions();
}

public void setinteractables(XMLStreamReader reader){
    int count = reader.getAttributeCount();

    for(int i = 0; i < count; i++){
        interactables[i] = reader.getAttributeValue(i);
    }
}

public void setAttValues(XMLStreamReader reader){
    int count = reader.getAttributeCount();
    for(int i = 0; i < count; i++){
        String att = reader.getAttributeLocalName(i);
        if(att !=""){
        switch(att){
        case "North": northExit=Integer.parseInt(att);

        case "South": southExit=Integer.parseInt(att);

        case "East": eastExit=Integer.parseInt(att);

        case "West": westExit=Integer.parseInt(att);

        case "NorthEast": northeastExit=Integer.parseInt(att);

        case "NorthWest": northwestExit=Integer.parseInt(att);

        case "SouthEast": southeastExit=Integer.parseInt(att);

        case "SouthWest": southwestExit=Integer.parseInt(att);

        case "Up": upExit=Integer.parseInt(att);

        case "Down": downExit=Integer.parseInt(att);

        case "ID": roomID=Integer.parseInt(att);
        }
        }
    }
}

public void setRoomDescription(XMLStreamReader reader) throws XMLStreamException{
    roomDescription = reader.getElementText();
}

public void setUpOptions(){
options.setCardinalPointers(northExit, southExit, eastExit, westExit);
options.setIntercardinalPointers(northeastExit, northwestExit, southeastExit, southwestExit);
options.setElevationPointers(upExit, downExit);
}
}

what can I do to make sure I dont have to state so many directions with so many variables?

here is a quick and rough idea of an Option class that I thought about, but i didn't finish deciding I am already too far in the wrong direction

public class Options {

private int northPointer = 0;
private int southPointer= 0;
private int eastPointer = 0;
private int westPointer = 0;
private int northeastPointer= 0;
private int northwestPointer = 0;
private int southeastPointer = 0;
private int southwestPointer = 0;
private int upPointer = 0;
private int downPointer = 0;
private String northInteractable = "";
private String southInteractable = "";
private String eastInteractable = "";
private String westInteractable = "";
private String northeastInteractable ="";
private String northwestInteractable = "";
private String southeastInteractable = "";
private String southwestInteractable = "";
private String upInteractable = "";
private String downInteractable = "";

public Options(){
}

public void setCardinalPointers(int north, int south, int east, int west){
    northPointer = north;
    southPointer = south;
    eastPointer = east;
    westPointer = west;
}
public void setIntercardinalPointers(int northeast, int northwest, int       southeast, int southwest){
    northeastPointer = northeast;
    northwestPointer=northwest;
    southeastPointer=southeast;
    southwestPointer=southwest;
}

public void setElevationPointers(int up, int down){
    upPointer = up;
    downPointer = down;
}



public String whatToReturn(String string){
    String importantPart = "";

    if(string.matches("travel.*")){
        String substring = string.substring(6);

    }

    else {
        importantPart = "Interactable";
        String substring = string.substring(6);
        if (substring.matches("\\bnorth\\b")) {
            if(northInteractable!=0){

            }
        }

        else if (substring.matches("\\bsouth\\b"))

        else if (substring.matches("\\beast\\b"))

        else if (substring.matches("\\bwest\\b"))

        else if (substring.contains("northeast"))

        else if (substring.contains("northwest"))

        else if (substring.contains("southeast"))

        else if (substring.contains("southwest"))

        else if (substring.contains("up"))

        else if (substring.contains("down"))
    }
    return importantPart;

}
}

I did not see the adventure tag until after I typed this, so I will start perusing through there, but will still post this, so my apologies if there is a good answer to this and I have yet to find it.

as a recap: what would be a good way to relate a few objects to create a room object (that gets its information from a file (XML being what im used to)) having exits, descriptions, and interactions. and the user interacting with these based off keywords that can be inputted freely, and not restricted to say, index values of array's holding keywords.

Im thinking when the user types something like "travel north" to first check if they typed a keyword, in this case being travel, then a direction. Then somewhree else checking if it states travel, check north with a possible northExit a room may or may not have. Then if its another keyword, say like check, to make it easy also have the exact same directions, but check for a different string.

Then if room "northExit" exists, get an option somehow, with a pointer to another roomID. though This thought process causes me issues when thinking about future possibility of requiring items for getting to the next room. Also where to store/acquire these options is causing some difficulties.

1

There are 1 answers

2
Cory Owens On BEST ANSWER

There are two things I would like to introduce to you. The first, in the enum. You can think of this as a special kind of class where all the possible options are enumerated in the class definition. This is perfect for things like, in your case, directions. Enums can be simple, where you just list all of the possible options for use in other classes:

public enum Direction {
    NORTH, NORTH_EAST, EAST, SOUTH_EAST, SOUTH, SOUTH_WEST, WEST, NOTH_WEST;
}

They can be a bit more complex, if you want them to have methods and attributes of their own:

public enum Direction {
    NORTH(true), NORTH_EAST(false), EAST(true), SOUTH_EAST(false), SOUTH(true), SOUTH_WEST(false), WEST(true), NOTH_WEST(false);

    private final boolean isCardinal;

    private Direction(boolean isCardinal){
        this.isCardinal = isCardinal;
    }

    public boolean isCardinal(){
        return isCardinal;
    }

    public static Collection<Direction> getCardinalDirections(){
        return Arrays.asList(Direction.values()).stream().filter(Direction::isCardinal).collect(Collectors.toList());
    }

    public static Collection<Direction> getIncardinalDirections(){
        return Arrays.asList(Direction.values()).stream().filter(x -> !x.isCardinal()).collect(Collectors.toList());
    }
}

Please read more about Java enum types here.

The second thing I would like to introduce to you is the data structure known as the Map. Maps are also known as Dictionaries, and that can often help understanding how they work. A Map will take one object and map it to another object, like how a Dictionary maps a word to its definition, or a phonebook maps a person's name to their phone number. We can simplify your Room class a ton by using a Map. I am not going to reproduce all of your code, since I'm focusing on your Room exists right now:

public class Room {

    private Map<Direction, Room> exits;

    public Room(){
        this.exits = new HashMap<>();
    }

    public void setExit(Direction direction, Room room){
        this.exits.put(direction, room);
    }

    public Room getExit(Direction direction){
        return this.exits.get(direction);
    }
}

Please read more about the Java Map interface here.

You will, of course, need to adapt your methods which are reading from XML, etc. But, now, your Room class should be greatly simplified.

I hope this points you in a helpful direction.