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;
import com.github.dtschust.zork.parser.ZorkGame;
public interface ZorkEvaluatable {
boolean evaluate(Zork zork);
}

View file

@ -8,13 +8,13 @@ import java.util.Set;
public class ZorkGame {
public final Set<String> inventory = new HashSet<>();
protected boolean running = false;
protected String currentRoom;
protected ZorkMap<ZorkRoom> rooms = new ZorkMap<>();
protected ZorkMap<ZorkItem> items = new ZorkMap<>();
protected ZorkMap<ZorkContainer> containers = new ZorkMap<>();
protected ZorkMap<ZorkCreature> creatures = new ZorkMap<>();
public final Set<String> inventory = new HashSet<>();
protected HashMap<String, String> objectLookup = new HashMap<>();
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.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<String> vulnerabilities = new HashSet<>();
final List<ZorkCondition> conditions = new ArrayList<>();
final List<String> prints = new ArrayList<>();
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");
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<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*/
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 <T> Iterable<T> 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<Element> 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;
}
}

View file

@ -6,10 +6,14 @@ import java.util.List;
public abstract class Action {
public abstract boolean matchesInput(final List<String> 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<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.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;
}
}

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

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;
}
public void open(){
public void open() {
open = true;
}

View file

@ -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<String> vulnerability = new HashSet<>();
public final List<ZorkCondition> conditions = new ArrayList<>();
public final List<String> print = new ArrayList<>();
public final List<String> action = new ArrayList<>();
public class ZorkCreature extends ZorkObject implements HasPrintsAndActions {
private final Set<String> vulnerabilities;
private final List<ZorkCondition> conditions;
private final List<String> print;
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);
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<String> getPrints() {
return Collections.unmodifiableList(print);
}
@Override
public List<String> getActions() {
return Collections.unmodifiableList(action);
}
}

View file

@ -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<String> turnOnPrint = new ArrayList<>();
public final List<String> turnOnAction = new ArrayList<>();
private final List<String> turnOnPrint;
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);
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;
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);
}
}

View file

@ -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<ZorkTrigger> trigger = new ArrayList<>();
private String status;
protected ZorkObject(String name, String description) {
this.name = name;

View file

@ -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));