Skip to content

Commit

Permalink
Wumpus World using Prolog and Java
Browse files Browse the repository at this point in the history
  • Loading branch information
s-webber committed Dec 28, 2020
1 parent 0295e80 commit a78483e
Show file tree
Hide file tree
Showing 36 changed files with 2,198 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
* text eol=lf

# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
*.ico binary
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
*.class
*.jar
*.zip
*.tmp
*.out
junit*.properties
/*.bat
/*.sh

.classpath
.project
.settings

/bin
/build
/lib
/tmp
/src/prolog/tmp
/temp
/projogGeneratedClasses
/target
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist: trusty
language: java
jdk:
- oraclejdk8
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,65 @@
# prolog-wumpus-world
Wumpus world simulator using Prolog for agent logic and Java for UI.

## About

This project contains a simulator and agent for a Wumpus World. Wumpus World is a problem discussed in [Artificial Intelligence: A Modern Approach](https://en.wikipedia.org/wiki/Artificial_Intelligence:_A_Modern_Approach), a university textbook on artificial intelligence.

The simulator and user interface is written in Java. The logic for controlling the agent is implemented in [Prolog](https://en.wikipedia.org/wiki/Prolog). The open source [Projog](http://projog.org "Prolog interpreter for Java") library is used to integrate Java with Prolog.

The images used by the application are taken from [Bootstrap Icons](https://icons.getbootstrap.com/), which is licensed under the MIT license.

## Rules


### Components
The world consists of a 4x4 grid of squares. The squares can contain the following items:

1. Agent. The agent moves around the 4x4 grid. The agent has an arrow that they can fire once.
2. Gold. If the agent enters a square that contains the gold then the agent can take it.
3. A wumpus. If the agent enters the square that contains the wumpus, and the wumpus is alive, then the agent is eaten and cannot continue. If the agent fires the arrow and the square immediately in front of the agent contains the wumpus then the wumpus dies.
4. Pits. If the agent enters a square that contains a pit then the agent falls into it and cannot continue.
5. Walls. If the agent attempts to enter a square that contains a wall then they will not be able to.

The wumpus, pits and walls are static - they remain in the same square they were allocated to when the world was created.

### Goal
The aim of the problem is to navigate the agent around the world to:

1. Find the square that contains the gold.
2. Take the gold.
3. Return to the square that the agent started from.
4. Climb out of the world.

### Actions
To interact with the world the agent can perform the following actions:

1. `FORWARD` Move into the square directly in front of where the agent is facing.
2. `RIGHT` Turn right.
3. `LEFT` Turn left.
4. `TAKE` Take the gold. Can only do this if in the same square as the gold.
5. `FIRE` Fire the arrow. Can only do this if have not already fired the arrow.
6. `CLIMB` Climb out of the world. Can only do this if located in the square the agent started from.

### Percepts
To help the agent reason about the world it receives percepts. The percepts the agent can receive are:

1. `STENCH` Indicates that the agent is next to a square that contains the wumpus or in the square that contains the wumpus.
2. `BREEZE` Indicates that the agent is next to a square that contains a pit.
3. `GLITTER` Indicates that the agent is in the same square as the gold.
4. `BUMP` Indicates that the agent's previous action (to move forward) caused them to bump into a wall.
5. `BREEZE` Indicates that the agent's previous action (to fire the arrow) killed the wumpus.

## Reusing the simulator

If someone would like to implement their own logic to navigate the agent around the world then they can do so by:

### Implementing the agent logic in Prolog
If implementing the logic using Prolog then they can replace the logic in `src/main/resources/prolog/wumpus.pl`. They will need to provide a `init/0` predicate which resets the agent's game state and a `process/2` predicate. The first argument of `process/2` will be a list of percepts. The second argument will be a variable that should be unified with an atom that indicates which action the agent should perform next.

### Implementing the agent logic in Java (or another JVM language)
If implementing the logic in a different language than Prolog then they will need to implement their own version of `org.projog.wumpus.GameController`. They will then need to alter `org.projog.wumpus.WumpusWorld` to create an instance of their implementation instead of `PrologGameController`.

## Resources

- [Calling Prolog from Java](http://projog.org/calling-prolog-from-java.html)
- [Bootstrap Icons GitHub](https://github.com/twbs/icons)
32 changes: 32 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.projog</groupId>
<artifactId>prolog-wumpus-world</artifactId>
<packaging>jar</packaging>
<version>0.1.0-SNAPSHOT</version>
<name>prolog-wumpus-world</name>
<url>http://projog.org</url>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.projog</groupId>
<artifactId>projog-core</artifactId>
<version>0.4.0</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

24 changes: 24 additions & 0 deletions src/main/java/org/projog/wumpus/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.projog.wumpus;

import java.io.PrintStream;
import java.util.Set;

import org.projog.wumpus.model.Action;
import org.projog.wumpus.model.Percept;

/** Logic used to control the agent. */
public interface GameController {
/** Set output stream so debug from controller can be displayed in UI. */
void setOut(PrintStream out);

/** Reset agent state back to the starting state. */
void reset();

/**
* Determines the next action that the agent should perform.
*
* @param percepts the inputs the agent can perceive
* @return the action the agent should perform
*/
Action process(Set<Percept> percepts);
}
60 changes: 60 additions & 0 deletions src/main/java/org/projog/wumpus/PrologGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.projog.wumpus;

import static java.util.stream.Collectors.toList;

import java.io.PrintStream;
import java.util.Set;

import org.projog.api.Projog;
import org.projog.api.QueryPlan;
import org.projog.api.QueryStatement;
import org.projog.wumpus.model.Action;
import org.projog.wumpus.model.Percept;

/** Facade to prolog code that contains the logic used to control the agent. */
class PrologGameController implements GameController {
private final Projog projog;
private final QueryPlan initQuery;
private final QueryPlan updateQuery;

PrologGameController() {
projog = new Projog();
projog.consultResource("prolog/wumpus.pl");
initQuery = projog.createPlan("init.");
updateQuery = projog.createPlan("process(Percepts,Action).");

reset();
}

/** Set output stream so debug from controller can be displayed in UI. */
@Override
public void setOut(PrintStream out) {
projog.setUserOutput(out);
}

/** Reset agent state back to the starting state. */
@Override
public synchronized void reset() {
initQuery.executeOnce();
}

/**
* Determines the next action that the agent should perform.
*
* @param percepts the inputs the agent can perceive
* @return the action the agent should perform
*/
@Override
public synchronized Action process(Set<Percept> percepts) {
try {
QueryStatement updateStatement = updateQuery.createStatement();
// Percept names of enum are upper-case but wumpus.pl expects lower-case versions, so convert here.
updateStatement.setListOfAtomNames("Percepts", percepts.stream().map(p -> p.toString().toLowerCase()).collect(toList()));
String action = updateStatement.findFirstAsAtomName();
return Action.valueOf(action.toUpperCase());
} catch (RuntimeException e) {
projog.printProjogStackTrace(e);
throw e;
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/org/projog/wumpus/WumpusWorld.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.projog.wumpus;

import org.projog.wumpus.model.World;
import org.projog.wumpus.view.UserInterface;

public class WumpusWorld {
public static void main(String[] args) {
GameController controller = new PrologGameController();
World state = new World();
new UserInterface(controller, state);
}
}
17 changes: 17 additions & 0 deletions src/main/java/org/projog/wumpus/model/Action.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.projog.wumpus.model;

/** An action that the agent can perform. */
public enum Action {
/** Move forward in the direction the agent is facing. */
FORWARD,
/** Turn to the right. */
RIGHT,
/** Turn to the left. */
LEFT,
/** Fire the arrow. */
FIRE,
/** Take the gold. */
TAKE,
/** Climb out of the maze. */
CLIMB
}
74 changes: 74 additions & 0 deletions src/main/java/org/projog/wumpus/model/Agent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.projog.wumpus.model;

/** Mutable object representing the current location and state of the agent. */
class Agent {
private Coordinate location;
private Direction direction = Direction.NORTH;
private AgentState state = AgentState.ACTIVE;
private ArrowState arrow = ArrowState.UNUSED;
private boolean hasGold;

Agent(Coordinate home) {
this.location = home;
}

AgentState getState() {
return state;
}

void setState(AgentState state) {
this.state = state;
}

Coordinate getLocation() {
return location;
}

void setLocation(Coordinate location) {
this.location = location;
}

Direction getDirection() {
return direction;
}

void setDirection(Direction direction) {
this.direction = direction;
}

boolean isHasGold() {
return hasGold;
}

void setHasGold(boolean hasGold) {
this.hasGold = hasGold;
}

void turnLeft() {
this.direction = direction.left();
}

void turnRight() {
this.direction = direction.right();
}

void setHasMissed() {
this.arrow = ArrowState.MISSED;
}

void setHasKilledWumpus() {
this.arrow = ArrowState.HIT;
}

boolean haveArrow() {
return arrow == ArrowState.UNUSED;
}

boolean haveNotKilledWumpus() {
return !haveKilledWumpus();
}

boolean haveKilledWumpus() {
return arrow == ArrowState.HIT;
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/projog/wumpus/model/AgentState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.projog.wumpus.model;

/** State of the agent. */
public enum AgentState {
/** The agent is alive and navigating the maze. */
ACTIVE,
/** The agent is dead because they have fallen into a pit or been eaten by the wumpus. */
DEAD,
/** The agent has climbed out of the maze. */
ESCAPED
}
11 changes: 11 additions & 0 deletions src/main/java/org/projog/wumpus/model/ArrowState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.projog.wumpus.model;

/** The state of the arrow that can be used to kill the wumpus. */
enum ArrowState {
/** The agent is carrying the arrow. */
UNUSED,
/** The arrow has been fired but it missed the wumpus. */
MISSED,
/** The arrow has been fired and it hit the wumpus. */
HIT
}
40 changes: 40 additions & 0 deletions src/main/java/org/projog/wumpus/model/Coordinate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.projog.wumpus.model;

/** A specific location within the maze. */
public class Coordinate {
private final int x;
private final int y;

public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}

Coordinate move(Direction d) {
return new Coordinate(x + d.getX(), y + d.getY());
}

public Coordinate minus(Coordinate c) {
return new Coordinate(x - c.x, y - c.y);
}

@Override
public String toString() {
return x + "," + y;
}

@Override
public boolean equals(Object o) {
if (o instanceof Coordinate) {
Coordinate other = (Coordinate) o;
return x == other.x && y == other.y;
} else {
return false;
}
}

@Override
public int hashCode() {
return x + (31 * y);
}
}
Loading

0 comments on commit a78483e

Please sign in to comment.