Some work in progress stuff

This commit is contained in:
Nico Marniok 2022-08-30 20:53:50 +02:00
parent b971d5a1ab
commit 75eb4771c3
8 changed files with 378 additions and 16 deletions

29
pom.xml
View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
@ -11,4 +9,29 @@
<groupId>schmaeddes</groupId> <groupId>schmaeddes</groupId>
<artifactId>untitledTextAdventure</artifactId> <artifactId>untitledTextAdventure</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.graalvm.js/js -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>22.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.graalvm.js/js-scriptengine -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>22.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.graalvm.truffle/truffle-api -->
<dependency>
<groupId>org.graalvm.truffle</groupId>
<artifactId>truffle-api</artifactId>
<version>22.2.0</version>
</dependency>
</dependencies>
</project> </project>

View file

@ -1,8 +1,19 @@
import java.io.IOException; import java.io.IOException;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.HostAccess;
import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine;
import game.logic.GameLogic; import game.logic.GameLogic;
import game.logic.Parser; import game.logic.Parser;
import game.state.CircularLocationException; import game.state.CircularLocationException;
import game.state.Entity;
import startup.Environment; import startup.Environment;
import startup.LoadStuff; import startup.LoadStuff;
@ -12,6 +23,19 @@ public class Main {
LoadStuff loadStuff = new LoadStuff(); LoadStuff loadStuff = new LoadStuff();
loadStuff.load(Environment.instance); loadStuff.load(Environment.instance);
ScriptEngine jse = GraalJSScriptEngine.create(
Engine.newBuilder().option("engine.WarnInterpreterOnly", "false").build(),
Context.newBuilder("js").allowHostAccess(HostAccess.ALL).allowHostClassLookup(s -> true));
try {
Entity t = new Entity("test");
jse.put("test", t);
jse.eval("console.log(test.toString());");
} catch (ScriptException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.exit(0);
Parser parser = new Parser(); Parser parser = new Parser();
try (GameLogic logic = new GameLogic(parser)) { try (GameLogic logic = new GameLogic(parser)) {
logic.loadGameState("games/damnCoolTextAdventureFTW.json"); logic.loadGameState("games/damnCoolTextAdventureFTW.json");

View file

@ -2,11 +2,14 @@ package game.logic;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import game.logic.actionsystem.Action; import game.logic.actionsystem.Action;
import game.logic.actionsystem.PlayerAction;
import game.logic.actionsystem.actions.GoDirection; import game.logic.actionsystem.actions.GoDirection;
import game.logic.actionsystem.actions.Open; import game.logic.actionsystem.actions.Open;
import game.logic.actionsystem.actions.TakeFrom; import game.logic.actionsystem.actions.TakeFrom;
@ -20,6 +23,7 @@ public class GameLogic implements Closeable {
private GameState state; private GameState state;
private Entity player; private Entity player;
private boolean discontinue = false; private boolean discontinue = false;
private Map<String, List<PlayerAction>> playerActions = new HashMap<>();
public GameLogic(Parser parser) { public GameLogic(Parser parser) {
this.parser = parser; this.parser = parser;
@ -162,6 +166,45 @@ public class GameLogic implements Closeable {
return true; return true;
} }
public void registerPlayerAction(PlayerAction action) {
List<PlayerAction> l = this.playerActions.get(action.getId());
if (l == null) {
this.playerActions.put(action.getId(), l = new LinkedList<>());
}
l.add(action);
}
/**
* Searches for the first player action that does not use any entity and
* executes it if one is found.
*
* @param id The action id
* @return <code>true</code>, if an action was found and executed
*/
public boolean tryExecutePlayerAction(String id) {
return this.tryExecutePlayerAction(id, null);
}
/**
* Searches for the first player action that can operate on the given set of
* entities and executes it if one is found.
*
* @param id The action id
* @param entities The entities on which the action shoud operate
* @return <code>true</code>, if an action was found and executed
*/
public boolean tryExecutePlayerAction(String id, EntitySet entities) {
List<PlayerAction> l = this.playerActions.get(id);
if (l != null) {
for (PlayerAction a : l) {
if (a.tryExecute(null, entities, this)) {
return true;
}
}
}
return false;
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
this.parser.close(); this.parser.close();

View file

@ -0,0 +1,122 @@
package game.logic.actionsystem;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import game.logic.GameLogic;
import game.state.Entity;
import game.state.EntitySet;
/**
* A player action represents a single action which operates on any number of
* entities.
*
* It has an identifier, e.g. <code>"combine"</code>, a signature of needed
* entities, and an executor. The signature consists multiple entries, each one
* being either a single entity or a group of entities. To execute a player
* action an <a href="#EntitySet"><code>EntitySet</code></a> must be given which
* can be matched against its signature.
*/
public class PlayerAction {
private record SignatureEntry(EntitySet entities, int count) {
}
private final String id;
private final PlayerActionExecutor executor;
private final List<SignatureEntry> signatureEntries = new LinkedList<>();
private int neededEntitiesTotalCount = 0;
/**
* Constructs a player action by providing an id and an executor.
*
* @param id The action id
* @param executor The executor which is called when
* <a href="#tryExecute">tryExecute</a> is called with an entity
* set which can be matched against the signature.
*/
public PlayerAction(String id, PlayerActionExecutor executor) {
this.id = id;
this.executor = executor;
}
/**
* Getter for the action's id.
*
* @return The action id
*/
public String getId() {
return this.id;
}
/**
* Pushes a new entry to this action' signature consisting of a single entity
* which is needed for execution.
*
* @param entity The needed entity to push
*/
public void pushNeededEntity(Entity entity) {
this.signatureEntries.add(new SignatureEntry(EntitySet.createTemporary(entity), 1));
++this.neededEntitiesTotalCount;
}
/**
* Pushes a new entry to this action's signature consisting of a set of entities
* of which a specific count is needed for execution.
*
* @param entities The set of needed entities
* @param count How many entities of the given set are needed for execution
*/
public void pushVaryingNeededEntites(EntitySet entities, int count) {
this.signatureEntries.add(new SignatureEntry(entities, count));
this.neededEntitiesTotalCount += count;
}
/**
* Tries to match the given entities to this action's signature and executes it
* if successful.
*
* @param primaryEntity An optional primary entity which will be matched
* against the first signature entry.
* @param secondaryEntities A set of entities which will be matched against the
* signature (excl. the first entry if
* <code>primaryEntity</code> was not
* <code>null</code>)
* @param logic The game logic
* @return <code>true</code>, if given entities match the signature and the
* action was executed
*/
public boolean tryExecute(Entity primaryEntity, EntitySet secondaryEntities, GameLogic logic) {
if ((primaryEntity == null ? 0 : 1)
+ (secondaryEntities == null ? 0 : secondaryEntities.getSize()) != this.neededEntitiesTotalCount) {
return false;
}
int entityCounts[] = new int[this.signatureEntries.size()];
Entity entitiesToUse[] = new Entity[this.neededEntitiesTotalCount];
if (primaryEntity != null) {
if (this.signatureEntries.isEmpty() || !this.signatureEntries.get(0).entities().contains(primaryEntity)) {
return false;
}
entitiesToUse[entityCounts[0]++] = primaryEntity;
}
if (secondaryEntities != null) {
for (Entity e : secondaryEntities.getAll()) {
int idx = 0;
for (int i = 0; i < this.signatureEntries.size(); ++i) {
SignatureEntry signatureEntry = this.signatureEntries.get(i);
if (entityCounts[i] != signatureEntry.count() && signatureEntry.entities().contains(e)) {
entitiesToUse[idx + entityCounts[i]++] = e;
break;
}
idx += signatureEntry.count();
}
}
}
if (Arrays.stream(entitiesToUse).anyMatch(Objects::isNull)) {
return false;
}
this.executor.execute(logic, entitiesToUse);
return true;
}
}

View file

@ -0,0 +1,21 @@
package game.logic.actionsystem;
import game.logic.GameLogic;
import game.state.Entity;
/**
* A player action executor provides specific instructions how to manipulate the
* game logic.
*/
public class PlayerActionExecutor {
/**
* Executes the game logic manupulation.
*
* @param logic The game logic
* @param args Arguments
*/
public void execute(GameLogic logic, Entity... args) {
// TODO
}
}

View file

@ -4,24 +4,42 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import game.logic.GameLogic;
import game.logic.actionsystem.PlayerAction;
import game.logic.actionsystem.PlayerActionExecutor;
/**
* Entities are the building blocks of the game logic's world.
*
* They can represent objects, creatures, the player(s), locations, and more.
* Each entity can also contain zero or more other entities, working as an
* abstract container. It can be connected to other entities through keywords,
* e.g. north, south, west, east. A connection can also be associated with
* another entity to model doors or other types of portals. Finally, an entity
* can store generic attributes.
*/
public class Entity { public class Entity {
public record EntityConnection(Entity to, Entity associatedEntity) { public record EntityConnection(Entity to, Entity associatedEntity) {
} }
private final String id; private final String id;
private final Set<String> attributes = new HashSet<>(); private final Set<String> attributes = new HashSet<>();
private Entity location; private Entity location;
private boolean closed = false; private boolean closed = false;
private final EntitySet contents = new EntitySet(); private final EntitySet contents;
private final Map<String, EntityConnection> connections = new HashMap<>(); private final Map<String, EntityConnection> connections = new HashMap<>();
private final Map<String, List<PlayerAction>> playerActions = new HashMap<>();
final Set<EntitySet> containingPersistentSets = new HashSet<>();
public Entity(String id, String... attributes) { public Entity(String id, String... attributes) {
this.id = id; this.id = id;
this.attributes.addAll(Arrays.asList(attributes)); this.attributes.addAll(Arrays.asList(attributes));
this.contents = EntitySet.createPersistent(this.id + "::contents");
} }
public String getId() { public String getId() {
@ -94,7 +112,8 @@ public class Entity {
this.connections.put(directionId, new EntityConnection(to, associatedEntity)); this.connections.put(directionId, new EntityConnection(to, associatedEntity));
} }
public void connectBidirectional(String dirIdFromThisToOther, Entity associatedEntity, String dirIdFromOtherToThis, Entity to) { public void connectBidirectional(String dirIdFromThisToOther, Entity associatedEntity, String dirIdFromOtherToThis,
Entity to) {
this.connections.put(dirIdFromThisToOther, new EntityConnection(to, associatedEntity)); this.connections.put(dirIdFromThisToOther, new EntityConnection(to, associatedEntity));
to.connections.put(dirIdFromOtherToThis, new EntityConnection(this, associatedEntity)); to.connections.put(dirIdFromOtherToThis, new EntityConnection(this, associatedEntity));
} }
@ -118,6 +137,60 @@ public class Entity {
return Collections.unmodifiableSet(this.connections.keySet()); return Collections.unmodifiableSet(this.connections.keySet());
} }
public PlayerAction pushPlayerAction(String id, PlayerActionExecutor executor) {
List<PlayerAction> l = this.playerActions.get(id);
if (l == null) {
this.playerActions.put(id, l = new LinkedList<>());
}
PlayerAction action = new PlayerAction(id, executor);
action.pushNeededEntity(this);
l.add(action);
return action;
}
/**
* Searches for the first player action that can operate on this entity and the
* given secondary entities and executes it if one is found.
*
* @param id The action id
* @param secondaryEntities The set of secondary entities on which the action
* should operate
* @return <code>true</code>, if an action was found and executed
*/
public boolean tryExecutePlayerAction(String id, EntitySet secondaryEntities, GameLogic logic) {
if (!this.tryExecutePlayerAction(this.playerActions.get(id), secondaryEntities, logic)) {
for (EntitySet set : this.containingPersistentSets) {
if (this.tryExecutePlayerAction(set.getPlayerActions(id), secondaryEntities, logic)) {
return true;
}
}
return false;
}
return true;
}
/**
* Searches for the first player action that can operate on this entity and no
* secondary entities executes it if one is found.
*
* @param id The action id
* @return <code>true</code>, if an action was found and executed
*/
public boolean tryExecutePlayerAction(String id, GameLogic logic) {
return this.tryExecutePlayerAction(id, null, logic);
}
private boolean tryExecutePlayerAction(List<PlayerAction> actions, EntitySet secondaryEntities, GameLogic logic) {
if (actions != null) {
for (PlayerAction a : actions) {
if (a.tryExecute(this, secondaryEntities, logic)) {
return true;
}
}
}
return false;
}
@Override @Override
public String toString() { public String toString() {
return this.id; return this.id;

View file

@ -3,21 +3,45 @@ package game.state;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import game.logic.GameLogic; import game.logic.GameLogic;
import game.logic.actionsystem.PlayerAction;
import game.logic.actionsystem.PlayerActionExecutor;
public class EntitySet { public class EntitySet {
private final Set<Entity> entities; public static EntitySet createTemporary(Entity... entities) {
return new EntitySet(null, Arrays.asList(entities));
public EntitySet(Entity... entities) {
this(Arrays.asList(entities));
} }
public EntitySet(Collection<Entity> entities) { public static EntitySet createTemporary(Collection<Entity> entities) {
return new EntitySet(null, entities);
}
public static EntitySet createPersistent(String name, Entity... entities) {
return new EntitySet(name, Arrays.asList(entities));
}
public static EntitySet createPersistent(String name, Collection<Entity> entities) {
return new EntitySet(name, entities);
}
private final String name;
private final Set<Entity> entities;
private final Map<String, List<PlayerAction>> playerActions = new HashMap<>();
private EntitySet(String name, Collection<Entity> entities) {
this.name = name;
this.entities = new HashSet<>(entities); this.entities = new HashSet<>(entities);
if (this.name != null) {
this.entities.forEach(e -> e.containingPersistentSets.add(this));
}
} }
public boolean isEmpty() { public boolean isEmpty() {
@ -28,12 +52,28 @@ public class EntitySet {
return Collections.unmodifiableSet(this.entities); return Collections.unmodifiableSet(this.entities);
} }
public int getSize() {
return this.entities.size();
}
public boolean add(Entity entity) { public boolean add(Entity entity) {
return this.entities.add(entity); if (this.entities.add(entity)) {
if (this.name != null) {
entity.containingPersistentSets.add(this);
}
return true;
}
return false;
} }
public boolean remove(Entity entity) { public boolean remove(Entity entity) {
return this.entities.remove(entity); if(this.entities.remove(entity)) {
if(this.name != null) {
entity.containingPersistentSets.remove(this);
}
return true;
}
return false;
} }
public boolean contains(Entity entity) { public boolean contains(Entity entity) {
@ -41,7 +81,7 @@ public class EntitySet {
} }
public Entity collapse(GameLogic logic) { public Entity collapse(GameLogic logic) {
if(this.entities.size() <= 1) { if (this.entities.size() <= 1) {
return this.entities.stream().findAny().orElse(null); return this.entities.stream().findAny().orElse(null);
} else { } else {
// TODO if more than 1 candidate, ask user to specify // TODO if more than 1 candidate, ask user to specify
@ -50,6 +90,22 @@ public class EntitySet {
} }
public EntitySet getFiltered(Predicate<Entity> acceptFunction) { public EntitySet getFiltered(Predicate<Entity> acceptFunction) {
return new EntitySet(this.entities.stream().filter(acceptFunction).toList()); return EntitySet.createTemporary(this.entities.stream().filter(acceptFunction).toList());
}
public PlayerAction pushPlayerAction(String id, PlayerActionExecutor executor) {
List<PlayerAction> l = this.playerActions.get(id);
if (l == null) {
this.playerActions.put(id, l = new LinkedList<>());
}
PlayerAction action = new PlayerAction(id, executor);
action.pushVaryingNeededEntites(this, 1);
l.add(action);
return action;
}
public List<PlayerAction> getPlayerActions(String id) {
List<PlayerAction> l = this.playerActions.get(id);
return l == null ? Collections.emptyList() : Collections.unmodifiableList(this.playerActions.get(id));
} }
} }

View file

@ -19,7 +19,7 @@ public class GameState {
} }
public EntitySet searchForEntity(EntityDescription description) { public EntitySet searchForEntity(EntityDescription description) {
return new EntitySet(this.entities.stream().filter(e -> e.getId().equals(description.getMainWord()) return EntitySet.createTemporary(this.entities.stream().filter(e -> e.getId().equals(description.getMainWord())
&& e.getAttributes().containsAll(description.getAttributes())).toList()); && e.getAttributes().containsAll(description.getAttributes())).toList());
} }
} }