diff --git a/src/main/java/com/github/dtschust/zork/ZorkEvaluatable.java b/src/main/java/com/github/dtschust/zork/ZorkEvaluatable.java index 590def1..f9aba99 100644 --- a/src/main/java/com/github/dtschust/zork/ZorkEvaluatable.java +++ b/src/main/java/com/github/dtschust/zork/ZorkEvaluatable.java @@ -1,7 +1,5 @@ package com.github.dtschust.zork; -import com.github.dtschust.zork.parser.ZorkGame; - public interface ZorkEvaluatable { boolean evaluate(Zork zork); } diff --git a/src/main/java/com/github/dtschust/zork/parser/ZorkGame.java b/src/main/java/com/github/dtschust/zork/parser/ZorkGame.java index ec912c8..37807d4 100644 --- a/src/main/java/com/github/dtschust/zork/parser/ZorkGame.java +++ b/src/main/java/com/github/dtschust/zork/parser/ZorkGame.java @@ -8,13 +8,13 @@ import java.util.Set; public class ZorkGame { + public final Set inventory = new HashSet<>(); protected boolean running = false; protected String currentRoom; protected ZorkMap rooms = new ZorkMap<>(); protected ZorkMap items = new ZorkMap<>(); protected ZorkMap containers = new ZorkMap<>(); protected ZorkMap creatures = new ZorkMap<>(); - public final Set inventory = new HashSet<>(); protected HashMap objectLookup = new HashMap<>(); public ZorkRoom getCurrentRoom() { diff --git a/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java b/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java index 7d0f709..422f844 100644 --- a/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java +++ b/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java @@ -3,13 +3,13 @@ package com.github.dtschust.zork.parser; import com.github.dtschust.zork.*; import com.github.dtschust.zork.types.*; import org.w3c.dom.CharacterData; -import org.w3c.dom.*; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; +import java.util.*; public class ZorkReader { @@ -29,95 +29,48 @@ public class ZorkReader { return "?"; } - private static String getItemOrDefault(Element element, String name, String base){ + private static String getItemOrDefault(Element element, String name, String base) { NodeList field = element.getElementsByTagName(name); return (field.getLength() > 0) ? getString((Element) field.item(0)) : base; } - public ZorkGame build() { - - ZorkGame data = new ZorkGame(); - - File file = new File(filename); - if (!file.canRead()) { - System.out.println("Error opening file. Exiting..."); - throw new RuntimeException(); - } - - try { - /* Open the xml file*/ - DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); - // Limit XML features to mitigate vulnerabilities - builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - builder.setFeature("http://xml.org/sax/features/external-general-entities", false); - builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - - Element rootElement = builder.newDocumentBuilder().parse(file).getDocumentElement(); - - /* Every single first generation child is a room, container, creature, or item. So load them in*/ - for (Node node : iterNodes(rootElement.getChildNodes())) { - Element element; - if (node instanceof Element) { - element = (Element) node; - String tagType = element.getTagName(); - - switch (tagType) { - /* If it's a room ... */ - case "room": - addRoom(data, element); - break; - /* If it's an item... */ - case "item": - addItem(data, element); - break; - /* If it's a container... */ - case "container": - addContainer(data, element); - break; - /* And finally, if it's a creature...*/ - case "creature": - addCreature(data, element); - break; - } - - } - } - } catch (Exception e) { - System.out.println("Invalid XML file, exiting"); - System.exit(-1); - //e.printStackTrace(); - } - - return data; - } - private static void addCreature(ZorkGame data, Element element) { - /* Get all possible creature attributes*/ - ZorkCreature tempCreature = new ZorkCreature( - getItemOrDefault(element, "name", ""), - getItemOrDefault(element, "description", "") - ); + final Set vulnerabilities = new HashSet<>(); + final List conditions = new ArrayList<>(); + final List prints = new ArrayList<>(); + final List actions = new ArrayList<>(); - readTriggersInObject(element, tempCreature); - tempCreature.updateStatus(getItemOrDefault(element, "status", "")); - - for (Element vuln : iterElements(element.getElementsByTagName("vulnerability"))) { - tempCreature.vulnerability.add(getString(vuln)); - } NodeList attacks = element.getElementsByTagName("attack"); for (Element attack : iterElements(attacks)) { - readConditionsInTrigger(attack, tempCreature.conditions); + readConditionsInTrigger(attack, conditions); for (Element print : iterElements(attack.getElementsByTagName("print"))) { - tempCreature.print.add(getString(print)); + prints.add(getString(print)); } for (Element action : iterElements(attack.getElementsByTagName("action"))) { - tempCreature.action.add(getString(action)); + actions.add(getString(action)); } } + for (Element vuln : iterElements(element.getElementsByTagName("vulnerability"))) { + vulnerabilities.add(getString(vuln)); + } + + /* Get all possible creature attributes */ + ZorkCreature tempCreature = new ZorkCreature( + getItemOrDefault(element, "name", ""), + getItemOrDefault(element, "description", ""), + vulnerabilities, + conditions, + prints, + actions + ); + + readTriggersInObject(element, tempCreature); + tempCreature.updateStatus(getItemOrDefault(element, "status", "")); + /* Put each creature in the creatures hashmap, the generic object hashmap, and the objectlookup hashmap*/ data.addObjectThroughLookup("creature", tempCreature); } @@ -149,29 +102,33 @@ public class ZorkReader { } private static void addItem(ZorkGame data, Element element) { + final List prints = new ArrayList<>(); + final List actions = new ArrayList<>(); + + NodeList turnOn = element.getElementsByTagName("turnon"); + if (turnOn.getLength() > 0) { + for (Element print : iterElements(element.getElementsByTagName("print"))) { + prints.add(getString(print)); + } + for (Element action : iterElements(element.getElementsByTagName("action"))) { + actions.add(getString(action)); + } + } + /* Get all possible item attributes*/ ZorkItem tempItem = new ZorkItem( getItemOrDefault(element, "name", ""), getItemOrDefault(element, "description", ""), - getItemOrDefault(element, "writing","") + getItemOrDefault(element, "writing", ""), + prints, + actions ); readTriggersInObject(element, tempItem); - tempItem.updateStatus(getItemOrDefault(element, "status", "")); - NodeList turnon = element.getElementsByTagName("turnon"); - if (turnon.getLength() > 0) { - for (Element print : iterElements(element.getElementsByTagName("print"))) { - tempItem.turnOnPrint.add(getString(print)); - } - for (Element action : iterElements(element.getElementsByTagName("action"))) { - tempItem.turnOnAction.add(getString(action)); - } - - } - - /* Put each item in the items hashmap, the generic objects hashmap, and store its type in objectlookup*/ + /* Put each item in the items hashmap, the generic objects hashmap, and store its type in object lookup */ data.addObjectThroughLookup("item", tempItem); + tempItem.updateStatus(getItemOrDefault(element, "status", "")); } private static void addRoom(ZorkGame data, Element element) { @@ -181,7 +138,7 @@ public class ZorkReader { getItemOrDefault(element, "name", ""), getItemOrDefault(element, "description", ""), getItemOrDefault(element, "type", "regular") - ); + ); readTriggersInObject(element, tempRoom); tempRoom.updateStatus(getItemOrDefault(element, "status", "")); @@ -258,10 +215,12 @@ public class ZorkReader { private static Iterable iterable(final NodeList nodeList) { return () -> new Iterator<>() { private int index = 0; + @Override public boolean hasNext() { return index < nodeList.getLength(); } + @Override public T next() { if (!hasNext()) @@ -278,4 +237,61 @@ public class ZorkReader { private static Iterable iterElements(final NodeList nodeList) { return iterable(nodeList); } + + public ZorkGame build() { + + ZorkGame data = new ZorkGame(); + + File file = new File(filename); + if (!file.canRead()) { + System.out.println("Error opening file. Exiting..."); + throw new RuntimeException(); + } + + try { + /* Open the xml file*/ + DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); + // Limit XML features to mitigate vulnerabilities + builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builder.setFeature("http://xml.org/sax/features/external-general-entities", false); + builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + + Element rootElement = builder.newDocumentBuilder().parse(file).getDocumentElement(); + + /* Every single first generation child is a room, container, creature, or item. So load them in*/ + for (Node node : iterNodes(rootElement.getChildNodes())) { + Element element; + if (node instanceof Element) { + element = (Element) node; + String tagType = element.getTagName(); + + switch (tagType) { + /* If it's a room ... */ + case "room": + addRoom(data, element); + break; + /* If it's an item... */ + case "item": + addItem(data, element); + break; + /* If it's a container... */ + case "container": + addContainer(data, element); + break; + /* And finally, if it's a creature...*/ + case "creature": + addCreature(data, element); + break; + } + + } + } + } catch (Exception e) { + System.out.println("Invalid XML file, exiting"); + System.exit(-1); + //e.printStackTrace(); + } + + return data; + } } diff --git a/src/main/java/com/github/dtschust/zork/repl/Action.java b/src/main/java/com/github/dtschust/zork/repl/Action.java index f6da0f7..ee888e2 100644 --- a/src/main/java/com/github/dtschust/zork/repl/Action.java +++ b/src/main/java/com/github/dtschust/zork/repl/Action.java @@ -6,10 +6,14 @@ import java.util.List; public abstract class Action { public abstract boolean matchesInput(final List arguments); + public int getMinimumArgCount() { return 1; } - public int getMaximumArgCount() { return Integer.MAX_VALUE; } + public int getMaximumArgCount() { + return Integer.MAX_VALUE; + } + public abstract void run(final ZorkGame game, final List arguments); } diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java index c555e5d..4d8a535 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java @@ -2,7 +2,6 @@ package com.github.dtschust.zork.repl.actions; import com.github.dtschust.zork.parser.ZorkGame; import com.github.dtschust.zork.repl.Action; -import com.github.dtschust.zork.repl.ActionDispatcher; import com.github.dtschust.zork.types.ZorkCreature; import java.util.List; @@ -29,15 +28,9 @@ public class AttackAction extends Action { if (game.getCurrentRoom().creature.contains(tempString)) { ZorkCreature tempCreature = (ZorkCreature) game.get("creature", tempString); if (tempCreature != null && game.inventory.contains(weapon)) { - if (tempCreature.attack(game, weapon)) { + if (tempCreature.isAttackSuccessful(game, weapon)) { System.out.println("You assault the " + tempString + " with the " + weapon + "."); - for (String print : tempCreature.print) { - System.out.println(print); - } - final ActionDispatcher effectsDispatcher = new ActionDispatcher(game); - for (final String action : tempCreature.action) { - effectsDispatcher.dispatch(action); - } + tempCreature.printAndExecuteActions(game); return; } } diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java index 015df31..b1d2a3a 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java @@ -2,7 +2,6 @@ package com.github.dtschust.zork.repl.actions; import com.github.dtschust.zork.parser.ZorkGame; import com.github.dtschust.zork.repl.Action; -import com.github.dtschust.zork.repl.ActionDispatcher; import com.github.dtschust.zork.types.ZorkItem; import java.util.List; @@ -29,13 +28,7 @@ public class TurnOnAction extends Action { ZorkItem tempItem = (ZorkItem) game.get("item", what); System.out.println("You activate the " + what + "."); if (tempItem != null) { - for (String print : tempItem.turnOnPrint) { - System.out.println(print); - } - final ActionDispatcher effectsDispatcher = new ActionDispatcher(game); - for (final String action : tempItem.turnOnAction) { - effectsDispatcher.dispatch(action); - } + tempItem.printAndExecuteActions(game); return; } } diff --git a/src/main/java/com/github/dtschust/zork/types/HasPrintsAndActions.java b/src/main/java/com/github/dtschust/zork/types/HasPrintsAndActions.java new file mode 100644 index 0000000..b3a422f --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/types/HasPrintsAndActions.java @@ -0,0 +1,22 @@ +package com.github.dtschust.zork.types; + +import com.github.dtschust.zork.parser.ZorkGame; +import com.github.dtschust.zork.repl.ActionDispatcher; + +import java.util.List; + +public interface HasPrintsAndActions { + List getPrints(); + + List getActions(); + + default void printAndExecuteActions(final ZorkGame game) { + for (final String print : getPrints()) { + System.out.println(print); + } + final ActionDispatcher effectsDispatcher = new ActionDispatcher(game); + for (final String action : getActions()) { + effectsDispatcher.dispatch(action); + } + } +} diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java b/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java index bad2af2..2c3d7c7 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java @@ -16,10 +16,11 @@ public class ZorkContainer extends ZorkObject { } - public boolean isOpen(){ + public boolean isOpen() { return open; } - public void open(){ + + public void open() { open = true; } diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkCreature.java b/src/main/java/com/github/dtschust/zork/types/ZorkCreature.java index c9a0219..082237a 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkCreature.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkCreature.java @@ -1,36 +1,51 @@ package com.github.dtschust.zork.types; -import com.github.dtschust.zork.Zork; import com.github.dtschust.zork.ZorkCondition; import com.github.dtschust.zork.parser.ZorkGame; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /* Creature*/ -public class ZorkCreature extends ZorkObject { - public final Set vulnerability = new HashSet<>(); - public final List conditions = new ArrayList<>(); - public final List print = new ArrayList<>(); - public final List action = new ArrayList<>(); +public class ZorkCreature extends ZorkObject implements HasPrintsAndActions { + private final Set vulnerabilities; + private final List conditions; + private final List print; + private final List action; - public ZorkCreature(String name, String description) { + public ZorkCreature(final String name, + final String description, + final Collection vulnerabilities, + final Collection conditions, + final Collection prints, + final Collection actions) { super(name, description); + this.vulnerabilities = new HashSet<>(vulnerabilities); + this.conditions = new ArrayList<>(conditions); + this.print = new ArrayList<>(prints); + this.action = new ArrayList<>(actions); } - /* Evaluate the success of an attack*/ - public boolean attack(ZorkGame game, String weapon) { - if (!vulnerability.contains(weapon)) { - return false; - } - for (ZorkCondition condition : conditions) { - if (!condition.evaluate(game)) { - return false; - } - } - return true; + + /** + * Given a game instance and a weapon, returns whether the attack is successful, i.e. if the creature is vulnerable + * to the weapon and all conditions for a successful attack are satisfied + * + * @param game the game + * @param weapon the weapon + * @return true if the attack is successful + */ + public boolean isAttackSuccessful(final ZorkGame game, final String weapon) { + return vulnerabilities.contains(weapon) && conditions.stream().allMatch(c -> c.evaluate(game)); + } + + @Override + public List getPrints() { + return Collections.unmodifiableList(print); + } + + @Override + public List getActions() { + return Collections.unmodifiableList(action); } } diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkItem.java b/src/main/java/com/github/dtschust/zork/types/ZorkItem.java index a565f24..622f26f 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkItem.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkItem.java @@ -1,16 +1,29 @@ package com.github.dtschust.zork.types; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /* Item*/ -public class ZorkItem extends ZorkObject { +public class ZorkItem extends ZorkObject implements HasPrintsAndActions { public final String writing; - public final List turnOnPrint = new ArrayList<>(); - public final List turnOnAction = new ArrayList<>(); + private final List turnOnPrint; + private final List turnOnAction; - public ZorkItem(String name, String description, String writing) { + public ZorkItem(String name, String description, String writing, List turnOnPrint, List turnOnAction) { super(name, description); this.writing = writing; + this.turnOnPrint = new ArrayList<>(turnOnPrint); + this.turnOnAction = new ArrayList<>(turnOnAction); + } + + @Override + public List getPrints() { + return Collections.unmodifiableList(turnOnPrint); + } + + @Override + public List getActions() { + return Collections.unmodifiableList(turnOnAction); } } diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkMap.java b/src/main/java/com/github/dtschust/zork/types/ZorkMap.java index d30c70a..fbdcf4f 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkMap.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkMap.java @@ -2,9 +2,9 @@ package com.github.dtschust.zork.types; import java.util.HashMap; -public class ZorkMap extends HashMap{ +public class ZorkMap extends HashMap { - public T put(T object){ + public T put(T object) { return put(object.name, object); } } diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkObject.java b/src/main/java/com/github/dtschust/zork/types/ZorkObject.java index e9333b4..8c76f7c 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkObject.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkObject.java @@ -9,9 +9,8 @@ import java.util.List; public abstract class ZorkObject { public final String name; public final String description; - private String status; - public final List trigger = new ArrayList<>(); + private String status; protected ZorkObject(String name, String description) { this.name = name; diff --git a/src/test/java/com/github/dtschust/zork/ZorkTest.java b/src/test/java/com/github/dtschust/zork/ZorkTest.java index b39a9e7..923f179 100644 --- a/src/test/java/com/github/dtschust/zork/ZorkTest.java +++ b/src/test/java/com/github/dtschust/zork/ZorkTest.java @@ -18,7 +18,7 @@ class ZorkTest { String gameExecution = "RunThroughResults.txt"; CommandReader run = new CommandReader(gameExecution); - IOWrapper io = new IOWrapper(false); + IOWrapper io = new IOWrapper(true); new Thread(() -> { try { catchSystemExit(() -> new Zork(gameConfig));