Deduplicated print-action logic

This commit is contained in:
Claudio Maggioni 2022-11-21 17:10:08 +01:00
parent 40675f5a39
commit a782ec5ad7
13 changed files with 199 additions and 145 deletions

View file

@ -1,7 +1,5 @@
package com.github.dtschust.zork; package com.github.dtschust.zork;
import com.github.dtschust.zork.parser.ZorkGame;
public interface ZorkEvaluatable { public interface ZorkEvaluatable {
boolean evaluate(Zork zork); boolean evaluate(Zork zork);
} }

View file

@ -8,13 +8,13 @@ import java.util.Set;
public class ZorkGame { public class ZorkGame {
public final Set<String> inventory = new HashSet<>();
protected boolean running = false; protected boolean running = false;
protected String currentRoom; protected String currentRoom;
protected ZorkMap<ZorkRoom> rooms = new ZorkMap<>(); protected ZorkMap<ZorkRoom> rooms = new ZorkMap<>();
protected ZorkMap<ZorkItem> items = new ZorkMap<>(); protected ZorkMap<ZorkItem> items = new ZorkMap<>();
protected ZorkMap<ZorkContainer> containers = new ZorkMap<>(); protected ZorkMap<ZorkContainer> containers = new ZorkMap<>();
protected ZorkMap<ZorkCreature> creatures = new ZorkMap<>(); protected ZorkMap<ZorkCreature> creatures = new ZorkMap<>();
public final Set<String> inventory = new HashSet<>();
protected HashMap<String, String> objectLookup = new HashMap<>(); protected HashMap<String, String> objectLookup = new HashMap<>();
public ZorkRoom getCurrentRoom() { public ZorkRoom getCurrentRoom() {

View file

@ -3,13 +3,13 @@ package com.github.dtschust.zork.parser;
import com.github.dtschust.zork.*; import com.github.dtschust.zork.*;
import com.github.dtschust.zork.types.*; import com.github.dtschust.zork.types.*;
import org.w3c.dom.CharacterData; 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 javax.xml.parsers.DocumentBuilderFactory;
import java.io.File; import java.io.File;
import java.util.Iterator; import java.util.*;
import java.util.List;
import java.util.NoSuchElementException;
public class ZorkReader { public class ZorkReader {
@ -29,95 +29,48 @@ public class ZorkReader {
return "?"; 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); NodeList field = element.getElementsByTagName(name);
return (field.getLength() > 0) ? getString((Element) field.item(0)) : base; 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) { private static void addCreature(ZorkGame data, Element element) {
/* Get all possible creature attributes*/ final Set<String> vulnerabilities = new HashSet<>();
ZorkCreature tempCreature = new ZorkCreature( final List<ZorkCondition> conditions = new ArrayList<>();
getItemOrDefault(element, "name", ""), final List<String> prints = new ArrayList<>();
getItemOrDefault(element, "description", "") final List<String> 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"); NodeList attacks = element.getElementsByTagName("attack");
for (Element attack : iterElements(attacks)) { for (Element attack : iterElements(attacks)) {
readConditionsInTrigger(attack, tempCreature.conditions); readConditionsInTrigger(attack, conditions);
for (Element print : iterElements(attack.getElementsByTagName("print"))) { for (Element print : iterElements(attack.getElementsByTagName("print"))) {
tempCreature.print.add(getString(print)); prints.add(getString(print));
} }
for (Element action : iterElements(attack.getElementsByTagName("action"))) { 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*/ /* Put each creature in the creatures hashmap, the generic object hashmap, and the objectlookup hashmap*/
data.addObjectThroughLookup("creature", tempCreature); data.addObjectThroughLookup("creature", tempCreature);
} }
@ -149,29 +102,33 @@ public class ZorkReader {
} }
private static void addItem(ZorkGame data, Element element) { private static void addItem(ZorkGame data, Element element) {
final List<String> prints = new ArrayList<>();
final List<String> 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*/ /* Get all possible item attributes*/
ZorkItem tempItem = new ZorkItem( ZorkItem tempItem = new ZorkItem(
getItemOrDefault(element, "name", ""), getItemOrDefault(element, "name", ""),
getItemOrDefault(element, "description", ""), getItemOrDefault(element, "description", ""),
getItemOrDefault(element, "writing","") getItemOrDefault(element, "writing", ""),
prints,
actions
); );
readTriggersInObject(element, tempItem); readTriggersInObject(element, tempItem);
tempItem.updateStatus(getItemOrDefault(element, "status", ""));
NodeList turnon = element.getElementsByTagName("turnon"); /* Put each item in the items hashmap, the generic objects hashmap, and store its type in object lookup */
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*/
data.addObjectThroughLookup("item", tempItem); data.addObjectThroughLookup("item", tempItem);
tempItem.updateStatus(getItemOrDefault(element, "status", ""));
} }
private static void addRoom(ZorkGame data, Element element) { private static void addRoom(ZorkGame data, Element element) {
@ -258,10 +215,12 @@ public class ZorkReader {
private static <T> Iterable<T> iterable(final NodeList nodeList) { private static <T> Iterable<T> iterable(final NodeList nodeList) {
return () -> new Iterator<>() { return () -> new Iterator<>() {
private int index = 0; private int index = 0;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return index < nodeList.getLength(); return index < nodeList.getLength();
} }
@Override @Override
public T next() { public T next() {
if (!hasNext()) if (!hasNext())
@ -278,4 +237,61 @@ public class ZorkReader {
private static Iterable<Element> iterElements(final NodeList nodeList) { private static Iterable<Element> iterElements(final NodeList nodeList) {
return iterable(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;
}
} }

View file

@ -6,10 +6,14 @@ import java.util.List;
public abstract class Action { public abstract class Action {
public abstract boolean matchesInput(final List<String> arguments); public abstract boolean matchesInput(final List<String> arguments);
public int getMinimumArgCount() { public int getMinimumArgCount() {
return 1; 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<String> arguments); public abstract void run(final ZorkGame game, final List<String> arguments);
} }

View file

@ -2,7 +2,6 @@ package com.github.dtschust.zork.repl.actions;
import com.github.dtschust.zork.parser.ZorkGame; import com.github.dtschust.zork.parser.ZorkGame;
import com.github.dtschust.zork.repl.Action; import com.github.dtschust.zork.repl.Action;
import com.github.dtschust.zork.repl.ActionDispatcher;
import com.github.dtschust.zork.types.ZorkCreature; import com.github.dtschust.zork.types.ZorkCreature;
import java.util.List; import java.util.List;
@ -29,15 +28,9 @@ public class AttackAction extends Action {
if (game.getCurrentRoom().creature.contains(tempString)) { if (game.getCurrentRoom().creature.contains(tempString)) {
ZorkCreature tempCreature = (ZorkCreature) game.get("creature", tempString); ZorkCreature tempCreature = (ZorkCreature) game.get("creature", tempString);
if (tempCreature != null && game.inventory.contains(weapon)) { 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 + "."); System.out.println("You assault the " + tempString + " with the " + weapon + ".");
for (String print : tempCreature.print) { tempCreature.printAndExecuteActions(game);
System.out.println(print);
}
final ActionDispatcher effectsDispatcher = new ActionDispatcher(game);
for (final String action : tempCreature.action) {
effectsDispatcher.dispatch(action);
}
return; return;
} }
} }

View file

@ -2,7 +2,6 @@ package com.github.dtschust.zork.repl.actions;
import com.github.dtschust.zork.parser.ZorkGame; import com.github.dtschust.zork.parser.ZorkGame;
import com.github.dtschust.zork.repl.Action; import com.github.dtschust.zork.repl.Action;
import com.github.dtschust.zork.repl.ActionDispatcher;
import com.github.dtschust.zork.types.ZorkItem; import com.github.dtschust.zork.types.ZorkItem;
import java.util.List; import java.util.List;
@ -29,13 +28,7 @@ public class TurnOnAction extends Action {
ZorkItem tempItem = (ZorkItem) game.get("item", what); ZorkItem tempItem = (ZorkItem) game.get("item", what);
System.out.println("You activate the " + what + "."); System.out.println("You activate the " + what + ".");
if (tempItem != null) { if (tempItem != null) {
for (String print : tempItem.turnOnPrint) { tempItem.printAndExecuteActions(game);
System.out.println(print);
}
final ActionDispatcher effectsDispatcher = new ActionDispatcher(game);
for (final String action : tempItem.turnOnAction) {
effectsDispatcher.dispatch(action);
}
return; return;
} }
} }

View file

@ -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<String> getPrints();
List<String> 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);
}
}
}

View file

@ -16,10 +16,11 @@ public class ZorkContainer extends ZorkObject {
} }
public boolean isOpen(){ public boolean isOpen() {
return open; return open;
} }
public void open(){
public void open() {
open = true; open = true;
} }

View file

@ -1,36 +1,51 @@
package com.github.dtschust.zork.types; package com.github.dtschust.zork.types;
import com.github.dtschust.zork.Zork;
import com.github.dtschust.zork.ZorkCondition; import com.github.dtschust.zork.ZorkCondition;
import com.github.dtschust.zork.parser.ZorkGame; import com.github.dtschust.zork.parser.ZorkGame;
import java.util.ArrayList; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/* Creature*/ /* Creature*/
public class ZorkCreature extends ZorkObject { public class ZorkCreature extends ZorkObject implements HasPrintsAndActions {
public final Set<String> vulnerability = new HashSet<>(); private final Set<String> vulnerabilities;
public final List<ZorkCondition> conditions = new ArrayList<>(); private final List<ZorkCondition> conditions;
public final List<String> print = new ArrayList<>(); private final List<String> print;
public final List<String> action = new ArrayList<>(); private final List<String> action;
public ZorkCreature(String name, String description) { public ZorkCreature(final String name,
final String description,
final Collection<String> vulnerabilities,
final Collection<ZorkCondition> conditions,
final Collection<String> prints,
final Collection<String> actions) {
super(name, description); 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*/ /* Evaluate the success of an attack*/
public boolean attack(ZorkGame game, String weapon) {
if (!vulnerability.contains(weapon)) { /**
return false; * 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));
} }
for (ZorkCondition condition : conditions) {
if (!condition.evaluate(game)) { @Override
return false; public List<String> getPrints() {
return Collections.unmodifiableList(print);
} }
}
return true; @Override
public List<String> getActions() {
return Collections.unmodifiableList(action);
} }
} }

View file

@ -1,16 +1,29 @@
package com.github.dtschust.zork.types; package com.github.dtschust.zork.types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/* Item*/ /* Item*/
public class ZorkItem extends ZorkObject { public class ZorkItem extends ZorkObject implements HasPrintsAndActions {
public final String writing; public final String writing;
public final List<String> turnOnPrint = new ArrayList<>(); private final List<String> turnOnPrint;
public final List<String> turnOnAction = new ArrayList<>(); private final List<String> turnOnAction;
public ZorkItem(String name, String description, String writing) { public ZorkItem(String name, String description, String writing, List<String> turnOnPrint, List<String> turnOnAction) {
super(name, description); super(name, description);
this.writing = writing; this.writing = writing;
this.turnOnPrint = new ArrayList<>(turnOnPrint);
this.turnOnAction = new ArrayList<>(turnOnAction);
}
@Override
public List<String> getPrints() {
return Collections.unmodifiableList(turnOnPrint);
}
@Override
public List<String> getActions() {
return Collections.unmodifiableList(turnOnAction);
} }
} }

View file

@ -2,9 +2,9 @@ package com.github.dtschust.zork.types;
import java.util.HashMap; import java.util.HashMap;
public class ZorkMap<T extends ZorkObject> extends HashMap<String, T>{ public class ZorkMap<T extends ZorkObject> extends HashMap<String, T> {
public T put(T object){ public T put(T object) {
return put(object.name, object); return put(object.name, object);
} }
} }

View file

@ -9,9 +9,8 @@ import java.util.List;
public abstract class ZorkObject { public abstract class ZorkObject {
public final String name; public final String name;
public final String description; public final String description;
private String status;
public final List<ZorkTrigger> trigger = new ArrayList<>(); public final List<ZorkTrigger> trigger = new ArrayList<>();
private String status;
protected ZorkObject(String name, String description) { protected ZorkObject(String name, String description) {
this.name = name; this.name = name;

View file

@ -18,7 +18,7 @@ class ZorkTest {
String gameExecution = "RunThroughResults.txt"; String gameExecution = "RunThroughResults.txt";
CommandReader run = new CommandReader(gameExecution); CommandReader run = new CommandReader(gameExecution);
IOWrapper io = new IOWrapper(false); IOWrapper io = new IOWrapper(true);
new Thread(() -> { new Thread(() -> {
try { try {
catchSystemExit(() -> new Zork(gameConfig)); catchSystemExit(() -> new Zork(gameConfig));