├── server
├── conf
│ ├── generate
│ │ └── generate.json
│ ├── maps
│ │ ├── test.bmp
│ │ └── test40x40.bmp
│ ├── server
│ │ └── server.json
│ ├── setup
│ │ └── test.txt
│ ├── SampleQualification.json
│ ├── AutoLaunch.json
│ ├── SampleConfig.json
│ ├── SampleConfig2.json
│ ├── sim
│ │ ├── sim1.json
│ │ ├── sim2.json
│ │ ├── sim3.json
│ │ ├── sim-q1.json
│ │ └── sim-q2.json
│ ├── sim-test
│ │ ├── tasks.json
│ │ ├── large-events.json
│ │ ├── meteor-shower.json
│ │ ├── more-obstacles.json
│ │ ├── grid-on-fire.json
│ │ ├── big-world.json
│ │ └── small-world.json
│ └── SimpleConfig.json
├── src
│ ├── main
│ │ └── java
│ │ │ └── massim
│ │ │ ├── game
│ │ │ ├── environment
│ │ │ │ ├── Terrain.java
│ │ │ │ ├── GameObject.java
│ │ │ │ ├── Positionable.java
│ │ │ │ ├── TaskBoard.java
│ │ │ │ ├── ClearEvent.java
│ │ │ │ ├── Block.java
│ │ │ │ ├── Dispenser.java
│ │ │ │ ├── Marker.java
│ │ │ │ ├── Attachable.java
│ │ │ │ └── Task.java
│ │ │ ├── Team.java
│ │ │ └── Entity.java
│ │ │ ├── util
│ │ │ ├── Util.java
│ │ │ ├── NameComparator.java
│ │ │ ├── RNG.java
│ │ │ ├── InputManager.java
│ │ │ ├── Log.java
│ │ │ └── IOUtil.java
│ │ │ ├── config
│ │ │ ├── TeamConfig.java
│ │ │ └── ServerConfig.java
│ │ │ ├── ReplayWriter.java
│ │ │ └── FrontDesk.java
│ └── test
│ │ └── java
│ │ └── massim
│ │ ├── protocol
│ │ └── data
│ │ │ └── PositionTest.java
│ │ └── game
│ │ └── environment
│ │ └── GridTest.java
└── pom.xml
├── broadcast
├── requirements.txt
└── server.py
├── docker
├── goal
│ ├── code
│ │ ├── dummy.pl
│ │ ├── main.mod2g
│ │ ├── event.mod2g
│ │ ├── actions.act2g
│ │ └── test.mas2g
│ ├── Dockerfile
│ └── eismassimconfig.json
├── .env
├── jason
│ ├── Dockerfile
│ └── code
│ │ ├── SampleMAS.mas2j
│ │ ├── build.gradle
│ │ └── logging.properties
├── server
│ └── Dockerfile
├── conf
│ └── eismassimconfig.json
├── javaagents
│ └── Dockerfile
├── README.md
└── docker-compose.yml
├── monitor
├── src
│ └── main
│ │ ├── resources
│ │ └── www
│ │ │ ├── favicon.ico
│ │ │ ├── status.html
│ │ │ ├── index.html
│ │ │ ├── status.css
│ │ │ ├── layout.css
│ │ │ └── loader.css
│ │ └── java
│ │ └── massim
│ │ └── monitor
│ │ ├── EventSink.java
│ │ └── Monitor.java
├── js
│ ├── statusInterfaces.ts
│ ├── util.ts
│ ├── statusCtrl.ts
│ ├── styles.ts
│ ├── statusView.ts
│ ├── view.ts
│ ├── interfaces.ts
│ ├── main.ts
│ └── ctrl.ts
├── tsconfig.json
├── rollup.config.js
├── package.json
└── pom.xml
├── .gitignore
├── eismassim
├── src
│ └── main
│ │ └── java
│ │ └── massim
│ │ └── eismassim
│ │ ├── Log.java
│ │ ├── Entity.java
│ │ └── entities
│ │ ├── StatusEntity.java
│ │ └── ScenarioEntity.java
├── conf
│ └── eismassimconfig.json
└── pom.xml
├── starterKits
├── jason
│ ├── MAPC2021.masj
│ ├── connectionA.asl
│ ├── conf
│ │ └── eismassimconfig.json
│ └── README.md
└── README.md
├── protocol
├── src
│ └── main
│ │ └── java
│ │ └── massim
│ │ └── protocol
│ │ ├── messages
│ │ ├── StatusRequestMessage.java
│ │ ├── ByeMessage.java
│ │ ├── SimStartMessage.java
│ │ ├── AuthRequestMessage.java
│ │ ├── AuthResponseMessage.java
│ │ ├── SimEndMessage.java
│ │ ├── scenario
│ │ │ ├── InitialPercept.java
│ │ │ ├── Actions.java
│ │ │ └── StepPercept.java
│ │ ├── ActionMessage.java
│ │ ├── RequestActionMessage.java
│ │ ├── StatusResponseMessage.java
│ │ └── Message.java
│ │ └── data
│ │ ├── Thing.java
│ │ ├── TaskInfo.java
│ │ └── Position.java
└── pom.xml
├── .github
└── workflows
│ ├── ci.yml
│ └── release.yml
├── javaagents
├── conf
│ └── BasicAgents
│ │ ├── javaagentsconfig.json
│ │ └── eismassimconfig.json
├── src
│ └── main
│ │ └── java
│ │ └── massim
│ │ └── javaagents
│ │ ├── agents
│ │ ├── BasicAgent.java
│ │ └── Agent.java
│ │ ├── MailService.java
│ │ ├── Main.java
│ │ └── Scheduler.java
└── pom.xml
├── docs
├── monitor.md
├── javaagents.md
└── protocol.md
├── pom.xml
├── README.md
└── dep.xml
/server/conf/generate/generate.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/broadcast/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp==3.7.4
2 |
--------------------------------------------------------------------------------
/docker/goal/code/dummy.pl:
--------------------------------------------------------------------------------
1 | :- dynamic
2 |
3 | step/1.
--------------------------------------------------------------------------------
/server/conf/maps/test.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentcontest/massim_2020/HEAD/server/conf/maps/test.bmp
--------------------------------------------------------------------------------
/server/conf/maps/test40x40.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentcontest/massim_2020/HEAD/server/conf/maps/test40x40.bmp
--------------------------------------------------------------------------------
/docker/goal/code/main.mod2g:
--------------------------------------------------------------------------------
1 | use "dummy" as knowledge.
2 | use "actions" as actionspec.
3 |
4 | module main {
5 | if true then pass.
6 | }
--------------------------------------------------------------------------------
/monitor/src/main/resources/www/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agentcontest/massim_2020/HEAD/monitor/src/main/resources/www/favicon.ico
--------------------------------------------------------------------------------
/docker/goal/code/event.mod2g:
--------------------------------------------------------------------------------
1 | use "dummy" as knowledge.
2 | use "actions" as actionspec.
3 |
4 | module event {
5 | if percept(step(_)) then move("s").
6 | }
--------------------------------------------------------------------------------
/docker/goal/code/actions.act2g:
--------------------------------------------------------------------------------
1 | use "dummy" as knowledge.
2 |
3 | % move
4 | define move(Dir) with
5 | pre { true }
6 | post { true }
7 |
8 | define pass as internal with
9 | pre{ true }
10 | post{ true }
--------------------------------------------------------------------------------
/docker/.env:
--------------------------------------------------------------------------------
1 | JAVA_VERSION=zulu@1.17.0-0
2 | JASON_JAVA_VERSION=zulu@1.15.0-2
3 | GOAL_URL=https://bitbucket.org/goalhub/runtime/downloads/runtime-2.2.0.jar
4 | JASON_URL=https://github.com/jason-lang/jason/releases/download/v3.0/jason-3.0.zip
--------------------------------------------------------------------------------
/docker/goal/code/test.mas2g:
--------------------------------------------------------------------------------
1 | use "../lib/eismassim.jar" as environment.
2 |
3 |
4 | define myMAPCAgent as agent {
5 | use "main" as main.
6 | use "event" as event.
7 | }
8 |
9 |
10 | launchpolicy{
11 | when * launch myMAPCAgent with name = *.
12 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | server/target
2 | server/logs
3 | server/replays
4 | server/results
5 | server/graphs
6 | target
7 | monitor/node
8 | monitor/node_modules
9 | monitor/src/main/resources/www/main.js
10 | *.iml
11 | .idea
12 | *.DS_Store
13 | docs/*.html
14 | .classpath
15 | .project
16 | .settings
17 | .vscode
18 | logs/*
19 |
--------------------------------------------------------------------------------
/docker/jason/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM buildpack-deps:stable-curl
2 |
3 | ARG JASON_JAVA_VERSION
4 | ARG JASON_URL
5 |
6 | ENV JABBA_COMMAND "install ${JASON_JAVA_VERSION} -o /jdk"
7 | RUN curl -L https://github.com/shyiko/jabba/raw/master/install.sh | bash
8 | ENV JAVA_HOME /jdk
9 | ENV PATH $JAVA_HOME/bin:$PATH
10 |
11 | RUN apt-get update
12 | RUN apt-get -y install gradle
--------------------------------------------------------------------------------
/server/conf/server/server.json:
--------------------------------------------------------------------------------
1 | {
2 | "tournamentMode" : "round-robin",
3 | "teamsPerMatch" : 2,
4 | "teamSize" : 10,
5 | "launch" : "key",
6 | "port" : 12300,
7 | "backlog" : 10000,
8 | "agentTimeout" : 4000,
9 | "resultPath" : "results",
10 | "logLevel" : "normal",
11 | "logPath" : "logs",
12 | "replayPath" : "replays",
13 | "maxPacketLength" : 65536
14 | }
15 |
--------------------------------------------------------------------------------
/eismassim/src/main/java/massim/eismassim/Log.java:
--------------------------------------------------------------------------------
1 | package massim.eismassim;
2 |
3 | /**
4 | * Very simple Logger.
5 | */
6 | public abstract class Log {
7 |
8 | public static void log(String message){
9 | System.out.println(message);
10 | }
11 |
12 | public static void flog(String format, Object... args){
13 | System.out.printf(format, args);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Terrain.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | public enum Terrain {
4 | EMPTY(0, "empty"),
5 | GOAL(1, "goal"),
6 | OBSTACLE(2, "obstacle");
7 |
8 | public final int id;
9 | public final String name;
10 |
11 | Terrain(int id, String name) {
12 | this.id = id;
13 | this.name = name;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/starterKits/jason/MAPC2021.masj:
--------------------------------------------------------------------------------
1 | /*
2 | * Sample Jason Project
3 | * for connecting Jason agents to the
4 | * MASSim server for the
5 | * Multi-Agent Programming Contest
6 | * https://multiagentcontest.org/
7 | */
8 |
9 | MAS mapc2021 {
10 |
11 | infrastructure: Centralised
12 |
13 | environment: jason.eis.EISAdapter
14 |
15 | agents:
16 | connectionA #2;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/docker/goal/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM buildpack-deps:stable-curl
2 |
3 | ARG JAVA_VERSION
4 | ARG GOAL_URL
5 |
6 | ENV JABBA_COMMAND "install ${JAVA_VERSION} -o /jdk"
7 | RUN curl -L https://github.com/shyiko/jabba/raw/master/install.sh | bash
8 | ENV JAVA_HOME /jdk
9 | ENV PATH $JAVA_HOME/bin:$PATH
10 |
11 | RUN mkdir /goal
12 | RUN curl -sL ${GOAL_URL} -o /goal/goal.jar
13 | COPY goal/eismassimconfig.json /goal/
--------------------------------------------------------------------------------
/docker/jason/code/SampleMAS.mas2j:
--------------------------------------------------------------------------------
1 | /*
2 | * Sample Jason Project
3 | * for connecting Jason agents to the
4 | * MASSim server for the
5 | * Multi-Agent Programming Contest
6 | * https://multiagentcontest.org/
7 | */
8 |
9 | MAS mapc2021 {
10 |
11 | infrastructure: Centralised
12 |
13 | environment: jason.eis.EISAdapter
14 |
15 | agents:
16 | agentA #15;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/util/Util.java:
--------------------------------------------------------------------------------
1 | package massim.util;
2 |
3 | /**
4 | * Class for random helper methods.
5 | */
6 | public abstract class Util {
7 |
8 | public static Integer tryParseInt(String maybeInt) {
9 | try {
10 | return Integer.parseInt(maybeInt);
11 | } catch (NumberFormatException e) {
12 | return null;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/starterKits/jason/connectionA.asl:
--------------------------------------------------------------------------------
1 | // Agent bob in project MAPC2018.mas2j
2 |
3 | /* Initial beliefs and rules */
4 |
5 | /* Initial goals */
6 |
7 | !start.
8 |
9 | /* Plans */
10 |
11 | +!start : true <-
12 | .print("hello massim world.").
13 |
14 | +step(X) : true <-
15 | .print("Received step percept:", X).
16 |
17 | +actionID(X) : true <-
18 | .print("Determining my action.", X);
19 | skip.
20 |
--------------------------------------------------------------------------------
/monitor/js/statusInterfaces.ts:
--------------------------------------------------------------------------------
1 | import { Redraw, ConnectionState, AgentStatus } from './interfaces';
2 |
3 | export interface StatusViewModel {
4 | state: ConnectionState
5 | data?: StatusData
6 | }
7 |
8 | export interface StatusCtrl {
9 | vm: StatusViewModel;
10 | redraw: Redraw
11 | }
12 |
13 | export interface StatusData {
14 | sim: string
15 | step: number
16 | steps: number
17 | entities: AgentStatus[]
18 | }
19 |
--------------------------------------------------------------------------------
/monitor/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "strictNullChecks": true,
5 | "noUnusedLocals": false,
6 | "noEmitOnError": true,
7 | "noImplicitAny": true,
8 | "noImplicitReturns": true,
9 | "noImplicitThis": true,
10 | "noUnusedParameters": false,
11 | "target": "ES2016",
12 | "lib": ["DOM", "ES2016"],
13 | "moduleResolution": "node"
14 | },
15 | "include": ["js/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/docker/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM buildpack-deps:stable-curl
2 |
3 | ARG JAVA_VERSION
4 |
5 | WORKDIR /usr/massim
6 |
7 | ENV JABBA_COMMAND "install ${JAVA_VERSION} -o /jdk"
8 | RUN curl -L https://github.com/shyiko/jabba/raw/master/install.sh | bash
9 | ENV JAVA_HOME /jdk
10 | ENV PATH $JAVA_HOME/bin:$PATH
11 |
12 | RUN apt-get update
13 | RUN apt-get -y install git maven
14 |
15 | RUN git clone https://github.com/agentcontest/massim_2020.git .
16 | RUN mvn clean package
--------------------------------------------------------------------------------
/server/conf/setup/test.txt:
--------------------------------------------------------------------------------
1 | # simple setup file for testing
2 |
3 | add 5 5 taskboard
4 |
5 | stop # stops further setup execution
6 |
7 | move 3 3 agentA1
8 | move 6 3 agentA2
9 | add 4 3 dispenser b1
10 | add 5 3 dispenser b2
11 | add 3 5 block b1
12 | terrain 5 5 obstacle
13 | terrain 5 6 empty
14 | terrain 6 6 goal
15 |
16 | # create task test 100 1,0,b1;2,0,b2
17 |
18 | create task test 100 1,0,b1
19 | move 10 10 agentA3
20 | add 11 10 block b1
21 | attach 10 10 11 10
--------------------------------------------------------------------------------
/monitor/src/main/resources/www/status.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Agent Content 2020 (Status)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/StatusRequestMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class StatusRequestMessage extends Message {
6 |
7 | public StatusRequestMessage() {}
8 |
9 | @Override
10 | public String getMessageType() {
11 | return Message.TYPE_STATUS_REQUEST;
12 | }
13 |
14 | @Override
15 | public JSONObject makeContent() {
16 | return new JSONObject();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | ci:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-java@v1
11 | with:
12 | java-version: 13
13 | - run: mvn test
14 | - run: mvn package
15 | - run: tar -xvzf target/massim-*.tar.gz
16 | - uses: actions/upload-artifact@v2-preview
17 | with:
18 | name: massim-2020-dev
19 | path: massim-*
20 |
--------------------------------------------------------------------------------
/monitor/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from '@rollup/plugin-node-resolve';
2 | import typescript from '@rollup/plugin-typescript';
3 | import commonjs from '@rollup/plugin-commonjs';
4 |
5 | export default {
6 | input: 'js/main.ts',
7 | output: {
8 | file: 'src/main/resources/www/main.js',
9 | format: 'iife',
10 | name: 'Massim',
11 | },
12 | plugins: [
13 | resolve(),
14 | typescript(),
15 | commonjs({
16 | extensions: ['.js', '.ts'],
17 | }),
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/starterKits/README.md:
--------------------------------------------------------------------------------
1 | # MAPC Starter Kits
2 |
3 | Here we gather packages that may help people get started using a(n agent)
4 | framework in no time by removing the effort of establishing the connection to
5 | the MAPC server.
6 |
7 | If you are looking for a way to connect your Java platform, you might want to
8 | have a look at [EISMASSim](../docs/eismassim.md).
9 |
10 | If you have such a _starter kit_ for a framework or language that is missing
11 | here and you want to share it, we would be happy to include it.
12 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/Team.java:
--------------------------------------------------------------------------------
1 | package massim.game;
2 |
3 | /**
4 | * Stores some team info.
5 | */
6 | public class Team {
7 |
8 | private long score = 0;
9 |
10 | private String name;
11 |
12 | Team(String name){
13 | this.name = name;
14 | }
15 |
16 | public String getName(){
17 | return name;
18 | }
19 |
20 | public void addScore(int points) {
21 | score += points;
22 | }
23 |
24 | public long getScore() {
25 | return score;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docker/conf/eismassimconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "scenario": "assemble2021",
3 | "host": "massimserver",
4 | "port": 12300,
5 | "scheduling": false,
6 | "timeout": 3000,
7 | "notifications": false,
8 | "exceptions": "true",
9 |
10 | "entities" : {},
11 |
12 | "multi-entities": [
13 | {
14 | "name-prefix": "agentA",
15 | "username-prefix": "agentA",
16 | "password": "1",
17 | "print-iilang": false,
18 | "print-json": false,
19 | "start-index": 1,
20 | "count": -1
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/docker/goal/eismassimconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "scenario": "assemble2021",
3 | "host": "massimserver",
4 | "port": 12300,
5 | "scheduling": false,
6 | "timeout": 100000,
7 | "notifications": false,
8 | "exceptions": false,
9 |
10 | "entities" : {},
11 |
12 | "multi-entities": [
13 | {
14 | "name-prefix": "entityA",
15 | "username-prefix": "agentA",
16 | "password": "1",
17 | "print-iilang": false,
18 | "print-json": false,
19 | "start-index": 1,
20 | "count": -1
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/docker/javaagents/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM buildpack-deps:stable-curl
2 |
3 | ARG JAVA_VERSION
4 |
5 | WORKDIR /usr/javaagents
6 |
7 | ENV JABBA_COMMAND "install ${JAVA_VERSION} -o /jdk"
8 | RUN curl -L https://github.com/shyiko/jabba/raw/master/install.sh | bash
9 | ENV JAVA_HOME /jdk
10 | ENV PATH $JAVA_HOME/bin:$PATH
11 |
12 | RUN apt-get update
13 | RUN apt-get -y install git maven
14 |
15 | RUN git clone https://github.com/agentcontest/massim_2020.git .
16 | RUN mvn clean package
17 |
18 | COPY lib/eismassimconfig.json ./javaagents/conf/BasicAgents
--------------------------------------------------------------------------------
/server/src/main/java/massim/util/NameComparator.java:
--------------------------------------------------------------------------------
1 | package massim.util;
2 |
3 | import java.util.Comparator;
4 |
5 | /**
6 | * Comparator for comparing strings by length first, then lexicographically. (for names with attached numbers)
7 | */
8 | public class NameComparator implements Comparator {
9 | @Override
10 | public int compare(String o1, String o2) {
11 | if (o1.length() > o2.length()) return 1;
12 | else if (o1.length() < o2.length()) return -1;
13 | return o1.compareTo(o2);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/GameObject.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.data.Thing;
5 |
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | public abstract class GameObject {
9 |
10 | private static AtomicInteger lastId = new AtomicInteger();
11 |
12 | private final int id = lastId.incrementAndGet();
13 |
14 | public final int getID() {
15 | return id;
16 | }
17 |
18 | public abstract Thing toPercept(Position relativeTo);
19 | }
20 |
--------------------------------------------------------------------------------
/monitor/src/main/resources/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Agent Contest 2020
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Positionable.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 |
5 | public abstract class Positionable extends GameObject {
6 |
7 | private Position position;
8 |
9 | public Positionable(Position position) {
10 | this.position = position;
11 | }
12 |
13 | public Position getPosition() {
14 | return position;
15 | }
16 |
17 | void setPosition(Position position){
18 | if (position == null) return;
19 | this.position = position;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/eismassim/conf/eismassimconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "scenario": "assemble2020",
3 | "host": "localhost",
4 | "port": 12300,
5 | "scheduling": true,
6 | "timeout": 3000,
7 | "notifications": false,
8 | "exceptions": false,
9 |
10 | "entities" : {},
11 |
12 | "multi-entities": [
13 | {
14 | "name-prefix": "connectionA",
15 | "username-prefix": "agentA",
16 | "password": "1",
17 | "print-iilang": false,
18 | "print-json": false,
19 | "start-index": 1,
20 | "count": -1
21 | }
22 | ],
23 |
24 | "status-entity": {
25 | "name": "statusConnection"
26 | }
27 | }
--------------------------------------------------------------------------------
/server/conf/SampleQualification.json:
--------------------------------------------------------------------------------
1 | {
2 | "server" : {
3 | "tournamentMode" : "round-robin",
4 | "teamsPerMatch" : 1,
5 | "launch" : "key",
6 | "port" : 12300,
7 | "backlog" : 10000,
8 | "agentTimeout" : 4000,
9 | "resultPath" : "results",
10 | "logLevel" : "normal",
11 | "logPath" : "logs",
12 | "replayPath" : "replays",
13 | "maxPacketLength" : 65536,
14 | "waitBetweenSimulations" : 5000
15 | },
16 |
17 | "match" : [
18 | "$(sim/sim-q1.json)",
19 | "$(sim/sim-q2.json)"
20 | ],
21 |
22 | "teams" : {
23 | "A" : {"prefix" : "agent", "password" : "1"}
24 | }
25 | }
--------------------------------------------------------------------------------
/monitor/src/main/resources/www/status.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #555;
3 | color: #bababa;
4 | font-family: Arial, Helvetica, sans-serif;
5 | max-width: 600px;
6 | margin: auto;
7 | }
8 |
9 | h1, h2, table {
10 | box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.2), 0 1px 5px 0 rgba(0,0,0,0.12);
11 | background-color: #252422;
12 | margin: 20px 0;
13 | padding: 20px;
14 | }
15 |
16 | table {
17 | color: white;
18 | width: 100%;
19 | }
20 | tbody {
21 | font-family: Courier New;
22 | }
23 | td {
24 | text-align: center;
25 | }
26 | td.no_action, td.unknown {
27 | background: red;
28 | }
29 |
--------------------------------------------------------------------------------
/monitor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mapc-webmonitor",
3 | "version": "2020.2.0",
4 | "private": true,
5 | "description": "Webmonitor for the Multi-Agent Programming Contest",
6 | "author": "Niklas Fiekas",
7 | "license": "AGPL-3.0-or-later",
8 | "dependencies": {
9 | "snabbdom": "^0.7.4"
10 | },
11 | "devDependencies": {
12 | "@rollup/plugin-commonjs": "^15.0.0",
13 | "@rollup/plugin-node-resolve": "^9.0.0",
14 | "@rollup/plugin-typescript": "^5.0.2",
15 | "rollup": "^2.26.9",
16 | "tslib": "^2.0.1",
17 | "typescript": "^4.0.2"
18 | },
19 | "scripts": {
20 | "prepare": "rollup --config"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/TaskBoard.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.data.Thing;
5 |
6 | public class TaskBoard extends Positionable {
7 |
8 | public TaskBoard(Position position) {
9 | super(position);
10 | }
11 |
12 | @Override
13 | public Thing toPercept(Position entity) {
14 | var local = getPosition().relativeTo(entity);
15 | return new Thing(local.x, local.y, Thing.TYPE_TASKBOARD, "");
16 | }
17 |
18 | @Override
19 | public String toString() {
20 | return "Taskboard" + getPosition();
21 | }
22 | }
--------------------------------------------------------------------------------
/starterKits/jason/conf/eismassimconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "scenario": "assemble2021",
3 | "host": "localhost",
4 | "port": 12300,
5 | "scheduling": false,
6 | "only-once": false,
7 | "timeout": 3000,
8 | "times": false,
9 | "notifications": false,
10 | "queued": false,
11 |
12 | "entities" : {},
13 |
14 | "multi-entities": [
15 | {
16 | "name-prefix": "connectionA",
17 | "username-prefix": "agentA",
18 | "password": "1",
19 | "print-iilang": false,
20 | "print-json": false,
21 | "start-index": 1,
22 | "count": -1
23 | }
24 | ],
25 |
26 | "status-entity": {
27 | "name": "statusConnection"
28 | }
29 | }
--------------------------------------------------------------------------------
/server/conf/AutoLaunch.json:
--------------------------------------------------------------------------------
1 | {
2 | "server" : {
3 | "tournamentMode" : "round-robin",
4 | "teamsPerMatch" : 2,
5 | "launch" : "5s",
6 | "port" : 12300,
7 | "backlog" : 10000,
8 | "agentTimeout" : 4000,
9 | "resultPath" : "results",
10 | "logLevel" : "normal",
11 | "logPath" : "logs",
12 | "replayPath" : "replays",
13 | "maxPacketLength" : 65536,
14 | "waitBetweenSimulations" : 5000
15 | },
16 |
17 | "match" : [
18 | "$(sim/sim1.json)",
19 | "$(sim/sim2.json)",
20 | "$(sim/sim3.json)"
21 | ],
22 |
23 | "teams" : {
24 | "A" : {"prefix" : "agent", "password" : "1"},
25 | "B" : {"prefix" : "agent", "password" : "1"}
26 | }
27 | }
--------------------------------------------------------------------------------
/javaagents/conf/BasicAgents/javaagentsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "agents" : [
3 | {
4 | "count": 20,
5 | "start-index": 1,
6 | "agent-prefix": "A",
7 | "entity-prefix": "connectionA",
8 | "team": "A",
9 | "class": "BasicAgent"
10 | },
11 | {
12 | "count": 30,
13 | "start-index": 21,
14 | "agent-prefix": "A",
15 | "entity-prefix": "connectionA",
16 | "team": "A",
17 | "class": "BasicAgent"
18 | },
19 | {
20 | "count": 50,
21 | "start-index": 1,
22 | "agent-prefix": "B",
23 | "entity-prefix": "connectionB",
24 | "team": "B",
25 | "class": "BasicAgent"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/ClearEvent.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 |
5 | public class ClearEvent {
6 |
7 | private Position position;
8 | private int step;
9 | private int radius;
10 |
11 | public ClearEvent(Position position, int step, int radius) {
12 | this.position = position;
13 | this.step = step;
14 | this.radius = radius;
15 | }
16 |
17 | public Position getPosition() {
18 | return position;
19 | }
20 |
21 | public int getStep() {
22 | return step;
23 | }
24 |
25 | public int getRadius() {
26 | return radius;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/monitor.md:
--------------------------------------------------------------------------------
1 | MASSim Monitor Documentation
2 | ============================
3 |
4 | The monitor allows you to view live matches and replays in your browser.
5 |
6 | Live monitor
7 | ------------
8 |
9 | [Start the server](server.md) with the `--monitor 8000` flag and navigate to
10 | [http://localhost:8000/](http://localhost:8000/) in your browser.
11 |
12 | Viewing a replay
13 | ----------------
14 |
15 | Start the monitor and provide a path to a replay directory.
16 |
17 | Usage:
18 |
19 | ```
20 | java -jar monitor/monitor-[version]-with-dependencies.jar [--port PORT]
21 | ```
22 |
23 | Then navigate to [http://localhost:8000/?/](http://localhost:8000/?/) (or similar)
24 | in your browser.
25 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/ByeMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class ByeMessage extends Message {
6 |
7 | private long time;
8 |
9 | public ByeMessage(long time) {
10 | this.time = time;
11 | }
12 |
13 | public ByeMessage(JSONObject content) {
14 | this.time = content.optLong("time");
15 | }
16 |
17 | @Override
18 | public String getMessageType() {
19 | return Message.TYPE_BYE;
20 | }
21 |
22 | @Override
23 | public JSONObject makeContent() {
24 | return new JSONObject();
25 | }
26 |
27 | public long getTime() {
28 | return time;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Block.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.data.Thing;
5 |
6 | public class Block extends Attachable {
7 |
8 | private String blockType;
9 |
10 | public Block(Position xy, String blockType) {
11 | super(xy);
12 | this.blockType = blockType;
13 | }
14 |
15 | public String getBlockType(){
16 | return this.blockType;
17 | }
18 |
19 | @Override
20 | public Thing toPercept(Position entityPosition) {
21 | Position relativePosition = getPosition().relativeTo(entityPosition);
22 | return new Thing(relativePosition.x, relativePosition.y, Thing.TYPE_BLOCK, blockType);
23 | }
24 | }
--------------------------------------------------------------------------------
/server/conf/SampleConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "server" : {
3 | "tournamentMode" : "round-robin",
4 | "teamsPerMatch" : 2,
5 | "launch" : "key",
6 | "port" : 12300,
7 | "backlog" : 10000,
8 | "agentTimeout" : 4000,
9 | "resultPath" : "results",
10 | "logLevel" : "normal",
11 | "logPath" : "logs",
12 | "replayPath" : "replays",
13 | "maxPacketLength" : 65536,
14 | "waitBetweenSimulations" : 5000
15 | },
16 |
17 | "manual-mode" : [
18 | ["A", "B"],
19 | ["B", "C"],
20 | ["A", "C"]
21 | ],
22 |
23 | "match" : [
24 | "$(sim/sim1.json)",
25 | "$(sim/sim2.json)",
26 | "$(sim/sim3.json)"
27 | ],
28 |
29 | "teams" : {
30 | "A" : {"prefix" : "agent", "password" : "1"},
31 | "B" : {"prefix" : "agent", "password" : "1"}
32 | }
33 | }
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 | # MASSim Docker files
2 |
3 | These may give you an idea of how to run the MASSim server and some of the agent platforms.
4 |
5 | To run the configurations (install Docker first, then):
6 |
7 | - Navigate to `docker-compose.yml` and comment (out) the parts that you (don't) want to run.
8 | - Follow the special instructions below for the chosen platform(s).
9 | - `docker-compose build`
10 | - `docker-compose up`
11 |
12 |
13 | ## Special instructions
14 |
15 | ### For EIS-enabled agent platforms
16 |
17 | - Build MASSim locally or download a release.
18 | - Copy `eismassim/target/eismassim-*-jar-with-dependencies.jar` to `lib/eismassim.jar`, and
19 | - copy an `eismassimconfig.json` to `lib/eismassimconfig.json`.
20 | - These will be mounted where necessary.
21 |
22 |
23 | ## Notes
24 |
25 | - You can configure the Java version and the URLs of the agent platform executables in the `.env` file.
--------------------------------------------------------------------------------
/javaagents/conf/BasicAgents/eismassimconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "scenario": "assemble2020",
3 | "host": "localhost",
4 | "port": 12300,
5 | "scheduling": false,
6 | "only-once": true,
7 | "timeout": 3000,
8 | "times": false,
9 | "notifications": false,
10 | "queued": false,
11 |
12 | "entities" : {},
13 |
14 | "multi-entities": [
15 | {
16 | "name-prefix": "connectionA",
17 | "username-prefix": "agentA",
18 | "password": "1",
19 | "print-iilang": false,
20 | "print-json": false,
21 | "start-index": 1,
22 | "count": -1
23 | },
24 | {
25 | "name-prefix": "connectionB",
26 | "username-prefix": "agentB",
27 | "password": "1",
28 | "print-iilang": false,
29 | "print-json": false,
30 | "start-index": 1,
31 | "count": -1
32 | }
33 | ],
34 |
35 | "status-entity": {
36 | "name": "statusConnection"
37 | }
38 | }
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Dispenser.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.data.Thing;
5 |
6 | public class Dispenser extends Positionable {
7 |
8 | private String blockType;
9 |
10 | public Dispenser(Position position, String blockType) {
11 | super(position);
12 | this.blockType = blockType;
13 | }
14 |
15 | public String getBlockType() {
16 | return blockType;
17 | }
18 |
19 | @Override
20 | public Thing toPercept(Position entityPosition) {
21 | Position local = getPosition().relativeTo(entityPosition);
22 | return new Thing(local.x, local.y, Thing.TYPE_DISPENSER, blockType);
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return "dispenser(" + getPosition().x + "," + getPosition().y + "," + blockType + ")";
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/conf/SampleConfig2.json:
--------------------------------------------------------------------------------
1 | {
2 | "server" : {
3 | "tournamentMode" : "round-robin",
4 | "teamsPerMatch" : 2,
5 | "launch" : "key",
6 | "port" : 12300,
7 | "backlog" : 10000,
8 | "agentTimeout" : 4000,
9 | "resultPath" : "results",
10 | "logLevel" : "normal",
11 | "logPath" : "logs",
12 | "replayPath" : "replays",
13 | "maxPacketLength" : 65536,
14 | "waitBetweenSimulations" : 5000
15 | },
16 |
17 | "manual-mode" : [
18 | ["A", "B"],
19 | ["B", "C"],
20 | ["A", "C"]
21 | ],
22 |
23 | "match" : [
24 | "$(sim-test/small-world.json)",
25 | "$(sim-test/big-world.json)",
26 | "$(sim-test/tasks.json)",
27 | "$(sim-test/large-events.json)",
28 | "$(sim-test/more-obstacles.json)",
29 | "$(sim-test/grid-on-fire.json)"
30 | ],
31 |
32 | "teams" : {
33 | "A" : {"prefix" : "agent", "password" : "1"},
34 | "B" : {"prefix" : "agent", "password" : "1"}
35 | }
36 | }
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-java@v1
14 | with:
15 | java-version: 13
16 | - run: mvn package
17 | - run: mv target/massim-*.tar.gz target/massim-2020.tar.gz
18 | - id: create_release
19 | uses: actions/create-release@v1
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 | with:
23 | tag_name: ${{ github.ref }}
24 | release_name: ${{ github.ref }}
25 | draft: false
26 | prerelease: true
27 | - uses: actions/upload-release-asset@v1
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | with:
31 | upload_url: ${{ steps.create_release.outputs.upload_url }}
32 | asset_path: target/massim-2020.tar.gz
33 | asset_name: massim-2020-${{ github.ref }}.tar.gz
34 | asset_content_type: application/gzip
35 |
--------------------------------------------------------------------------------
/monitor/js/util.ts:
--------------------------------------------------------------------------------
1 | import { AgentStatus } from './interfaces';
2 |
3 | export function compareAgent(a: AgentStatus, b: AgentStatus): number {
4 | if (a.team < b.team) return -1;
5 | else if (a.team > b.team) return 1;
6 |
7 | const suffixA = parseInt(a.name.replace(/^[^\d]*/, ''), 10);
8 | const suffixB = parseInt(b.name.replace(/^[^\d]*/, ''), 10);
9 | if (suffixA < suffixB) return -1;
10 | else if (suffixA > suffixB) return 1;
11 |
12 | if (a.name < b.name) return -1;
13 | else if (a.name > b.name) return 1;
14 | else return 0;
15 | }
16 |
17 | export function compareNumbered(a: string, b: string): number {
18 | const firstNumberRegex = /^[A-Za-z_-]*(\d+)/;
19 | const matchA = a.match(firstNumberRegex);
20 | const matchB = b.match(firstNumberRegex);
21 | const idxA = matchA ? parseInt(matchA[1], 10) : -1;
22 | const idxB = matchB ? parseInt(matchB[1], 10) : -1;
23 | if (idxA < idxB) return -1;
24 | else if (idxA > idxB) return 1;
25 |
26 | if (a < b) return -1;
27 | else if (a > b) return 1;
28 | else return 0;
29 | }
30 |
--------------------------------------------------------------------------------
/eismassim/src/main/java/massim/eismassim/Entity.java:
--------------------------------------------------------------------------------
1 | package massim.eismassim;
2 |
3 | import eis.PerceptUpdate;
4 | import eis.exceptions.PerceiveException;
5 |
6 | public abstract class Entity implements Runnable {
7 |
8 | private String name;
9 |
10 | public Entity(String name) {
11 | this.name = name;
12 | }
13 |
14 | public String getName() {
15 | return this.name;
16 | }
17 |
18 | /**
19 | * Retrieves all percepts for this entity.
20 | * If scheduling is enabled, the method blocks until a new action id, i.e. new percepts, are received
21 | * or the configured timeout is reached.
22 | * If queued is enabled, scheduling is overridden. Also, if queued is enabled, this method has to be called
23 | * repeatedly, as only one collection of percepts is removed from the queue with each call (until an empty list
24 | * is returned).
25 | * @return the percepts for this entity
26 | * @throws PerceiveException if timeout configured and occurred
27 | */
28 | public abstract PerceptUpdate getPercepts() throws PerceiveException;
29 | }
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/SimStartMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | public abstract class SimStartMessage extends Message {
6 |
7 | private long time;
8 |
9 | public SimStartMessage(JSONObject content) {
10 | this.time = content.optLong("time");
11 | }
12 |
13 | public SimStartMessage(long time) {
14 | this.time = time;
15 | }
16 |
17 | @Override
18 | public String getMessageType() {
19 | return Message.TYPE_SIM_START;
20 | }
21 |
22 | @Override
23 | public JSONObject makeContent() {
24 | JSONObject content = new JSONObject();
25 | content.put("time", time);
26 | content.put("percept", makePercept());
27 | return content;
28 | }
29 |
30 | /**
31 | * Create the JSON representation of the percept part.
32 | * Will be appended under the "percept" key of the "content" object.
33 | */
34 | public abstract JSONObject makePercept();
35 |
36 | public long getTime() {
37 | return time;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/conf/sim/sim1.json:
--------------------------------------------------------------------------------
1 | {
2 | "NOsetup" : "conf/setup/test.txt",
3 |
4 | "steps" : 750,
5 | "randomSeed" : 17,
6 | "randomFail" : 1,
7 | "entities" : {"standard" : 15},
8 | "clusterBounds" : [1,3],
9 |
10 | "clearSteps" : 3,
11 | "clearEnergyCost" : 30,
12 | "disableDuration" : 4,
13 | "maxEnergy" : 300,
14 | "attachLimit" : 10,
15 |
16 | "grid" : {
17 | "height" : 70,
18 | "width" : 70,
19 | "instructions": [
20 | ["cave", 0.45, 9, 5, 4]
21 | ],
22 | "goals": {
23 | "number" : 3,
24 | "size" : [1,2]
25 | }
26 | },
27 |
28 | "blockTypes" : [3, 3],
29 | "dispensers" : [5, 10],
30 |
31 | "tasks" : {
32 | "size" : [1, 4],
33 | "duration" : [100, 200],
34 | "probability" : 0.05,
35 | "taskboards" : 3,
36 | "rewardDecay" : [1, 2],
37 | "lowerRewardLimit" : 10,
38 | "distanceToTaskboards" : 8
39 | },
40 |
41 | "events" : {
42 | "chance" : 15,
43 | "radius" : [3, 5],
44 | "warning" : 5,
45 | "create" : [-3, 1],
46 | "perimeter" : 2
47 | }
48 | }
--------------------------------------------------------------------------------
/server/conf/sim/sim2.json:
--------------------------------------------------------------------------------
1 | {
2 | "NOsetup" : "conf/setup/test.txt",
3 |
4 | "steps" : 750,
5 | "randomSeed" : 17,
6 | "randomFail" : 1,
7 | "entities" : {"standard" : 30},
8 | "clusterBounds" : [2,5],
9 |
10 | "clearSteps" : 3,
11 | "clearEnergyCost" : 30,
12 | "disableDuration" : 4,
13 | "maxEnergy" : 300,
14 | "attachLimit" : 10,
15 |
16 | "grid" : {
17 | "height" : 80,
18 | "width" : 80,
19 | "instructions": [
20 | ["cave", 0.45, 9, 5, 4]
21 | ],
22 | "goals": {
23 | "number" : 3,
24 | "size" : [1,2]
25 | }
26 | },
27 |
28 | "blockTypes" : [3, 3],
29 | "dispensers" : [5, 10],
30 |
31 | "tasks" : {
32 | "size" : [1, 4],
33 | "duration" : [100, 200],
34 | "probability" : 0.05,
35 | "taskboards" : 4,
36 | "rewardDecay" : [1, 2],
37 | "lowerRewardLimit" : 10,
38 | "distanceToTaskboards" : 9
39 | },
40 |
41 | "events" : {
42 | "chance" : 15,
43 | "radius" : [3, 5],
44 | "warning" : 5,
45 | "create" : [-3, 1],
46 | "perimeter" : 2
47 | }
48 | }
--------------------------------------------------------------------------------
/server/conf/sim/sim3.json:
--------------------------------------------------------------------------------
1 | {
2 | "NOsetup" : "conf/setup/test.txt",
3 |
4 | "steps" : 750,
5 | "randomSeed" : 17,
6 | "randomFail" : 1,
7 | "entities" : {"standard" : 50},
8 | "clusterBounds" : [3,10],
9 |
10 | "clearSteps" : 3,
11 | "clearEnergyCost" : 30,
12 | "disableDuration" : 4,
13 | "maxEnergy" : 300,
14 | "attachLimit" : 10,
15 |
16 | "grid" : {
17 | "height" : 100,
18 | "width" : 100,
19 | "instructions": [
20 | ["cave", 0.45, 9, 5, 4]
21 | ],
22 | "goals": {
23 | "number" : 3,
24 | "size" : [1,2]
25 | }
26 | },
27 |
28 | "blockTypes" : [3, 3],
29 | "dispensers" : [5, 10],
30 |
31 | "tasks" : {
32 | "size" : [1, 4],
33 | "duration" : [100, 200],
34 | "taskboards" : 5,
35 | "probability" : 0.05,
36 | "rewardDecay" : [1, 2],
37 | "lowerRewardLimit" : 10,
38 | "distanceToTaskboards" : 10
39 | },
40 |
41 | "events" : {
42 | "chance" : 15,
43 | "radius" : [3, 5],
44 | "warning" : 5,
45 | "create" : [-3, 1],
46 | "perimeter" : 2
47 | }
48 | }
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Marker.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.data.Thing;
5 |
6 | /**
7 | * A simple marker marking a position.
8 | */
9 | public class Marker extends Positionable {
10 |
11 | private Type type;
12 |
13 | public Marker(Position pos, Type type) {
14 | super(pos);
15 | this.type = type;
16 | }
17 |
18 | public Type getType() {
19 | return type;
20 | }
21 |
22 | @Override
23 | public Thing toPercept(Position relativeTo) {
24 | var pos = getPosition().relativeTo(relativeTo);
25 | return new Thing(pos.x, pos.y, Thing.TYPE_MARKER, type.name);
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return "Marker(" + getPosition()+"," + type+")";
31 | }
32 |
33 | public enum Type {
34 | CLEAR("clear"),
35 | CLEAR_PERIMETER("cp"),
36 | CLEAR_IMMEDIATE("ci");
37 |
38 | public final String name;
39 |
40 | Type(String name) {
41 | this.name = name;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/AuthRequestMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class AuthRequestMessage extends Message {
6 |
7 | private String username;
8 | private String password;
9 |
10 | public AuthRequestMessage(JSONObject content) {
11 | this.username = content.optString("user");
12 | this.password = content.optString("pw");
13 | }
14 |
15 | public AuthRequestMessage(String username, String password) {
16 | this.username = username;
17 | this.password = password;
18 | }
19 |
20 | @Override
21 | public String getMessageType() {
22 | return Message.TYPE_AUTH_REQUEST;
23 | }
24 |
25 | @Override
26 | public JSONObject makeContent() {
27 | JSONObject content = new JSONObject();
28 | content.put("user", username);
29 | content.put("pw", password);
30 | return content;
31 | }
32 |
33 | public String getUsername() {
34 | return username;
35 | }
36 |
37 | public String getPassword() {
38 | return password;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/starterKits/jason/README.md:
--------------------------------------------------------------------------------
1 | # MASSim Starter Kit for Jason 3.0
2 |
3 | This is a starter kit to connect [Jason](http://jason.sourceforge.net/) agents
4 | to the [MASSim server](https://multiagentcontest.org) in a few simple steps.
5 |
6 | 1. Prepare the Jason project
7 | - Create a ``lib`` folder.
8 | - Find the ``jason-3.0.jar`` and put it into the ``lib`` folder (it's part of the Jason package)
9 | - Download or compile EISMASSim and put ``eismassim-X.Y-jar-with-dependencies.jar``
10 | into the ``lib`` folder as well. Use the version matching the server you want to
11 | connect to.
12 | - Navigate to the ``conf`` folder and add enough entities matching the current MASSim scenario.
13 | - Adapt the number of agents in the ``mas2j`` file.
14 | 1. Start the MASSim server.
15 | - follow the instructions provided
16 | 1. Done. You can run the agents now, e.g. from the Jason IDE.
17 |
18 | ## Details
19 |
20 | The Jason environment is provided in the ``EISAdapter`` class. It uses EISMASSim
21 | to communicate with the MASSim server to get percepts and reply with actions.
22 | You can find the available percepts and actions in the MASSim documentation.
23 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/AuthResponseMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class AuthResponseMessage extends Message {
6 |
7 | public final static String OK = "ok";
8 | public final static String FAIL = "fail";
9 |
10 | private long time;
11 | private String result;
12 |
13 | public AuthResponseMessage(JSONObject content) {
14 | this.time = content.optLong("time");
15 | this.result = content.optString("result");
16 | }
17 |
18 | public AuthResponseMessage(long time, String result) {
19 | this.time = time;
20 | this.result = result;
21 | }
22 |
23 | @Override
24 | public String getMessageType() {
25 | return Message.TYPE_AUTH_RESPONSE;
26 | }
27 |
28 | @Override
29 | public JSONObject makeContent() {
30 | JSONObject content = new JSONObject();
31 | content.put("result", result);
32 | return content;
33 | }
34 |
35 | public long getTime() {
36 | return time;
37 | }
38 |
39 | public String getResult() {
40 | return result;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/server/conf/sim/sim-q1.json:
--------------------------------------------------------------------------------
1 | {
2 | "description" : "qualification round 1 (candidate) config. goal: complete 1 task.",
3 |
4 | "steps" : 300,
5 | "randomSeed" : 17,
6 | "randomFail" : 1,
7 | "entities" : {"standard" : 15},
8 | "clusterBounds" : [1,3],
9 |
10 | "clearSteps" : 3,
11 | "clearEnergyCost" : 30,
12 | "disableDuration" : 4,
13 | "maxEnergy" : 300,
14 | "attachLimit" : 10,
15 |
16 | "grid" : {
17 | "height" : 70,
18 | "width" : 70,
19 | "instructions": [
20 | ["cave", 0.45, 9, 5, 4]
21 | ],
22 | "goals": {
23 | "number" : 3,
24 | "size" : [1,2]
25 | }
26 | },
27 |
28 | "blockTypes" : [3, 3],
29 | "dispensers" : [5, 10],
30 |
31 | "tasks" : {
32 | "size" : [2, 3],
33 | "duration" : [100, 200],
34 | "probability" : 0.1,
35 | "taskboards" : 3,
36 | "rewardDecay" : [1, 2],
37 | "lowerRewardLimit" : 10,
38 | "distanceToTaskboards" : 8
39 | },
40 |
41 | "events" : {
42 | "chance" : 15,
43 | "radius" : [3, 5],
44 | "warning" : 5,
45 | "create" : [-3, 1],
46 | "perimeter" : 2
47 | }
48 | }
--------------------------------------------------------------------------------
/server/conf/sim/sim-q2.json:
--------------------------------------------------------------------------------
1 | {
2 | "description" : "qualification round 2 (candidate) config. goal: complete 1 task.",
3 |
4 | "steps" : 300,
5 | "randomSeed" : 17,
6 | "randomFail" : 1,
7 | "entities" : {"standard" : 50},
8 | "clusterBounds" : [3,10],
9 |
10 | "clearSteps" : 3,
11 | "clearEnergyCost" : 30,
12 | "disableDuration" : 4,
13 | "maxEnergy" : 300,
14 | "attachLimit" : 10,
15 |
16 | "grid" : {
17 | "height" : 60,
18 | "width" : 100,
19 | "instructions": [
20 | ["cave", 0.45, 9, 5, 4]
21 | ],
22 | "goals": {
23 | "number" : 4,
24 | "size" : [1,2]
25 | }
26 | },
27 |
28 | "blockTypes" : [3, 3],
29 | "dispensers" : [5, 10],
30 |
31 | "tasks" : {
32 | "size" : [2, 3],
33 | "duration" : [100, 200],
34 | "taskboards" : 5,
35 | "probability" : 0.1,
36 | "rewardDecay" : [1, 2],
37 | "lowerRewardLimit" : 10,
38 | "distanceToTaskboards" : 10
39 | },
40 |
41 | "events" : {
42 | "chance" : 15,
43 | "radius" : [3, 5],
44 | "warning" : 5,
45 | "create" : [-3, 1],
46 | "perimeter" : 2
47 | }
48 | }
--------------------------------------------------------------------------------
/server/conf/sim-test/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "more tasks, bigger tasks",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 15},
10 | "clusterBounds" : [1,3],
11 |
12 | "clearSteps" : 3,
13 | "clearEnergyCost" : 30,
14 | "disableDuration" : 4,
15 | "maxEnergy" : 300,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 70,
20 | "width" : 70,
21 | "instructions": [
22 | ["cave", 0.45, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 3,
26 | "size" : [1,2]
27 | }
28 | },
29 |
30 | "blockTypes" : [3, 3],
31 | "dispensers" : [5, 10],
32 |
33 | "tasks" : {
34 | "size" : [1, 5],
35 | "duration" : [100, 200],
36 | "probability" : 0.1,
37 | "taskboards" : 5,
38 | "rewardDecay" : [1, 2],
39 | "lowerRewardLimit" : 20,
40 | "distanceToTaskboards" : 8
41 | },
42 |
43 | "events" : {
44 | "chance" : 15,
45 | "radius" : [3, 5],
46 | "warning" : 5,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
--------------------------------------------------------------------------------
/monitor/js/statusCtrl.ts:
--------------------------------------------------------------------------------
1 | import { Redraw } from './interfaces';
2 | import { StatusCtrl, StatusViewModel } from './statusInterfaces';
3 |
4 | export function makeStatusCtrl(redraw: Redraw): StatusCtrl {
5 | const vm: StatusViewModel = {
6 | state: 'connecting'
7 | };
8 |
9 | function connect() {
10 | const protocol = document.location.protocol === 'https:' ? 'wss:' : 'ws:';
11 | const path = document.location.pathname.substr(0, document.location.pathname.lastIndexOf('/'));
12 | const ws = new WebSocket(protocol + '//' + document.location.host + path + '/live/status');
13 |
14 | ws.onmessage = (msg) => {
15 | const data = JSON.parse(msg.data);
16 | console.log(data);
17 | vm.data = data;
18 | redraw();
19 | };
20 |
21 | ws.onopen = () => {
22 | console.log('Connected');
23 | vm.state = 'online';
24 | redraw();
25 | };
26 |
27 | ws.onclose = () => {
28 | console.log('Disconnected');
29 | setTimeout(() => connect(), 5000);
30 | vm.data = undefined;
31 | vm.state = 'offline';
32 | redraw();
33 | };
34 | }
35 |
36 | connect();
37 |
38 | return {
39 | vm,
40 | redraw
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/server/conf/sim-test/large-events.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "fewer but large events",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 15},
10 | "clusterBounds" : [1,3],
11 |
12 | "clearSteps" : 3,
13 | "clearEnergyCost" : 30,
14 | "disableDuration" : 4,
15 | "maxEnergy" : 300,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 70,
20 | "width" : 70,
21 | "instructions": [
22 | ["cave", 0.5, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 5,
26 | "size" : [1,2]
27 | }
28 | },
29 |
30 | "blockTypes" : [3, 3],
31 | "dispensers" : [10, 15],
32 |
33 | "tasks" : {
34 | "size" : [1, 4],
35 | "duration" : [100, 200],
36 | "probability" : 0.05,
37 | "taskboards" : 4,
38 | "rewardDecay" : [1, 2],
39 | "lowerRewardLimit" : 10,
40 | "distanceToTaskboards" : 8
41 | },
42 |
43 | "events" : {
44 | "chance" : 5,
45 | "radius" : [8, 10],
46 | "warning" : 10,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
--------------------------------------------------------------------------------
/server/conf/sim-test/meteor-shower.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "some more events",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 15},
10 | "clusterBounds" : [1,3],
11 |
12 | "clearSteps" : 3,
13 | "clearEnergyCost" : 30,
14 | "disableDuration" : 4,
15 | "maxEnergy" : 300,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 70,
20 | "width" : 70,
21 | "instructions": [
22 | ["cave", 0.55, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 5,
26 | "size" : [1,2]
27 | }
28 | },
29 |
30 | "blockTypes" : [3, 3],
31 | "dispensers" : [10, 15],
32 |
33 | "tasks" : {
34 | "size" : [1, 4],
35 | "duration" : [100, 200],
36 | "probability" : 0.05,
37 | "taskboards" : 4,
38 | "rewardDecay" : [1, 2],
39 | "lowerRewardLimit" : 10,
40 | "distanceToTaskboards" : 8
41 | },
42 |
43 | "events" : {
44 | "chance" : 100,
45 | "radius" : [8, 10],
46 | "warning" : 10,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/server/conf/sim-test/more-obstacles.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "more obstacles, fewer events",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 15},
10 | "clusterBounds" : [1,3],
11 |
12 | "clearSteps" : 3,
13 | "clearEnergyCost" : 30,
14 | "disableDuration" : 4,
15 | "maxEnergy" : 300,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 70,
20 | "width" : 70,
21 | "instructions": [
22 | ["cave", 0.55, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 5,
26 | "size" : [0,1]
27 | }
28 | },
29 |
30 | "blockTypes" : [3, 3],
31 | "dispensers" : [10, 15],
32 |
33 | "tasks" : {
34 | "size" : [1, 4],
35 | "duration" : [100, 200],
36 | "probability" : 0.05,
37 | "taskboards" : 4,
38 | "rewardDecay" : [1, 2],
39 | "lowerRewardLimit" : 10,
40 | "distanceToTaskboards" : 8
41 | },
42 |
43 | "events" : {
44 | "chance" : 5,
45 | "radius" : [3, 5],
46 | "warning" : 5,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
--------------------------------------------------------------------------------
/server/conf/sim-test/grid-on-fire.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "single goal zone, smaller tasks, lower energy",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 15},
10 | "clusterBounds" : [1,3],
11 |
12 | "clearSteps" : 2,
13 | "clearEnergyCost" : 15,
14 | "disableDuration" : 3,
15 | "maxEnergy" : 60,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 70,
20 | "width" : 70,
21 | "instructions": [
22 | ["cave", 0.55, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 1,
26 | "size" : [4,6]
27 | }
28 | },
29 |
30 | "blockTypes" : [3, 3],
31 | "dispensers" : [5, 10],
32 |
33 | "tasks" : {
34 | "size" : [2, 3],
35 | "duration" : [70, 120],
36 | "probability" : 0.06,
37 | "taskboards" : 4,
38 | "rewardDecay" : [1, 2],
39 | "lowerRewardLimit" : 10,
40 | "distanceToTaskboards" : 8
41 | },
42 |
43 | "events" : {
44 | "chance" : 15,
45 | "radius" : [3, 5],
46 | "warning" : 5,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/server/conf/sim-test/big-world.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "50 agents, small clusters, non-square map, complex layout",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 50},
10 | "clusterBounds" : [1,5],
11 |
12 | "clearSteps" : 3,
13 | "clearEnergyCost" : 30,
14 | "disableDuration" : 4,
15 | "maxEnergy" : 300,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 50,
20 | "width" : 100,
21 | "instructions": [
22 | ["cave", 0.55, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 3,
26 | "size" : [1,2]
27 | }
28 | },
29 |
30 | "blockTypes" : [3, 3],
31 | "dispensers" : [10, 15],
32 |
33 | "tasks" : {
34 | "size" : [1, 4],
35 | "duration" : [100, 200],
36 | "taskboards" : 5,
37 | "probability" : 0.1,
38 | "rewardDecay" : [1, 1],
39 | "lowerRewardLimit" : 10,
40 | "distanceToTaskboards" : 10
41 | },
42 |
43 | "events" : {
44 | "chance" : 15,
45 | "radius" : [3, 5],
46 | "warning" : 5,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
--------------------------------------------------------------------------------
/server/conf/sim-test/small-world.json:
--------------------------------------------------------------------------------
1 | {
2 | "description" : "small grid, non-square, small clusters, few goals etc., simpler tasks",
3 |
4 | "NOsetup" : "conf/setup/test.txt",
5 |
6 | "steps" : 750,
7 | "randomSeed" : 17,
8 | "randomFail" : 1,
9 | "entities" : {"standard" : 30},
10 | "clusterBounds" : [1,2],
11 |
12 | "clearSteps" : 3,
13 | "clearEnergyCost" : 30,
14 | "disableDuration" : 4,
15 | "maxEnergy" : 300,
16 | "attachLimit" : 10,
17 |
18 | "grid" : {
19 | "height" : 32,
20 | "width" : 64,
21 | "instructions": [
22 | ["cave", 0.45, 9, 5, 4]
23 | ],
24 | "goals": {
25 | "number" : 2,
26 | "size" : [1,1]
27 | }
28 | },
29 |
30 | "blockTypes" : [2, 2],
31 | "dispensers" : [5, 7],
32 |
33 | "tasks" : {
34 | "size" : [1, 3],
35 | "duration" : [100, 200],
36 | "probability" : 0.1,
37 | "taskboards" : 2,
38 | "rewardDecay" : [1, 1],
39 | "lowerRewardLimit" : 15,
40 | "distanceToTaskboards" : 9
41 | },
42 |
43 | "events" : {
44 | "chance" : 15,
45 | "radius" : [3, 5],
46 | "warning" : 5,
47 | "create" : [-3, 1],
48 | "perimeter" : 2
49 | }
50 | }
--------------------------------------------------------------------------------
/server/src/test/java/massim/protocol/data/PositionTest.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.data;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | public class PositionTest {
8 |
9 | @Test
10 | public void distanceTo() {
11 | int dim = 100;
12 | Position.setGridDimensions(dim, dim);
13 |
14 | var p1 = Position.of(99,99);
15 | var p2 = Position.of(0,99);
16 | var p3 = Position.of(99,0);
17 | var p4 = Position.of(0,0);
18 | var p5 = Position.of(0,50);
19 |
20 | assert p1.distanceTo(p2) == 1;
21 | assert p1.distanceTo(p3) == 1;
22 | assert p1.distanceTo(p4) == 2;
23 | assert p2.distanceTo(p3) == 2;
24 | assert p2.distanceTo(p4) == 1;
25 | assert p3.distanceTo(p4) == 1;
26 |
27 | assert p1.distanceTo(p2) == p2.distanceTo(p1);
28 | assert p1.distanceTo(p3) == p3.distanceTo(p1);
29 | assert p1.distanceTo(p4) == p4.distanceTo(p1);
30 | assert p2.distanceTo(p3) == p3.distanceTo(p2);
31 | assert p2.distanceTo(p4) == p4.distanceTo(p2);
32 | assert p3.distanceTo(p4) == p4.distanceTo(p3);
33 |
34 | assert p4.distanceTo(p5) == 50;
35 | assert p5.distanceTo(p4) == 50;
36 | }
37 | }
--------------------------------------------------------------------------------
/monitor/js/styles.ts:
--------------------------------------------------------------------------------
1 | export type Style = {
2 | background: string,
3 | color: string,
4 | };
5 |
6 | const teams: Style[] = [
7 | { background: '#0000ff', color: 'white' },
8 | { background: '#00ff00', color: 'black' },
9 | { background: '#ff1493', color: 'white' },
10 | { background: '#8b0000', color: 'white' },
11 | { background: '#ed553b', color: 'white' },
12 | { background: '#a63d40', color: 'white' },
13 | { background: '#e9b872', color: 'black' },
14 | { background: '#90a959', color: 'white' },
15 | { background: '#6494aa', color: 'white' },
16 | { background: '#192457', color: 'white' },
17 | { background: '#2b5397', color: 'white' },
18 | { background: '#a2dcdc', color: 'black' },
19 | { background: '#27ec5f', color: 'black' },
20 | { background: '#3ab1ad', color: 'white' },
21 | ];
22 |
23 | export function team(index: number): Style {
24 | return teams[index % teams.length];
25 | }
26 |
27 | export const goal = 'rgba(255, 0, 0, 0.4)';
28 | export const goalOnLight = '#f58f8f';
29 |
30 | export const obstacle = '#333';
31 |
32 | export const board = '#00ffff';
33 |
34 | export const blocks = ['#41470b', '#78730d', '#bab217', '#e3d682', '#b3a06f', '#9c7640', '#5a4c35'];
35 |
36 | export const hover = 'rgba(180, 180, 255, 0.4)';
37 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/SimEndMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class SimEndMessage extends Message {
6 |
7 | private long time;
8 | private long score;
9 | private int ranking;
10 |
11 | public SimEndMessage(JSONObject content) {
12 | this.time = content.optLong("time");
13 | this.score = content.optLong("score", -1);
14 | this.ranking = content.optInt("ranking", -1);
15 | }
16 |
17 | public SimEndMessage(long score, int ranking) {
18 | this.time = System.currentTimeMillis();
19 | this.score = score;
20 | this.ranking = ranking;
21 | }
22 |
23 | @Override
24 | public String getMessageType() {
25 | return Message.TYPE_SIM_END;
26 | }
27 |
28 | @Override
29 | public JSONObject makeContent() {
30 | JSONObject json = new JSONObject();
31 | json.put("time", time);
32 | json.put("score", score);
33 | json.put("ranking", ranking);
34 | return json;
35 | }
36 |
37 | public long getTime() {
38 | return time;
39 | }
40 |
41 | public long getScore() {
42 | return score;
43 | }
44 |
45 | public int getRanking() {
46 | return ranking;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/docker/jason/code/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | Project jason_MAPC_Test
3 |
4 | Gradle build file for Jason Application
5 | */
6 |
7 | defaultTasks 'run'
8 |
9 | apply plugin: 'java'
10 | apply plugin: 'eclipse'
11 |
12 | version '1.0'
13 | group 'org.jason'
14 |
15 | // java {
16 | // toolchain {
17 | // languageVersion = JavaLanguageVersion.of(15)
18 | // }
19 | // }
20 |
21 | repositories {
22 | mavenCentral()
23 |
24 | //maven { url "http://jacamo.sourceforge.net/maven2" }
25 | maven { url "https://raw.github.com/jacamo-lang/mvn-repo/master" }
26 | maven { url "https://jade.tilab.com/maven/" }
27 |
28 | flatDir { dirs 'lib' }
29 | }
30 |
31 | dependencies {
32 | compile group: 'org.jason', name: 'jason' , version: '3.0-SNAPSHOT'
33 | implementation name: 'eismassim'
34 | }
35 |
36 | sourceSets {
37 | main {
38 | java {
39 | srcDir 'src/java'
40 | }
41 | resources {
42 | srcDir 'src/resources'
43 | }
44 | }
45 | }
46 |
47 | task run (type: JavaExec, dependsOn: 'classes') {
48 | description 'runs the application'
49 | main = 'jason.infra.local.RunLocalMAS'
50 | args 'SampleMAS.mas2j'
51 | classpath sourceSets.main.runtimeClasspath
52 | }
53 |
54 | clean {
55 | delete 'bin'
56 | delete 'build'
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/data/Thing.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.data;
2 |
3 | import org.json.JSONObject;
4 |
5 | public class Thing {
6 |
7 | public static final String TYPE_ENTITY = "entity";
8 | public static final String TYPE_BLOCK = "block";
9 | public static final String TYPE_DISPENSER = "dispenser";
10 | public static final String TYPE_MARKER = "marker";
11 | public static final String TYPE_TASKBOARD = "taskboard";
12 |
13 | public int x;
14 | public int y;
15 | public String type;
16 | public String details;
17 |
18 | public Thing(int x, int y, String type, String details) {
19 | this.x = x;
20 | this.y = y;
21 | this.type = type;
22 | this.details = details;
23 | }
24 |
25 | public JSONObject toJSON() {
26 | JSONObject thing = new JSONObject();
27 | thing.put("x", x);
28 | thing.put("y", y);
29 | thing.put("type", type);
30 | thing.put("details", details);
31 | return thing;
32 | }
33 |
34 | public static Thing fromJson(JSONObject jsonThing) {
35 | return new Thing(jsonThing.getInt("x"), jsonThing.getInt("y"), jsonThing.getString("type"), jsonThing.getString("details"));
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return String.format("Thing((%d,%d), %s, %s)", x, y, type, details);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/javaagents/src/main/java/massim/javaagents/agents/BasicAgent.java:
--------------------------------------------------------------------------------
1 | package massim.javaagents.agents;
2 |
3 | import eis.iilang.*;
4 | import massim.javaagents.MailService;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * A very basic agent.
10 | */
11 | public class BasicAgent extends Agent {
12 |
13 | private int lastID = -1;
14 |
15 | /**
16 | * Constructor.
17 | * @param name the agent's name
18 | * @param mailbox the mail facility
19 | */
20 | public BasicAgent(String name, MailService mailbox) {
21 | super(name, mailbox);
22 | }
23 |
24 | @Override
25 | public void handlePercept(Percept percept) {}
26 |
27 | @Override
28 | public void handleMessage(Percept message, String sender) {}
29 |
30 | @Override
31 | public Action step() {
32 | List percepts = getPercepts();
33 | for (Percept percept : percepts) {
34 | if (percept.getName().equals("actionID")) {
35 | Parameter param = percept.getParameters().get(0);
36 | if (param instanceof Numeral) {
37 | int id = ((Numeral) param).getValue().intValue();
38 | if (id > lastID) {
39 | lastID = id;
40 | return new Action("move", new Identifier("n"));
41 | }
42 | }
43 | }
44 | }
45 | return null;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/docs/javaagents.md:
--------------------------------------------------------------------------------
1 | # MASSim Javaagents Documentation
2 |
3 | This module provides some very basic agent framework written in Java. EISMASSim
4 | is integrated so that agents can communicate with the MASSim server out of the box.
5 |
6 | Some very basic agents are included mainly for testing purposes.
7 |
8 | ## Create your own agent
9 | * Add a new class for your agent somewhere in _massim.javaagents.agents_
10 | * Make your class extend _massim.javaagents.agents.Agent_
11 | * Add your class with a type name to the _setEnvironment()_ method of _massim.javaagents.Scheduler_
12 | * Create a JSON configuration file for your agents
13 |
14 | ### Java agents configuration file
15 | A sample configuration might look like this
16 |
17 | ```json
18 | {
19 | "agents" : [
20 | {
21 | "count": 20,
22 | "start-index": 0,
23 | "agent-prefix": "A",
24 | "entity-prefix": "connectionA",
25 | "team": "A",
26 | "class": "BasicAgent"
27 | },
28 | ...
29 | ]
30 | }
31 | ```
32 |
33 | The attributes are
34 | * __count__: how many agents to create
35 | * __start-index__: the index that is first appended to the prefixes
36 | * __agent-prefix__: the prefix for all agents' names
37 | * __entity-prefix__: the prefix of all entity connections
38 | * __team__: the agents' team name
39 | * __class__: the agents' type as registered in the scheduler class
40 |
41 | Of course you can specify multiple blocks to configure multiple teams or sets of agents with different agent classes in the same file.
--------------------------------------------------------------------------------
/protocol/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | massim
8 | protocol
9 | 2.3
10 |
11 | jar
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 | 3.8.0
23 |
24 | 13
25 | 13
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | junit
34 | junit
35 | 4.13.1
36 |
37 |
38 | org.json
39 | json
40 | 20201115
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/monitor/js/statusView.ts:
--------------------------------------------------------------------------------
1 | import { h } from 'snabbdom';
2 | import { VNode } from 'snabbdom/vnode';
3 |
4 | import { StatusCtrl, StatusData } from './statusInterfaces';
5 | import { compareAgent } from './util';
6 | import * as styles from './styles';
7 |
8 | function view(data: StatusData): VNode[] {
9 | data.entities.sort(compareAgent);
10 |
11 | const teams: string[] = [];
12 | for (const entity of data.entities) {
13 | if (teams.indexOf(entity.team) == -1) teams.push(entity.team);
14 | }
15 |
16 | return [
17 | h('h2', `Step ${data.step}/${data.steps - 1}`),
18 | h('table', [
19 | h('thead', [
20 | h('tr', [
21 | h('th', 'Team'),
22 | h('th', 'Agent'),
23 | h('th', 'Last action'),
24 | h('th', 'Last action result')
25 | ])
26 | ]),
27 | h('tbody', data.entities.map((entity) => {
28 | const teamStyle = { style: styles.team(teams.indexOf(entity.team)) };
29 | return h('tr', [
30 | h('td', teamStyle, entity.team),
31 | h('td', teamStyle, entity.name),
32 | h('td', { attrs: { class: entity.action } }, entity.action),
33 | h('td', { attrs: { class: entity.actionResult } }, entity.actionResult)
34 | ]);
35 | }))
36 | ])
37 | ];
38 | }
39 |
40 | export function statusView(ctrl: StatusCtrl): VNode {
41 | return h('div#status', [
42 | h('h1', ['Status: ', ctrl.vm.data ? ctrl.vm.data.sim : ctrl.vm.state]),
43 | ...(ctrl.vm.data ? view(ctrl.vm.data) : [])
44 | ]);
45 | }
46 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | massim
4 | massim
5 | 2020-2.0
6 | pom
7 |
8 | MAPC Package
9 | http://multiagentcontest.org
10 |
11 |
12 | UTF-8
13 |
14 |
15 |
16 | server
17 | eismassim
18 | protocol
19 | javaagents
20 | monitor
21 |
22 |
23 |
24 |
25 |
26 | maven-clean-plugin
27 | 3.1.0
28 |
29 |
30 | auto-clean
31 | initialize
32 |
33 | clean
34 |
35 |
36 |
37 |
38 |
39 | maven-assembly-plugin
40 |
41 |
42 |
43 | attached
44 |
45 | package
46 |
47 |
48 |
49 | dep.xml
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/util/RNG.java:
--------------------------------------------------------------------------------
1 | package massim.util;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 | import java.util.Random;
6 |
7 | /**
8 | * Implements the random number generation (wraps standard Java Random for now).
9 | */
10 | public abstract class RNG {
11 |
12 | private static Random random = new Random(System.currentTimeMillis());
13 |
14 | /**
15 | * Initializes the rng to the given seed.
16 | * @param seed the seed for the rng
17 | */
18 | public static synchronized void initialize(long seed){
19 | random = new Random(seed);
20 | }
21 |
22 | /**
23 | * @see Random#nextInt()
24 | */
25 | public static synchronized int nextInt(){
26 | return random.nextInt();
27 | }
28 |
29 | /**
30 | * (upper bound exclusive)
31 | * @see Random#nextInt(int)
32 | */
33 | public static synchronized int nextInt(int bound){
34 | return random.nextInt(bound);
35 | }
36 |
37 | /**
38 | * @see Random#nextDouble()
39 | */
40 | public static synchronized double nextDouble(){ return random.nextDouble(); }
41 |
42 | /**
43 | * Shuffles a list with the internal random object.
44 | * @see Collections#shuffle
45 | * @param list the list to shuffle
46 | */
47 | public static synchronized void shuffle(List> list){
48 | Collections.shuffle(list, random);
49 | }
50 |
51 | public static synchronized int betweenClosed(int lower, int upper){
52 | return lower + nextInt(upper - lower + 1);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/config/TeamConfig.java:
--------------------------------------------------------------------------------
1 | package massim.config;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * Represents one configured team.
7 | * @author ta10
8 | */
9 | public class TeamConfig {
10 | private String name;
11 |
12 | private Map passwords = new HashMap<>();
13 | private Vector agents = new Vector<>();
14 |
15 | /**
16 | * Creates a new team with the given name.
17 | * @param name the name of the team
18 | */
19 | public TeamConfig(String name){
20 | this.name = name;
21 | }
22 |
23 | /**
24 | * @return the name of the team
25 | */
26 | public String getName(){
27 | return name;
28 | }
29 |
30 | /**
31 | * Adds an agent to the team (in order).
32 | * @param name name of the agent
33 | * @param password the agent's password
34 | */
35 | public void addAgent(String name, String password){
36 | agents.add(name);
37 | passwords.put(name, password);
38 | }
39 |
40 | /**
41 | * @param agentName name of an agent
42 | * @return the agent's password or null if no such agent exists
43 | */
44 | public String getPassword(String agentName){
45 | return passwords.get(agentName);
46 | }
47 |
48 | /**
49 | * Creates a new list containing the names of all agents in this team.
50 | * @return the new set
51 | */
52 | public ArrayList getAgentNames(){
53 | return new ArrayList<>(agents);
54 | }
55 |
56 | public int size() {
57 | return agents.size();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/scenario/InitialPercept.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages.scenario;
2 |
3 | import massim.protocol.messages.SimStartMessage;
4 | import org.json.JSONObject;
5 |
6 | public class InitialPercept extends SimStartMessage {
7 |
8 | public String agentName;
9 | public String teamName;
10 | public int teamSize;
11 | public int steps;
12 | public int vision;
13 |
14 | public InitialPercept(JSONObject content) {
15 | super(content);
16 | parsePercept(content.getJSONObject("percept"));
17 | }
18 |
19 | public InitialPercept(String agentName, String teamName, int teamSize, int steps, int vision) {
20 | super(System.currentTimeMillis());
21 | this.agentName = agentName;
22 | this.teamName = teamName;
23 | this.teamSize = teamSize;
24 | this.steps = steps;
25 | this.vision = vision;
26 | }
27 |
28 | @Override
29 | public JSONObject makePercept() {
30 | JSONObject percept = new JSONObject();
31 | percept.put("name", agentName);
32 | percept.put("team", teamName);
33 | percept.put("teamSize", teamSize);
34 | percept.put("steps", steps);
35 | percept.put("vision", vision);
36 | return percept;
37 | }
38 |
39 | private void parsePercept(JSONObject percept) {
40 | agentName = percept.getString("name");
41 | teamName = percept.getString("team");
42 | steps = percept.getInt("steps");
43 | teamSize = percept.getInt("teamSize");
44 | vision = percept.getInt("vision");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | networks:
4 | main:
5 |
6 | services:
7 | massimserver:
8 | image: massimserver
9 | build:
10 | context: ./server/
11 | args:
12 | JAVA_VERSION: ${JAVA_VERSION}
13 | networks:
14 | - main
15 | ports:
16 | - "12300:12300"
17 | command: bash -c "java -jar server/target/server-*-jar-with-dependencies.jar -conf ./server/conf/AutoLaunch.json"
18 |
19 | # javaagents:
20 | # image: javaagents
21 | # build:
22 | # context: .
23 | # dockerfile: javaagents/Dockerfile
24 | # args:
25 | # JAVA_VERSION: ${JAVA_VERSION}
26 | # networks:
27 | # - main
28 | # command: bash -c "java -jar javaagents/target/javaagents-*-jar-with-dependencies.jar ./javaagents/conf/BasicAgents"
29 |
30 | # goal:
31 | # build:
32 | # context: .
33 | # dockerfile: goal/Dockerfile
34 | # args:
35 | # JAVA_VERSION: ${JAVA_VERSION}
36 | # GOAL_URL: ${GOAL_URL}
37 | # networks:
38 | # - main
39 | # volumes:
40 | # - ./goal/code:/goal/code
41 | # - ./lib:/goal/lib
42 | # working_dir: /goal
43 | # command: bash -c "java -jar ./goal.jar ./code/test.mas2g -v"
44 |
45 | jason:
46 | image: jason
47 | build:
48 | context: .
49 | dockerfile: jason/Dockerfile
50 | args:
51 | JASON_JAVA_VERSION: ${JASON_JAVA_VERSION}
52 | JASON_URL: ${JASON_URL}
53 | working_dir: /jason/code
54 | networks:
55 | - main
56 | volumes:
57 | - ./jason/code:/jason/code
58 | - ./lib:/jason/code/lib
59 | - ./conf:/jason/code/conf
60 | command: bash -c "gradle --console=plain"
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/ActionMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import massim.protocol.messages.scenario.Actions;
4 | import org.json.JSONArray;
5 | import org.json.JSONObject;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class ActionMessage extends Message{
11 |
12 | private String actionType;
13 | private long id;
14 | private List params;
15 |
16 | public ActionMessage(JSONObject content) {
17 | this.actionType = content.optString("type", Actions.UNKNOWN_ACTION);
18 | this.id = content.optLong("id", -1);
19 | JSONArray p = content.optJSONArray("p");
20 | this.params = new ArrayList<>();
21 | for(int i = 0; i < p.length(); i++) {
22 | this.params.add(p.optString(i));
23 | }
24 | }
25 |
26 | public ActionMessage(String actionType, long id, List params) {
27 | this.actionType = actionType;
28 | this.id = id;
29 | this.params = params;
30 | }
31 |
32 | public String getActionType() {
33 | return actionType;
34 | }
35 |
36 | public long getId() {
37 | return id;
38 | }
39 |
40 | public List getParams() {
41 | return params;
42 | }
43 |
44 | @Override
45 | public String getMessageType() {
46 | return Message.TYPE_ACTION;
47 | }
48 |
49 | @Override
50 | public JSONObject makeContent() {
51 | JSONObject content = new JSONObject();
52 | content.put("type", actionType);
53 | content.put("id", id);
54 | JSONArray params = new JSONArray();
55 | this.params.forEach(params::put);
56 | content.put("p", params);
57 | return content;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/data/TaskInfo.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.data;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 |
6 | import java.util.*;
7 |
8 | public class TaskInfo {
9 | public String name;
10 | public int deadline;
11 | public int reward;
12 | public List requirements;
13 |
14 | public TaskInfo(String name, int deadline, int reward, Set requirements) {
15 | this.name = name;
16 | this.deadline = deadline;
17 | this.requirements = new ArrayList<>(requirements);
18 | this.requirements.sort((t1, t2) -> {
19 | var r = Integer.compare(t1.x, t2.x);
20 | if (r == 0) return Integer.compare(t1.y, t2.y);
21 | return r;
22 | });
23 | this.reward = reward;
24 | }
25 |
26 | public JSONObject toJSON() {
27 | JSONObject task = new JSONObject();
28 | task.put("name", name);
29 | task.put("deadline", deadline);
30 | task.put("reward", reward);
31 | JSONArray jsonReqs = new JSONArray();
32 | for (Thing requirement : requirements) {
33 | jsonReqs.put(requirement.toJSON());
34 | }
35 | task.put("requirements", jsonReqs);
36 | return task;
37 | }
38 |
39 | public static TaskInfo fromJson(JSONObject jsonTask) {
40 | Set requirements = new HashSet<>();
41 | JSONArray jsonRequirements = jsonTask.getJSONArray("requirements");
42 | for (int i = 0; i < jsonRequirements.length(); i++) {
43 | requirements.add(Thing.fromJson(jsonRequirements.getJSONObject(i)));
44 | }
45 | return new TaskInfo(jsonTask.getString("name"), jsonTask.getInt("deadline"),
46 | jsonTask.getInt("reward"), requirements);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/conf/SimpleConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "server" : {
3 | "tournamentMode" : "round-robin",
4 | "teamsPerMatch" : 1,
5 | "launch" : "key",
6 | "port" : 12300,
7 | "backlog" : 10000,
8 | "agentTimeout" : 4000,
9 | "resultPath" : "results",
10 | "logLevel" : "normal",
11 | "logPath" : "logs",
12 | "replayPath" : "replays",
13 | "maxPacketLength" : 65536,
14 | "waitBetweenSimulations" : 5000
15 | },
16 |
17 | "manual-mode" : [
18 | ["A", "B"],
19 | ["B", "C"],
20 | ["A", "C"]
21 | ],
22 |
23 | "match" : [
24 | {
25 | "NOsetup" : "conf/setup/test.txt",
26 |
27 | "steps" : 750,
28 | "randomSeed" : 17,
29 | "randomFail" : 0,
30 | "entities" : {"standard" : 15},
31 | "clusterBounds" : [2,3],
32 |
33 | "clearSteps" : 3,
34 | "clearEnergyCost" : 30,
35 | "disableDuration" : 4,
36 | "maxEnergy" : 300,
37 | "attachLimit" : 10,
38 |
39 | "grid" : {
40 | "height" : 50,
41 | "width" : 50,
42 | "instructions": [
43 | ["cave", 0.45, 9, 5, 4]
44 | ],
45 | "goals": {
46 | "number" : 5,
47 | "size" : [1,3]
48 | }
49 | },
50 |
51 | "blockTypes" : [1, 2],
52 | "dispensers" : [5, 10],
53 |
54 | "tasks" : {
55 | "size" : [1, 1],
56 | "duration" : [200, 200],
57 | "probability" : 0.05,
58 | "taskboards" : 3,
59 | "rewardDecay" : [1, 2],
60 | "lowerRewardLimit" : 10,
61 | "distanceToTaskboards" : 8
62 | },
63 |
64 | "events" : {
65 | "chance" : 0,
66 | "radius" : [3, 5],
67 | "warning" : 5,
68 | "create" : [-3, 1],
69 | "perimeter" : 2
70 | }
71 | }
72 | ],
73 |
74 | "teams" : {
75 | "A" : {"prefix" : "agent", "password" : "1"}
76 | }
77 | }
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/scenario/Actions.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages.scenario;
2 |
3 | import java.util.List;
4 |
5 | public abstract class Actions {
6 |
7 | public final static String NO_ACTION = "no_action";
8 | public final static String UNKNOWN_ACTION = "unknown_action";
9 |
10 | public final static String MOVE = "move";
11 | public final static String ATTACH = "attach";
12 | public final static String DETACH = "detach";
13 | public final static String ROTATE = "rotate";
14 | public final static String CONNECT = "connect";
15 | public final static String REQUEST = "request";
16 | public final static String SUBMIT = "submit";
17 | public final static String CLEAR = "clear";
18 | public final static String DISCONNECT = "disconnect";
19 | public final static String SKIP = "skip";
20 | public final static String ACCEPT = "accept";
21 |
22 | public static final List ALL_ACTIONS = List.of(
23 | MOVE, ATTACH, DETACH, ROTATE, CONNECT, REQUEST, SUBMIT, CLEAR, DISCONNECT, SKIP, ACCEPT);
24 |
25 | public final static String RESULT_UNPROCESSED = "unprocessed";
26 | public final static String RESULT_SUCCESS = "success";
27 | public final static String RESULT_F = "failed";
28 | public final static String RESULT_F_RANDOM = "failed_random";
29 | public final static String RESULT_F_PARAMETER = "failed_parameter";
30 | public final static String RESULT_F_PATH = "failed_path";
31 | public final static String RESULT_F_PARTNER = "failed_partner";
32 | public final static String RESULT_F_TARGET = "failed_target";
33 | public static final String RESULT_F_BLOCKED = "failed_blocked";
34 | public static final String RESULT_F_STATUS = "failed_status";
35 | public static final String RESULT_F_RESOURCES = "failed_resources";
36 | public static final String RESULT_F_LOCATION = "failed_location";
37 | }
38 |
--------------------------------------------------------------------------------
/monitor/src/main/resources/www/layout.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | background: #161512;
5 | color: #bababa;
6 | font-family: Arial, Helvetica, sans-serif;
7 | overflow: hidden;
8 | }
9 | a {
10 | color: #bababa;
11 | cursor: pointer;
12 | text-decoration: underline;
13 | }
14 | p {
15 | margin: 0;
16 | }
17 |
18 | #overlay {
19 | position: absolute;
20 | top: 10px;
21 | left: 10px;
22 | width: 340px;
23 | max-height: calc(100% - 20px);
24 | background: #161512cc;
25 | overflow-y: auto;
26 | overflow-x: hidden;
27 | }
28 |
29 | .replay button {
30 | padding: 0;
31 | line-height: 1.5em;
32 | width: 19%;
33 | }
34 | .replay {
35 | word-wrap: anywhere;
36 | }
37 |
38 | #overlay canvas {
39 | margin: 0.8em auto;
40 | display: block;
41 | }
42 |
43 | span.no_action, span.unknown {
44 | color: red;
45 | }
46 |
47 | .team {
48 | padding: 4px;
49 | }
50 |
51 | #monitor > canvas, #monitor > .maps {
52 | position: absolute;
53 | top: 0;
54 | left: 0;
55 | right: 0;
56 | bottom: 0;
57 | width: 100%;
58 | height: 100%;
59 | }
60 |
61 | #monitor > .maps {
62 | padding-top: 10px;
63 | padding-left: 350px;
64 | overflow-y: auto;
65 | width: calc(100% - 350px);
66 | }
67 |
68 | .box, .map {
69 | box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.2), 0 1px 5px 0 rgba(0,0,0,0.12);
70 | background-color: #252422;
71 | margin: 0.5em;
72 | padding: 0.5em;
73 | }
74 |
75 | ul {
76 | margin: 0.4em 0;
77 | padding-left: 1.5em;
78 | }
79 |
80 | .map {
81 | width: 250px;
82 | float: left;
83 | }
84 | .map.no_action, .map.unknown{
85 | background: #901717;
86 | }
87 | .map a {
88 | display: block;
89 | }
90 | .map a.team {
91 | text-decoration: none;
92 | }
93 | .map .meta {
94 | height: 5em;
95 | font-size: 0.8em;
96 | }
97 |
98 | canvas {
99 | cursor: crosshair;
100 | }
101 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/config/ServerConfig.java:
--------------------------------------------------------------------------------
1 | package massim.config;
2 |
3 | import org.json.JSONObject;
4 |
5 | import java.util.*;
6 |
7 | /**
8 | * Holds all massim.config values relevant for the server part. Initialized with the massim.config JSON object.
9 | * Also references all other massim.config objects.
10 | * @author ta10
11 | */
12 | public class ServerConfig {
13 |
14 | public final static String MODE_ROUND_ROBIN = "round-robin";
15 | public final static String MODE_MANUAL = "manual";
16 | public final static String MODE_RANDOM = "random";
17 |
18 | public String tournamentMode;
19 | public String launch;
20 | public int teamsPerMatch;
21 | public List teams = new ArrayList<>();
22 | public List simConfigs = new ArrayList<>();
23 | public int port;
24 | public int backlog;
25 | public Map accounts = new HashMap<>();
26 | public long agentTimeout;
27 | public String logPath;
28 | public String resultPath;
29 |
30 | /**
31 | * The level at which to log.
32 | */
33 | public String logLevel;
34 |
35 | /**
36 | * All teams to participate in any simulation.
37 | */
38 | public List> manualModeTeams;
39 |
40 | /**
41 | * The maximum length of a received XML document (in bytes). Larger files will not be processed.
42 | */
43 | public int maxPacketLength;
44 |
45 | /**
46 | * The path were replays should be saved. If null, replay won't be saved.
47 | */
48 | public String replayPath;
49 |
50 | /**
51 | * The port for the webmonitor or 0.
52 | */
53 | public int monitorPort;
54 |
55 | /**
56 | * The amount of ms to pause between simulations.
57 | */
58 | public int waitBetweenSimulations = 0;
59 |
60 | /**
61 | * Actual number of agents required in each simulation.
62 | */
63 | public List teamSizes = new ArrayList<>();
64 | }
65 |
--------------------------------------------------------------------------------
/docker/jason/code/logging.properties:
--------------------------------------------------------------------------------
1 | # Jason Default log configuration
2 | #
3 | # Comment/uncomment the following lines to setup your log
4 | #
5 |
6 | # default Jason MAS Console
7 | handlers = jason.runtime.MASConsoleLogHandler
8 |
9 | # To use the ConsoleHandler, use the following line instead.
10 | #handlers= java.util.logging.ConsoleHandler
11 |
12 | # To also add the FileHandler, use the following line instead.
13 | #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
14 |
15 | # Default logging level. Other values are:
16 | # SEVERE (only severe messages)
17 | # WARNING (only warnings and severe messages)
18 | # INFO (normal output)
19 | # FINE (debug level of messages)
20 | .level = INFO
21 |
22 | ############################################################
23 | # Handler specific properties.
24 | # Describes specific configuration info for Handlers.
25 | ############################################################
26 |
27 | # Jason Handler parameters
28 | jason.runtime.MASConsoleLogHandler.level = ALL
29 | jason.runtime.MASConsoleLogHandler.formatter = jason.runtime.MASConsoleLogFormatter
30 | # set one text area for each agent
31 | jason.runtime.MASConsoleLogHandler.tabbed = false
32 | jason.runtime.MASConsoleLogHandler.colors = false
33 |
34 | # default file output is in project's directory.
35 | java.util.logging.FileHandler.pattern = mas.log
36 | #java.util.logging.FileHandler.pattern = mas-%u.log
37 | java.util.logging.FileHandler.limit = 500000
38 | java.util.logging.FileHandler.count = 1
39 | java.util.logging.FileHandler.formatter = jason.runtime.MASConsoleLogFormatter
40 | #java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
41 |
42 | # Limit the message that are printed on the console to FINE and above.
43 | java.util.logging.ConsoleHandler.level = FINE
44 | java.util.logging.ConsoleHandler.formatter = jason.runtime.MASConsoleLogFormatter
45 |
46 | java.level=OFF
47 | javax.level=OFF
48 | sun.level=OFF
49 | jade.level=OFF
50 |
--------------------------------------------------------------------------------
/monitor/js/view.ts:
--------------------------------------------------------------------------------
1 | import { h } from 'snabbdom/h';
2 | import { VNode } from 'snabbdom/vnode';
3 |
4 | import { Ctrl } from './ctrl';
5 | import { overlay } from './overlay';
6 | import { MapCtrl, mapView } from './map';
7 | import * as styles from './styles';
8 |
9 | export function view(ctrl: Ctrl): VNode {
10 | return h('div#monitor', [
11 | ctrl.maps.length ? agentView(ctrl) : mapView(ctrl.map),
12 | overlay(ctrl),
13 | ]);
14 | }
15 |
16 | function agentView(ctrl: Ctrl): VNode | undefined {
17 | if (!ctrl.vm.static) return;
18 |
19 | return h('div.maps', ctrl.maps.map(m => {
20 | const agent = m.selectedAgent();
21 | if (!agent) return;
22 | const acceptedTask = agent.acceptedTask;
23 | return h('div', {
24 | class: (agent.action && agent.actionResult) ? {
25 | 'map': true,
26 | [agent.action]: true,
27 | [agent.actionResult]: true,
28 | } : {
29 | map: true,
30 | },
31 | }, [
32 | h('a.team', {
33 | style: m.vm.selected === ctrl.map.vm.selected ? {
34 | background: 'white',
35 | color: 'black',
36 | } : styles.team(ctrl.vm.teamNames.indexOf(agent.team)),
37 | on: {
38 | click() {
39 | ctrl.map.vm.selected = agent.id;
40 | ctrl.toggleMaps();
41 | },
42 | },
43 | }, `${agent.name} (${agent.x}|${agent.y})`),
44 | mapView(m, {
45 | size: 250,
46 | viewOnly: true,
47 | }),
48 | h('div.meta', [
49 | h('div', `energy = ${agent.energy}`),
50 | agent.action ? h('div', `${agent.action}(…) = ${agent.actionResult}`) : undefined,
51 | acceptedTask ? h('a', {
52 | on: {
53 | click() {
54 | ctrl.vm.taskName = acceptedTask;
55 | ctrl.redraw();
56 | }
57 | }
58 | }, agent.acceptedTask) : undefined,
59 | agent.disabled ? h('div', 'disabled') : undefined,
60 | ]),
61 | ]);
62 | }));
63 | }
64 |
--------------------------------------------------------------------------------
/monitor/js/interfaces.ts:
--------------------------------------------------------------------------------
1 | export type Redraw = () => void
2 |
3 | export type ConnectionState = 'offline' | 'online' | 'connecting' | 'error'
4 |
5 | export type BlockType = string
6 |
7 | export interface StaticWorld {
8 | sim: string
9 | grid: Grid
10 | teams: { [key: string]: Team }
11 | blockTypes: BlockType[]
12 | steps: number
13 | }
14 |
15 | export interface Team {
16 | name: string
17 | }
18 |
19 | export interface Grid {
20 | width: number
21 | height: number
22 | }
23 |
24 | type Terrain = 0 | 1 | 2
25 |
26 | export interface DynamicWorld {
27 | step: number
28 | entities: Agent[]
29 | blocks: Block[]
30 | dispensers: Dispenser[]
31 | taskboards?: TaskBoard[]
32 | tasks: Task[]
33 | clear: ClearEvent[]
34 | cells: Terrain[][]
35 | scores: { [team: string]: number }
36 | }
37 |
38 | export interface Positionable {
39 | x: number
40 | y: number
41 | }
42 |
43 | export interface AgentStatus {
44 | name: string
45 | team: string
46 | action: string // can be empty string before first step
47 | actionResult: string
48 | }
49 |
50 | export interface Agent extends Positionable, AgentStatus {
51 | id: number
52 | energy: number
53 | vision: number
54 | attached?: Pos[]
55 | disabled?: boolean
56 | actionParams: string[]
57 | acceptedTask?: string // can be empty string before first accept
58 | }
59 |
60 | export interface Block extends Positionable {
61 | type: BlockType
62 | attached?: Pos[]
63 | }
64 |
65 | export interface Dispenser extends Positionable {
66 | id: number
67 | type: BlockType
68 | }
69 |
70 | export type TaskBoard = Positionable
71 |
72 | export interface Task {
73 | reward: number
74 | name: string
75 | deadline: number
76 | requirements: Block[]
77 | }
78 |
79 | export interface ClearEvent extends Positionable {
80 | radius: number
81 | }
82 |
83 | export interface Rect {
84 | x1: number
85 | x2: number
86 | y1: number
87 | y2: number
88 | width: number
89 | height: number
90 | }
91 |
92 | export type Pos = Positionable
93 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/ReplayWriter.java:
--------------------------------------------------------------------------------
1 | package massim;
2 |
3 | import massim.util.Log;
4 | import org.json.JSONObject;
5 |
6 | import java.io.File;
7 | import java.io.FileWriter;
8 | import java.io.IOException;
9 | import java.nio.file.Paths;
10 |
11 | public class ReplayWriter {
12 |
13 | private static final int GROUP_SIZE = 5;
14 |
15 | private String replayPath;
16 |
17 | private int lastGroup = -1;
18 | private JSONObject cache = new JSONObject();
19 |
20 | public ReplayWriter(String replayPath) {
21 | this.replayPath = replayPath;
22 | }
23 |
24 | public void updateState(String simId, String startTime, JSONObject world) {
25 | if (!isStatic(world)) {
26 | int step = world.optInt("step");
27 | int group = step / GROUP_SIZE;
28 | String stepStr = String.valueOf(step);
29 |
30 | if (lastGroup != group || cache.has(stepStr)) cache = new JSONObject();
31 | cache.put(stepStr, world);
32 | write(startTime, simId, String.valueOf(group * GROUP_SIZE), cache);
33 |
34 | lastGroup = group;
35 | } else {
36 | write(startTime, simId, "static", world);
37 | }
38 | }
39 |
40 | private boolean isStatic(JSONObject world) {
41 | return world.has("grid");
42 | }
43 |
44 | private void write(String startTime, String simId, String name, JSONObject json) {
45 | if(json == null) {
46 | Log.log(Log.Level.ERROR, "No JSON object to write.");
47 | return;
48 | }
49 | String prefix = startTime + "-" + simId;
50 | File file = Paths.get(this.replayPath, prefix, name + ".json").toFile();
51 | File dir = file.getParentFile();
52 | if (!dir.exists()) dir.mkdirs();
53 |
54 | try {
55 | FileWriter writer = new FileWriter(file);
56 | json.write(writer);
57 | writer.close();
58 | } catch (IOException e) {
59 | e.printStackTrace();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/RequestActionMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONObject;
4 |
5 | /**
6 | * Should be sub-classed for request-action messages including an agent's current percepts.
7 | */
8 | public abstract class RequestActionMessage extends Message {
9 |
10 | private long time;
11 | private long id;
12 | private long deadline;
13 | private int step;
14 |
15 | public RequestActionMessage(JSONObject content) {
16 | this.time = content.optLong("time");
17 | this.id = content.optLong("id", -1);
18 | this.deadline = content.optLong("deadline", -1);
19 | this.step = content.optInt("step", -1);
20 | }
21 |
22 | public RequestActionMessage(long time, long id, long deadline, int step) {
23 | this.time = time;
24 | this.id = id;
25 | this.deadline = deadline;
26 | this.step = step;
27 | }
28 |
29 | @Override
30 | public String getMessageType() {
31 | return Message.TYPE_REQUEST_ACTION;
32 | }
33 |
34 | @Override
35 | public JSONObject makeContent() {
36 | JSONObject content = new JSONObject();
37 | content.put("id", id);
38 | content.put("time" , time);
39 | content.put("deadline", deadline);
40 | content.put("step", step);
41 | content.put("percept", makePercept());
42 | return content;
43 | }
44 |
45 | /**
46 | * Create the JSON representation of the percept part.
47 | * Will be appended under the "percept" key of the "content" object.
48 | */
49 | public abstract JSONObject makePercept();
50 |
51 | public void updateIdAndDeadline(long id, long deadline) {
52 | this.id = id;
53 | this.deadline = deadline;
54 | }
55 |
56 | public long getTime() {
57 | return time;
58 | }
59 |
60 | public long getId() {
61 | return id;
62 | }
63 |
64 | public long getDeadline() {
65 | return deadline;
66 | }
67 |
68 | public int getStep() {
69 | return step;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/monitor/src/main/java/massim/monitor/EventSink.java:
--------------------------------------------------------------------------------
1 | package massim.monitor;
2 |
3 | import org.webbitserver.BaseWebSocketHandler;
4 | import org.webbitserver.WebSocketConnection;
5 |
6 | import java.util.HashSet;
7 | import java.util.concurrent.locks.Lock;
8 | import java.util.concurrent.locks.ReentrantReadWriteLock;
9 |
10 | public class EventSink extends BaseWebSocketHandler {
11 | private final String name;
12 | private String latestStatic;
13 | private String latestDynamic;
14 | private final ReentrantReadWriteLock poolLock = new ReentrantReadWriteLock();
15 | private final HashSet pool = new HashSet();
16 |
17 | public EventSink(String name) {
18 | this.name = name;
19 | }
20 |
21 | @Override
22 | public void onOpen(WebSocketConnection client) {
23 | Lock lock = poolLock.writeLock();
24 | lock.lock();
25 | try {
26 | pool.add(client);
27 | if (latestStatic != null) client.send(latestStatic);
28 | if (latestDynamic != null) client.send(latestDynamic);
29 | System.out.println(String.format("[ MONITOR ] %s: %d connection(s)", name, pool.size()));
30 | } finally {
31 | lock.unlock();
32 | }
33 | }
34 |
35 | @Override
36 | public void onClose(WebSocketConnection client) {
37 | Lock lock = poolLock.writeLock();
38 | lock.lock();
39 | try {
40 | pool.remove(client);
41 | System.out.println(String.format("[ MONITOR ] %s: %d connection(s)", name, pool.size()));
42 | } finally {
43 | lock.unlock();
44 | }
45 | }
46 |
47 | public void broadcast(String message, boolean dynamic) {
48 | if (dynamic) this.latestDynamic = message;
49 | else this.latestStatic = message;
50 |
51 | Lock lock = poolLock.readLock();
52 | lock.lock();
53 | try {
54 | for (WebSocketConnection client: pool) {
55 | client.send(message);
56 | }
57 | } finally {
58 | lock.unlock();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/javaagents/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | massim
8 | javaagents
9 | 2020-1.0
10 |
11 |
12 | UTF-8
13 |
14 |
15 |
16 |
17 |
18 | org.apache.maven.plugins
19 | maven-compiler-plugin
20 | 3.8.0
21 |
22 | 13
23 | 13
24 |
25 |
26 |
27 | maven-assembly-plugin
28 |
29 |
30 | package
31 |
32 | single
33 |
34 |
35 |
36 |
37 |
38 |
39 | massim.javaagents.Main
40 |
41 |
42 |
43 | jar-with-dependencies
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | massim
53 | eismassim
54 | 4.3.2
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Attachable.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.game.Entity;
4 | import massim.protocol.data.Position;
5 |
6 | import java.util.ArrayList;
7 | import java.util.HashSet;
8 | import java.util.Set;
9 |
10 | public abstract class Attachable extends Positionable {
11 |
12 | private Set attachments = new HashSet<>();
13 |
14 | public Attachable(Position position) {
15 | super(position);
16 | }
17 |
18 | void attach(Attachable other) {
19 | attachments.add(other);
20 | other.requestAttachment(this);
21 | }
22 |
23 | void detach(Attachable other) {
24 | attachments.remove(other);
25 | other.requestDetachment(this);
26 | }
27 |
28 | Set getAttachments() {
29 | return new HashSet<>(attachments);
30 | }
31 |
32 | public void detachAll() {
33 | new ArrayList<>(attachments).forEach(this::detach);
34 | }
35 |
36 | private void requestAttachment(Attachable requester) {
37 | attachments.add(requester);
38 | }
39 |
40 | private void requestDetachment(Attachable requester) {
41 | attachments.remove(requester);
42 | }
43 |
44 | /**
45 | * @return a set of all attachments and attachments attached to these attachments (and so on)
46 | * including this Attachable
47 | */
48 | public Set collectAllAttachments() {
49 | var attachables = new HashSet();
50 | attachables.add(this);
51 | Set newAttachables = new HashSet<>(attachables);
52 | while (!newAttachables.isEmpty()) {
53 | var tempAttachables = new HashSet();
54 | for (Attachable a : newAttachables) {
55 | for (Attachable a2 : a.getAttachments()) {
56 | if (attachables.add(a2)) tempAttachables.add(a2);
57 | }
58 | }
59 | newAttachables = tempAttachables;
60 | }
61 | return attachables;
62 | }
63 |
64 | public boolean isAttachedToAnotherEntity() {
65 | return collectAllAttachments().stream().anyMatch(a -> a instanceof Entity && a != this);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/StatusResponseMessage.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 |
6 | public class StatusResponseMessage extends Message {
7 |
8 | public final long time;
9 | public final String[] teams;
10 | public final Integer[] teamSizes;
11 | public final int currentSimulation;
12 |
13 | public StatusResponseMessage(JSONObject content) {
14 | this.time = content.optLong("time");
15 |
16 | var jTeams = content.optJSONArray("teams");
17 | teams = new String[jTeams.length()];
18 | for (int i = 0; i < jTeams.length(); i++) {
19 | teams[i] = jTeams.getString(i);
20 | }
21 |
22 | var jTeamSizes = content.optJSONArray("teamSizes");
23 | teamSizes = new Integer[jTeamSizes.length()];
24 | for (int i = 0; i < jTeamSizes.length(); i++) {
25 | teamSizes[i] = jTeamSizes.getInt(i);
26 | }
27 |
28 | this.currentSimulation = content.optInt("currentSimulation");
29 | }
30 |
31 | public StatusResponseMessage(long time, String[] teams, Integer[] teamSizes, int currentSimulation) {
32 | this.time = time;
33 | this.teams = teams;
34 | this.teamSizes = teamSizes;
35 | this.currentSimulation = currentSimulation;
36 | }
37 |
38 | @Override
39 | public String getMessageType() {
40 | return Message.TYPE_STATUS_RESPONSE;
41 | }
42 |
43 | @Override
44 | public JSONObject makeContent() {
45 | JSONObject content = new JSONObject();
46 |
47 | content.put("time", time);
48 |
49 | var jTeams = new JSONArray();
50 | for (String team : teams) {
51 | jTeams.put(team);
52 | }
53 | content.put("teams", jTeams);
54 |
55 | var jTeamSizes = new JSONArray();
56 | for (Integer teamSize : teamSizes) {
57 | jTeamSizes.put(teamSize);
58 | }
59 | content.put("teamSizes", jTeamSizes);
60 |
61 | content.put("currentSimulation", currentSimulation);
62 |
63 | return content;
64 | }
65 |
66 | public long getTime() {
67 | return time;
68 | }
69 |
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/javaagents/src/main/java/massim/javaagents/MailService.java:
--------------------------------------------------------------------------------
1 | package massim.javaagents;
2 |
3 | import eis.iilang.Percept;
4 | import massim.javaagents.agents.Agent;
5 |
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Vector;
10 | import java.util.logging.Logger;
11 |
12 | /**
13 | * A simple register for agents that forwards messages.
14 | */
15 | public class MailService {
16 |
17 | private Map register = new HashMap<>();
18 | private Map> agentsByTeam = new HashMap<>();
19 | private Map teamForAgent = new HashMap<>();
20 | private Logger logger = Logger.getLogger("agents");
21 |
22 | /**
23 | * Registers an agent with this mail service. The agent will now receive messages.
24 | * @param agent the agent to register
25 | * @param team the agent's team (needed for broadcasts)
26 | */
27 | void registerAgent(Agent agent, String team){
28 | register.put(agent.getName(), agent);
29 | agentsByTeam.putIfAbsent(team, new Vector<>());
30 | agentsByTeam.get(team).add(agent);
31 | teamForAgent.put(agent.getName(), team);
32 | }
33 |
34 | /**
35 | * Adds a message to this mailbox.
36 | * @param message the message to add
37 | * @param to the receiving agent
38 | * @param from the agent sending the message
39 | */
40 | public void sendMessage(Percept message, String to, String from){
41 |
42 | Agent recipient = register.get(to);
43 |
44 | if(recipient == null) {
45 | logger.warning("Cannot deliver message to " + to + "; unknown target,");
46 | }
47 | else{
48 | recipient.handleMessage(message, from);
49 | }
50 | }
51 |
52 | /**
53 | * Sends a message to all agents of the sender's team (except the sender).
54 | * @param message the message to broadcast
55 | * @param sender the sending agent
56 | */
57 | public void broadcast(Percept message, String sender) {
58 | agentsByTeam.get(teamForAgent.get(sender)).stream()
59 | .map(Agent::getName)
60 | .filter(ag -> !ag.equals(sender))
61 | .forEach(ag -> sendMessage(message, ag, sender));
62 | }
63 | }
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/Message.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages;
2 |
3 | import massim.protocol.messages.scenario.InitialPercept;
4 | import massim.protocol.messages.scenario.StepPercept;
5 | import org.json.JSONObject;
6 |
7 | public abstract class Message {
8 |
9 | public final static String TYPE_REQUEST_ACTION = "request-action";
10 | public final static String TYPE_ACTION = "action";
11 | public final static String TYPE_AUTH_REQUEST = "auth-request";
12 | public final static String TYPE_AUTH_RESPONSE = "auth-response";
13 | public final static String TYPE_SIM_START = "sim-start";
14 | public final static String TYPE_SIM_END = "sim-end";
15 | public final static String TYPE_BYE = "bye";
16 | public final static String TYPE_STATUS_REQUEST = "status-request";
17 | public final static String TYPE_STATUS_RESPONSE = "status-response";
18 |
19 | public abstract String getMessageType();
20 |
21 | public abstract JSONObject makeContent();
22 |
23 | public JSONObject toJson() {
24 | JSONObject message = new JSONObject();
25 | message.put("type", getMessageType());
26 | message.put("content", makeContent());
27 | return message;
28 | }
29 |
30 | public static Message buildFromJson(JSONObject src) {
31 | if(src == null) return null;
32 | String type = src.optString("type");
33 | JSONObject content = src.optJSONObject("content");
34 | if(content == null) return null;
35 | switch(type) {
36 | case TYPE_ACTION: return new ActionMessage(content);
37 | case TYPE_REQUEST_ACTION: return new StepPercept(content);
38 | case TYPE_AUTH_RESPONSE: return new AuthResponseMessage(content);
39 | case TYPE_AUTH_REQUEST: return new AuthRequestMessage(content);
40 | case TYPE_BYE: return new ByeMessage(content);
41 | case TYPE_SIM_START: return new InitialPercept(content);
42 | case TYPE_SIM_END: return new SimEndMessage(content);
43 | case TYPE_STATUS_REQUEST: return new StatusRequestMessage();
44 | case TYPE_STATUS_RESPONSE: return new StatusResponseMessage(content);
45 | default: System.out.println("Message of type " + type + " cannot be build.");
46 | }
47 | return null;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/monitor/js/main.ts:
--------------------------------------------------------------------------------
1 | import { init } from 'snabbdom';
2 | import { VNode } from 'snabbdom/vnode';
3 | import { classModule } from 'snabbdom/modules/class';
4 | import { attributesModule } from 'snabbdom/modules/attributes';
5 | import { eventListenersModule } from 'snabbdom/modules/eventlisteners';
6 | import { styleModule } from 'snabbdom/modules/style';
7 |
8 | import { Ctrl } from './ctrl';
9 | import { view } from './view';
10 |
11 | import { StatusCtrl } from './statusInterfaces';
12 | import { makeStatusCtrl } from './statusCtrl';
13 | import { statusView } from './statusView';
14 |
15 | const patch = init([
16 | classModule,
17 | attributesModule,
18 | styleModule,
19 | eventListenersModule
20 | ]);
21 |
22 | export function Monitor(element: Element) {
23 | let vnode: VNode | Element = element;
24 | let ctrl: Ctrl;
25 |
26 | let redrawRequested = false;
27 |
28 | const redraw = function() {
29 | if (redrawRequested) return;
30 | redrawRequested = true;
31 | requestAnimationFrame(() => {
32 | redrawRequested = false;
33 | vnode = patch(vnode, view(ctrl));
34 | });
35 | };
36 |
37 | const hashChange = function() {
38 | if (ctrl.replay) {
39 | const step = parseInt(document.location.hash.substr(1), 10);
40 | if (step > 0) ctrl.replay.setStep(step);
41 | else if (!document.location.hash) ctrl.replay.start();
42 | }
43 | };
44 |
45 | const replayPath = window.location.search.length > 1 ? window.location.search.substr(1) : undefined;
46 | ctrl = new Ctrl(redraw, replayPath);
47 |
48 | hashChange();
49 | window.onhashchange = hashChange;
50 |
51 | redraw();
52 |
53 | /* canvas.addEventListener('mousemove', e => {
54 | if (!ctrl.vm.static) return;
55 | ctrl.setHover(invClientPos(canvas, ctrl.vm.static, e.clientX, e.clientY));
56 | });
57 | canvas.addEventListener('mouseleave', e => {
58 | ctrl.setHover(undefined);
59 | }); */
60 | }
61 |
62 | export function Status(target: Element) {
63 | let vnode: VNode | Element = target;
64 | let ctrl: StatusCtrl;
65 |
66 | let redrawRequested = false;
67 |
68 | const redraw = function() {
69 | if (redrawRequested) return;
70 | redrawRequested = true;
71 | requestAnimationFrame(() => {
72 | redrawRequested = false;
73 | vnode = patch(vnode, statusView(ctrl));
74 | });
75 | };
76 |
77 | ctrl = makeStatusCtrl(redraw);
78 |
79 | redraw();
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MASSim 2020: Agents Assemble II
2 | ===============================
3 |
4 | [](https://github.com/agentcontest/massim_2020/actions?query=workflow%3A%22Continuous+Integration%22)
5 |
6 | _MASSim_ (Multi-Agent Systems Simulation Platform), the simulation (server)
7 | software used in the
8 | [Multi-Agent Programming Contest](https://multiagentcontest.org/),
9 | where participants program agents to compete with each other in a
10 | predefined game.
11 |
12 | _MASSim_ simulations run in discrete steps. Agents connect remotely to the
13 | contest server, receive percepts and send their actions, which are in turn
14 | executed by _MASSim_.
15 |
16 |
17 |
18 |
19 |
20 | Download
21 | --------
22 |
23 | We upload **binary releases** to GitHub: https://github.com/agentcontest/massim_2020/releases
24 |
25 | There also are (potentially unstable) [development snapshots](https://github.com/agentcontest/massim_2020/actions?query=workflow%3A%22Continuous+Integration%22) attached as artifacts to each commit.
26 |
27 | Building MASSim
28 | ---------------
29 |
30 | The build requires Maven and OpenJDK 13.
31 |
32 | Run `mvn package` in the main directory. Maven should automatically
33 | fetch all necessary dependencies.
34 |
35 | Documentation
36 | -------------
37 |
38 | [server.md](docs/server.md) describes how the _MASSim_ server can be configured and started.
39 |
40 | [scenario.md](docs/scenario.md) contains the description of the current scenario.
41 |
42 | [protocol.md](docs/protocol.md) describes the _MASSim_ protocol, i.e. message formats for communicating with the _MASSim_ server.
43 |
44 | [eismassim.md](docs/eismassim.md) explains _EISMASSim_, a Java library using the Environment Interface Standard (EIS) to communicate with the _MASSim_ server, that can be used with platforms which support the EIS.
45 |
46 | [javaagents.md](docs/javaagents.md) gives a short introduction to the java agents framework, which holds skeletons that can already communicate with the MASSim server and have basic agent capabilities.
47 |
48 | [monitor.md](docs/monitor.md) describes how to view live matches and replays in the browser.
49 |
50 | License
51 | -------
52 |
53 | _MASSim_ is licensed under the AGPLv3+. See COPYING.txt for the full
54 | license text.
55 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/environment/Task.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.data.TaskInfo;
5 | import massim.protocol.data.Thing;
6 |
7 | import java.util.HashSet;
8 | import java.util.Map;
9 | import java.util.Set;
10 | import java.util.stream.Collectors;
11 |
12 | public class Task {
13 |
14 | private static int lowerRewardLimit;
15 |
16 | private String name;
17 | private Map requirements;
18 | private int deadline;
19 | private boolean completed = false;
20 | private int reward;
21 | private int rewardDecay;
22 | private int minimumReward;
23 |
24 | public Task(String name, int deadline, Map requirements, int rewardDecay) {
25 | this.name = name;
26 | this.deadline = deadline;
27 | this.requirements = requirements;
28 | this.reward = (int) (10 * Math.pow(requirements.size(), 2));
29 | this.rewardDecay = rewardDecay;
30 | this.minimumReward = (int) Math.ceil(reward/100. * lowerRewardLimit);
31 | }
32 |
33 | public static void setLowerRewardLimit(int newLimit) {
34 | lowerRewardLimit = newLimit;
35 | }
36 |
37 | public String getName() {
38 | return name;
39 | }
40 |
41 | public int getDeadline() {
42 | return deadline;
43 | }
44 |
45 | public boolean isCompleted() {
46 | return completed;
47 | }
48 |
49 | public void complete() {
50 | completed = true;
51 | }
52 |
53 | public Map getRequirements() {
54 | return requirements;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return requirements.entrySet()
60 | .stream()
61 | .map(e -> "task(" + name + "," + e.getKey() + ","+e.getValue()+")")
62 | .collect(Collectors.joining(","));
63 | }
64 |
65 | /**
66 | * decrease reward
67 | */
68 | public void preStep() {
69 | this.reward = (this.reward*(100-this.rewardDecay)) / 100;
70 | if (this.reward <= minimumReward) reward = minimumReward;
71 | }
72 |
73 | public int getReward() {
74 | return this.reward;
75 | }
76 |
77 | public TaskInfo toPercept() {
78 | Set reqs = new HashSet<>();
79 | requirements.forEach((pos, type) -> {
80 | reqs.add(new Thing(pos.x, pos.y, type, ""));
81 | });
82 | return new TaskInfo(name, deadline, getReward(), reqs);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/util/InputManager.java:
--------------------------------------------------------------------------------
1 | package massim.util;
2 |
3 | import java.io.IOException;
4 | import java.util.Scanner;
5 | import java.util.concurrent.LinkedBlockingQueue;
6 |
7 | /**
8 | * Listens for text inputs. If started, nothing else should read from {@link System#in}.
9 | * To wait until the next empty line is submitted, threads can synchronize and wait on this object, e.g.
10 | * {@code synchronized (inputManager) {try {inputManager.wait();} catch (InterruptedException ignored) {}}}
11 | */
12 | public class InputManager {
13 |
14 | private LinkedBlockingQueue inputQueue = new LinkedBlockingQueue<>();
15 | private Scanner scanner = new Scanner(System.in);
16 | private boolean stopped = false;
17 |
18 | /**
19 | * Starts listening on standard input
20 | */
21 | public void start() {
22 | new Thread(() -> {
23 | String line;
24 | while (!stopped) {
25 | try {
26 | if (scanner.hasNextLine()) {
27 | line = scanner.nextLine().replace("\n", "");
28 | if (line.equals("")) {
29 | // notify all threads waiting for an empty line (also known as ENTER)
30 | synchronized (this) {
31 | InputManager.this.notifyAll();
32 | }
33 | } else {
34 | Log.log(Log.Level.NORMAL, "You typed: " + line);
35 | inputQueue.add(line);
36 | }
37 | } else
38 | Thread.sleep(200);
39 | } catch (IllegalStateException e) {
40 | stopped = true;
41 | break;
42 | } catch (InterruptedException ignored) {
43 | }
44 | }
45 | }).start();
46 | }
47 |
48 | /**
49 | * Stops listening for new inputs
50 | */
51 | public void stop(){
52 | this.stopped = true;
53 | try {
54 | System.in.close();
55 | } catch (IOException ignored) {}
56 | }
57 |
58 | /**
59 | * @return the most recent input - blocks until one is available if the input queue is empty
60 | * @throws InterruptedException if interrupted while waiting
61 | */
62 | public String take() throws InterruptedException {
63 | return inputQueue.take();
64 | }
65 |
66 | /**
67 | * @return true if at least one input is buffered and has not been taken yet
68 | */
69 | public boolean hasInput() {
70 | return inputQueue.size() > 0;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/javaagents/src/main/java/massim/javaagents/Main.java:
--------------------------------------------------------------------------------
1 | package massim.javaagents;
2 |
3 | import eis.exceptions.ManagementException;
4 | import eis.iilang.EnvironmentState;
5 | import massim.eismassim.EnvironmentInterface;
6 |
7 | import java.io.File;
8 | import java.util.Scanner;
9 |
10 | /**
11 | * Starts a new scheduler.
12 | */
13 | public class Main {
14 |
15 | public static void main( String[] args ) {
16 |
17 | String configDir = "";
18 |
19 | System.out.println("PHASE 1: INSTANTIATING SCHEDULER");
20 | if (args.length != 0) configDir = args[0];
21 | else {
22 | System.out.println("PHASE 1.2: CHOOSE CONFIGURATION");
23 | File confDir = new File("conf");
24 | confDir.mkdirs();
25 | File[] confFiles = confDir.listFiles(File::isDirectory);
26 | if (confFiles == null || confFiles.length == 0) {
27 | System.out.println("No javaagents config files available - exit JavaAgents.");
28 | System.exit(0);
29 | }
30 | else {
31 | System.out.println("Choose a number:");
32 | for (int i = 0; i < confFiles.length; i++) {
33 | System.out.println(i + " " + confFiles[i]);
34 | }
35 | Scanner in = new Scanner(System.in);
36 | Integer confNum = null;
37 | while (confNum == null) {
38 | try {
39 | confNum = Integer.parseInt(in.next());
40 | if (confNum < 0 || confNum > confFiles.length - 1){
41 | System.out.println("No config for that number, try again:");
42 | confNum = null;
43 | }
44 | } catch (Exception e) {
45 | System.out.println("Invalid number, try again:");
46 | }
47 | }
48 | configDir = confFiles[confNum].getPath();
49 | }
50 | }
51 | Scheduler scheduler = new Scheduler(configDir);
52 |
53 | System.out.println("PHASE 2: INSTANTIATING ENVIRONMENT");
54 | EnvironmentInterface ei = new EnvironmentInterface(configDir + File.separator + "eismassimconfig.json");
55 |
56 | try {
57 | ei.start();
58 | } catch (ManagementException e) {
59 | e.printStackTrace();
60 | }
61 |
62 | System.out.println("PHASE 3: CONNECTING SCHEDULER AND ENVIRONMENT");
63 | scheduler.setEnvironment(ei);
64 |
65 | System.out.println("PHASE 4: RUNNING");
66 | int step = 0;
67 | while ((ei.getState() == EnvironmentState.RUNNING)) {
68 | System.out.println("SCHEDULER STEP " + step);
69 | scheduler.step();
70 | step++;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/eismassim/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | massim
8 | eismassim
9 | 4.3.2
10 |
11 | jar
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 | 3.6.1
23 |
24 | 13
25 | 13
26 |
27 |
28 |
29 | maven-assembly-plugin
30 |
31 |
32 | package
33 |
34 | single
35 |
36 |
37 |
38 |
39 |
40 |
41 | massim.eismassim.EnvironmentInterface
42 |
43 |
44 |
45 | jar-with-dependencies
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | eishub-mvn-repo
55 | https://raw.github.com/eishub/mvn-repo/master
56 |
57 |
58 |
59 |
60 |
61 | eishub
62 | eis
63 | 0.7.0
64 |
65 |
66 | massim
67 | protocol
68 | 2.3
69 |
70 |
71 |
72 | org.json
73 | json
74 | 20201115
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/monitor/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | massim
5 | monitor
6 | 2.3
7 |
8 |
9 | UTF-8
10 |
11 |
12 |
13 |
14 |
15 | org.apache.maven.plugins
16 | maven-compiler-plugin
17 | 3.8.0
18 |
19 | 13
20 | 13
21 |
22 |
23 |
24 | maven-assembly-plugin
25 |
26 |
27 | package
28 |
29 | single
30 |
31 |
32 |
33 |
34 |
35 |
36 | massim.monitor.Monitor
37 |
38 |
39 |
40 | jar-with-dependencies
41 |
42 |
43 |
44 |
45 | com.github.eirslett
46 | frontend-maven-plugin
47 | 1.4
48 |
49 |
50 | generate-resources
51 |
52 | install-node-and-npm
53 | npm
54 |
55 |
56 |
57 |
58 | v12.18.2
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | jitpack.io
67 | https://jitpack.io
68 |
69 |
70 |
71 |
72 |
73 | com.github.webbit
74 | webbit
75 | f628a7a3ffdd8c288514784f5b0426faaee2a2e3
76 |
77 |
78 | org.json
79 | json
80 | 20190722
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/dep.xml:
--------------------------------------------------------------------------------
1 |
2 | bin
3 |
4 | tar.gz
5 |
6 |
7 |
8 |
9 | ${project.basedir}
10 | /
11 |
12 | README.md
13 | COPYING.txt
14 | CHANGELOG.md
15 |
16 |
17 |
18 | docs
19 | docs
20 |
21 |
22 | starterKits
23 | starterKits
24 |
25 |
26 |
27 |
28 | server/target
29 | server
30 |
31 | *.jar
32 |
33 |
34 |
35 | server/conf
36 | server/conf
37 |
38 |
39 | server/osm
40 | server/osm
41 |
42 | *.osm.pbf
43 |
44 |
45 |
46 |
47 |
48 | protocol/target
49 | protocol
50 |
51 | *.jar
52 |
53 |
54 |
55 |
56 |
57 | javaagents/target
58 | javaagents
59 |
60 | *.jar
61 |
62 |
63 |
64 | javaagents/conf
65 | javaagents/conf
66 |
67 |
68 |
69 |
70 | eismassim/target
71 | eismassim
72 |
73 | *.jar
74 |
75 |
76 |
77 | eismassim/conf
78 | eismassim/conf
79 |
80 | *.json
81 |
82 |
83 |
84 |
85 |
86 | monitor/target
87 | monitor
88 |
89 | *.jar
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/server/src/test/java/massim/game/environment/GridTest.java:
--------------------------------------------------------------------------------
1 | package massim.game.environment;
2 |
3 | import massim.config.TeamConfig;
4 | import massim.game.environment.Block;
5 | import massim.game.environment.Terrain;
6 | import massim.protocol.data.Position;
7 | import massim.protocol.data.Thing;
8 | import massim.protocol.messages.scenario.Actions;
9 | import massim.protocol.messages.scenario.StepPercept;
10 | import massim.util.RNG;
11 |
12 | import org.json.JSONArray;
13 | import org.json.JSONObject;
14 |
15 | import static org.junit.Assert.assertNotNull;
16 |
17 | import java.util.*;
18 |
19 | public class GridTest {
20 | private JSONObject gridjson;
21 |
22 | @org.junit.Before
23 | public void setUp() {
24 | RNG.initialize(17);
25 |
26 | this.gridjson = new JSONObject();
27 | this.gridjson.put("height", 70);
28 | this.gridjson.put("width", 70);
29 | this.gridjson.put("instructions", new JSONArray("[[\"cave\", 0.45, 9, 5, 4]]"));
30 | this.gridjson.put("goals", new JSONObject("{\"number\" : 3,\"size\" : [1,2]}"));
31 | }
32 |
33 | @org.junit.Test
34 | public void findRandomFreeClusterPosition() {
35 | this.gridjson.put("height", 5);
36 | this.gridjson.put("width", 5);
37 | System.out.println(this.gridjson.toString());
38 | Grid grid = new Grid(this.gridjson, 10, 8);
39 |
40 | printGridTerrain(grid);
41 |
42 | System.out.println("Testing cluster size 1");
43 | RNG.initialize(15);
44 | ArrayList cluster = grid.findRandomFreeClusterPosition(1);
45 | assertNotNull(cluster);
46 | assert(cluster.size()==1);
47 |
48 | assert(grid.getTerrain(cluster.get(0)) == Terrain.EMPTY);
49 | assert(cluster.get(0).toString().equals("(2,2)"));
50 |
51 | System.out.println("Testing cluster size 3");
52 | RNG.initialize(15);
53 | printGridTerrain(grid);
54 | ArrayList cluster3 = grid.findRandomFreeClusterPosition(3);
55 | assertNotNull(cluster3);
56 | assert(cluster3.size()==3);
57 |
58 | assert(grid.getTerrain(cluster3.get(0)) == Terrain.EMPTY);
59 | assert(cluster3.get(0).toString().equals("(3,0)"));
60 | assert(grid.getTerrain(cluster3.get(1)) == Terrain.EMPTY);
61 | assert(cluster3.get(1).toString().equals("(3,1)"));
62 | assert(grid.getTerrain(cluster3.get(2)) == Terrain.EMPTY);
63 | assert(cluster3.get(2).toString().equals("(4,0)"));
64 | }
65 |
66 | private void printGridTerrain(Grid grid){
67 | for (int x=0; x < grid.getDimX(); x++){
68 | System.out.println(" ");
69 | for (int y=0; y < grid.getDimY(); y++)
70 | System.out.print(" "+String.format("%1$5s",grid.getTerrain(new Position(x, y)).toString()));
71 | }
72 | System.out.println(" ");
73 | }
74 | private void printGridAgents(Grid grid){
75 | for (int x=0; x < grid.getDimX(); x++){
76 | System.out.println(" ");
77 | for (int y=0; y < grid.getDimY(); y++){
78 | System.out.print(" "+String.format("%1$5s",grid.getThings(new Position(x, y)).toString()));
79 | }
80 | }
81 | System.out.println(" ");
82 | }
83 | }
--------------------------------------------------------------------------------
/server/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | massim
8 | server
9 | 2020-2.0
10 |
11 | jar
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 |
19 |
20 | org.apache.maven.plugins
21 | maven-compiler-plugin
22 | 3.8.0
23 |
24 | 13
25 | 13
26 |
27 |
28 |
29 | maven-assembly-plugin
30 |
31 |
32 | package
33 |
34 | single
35 |
36 |
37 |
38 |
39 |
40 |
41 | massim.Server
42 |
43 |
44 |
45 | jar-with-dependencies
46 |
47 |
48 |
49 |
50 | org.codehaus.mojo
51 | exec-maven-plugin
52 | 1.6.0
53 |
54 | massim.Server
55 | java
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | massim
64 | protocol
65 | 2.3
66 |
67 |
68 | massim
69 | monitor
70 | 2.3
71 |
72 |
73 |
74 | org.json
75 | json
76 | 20190722
77 |
78 |
79 |
80 | junit
81 | junit
82 | 4.13.1
83 | compile
84 |
85 |
86 | org.jetbrains
87 | annotations
88 | 13.0
89 | compile
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/javaagents/src/main/java/massim/javaagents/agents/Agent.java:
--------------------------------------------------------------------------------
1 | package massim.javaagents.agents;
2 |
3 | import eis.iilang.Percept;
4 | import massim.javaagents.MailService;
5 |
6 | import java.util.*;
7 |
8 | /**
9 | * An abstract Java agent.
10 | */
11 | public abstract class Agent {
12 |
13 | private final String name;
14 | private final MailService mailbox;
15 | private final Set percepts = Collections.synchronizedSet(new HashSet<>());
16 |
17 | /**
18 | * Constructor
19 | * @param name the agent's name
20 | * @param mailbox the mail facility
21 | */
22 | Agent(String name, MailService mailbox){
23 | this.name = name;
24 | this.mailbox = mailbox;
25 | }
26 |
27 | /**
28 | * Handles a percept.
29 | * This method is used only if the EIS is configured to handle percepts as notifications.
30 | * @param percept the percept to process
31 | */
32 | public abstract void handlePercept(Percept percept);
33 |
34 | /**
35 | * Called for each step.
36 | */
37 | public abstract eis.iilang.Action step();
38 |
39 | /**
40 | * @return the name of the agent
41 | */
42 | public String getName() {
43 | return name;
44 | }
45 |
46 | /**
47 | * Sends a percept as a message to the given agent.
48 | * The receiver agent may fetch the message the next time it is stepped.
49 | * @param message the message to deliver
50 | * @param receiver the receiving agent
51 | * @param sender the agent sending the message
52 | */
53 | protected void sendMessage(Percept message, String receiver, String sender){
54 | mailbox.sendMessage(message, receiver, sender);
55 | }
56 |
57 | /**
58 | * Broadcasts a message to the entire team.
59 | * @param message the message to broadcast
60 | * @param sender the agent sending the message
61 | */
62 | void broadcast(Percept message, String sender){
63 | mailbox.broadcast(message, sender);
64 | }
65 |
66 | /**
67 | * Called if another agent sent a message to this agent; so technically this is part of another agent's step method.
68 | *
69 | * @param message the message that was sent
70 | * @param sender name of the agent who sent the message
71 | */
72 | public abstract void handleMessage(Percept message, String sender);
73 |
74 | /**
75 | * Sets the percepts for this agent. Should only be called from the outside.
76 | * @param addList the new percepts for this agent.
77 | * @param delList the now invalid percepts for this agent.
78 | */
79 | public void setPercepts(List addList, List delList) {
80 | this.percepts.removeAll(delList);
81 | this.percepts.addAll(addList);
82 | }
83 |
84 | /**
85 | * Prints a message to std out prefixed with the agent's name.
86 | * @param message the message to say
87 | */
88 | void say(String message){
89 | System.out.println("[ " + name + " ] " + message);
90 | }
91 |
92 | /**
93 | * Returns a list of this agent's percepts. Percepts are set by the scheduler
94 | * each time before the step() method is called.
95 | * Percepts are cleared before each step, so relevant information needs to be stored somewhere else
96 | * by the agent.
97 | * @return a list of all new percepts for the current step
98 | */
99 | List getPercepts(){
100 | return new ArrayList<>(percepts);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/broadcast/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | """Live broadcasts for multiagentcontest.org"""
4 |
5 | import argparse
6 | import asyncio
7 | import aiohttp.web
8 | import json
9 | import logging
10 | import os
11 | import sys
12 |
13 |
14 | MONITOR_WWW = "../monitor/src/main/resources/www"
15 |
16 |
17 | class LiveBroadcast:
18 | def __init__(self, args):
19 | self.args = args
20 | self.dynamic = []
21 | self.step = args.step
22 | self.step_changed = asyncio.Condition()
23 | self.connected = asyncio.Condition()
24 |
25 | with open(os.path.join(args.path, "static.json")) as f:
26 | self.static = json.load(f)
27 |
28 | for step in range(self.static["steps"]):
29 | if step % 5 == 0:
30 | with open(os.path.join(args.path, "{}.json".format(step))) as f:
31 | group = json.load(f)
32 | self.dynamic.append(group[str(step)])
33 |
34 | @asyncio.coroutine
35 | def run(self):
36 | print("Waiting for first client ...")
37 | with (yield from self.connected):
38 | yield from self.connected.wait()
39 |
40 | print("Waiting for delay ({}s) ...".format(args.delay))
41 | yield from asyncio.sleep(args.delay)
42 |
43 | print("Broadcast with {}s per frame ...".format(args.speed))
44 | for step in range(self.step, self.static["steps"]):
45 | self.step = step
46 | print(self.args.path, self.step, "/", self.static["steps"] - 1)
47 |
48 | with (yield from self.step_changed):
49 | self.step_changed.notify_all()
50 |
51 | yield from asyncio.sleep(args.speed)
52 |
53 | @asyncio.coroutine
54 | def live(self, req):
55 | print("Client connected.")
56 | with (yield from self.connected):
57 | self.connected.notify()
58 |
59 | ws = aiohttp.web.WebSocketResponse()
60 | yield from ws.prepare(req)
61 |
62 | yield from ws.send_json(self.static)
63 | yield from ws.send_json(self.dynamic[self.step])
64 |
65 | while True:
66 | with (yield from self.step_changed):
67 | yield from self.step_changed.wait()
68 | yield from ws.send_json(self.dynamic[self.step])
69 |
70 | return ws
71 |
72 |
73 | def static(path):
74 | def handler(request):
75 | return aiohttp.web.FileResponse(os.path.join(os.path.dirname(__file__), path))
76 | return handler
77 |
78 |
79 | @asyncio.coroutine
80 | def main(args):
81 | broadcast = LiveBroadcast(args)
82 |
83 | asyncio.get_event_loop().create_task(broadcast.run())
84 |
85 | app = aiohttp.web.Application()
86 | app.router.add_route("GET", "/", static(os.path.join(MONITOR_WWW, "index.html")))
87 | app.router.add_route("GET", "/live/monitor", broadcast.live)
88 | app.router.add_static("/", MONITOR_WWW)
89 | return app
90 |
91 |
92 | if __name__ == "__main__":
93 | logging.basicConfig(level=logging.DEBUG)
94 |
95 | parser = argparse.ArgumentParser(description=__doc__)
96 | parser.add_argument("path", metavar="PATH", help="replay directory (containing static.json)")
97 | parser.add_argument("--step", type=int, default=0)
98 | parser.add_argument("--delay", type=float, default=10)
99 | parser.add_argument("--speed", type=float, default=0.5)
100 | parser.add_argument("--bind", default="127.0.0.1")
101 | parser.add_argument("--port", default=8000)
102 | args = parser.parse_args()
103 |
104 | aiohttp.web.run_app(main(args), host=args.bind, port=args.port)
105 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/game/Entity.java:
--------------------------------------------------------------------------------
1 | package massim.game;
2 |
3 | import massim.game.environment.Attachable;
4 | import massim.game.environment.Task;
5 | import massim.protocol.data.Position;
6 | import massim.protocol.data.Thing;
7 | import massim.protocol.messages.ActionMessage;
8 | import massim.protocol.messages.scenario.Actions;
9 |
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 |
14 | /**
15 | * A controllable entity in the simulation.
16 | */
17 | public class Entity extends Attachable {
18 |
19 | static int maxEnergy = 0;
20 | static int clearEnergyCost = 0;
21 | static int disableDuration = 0;
22 |
23 | private String agentName;
24 | private String teamName;
25 | private String lastAction = "";
26 | private List lastActionParams = Collections.emptyList();
27 | private String lastActionResult = "";
28 |
29 | private int vision = 5;
30 | private int energy;
31 |
32 | private int previousClearStep = -1;
33 | private Position previousClearPosition = Position.of(-1, -1);
34 | private int clearCounter = 0;
35 |
36 | private int disabled = 0;
37 |
38 | private Task acceptedTask;
39 |
40 | public Entity(Position xy, String agentName, String teamName) {
41 | super(xy);
42 | this.agentName = agentName;
43 | this.teamName = teamName;
44 | this.energy = maxEnergy;
45 | }
46 |
47 | @Override
48 | public Thing toPercept(Position origin) {
49 | Position localPosition = getPosition().relativeTo(origin);
50 | return new Thing(localPosition.x, localPosition.y, Thing.TYPE_ENTITY, teamName);
51 | }
52 |
53 | /**
54 | * recharge and repair
55 | */
56 | void preStep() {
57 | disabled--;
58 | energy = Math.min(energy + 1, maxEnergy);
59 | }
60 |
61 | String getTeamName() {
62 | return teamName;
63 | }
64 |
65 | void setLastActionResult(String result) {
66 | this.lastActionResult = result;
67 | }
68 |
69 | String getAgentName() {
70 | return agentName;
71 | }
72 |
73 | void setNewAction(ActionMessage action) {
74 | this.lastAction = action.getActionType();
75 | this.lastActionResult = Actions.RESULT_UNPROCESSED;
76 | this.lastActionParams = action.getParams();
77 | }
78 |
79 | String getLastAction() {
80 | return lastAction;
81 | }
82 |
83 | List getLastActionParams() {
84 | return lastActionParams;
85 | }
86 |
87 | String getLastActionResult() {
88 | return lastActionResult;
89 | }
90 |
91 | int getVision() {
92 | return vision;
93 | }
94 |
95 | void recordClearAction(int step, Position position) {
96 | previousClearPosition = position;
97 | previousClearStep = step;
98 | }
99 |
100 | int getPreviousClearStep() {
101 | return previousClearStep;
102 | }
103 |
104 | Position getPreviousClearPosition() {
105 | return previousClearPosition;
106 | }
107 |
108 | int incrementClearCounter() {
109 | return ++clearCounter;
110 | }
111 |
112 | void resetClearCounter() {
113 | clearCounter = 0;
114 | }
115 |
116 | void disable() {
117 | disabled = disableDuration;
118 | detachAll();
119 | }
120 |
121 | boolean isDisabled() {
122 | return disabled > 0;
123 | }
124 |
125 | int getEnergy() {
126 | return energy;
127 | }
128 |
129 | void consumeClearEnergy() {
130 | energy -= clearEnergyCost;
131 | }
132 |
133 | void acceptTask(Task t) {
134 | this.acceptedTask = t;
135 | }
136 |
137 | String getTask() {
138 | if (acceptedTask == null) return "";
139 | return acceptedTask.getName();
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/monitor/src/main/resources/www/loader.css:
--------------------------------------------------------------------------------
1 | /* Loader from http://projects.lukehaas.me/css-loaders/ */
2 |
3 | .loader {
4 | font-size: 20px;
5 | margin: 100px auto;
6 | width: 1em;
7 | height: 1em;
8 | border-radius: 50%;
9 | position: relative;
10 | text-indent: -9999em;
11 | -webkit-animation: load4 1.3s infinite linear;
12 | animation: load4 1.3s infinite linear;
13 | -webkit-transform: translateZ(0);
14 | -ms-transform: translateZ(0);
15 | transform: translateZ(0);
16 | }
17 |
18 | @-webkit-keyframes load4 {
19 | 0%,
20 | 100% {
21 | box-shadow: 0 -3em 0 0.2em #ffffff, 2em -2em 0 0em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0 0 -1em #ffffff, -2em -2em 0 0 #ffffff;
22 | }
23 | 12.5% {
24 | box-shadow: 0 -3em 0 0 #ffffff, 2em -2em 0 0.2em #ffffff, 3em 0 0 0 #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
25 | }
26 | 25% {
27 | box-shadow: 0 -3em 0 -0.5em #ffffff, 2em -2em 0 0 #ffffff, 3em 0 0 0.2em #ffffff, 2em 2em 0 0 #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
28 | }
29 | 37.5% {
30 | box-shadow: 0 -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0em 0 0 #ffffff, 2em 2em 0 0.2em #ffffff, 0 3em 0 0em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0em 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
31 | }
32 | 50% {
33 | box-shadow: 0 -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 0em #ffffff, 0 3em 0 0.2em #ffffff, -2em 2em 0 0 #ffffff, -3em 0em 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
34 | }
35 | 62.5% {
36 | box-shadow: 0 -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 0 #ffffff, -2em 2em 0 0.2em #ffffff, -3em 0 0 0 #ffffff, -2em -2em 0 -1em #ffffff;
37 | }
38 | 75% {
39 | box-shadow: 0em -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0em 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 0 #ffffff, -3em 0em 0 0.2em #ffffff, -2em -2em 0 0 #ffffff;
40 | }
41 | 87.5% {
42 | box-shadow: 0em -3em 0 0 #ffffff, 2em -2em 0 -1em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 0 #ffffff, -3em 0em 0 0 #ffffff, -2em -2em 0 0.2em #ffffff;
43 | }
44 | }
45 |
46 | @keyframes load4 {
47 | 0%,
48 | 100% {
49 | box-shadow: 0 -3em 0 0.2em #ffffff, 2em -2em 0 0em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0 0 -1em #ffffff, -2em -2em 0 0 #ffffff;
50 | }
51 | 12.5% {
52 | box-shadow: 0 -3em 0 0 #ffffff, 2em -2em 0 0.2em #ffffff, 3em 0 0 0 #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
53 | }
54 | 25% {
55 | box-shadow: 0 -3em 0 -0.5em #ffffff, 2em -2em 0 0 #ffffff, 3em 0 0 0.2em #ffffff, 2em 2em 0 0 #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
56 | }
57 | 37.5% {
58 | box-shadow: 0 -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0em 0 0 #ffffff, 2em 2em 0 0.2em #ffffff, 0 3em 0 0em #ffffff, -2em 2em 0 -1em #ffffff, -3em 0em 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
59 | }
60 | 50% {
61 | box-shadow: 0 -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 0em #ffffff, 0 3em 0 0.2em #ffffff, -2em 2em 0 0 #ffffff, -3em 0em 0 -1em #ffffff, -2em -2em 0 -1em #ffffff;
62 | }
63 | 62.5% {
64 | box-shadow: 0 -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 0 #ffffff, -2em 2em 0 0.2em #ffffff, -3em 0 0 0 #ffffff, -2em -2em 0 -1em #ffffff;
65 | }
66 | 75% {
67 | box-shadow: 0em -3em 0 -1em #ffffff, 2em -2em 0 -1em #ffffff, 3em 0em 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 0 #ffffff, -3em 0em 0 0.2em #ffffff, -2em -2em 0 0 #ffffff;
68 | }
69 | 87.5% {
70 | box-shadow: 0em -3em 0 0 #ffffff, 2em -2em 0 -1em #ffffff, 3em 0 0 -1em #ffffff, 2em 2em 0 -1em #ffffff, 0 3em 0 -1em #ffffff, -2em 2em 0 0 #ffffff, -3em 0em 0 0 #ffffff, -2em -2em 0 0.2em #ffffff;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/data/Position.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.data;
2 |
3 | import org.json.JSONArray;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Objects;
7 |
8 | public final class Position {
9 |
10 | private static int dimX = 0;
11 | private static int dimY = 0;
12 |
13 | public final int x;
14 | public final int y;
15 |
16 | public Position(int x, int y) {
17 | this.x = x;
18 | this.y = y;
19 | }
20 |
21 | @Override
22 | public boolean equals(Object other) {
23 | if (!(other instanceof Position)) return false;
24 | return ((Position) other).x == x && ((Position) other).y == y;
25 | }
26 |
27 | @Override
28 | public int hashCode() {
29 | return Objects.hash(x, y);
30 | }
31 |
32 | public static void setGridDimensions(int dimX, int dimY) {
33 | Position.dimX = dimX;
34 | Position.dimY = dimY;
35 | }
36 |
37 | /**
38 | * @return the same position but wrapped back into the bounds
39 | */
40 | public static Position wrapped(int someX, int someY) {
41 | // handle negative values correctly
42 | return Position.of(Math.floorMod(someX, dimX), Math.floorMod(someY, dimY));
43 | }
44 |
45 | public Position wrapped() {
46 | return Position.wrapped(x, y);
47 | }
48 |
49 | public int distanceTo(Position other) {
50 | int dx = Math.abs(x - other.x);
51 | if (dx > dimX/2.0) dx = dimX - dx;
52 | int dy = Math.abs(y - other.y);
53 | if (dy > dimY/2.0) dy = dimY - dy;
54 | return dx + dy;
55 | }
56 |
57 | public Position moved(String direction, int distance) {
58 | switch (direction) {
59 | case "n": return Position.wrapped(x, y - distance);
60 | case "s": return Position.wrapped(x, y + distance);
61 | case "w": return Position.wrapped(x - distance, y);
62 | case "e": return Position.wrapped(x + distance, y);
63 | }
64 | return Position.of(x, y);
65 | }
66 |
67 | public static Position of(int x, int y) {
68 | return new Position(x, y);
69 | }
70 |
71 | @Override
72 | public String toString() {
73 | return "(" + x + "," + y + ")";
74 | }
75 |
76 | public Position translate(int x, int y) {
77 | return Position.wrapped(this.x + x, this.y + y);
78 | }
79 |
80 | public Position translate(Position other) {
81 | return translate(other.x, other.y);
82 | }
83 |
84 | public Position relativeTo(Position origin) {
85 | var dx = x - origin.x;
86 | if (dx < -(dimX / 2.0)) dx += dimX;
87 | else if (dx > dimX / 2.0) dx -= dimX;
88 | var dy = y - origin.y;
89 | if (dy < -(dimY / 2.0)) dy += dimY;
90 | else if (dy > dimY / 2.0) dy -= dimY;
91 | return Position.of(dx, dy);
92 | }
93 |
94 | public JSONArray toJSON() {
95 | JSONArray result = new JSONArray();
96 | result.put(x);
97 | result.put(y);
98 | return result;
99 | }
100 |
101 | public static Position fromJSON(JSONArray json) {
102 | return Position.of(json.getInt(0), json.getInt(1));
103 | }
104 |
105 | /**
106 | * @return list containing all positions belonging to the area around this position within the given radius.
107 | */
108 | public ArrayList spanArea(int radius) {
109 | var area = new ArrayList();
110 | for (var dx = -radius; dx <= radius; dx++) {
111 | var cx = x + dx;
112 | var dy = radius - Math.abs(dx);
113 | for (var cy = y - dy; cy <= y + dy; cy++) {
114 | area.add(Position.wrapped(cx, cy));
115 | }
116 | }
117 | return area;
118 | }
119 |
120 | /**
121 | * @return this position rotated 90 degrees in the given direction
122 | */
123 | public Position rotated90(Position center, boolean clockwise) {
124 | var pos = this.relativeTo(center);
125 | // the rotation is calculated relative to the rotation center
126 | //var pos = Position.of(center.x + relative.x, center.y + relative.y);
127 | var dx = clockwise? -pos.y : pos.y;
128 | var dy = clockwise? pos.x : -pos.x;
129 | return Position.wrapped(center.x + dx, center.y + dy);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/monitor/src/main/java/massim/monitor/Monitor.java:
--------------------------------------------------------------------------------
1 | package massim.monitor;
2 |
3 | import org.json.JSONObject;
4 | import org.webbitserver.BaseWebSocketHandler;
5 | import org.webbitserver.WebServer;
6 | import org.webbitserver.WebServers;
7 | import org.webbitserver.WebSocketConnection;
8 | import org.webbitserver.handler.EmbeddedResourceHandler;
9 | import org.webbitserver.handler.HttpToWebSocketHandler;
10 | import org.webbitserver.handler.StaticFileHandler;
11 | import org.webbitserver.handler.StringHttpHandler;
12 |
13 | import java.net.InetSocketAddress;
14 | import java.net.URI;
15 | import java.nio.file.Paths;
16 | import java.util.HashSet;
17 | import java.util.Scanner;
18 | import java.util.concurrent.ExecutionException;
19 | import java.util.concurrent.ExecutorService;
20 | import java.util.concurrent.Executors;
21 | import java.util.concurrent.locks.Lock;
22 | import java.util.concurrent.locks.ReentrantReadWriteLock;
23 |
24 | /**
25 | * The web monitor for the MASSim server.
26 | */
27 | public class Monitor {
28 |
29 | private final EventSink monitorSink = new EventSink("monitor");
30 |
31 | private final EventSink statusSink = new EventSink("status");
32 |
33 | /**
34 | * Constructor.
35 | * Used by the massim server to create the "live" monitor.
36 | */
37 | public Monitor(int port) throws ExecutionException, InterruptedException {
38 | ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
39 | InetSocketAddress bind = new InetSocketAddress(port);
40 | String publicUri = "http://localhost:" + port + "/";
41 |
42 | WebServer server = WebServers.createWebServer(executor, bind, URI.create(publicUri))
43 | .add("/live/monitor", monitorSink)
44 | .add("/live/status", statusSink)
45 | .add(new EmbeddedResourceHandler("www"))
46 | .start()
47 | .get();
48 |
49 | System.out.println(String.format("[ MONITOR ] Live monitor: %s", publicUri));
50 | System.out.println(String.format("[ MONITOR ] Live status: %sstatus.html", publicUri));
51 | }
52 |
53 | /**
54 | * Creates a new monitor to watch replays with.
55 | * @param replayPath the path to a replay file
56 | */
57 | Monitor(int port, String replayPath) throws ExecutionException, InterruptedException {
58 | // read index.html from resources
59 | String html = new Scanner(Monitor.class.getClassLoader().getResourceAsStream("www/index.html"), "UTF-8")
60 | .useDelimiter("\\A")
61 | .next();
62 |
63 | ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
64 | InetSocketAddress bind = new InetSocketAddress(port);
65 | String publicUri = "http://localhost:" + port + "/";
66 |
67 | WebServer server = WebServers.createWebServer(executor, bind, URI.create(publicUri))
68 | .add(new EmbeddedResourceHandler("www"))
69 | .add("/?/", new StringHttpHandler("text/html", html))
70 | .add(new StaticFileHandler(replayPath))
71 | .start()
72 | .get();
73 |
74 | System.out.println(String.format("[ MONITOR ] Viewing replay %s on %s?/", replayPath, publicUri));
75 | }
76 |
77 | /**
78 | * Updates the current state of the monitor.
79 | * Called by the massim server after each step.
80 | */
81 | public void updateState(JSONObject state) {
82 | monitorSink.broadcast(state.toString(), !state.has("grid"));
83 | }
84 |
85 | public void updateStatus(JSONObject status) {
86 | statusSink.broadcast(status.toString(), true);
87 | }
88 |
89 | public static void main(String[] args) throws ExecutionException, InterruptedException {
90 | int port = 8000;
91 | String path = null;
92 |
93 | for (int i = 0; i < args.length; i++) {
94 | switch (args[i]) {
95 | case "--port":
96 | port = Integer.parseInt(args[++i]);
97 | break;
98 | default:
99 | path = args[i];
100 | break;
101 | }
102 | }
103 |
104 | if (path == null) {
105 | System.out.println("Usage: java -jar monitor.jar [--port PORT] ");
106 | return;
107 | }
108 |
109 | if (!Paths.get(path, "static.json").toFile().exists()) {
110 | System.out.println("Not a replay. static.json does not seem to exist in this directory.");
111 | return;
112 | }
113 |
114 | new Monitor(port, path);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/eismassim/src/main/java/massim/eismassim/entities/StatusEntity.java:
--------------------------------------------------------------------------------
1 | package massim.eismassim.entities;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.OutputStreamWriter;
6 | import java.net.Socket;
7 | import java.nio.charset.StandardCharsets;
8 | import java.util.*;
9 |
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import eis.PerceptUpdate;
14 | import eis.iilang.Identifier;
15 | import eis.iilang.Numeral;
16 | import eis.iilang.ParameterList;
17 | import eis.iilang.Percept;
18 | import massim.eismassim.Entity;
19 | import massim.eismassim.Log;
20 | import massim.protocol.messages.Message;
21 | import massim.protocol.messages.StatusRequestMessage;
22 | import massim.protocol.messages.StatusResponseMessage;
23 |
24 | public class StatusEntity extends Entity {
25 |
26 | private final String host;
27 | private final int port;
28 |
29 | private final Set statusPercepts = new HashSet<>();
30 | private final Set previousStatusPercepts = new HashSet<>();
31 |
32 | public StatusEntity(String name, String host, int port) {
33 | super(name);
34 | this.host = host;
35 | this.port = port;
36 | }
37 |
38 | public static StatusResponseMessage queryServerStatus(String host, int port) throws IOException {
39 |
40 | Message result;
41 | try (var socket = new Socket(host, port);
42 | var out = socket.getOutputStream();
43 | var in = socket.getInputStream()) {
44 | var statusRequest = new StatusRequestMessage();
45 | var osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
46 | osw.write(statusRequest.toJson().toString());
47 | osw.write(0);
48 | osw.flush();
49 |
50 | var buffer = new ByteArrayOutputStream();
51 | int read;
52 | while ((read = in.read()) != 0) {
53 | if (read == -1)
54 | throw new IOException();
55 | buffer.write(read);
56 | }
57 | String message = buffer.toString(StandardCharsets.UTF_8);
58 | try {
59 | result = Message.buildFromJson(new JSONObject(message));
60 | if (result instanceof StatusResponseMessage)
61 | return (StatusResponseMessage) result;
62 | } catch (JSONException ignored) {}
63 | }
64 | return null;
65 | }
66 |
67 | public void start() {
68 | new Thread(this).start();
69 | }
70 |
71 | @Override
72 | public void run() {
73 | while (true) {
74 | Log.log(getName() + ": Query server status");
75 | StatusResponseMessage status = null;
76 | try {
77 | status = queryServerStatus(host, port);
78 | } catch (IOException ignored) {}
79 | var percepts = new LinkedList();
80 | if (status != null) {
81 | var teams = new ParameterList();
82 | for (var team : status.teams) {
83 | teams.add(new Identifier(team));
84 | }
85 | percepts.add(new Percept("teams", teams));
86 |
87 | var teamSizes = new ParameterList();
88 | for (var teamSize : status.teamSizes) {
89 | teamSizes.add(new Numeral(teamSize));
90 | }
91 | percepts.add(new Percept("teamSizes", teamSizes));
92 |
93 | percepts.add(new Percept("currentSim", new Numeral(status.currentSimulation)));
94 |
95 | int currentIndex = Math.max(status.currentSimulation, 0);
96 | percepts.add(new Percept("currentTeamSize", new Numeral(status.teamSizes[currentIndex])));
97 | } else {
98 | percepts.add(new Percept("error"));
99 | }
100 |
101 | updatePercepts(percepts);
102 |
103 | try {
104 | Thread.sleep(2000);
105 | } catch (InterruptedException e) {
106 | e.printStackTrace();
107 | }
108 | }
109 | }
110 |
111 | private synchronized void updatePercepts(List percepts) {
112 | statusPercepts.clear();
113 | statusPercepts.addAll(percepts);
114 | }
115 |
116 | @Override
117 | public synchronized PerceptUpdate getPercepts() {
118 | var addList = new ArrayList<>(statusPercepts);
119 | addList.removeAll(previousStatusPercepts);
120 | var delList = new ArrayList<>(previousStatusPercepts);
121 | delList.removeAll(statusPercepts);
122 | previousStatusPercepts.clear();
123 | previousStatusPercepts.addAll(statusPercepts);
124 | return new PerceptUpdate(addList, delList);
125 | }
126 | }
--------------------------------------------------------------------------------
/protocol/src/main/java/massim/protocol/messages/scenario/StepPercept.java:
--------------------------------------------------------------------------------
1 | package massim.protocol.messages.scenario;
2 |
3 | import massim.protocol.data.Position;
4 | import massim.protocol.messages.RequestActionMessage;
5 | import massim.protocol.data.TaskInfo;
6 | import massim.protocol.data.Thing;
7 | import org.json.JSONArray;
8 | import org.json.JSONObject;
9 |
10 | import java.util.*;
11 |
12 | public class StepPercept extends RequestActionMessage {
13 |
14 | public Set things = new HashSet<>();
15 | public Set taskInfo = new HashSet<>();
16 | public Map> terrain = new HashMap<>();
17 | public long score;
18 | public String lastAction;
19 | public String lastActionResult;
20 | public List lastActionParams = new ArrayList<>();
21 | public Set attachedThings = new HashSet<>();
22 | public int energy;
23 | public boolean disabled;
24 | public String task;
25 |
26 | public StepPercept(JSONObject content) {
27 | super(content);
28 | parsePercept(content.getJSONObject("percept"));
29 | }
30 |
31 | public StepPercept(int step, long score, Set things, Map> terrain,
32 | Set taskInfo, String action, List lastActionParams, String result,
33 | Set attachedThings, String task) {
34 | super(System.currentTimeMillis(), -1, -1, step); // id and deadline are updated later
35 | this.score = score;
36 | this.things.addAll(things);
37 | this.taskInfo.addAll(taskInfo);
38 | this.lastAction = action;
39 | this.lastActionResult = result;
40 | this.terrain = terrain;
41 | this.lastActionParams.addAll(lastActionParams);
42 | this.attachedThings = attachedThings;
43 | this.task = task;
44 | }
45 |
46 | @Override
47 | public JSONObject makePercept() {
48 | var percept = new JSONObject();
49 | var jsonThings = new JSONArray();
50 | var jsonTasks = new JSONArray();
51 | var jsonTerrain = new JSONObject();
52 | percept.put("score", score);
53 | percept.put("things", jsonThings);
54 | percept.put("tasks", jsonTasks);
55 | percept.put("terrain", jsonTerrain);
56 | percept.put("energy", energy);
57 | percept.put("disabled", disabled);
58 | percept.put("task", task);
59 | things.forEach(t -> jsonThings.put(t.toJSON()));
60 | taskInfo.forEach(t -> jsonTasks.put(t.toJSON()));
61 | terrain.forEach((t, positions) -> {
62 | JSONArray jsonPositions = new JSONArray();
63 | positions.forEach(p -> jsonPositions.put(p.toJSON()));
64 | jsonTerrain.put(t, jsonPositions);
65 | });
66 | percept.put("lastAction", lastAction);
67 | percept.put("lastActionResult", lastActionResult);
68 | var params = new JSONArray();
69 | lastActionParams.forEach(params::put);
70 | percept.put("lastActionParams", params);
71 | JSONArray attached = new JSONArray();
72 | attachedThings.forEach(a -> {
73 | JSONArray pos = new JSONArray();
74 | pos.put(a.x);
75 | pos.put(a.y);
76 | attached.put(pos);
77 | });
78 | percept.put("attached", attached);
79 | return percept;
80 | }
81 |
82 | private void parsePercept(JSONObject percept) {
83 | score = percept.getLong("score");
84 | JSONArray jsonThings = percept.getJSONArray("things");
85 | JSONArray jsonTasks = percept.getJSONArray("tasks");
86 | for (int i = 0; i < jsonThings.length(); i++) {
87 | JSONObject jsonThing = jsonThings.getJSONObject(i);
88 | things.add(Thing.fromJson(jsonThing));
89 | }
90 | for (int i = 0; i < jsonTasks.length(); i++) {
91 | JSONObject jsonTask = jsonTasks.getJSONObject(i);
92 | taskInfo.add(TaskInfo.fromJson(jsonTask));
93 | }
94 | lastAction = percept.getString("lastAction");
95 | lastActionResult = percept.getString("lastActionResult");
96 | JSONObject jsonTerrain = percept.getJSONObject("terrain");
97 | jsonTerrain.keys().forEachRemaining(t -> {
98 | Set positions = new HashSet<>();
99 | JSONArray jsonPositions = jsonTerrain.getJSONArray(t);
100 | for (int i = 0; i < jsonPositions.length(); i++) {
101 | positions.add(Position.fromJSON(jsonPositions.getJSONArray(i)));
102 | }
103 | terrain.put(t, positions);
104 | });
105 | var params = percept.getJSONArray("lastActionParams");
106 | for (int i = 0; i < params.length(); i++) lastActionParams.add(params.getString(i));
107 | JSONArray jsonAttached = percept.getJSONArray("attached");
108 | for (int i = 0; i < jsonAttached.length(); i++) {
109 | JSONArray pos = jsonAttached.getJSONArray(i);
110 | attachedThings.add(Position.of(pos.getInt(0), pos.getInt(1)));
111 | }
112 | energy = percept.getInt("energy");
113 | disabled = percept.getBoolean("disabled");
114 | task = percept.getString("task");
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/eismassim/src/main/java/massim/eismassim/entities/ScenarioEntity.java:
--------------------------------------------------------------------------------
1 | package massim.eismassim.entities;
2 |
3 | import eis.iilang.*;
4 | import massim.eismassim.ConnectedEntity;
5 | import massim.protocol.messages.ActionMessage;
6 | import massim.protocol.messages.RequestActionMessage;
7 | import massim.protocol.messages.SimEndMessage;
8 | import massim.protocol.messages.SimStartMessage;
9 | import massim.protocol.messages.scenario.InitialPercept;
10 | import massim.protocol.messages.scenario.StepPercept;
11 | import org.json.JSONObject;
12 |
13 | import java.util.*;
14 |
15 | /**
16 | * An EIS compatible entity.
17 | */
18 | public class ScenarioEntity extends ConnectedEntity {
19 |
20 | public ScenarioEntity(String name, String host, int port, String username, String password) {
21 | super(name, host, port, username, password);
22 | }
23 |
24 | @Override
25 | protected List simStartToIIL(SimStartMessage startPercept) {
26 |
27 | List ret = new ArrayList<>();
28 | if(!(startPercept instanceof InitialPercept)) return ret; // protocol incompatibility
29 | InitialPercept simStart = (InitialPercept) startPercept;
30 |
31 | ret.add(new Percept("name", new Identifier(simStart.agentName)));
32 | ret.add(new Percept("team", new Identifier(simStart.teamName)));
33 | ret.add(new Percept("teamSize", new Numeral(simStart.teamSize)));
34 | ret.add(new Percept("steps", new Numeral(simStart.steps)));
35 | ret.add(new Percept("vision", new Numeral(simStart.vision)));
36 |
37 | return ret;
38 | }
39 |
40 | @Override
41 | protected Collection requestActionToIIL(RequestActionMessage message) {
42 | var ret = new HashSet();
43 | if(!(message instanceof StepPercept)) return ret; // percept incompatible with entity
44 | var percept = (StepPercept) message;
45 |
46 | ret.add(new Percept("actionID", new Numeral(percept.getId())));
47 | ret.add(new Percept("timestamp", new Numeral(percept.getTime())));
48 | ret.add(new Percept("deadline", new Numeral(percept.getDeadline())));
49 |
50 | ret.add(new Percept("step", new Numeral(percept.getStep())));
51 |
52 | ret.add(new Percept("lastAction", new Identifier(percept.lastAction)));
53 | ret.add(new Percept("lastActionResult", new Identifier(percept.lastActionResult)));
54 | var params = new ParameterList();
55 | percept.lastActionParams.forEach(p -> params.add(new Identifier(p)));
56 | ret.add(new Percept("lastActionParams", params));
57 | ret.add(new Percept("score", new Numeral(percept.score)));
58 |
59 | percept.things.forEach(thing -> ret.add(new Percept("thing",
60 | new Numeral(thing.x), new Numeral(thing.y), new Identifier(thing.type), new Identifier(thing.details))));
61 |
62 | percept.taskInfo.forEach(task -> {
63 | var reqs = new ParameterList();
64 | for(var req : task.requirements) {
65 | reqs.add(new Function("req", new Numeral(req.x), new Numeral(req.y),
66 | new Identifier(req.type)));
67 | }
68 | ret.add(new Percept("task", new Identifier(task.name), new Numeral(task.deadline), new Numeral(task.reward), reqs));
69 | });
70 | if (!percept.task.equals("")) ret.add(new Percept("accepted", new Identifier(percept.task)));
71 |
72 | percept.terrain.forEach((terrain, positions) -> positions.forEach(position ->
73 | ret.add(new Percept(terrain, new Numeral(position.x), new Numeral(position.y)))));
74 |
75 | percept.attachedThings.forEach(pos -> ret.add(
76 | new Percept("attached", new Numeral(pos.x), new Numeral(pos.y))));
77 |
78 | ret.add(new Percept("energy", new Numeral(percept.energy)));
79 | ret.add(new Percept("disabled", new Identifier(percept.disabled? "true" : "false")));
80 |
81 | return ret;
82 | }
83 |
84 | @Override
85 | protected Collection simEndToIIL(SimEndMessage endPercept) {
86 | HashSet ret = new HashSet<>();
87 | if (endPercept != null){
88 | ret.add(new Percept("ranking", new Numeral(endPercept.getRanking())));
89 | ret.add(new Percept("score", new Numeral(endPercept.getScore())));
90 | }
91 | return ret;
92 | }
93 |
94 | @Override
95 | public JSONObject actionToJSON(long actionID, Action action) {
96 |
97 | // translate parameters to String
98 | List parameters = new Vector<>();
99 | action.getParameters().forEach(param -> {
100 | if (param instanceof Identifier){
101 | parameters.add(((Identifier) param).getValue());
102 | }
103 | else if(param instanceof Numeral){
104 | parameters.add(((Numeral) param).getValue().toString());
105 | }
106 | else{
107 | log("Cannot translate parameter " + param);
108 | parameters.add(""); // add empty parameter so the order is not invalidated
109 | }
110 | });
111 |
112 | // create massim protocol action
113 | ActionMessage msg = new ActionMessage(action.getName(), actionID, parameters);
114 | return msg.toJson();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/util/Log.java:
--------------------------------------------------------------------------------
1 | package massim.util;
2 |
3 | import java.io.File;
4 | import java.io.FileWriter;
5 | import java.io.IOException;
6 | import java.io.OutputStream;
7 | import java.util.*;
8 |
9 | /**
10 | * Logger utility for the MASSim server. Supports 4 different log levels.
11 | */
12 | public class Log {
13 |
14 | public enum Level { CRITICAL, ERROR, NORMAL, DEBUG }
15 |
16 | private static Level logLevel = Level.NORMAL;
17 | private static File outputFile = null;
18 | private static FileWriter writer = null;
19 |
20 | private static Map outputs = new HashMap<>();
21 | private static Map typeStrings = new HashMap<>();
22 |
23 | static{ // initialization
24 | outputs.put(Level.CRITICAL, System.err);
25 | outputs.put(Level.ERROR, System.err);
26 | outputs.put(Level.NORMAL, System.out);
27 | outputs.put(Level.DEBUG, System.out);
28 |
29 | typeStrings.put(Level.CRITICAL, "[ CRITICAL ] ");
30 | typeStrings.put(Level.ERROR, "[ ERROR ] ");
31 | typeStrings.put(Level.NORMAL, "[ NORMAL ] ");
32 | typeStrings.put(Level.DEBUG, "[ DEBUG ] ");
33 |
34 | Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
35 | e.printStackTrace();
36 | logToFile(typeStrings.get(Level.ERROR) + e + " : " + Arrays.toString(e.getStackTrace()));
37 | });
38 | }
39 |
40 | /**
41 | * Logs a string with a certain log level to the respective output stream if the current log level permits.
42 | * @param level the log level at which to log
43 | * @param message the message to log
44 | */
45 | private static synchronized void logString(Level level, String message) {
46 | if (level.ordinal() <= logLevel.ordinal()){
47 | OutputStream out = outputs.get(level);
48 | String typeString = typeStrings.get(level);
49 | try {
50 | out.write(typeString.getBytes());
51 | out.write(message.getBytes());
52 | logToFile(typeString + message);
53 | } catch (IOException e) {
54 | System.err.println("Error while trying to write log string: " + message);
55 | }
56 | }
57 | }
58 |
59 | /**
60 | * Logs a string at the given log level if the level is currently being logged.
61 | * In {@link Level#DEBUG}, some meta info is prepended.
62 | * @param type the log level to use
63 | * @param msg the message to log
64 | */
65 | public static void log(Level type, String msg) {
66 |
67 | String metaInfo = "";
68 | if (logLevel == Level.DEBUG){
69 | int maxMetaLength = 90;
70 | metaInfo = getMetaInfo();
71 | if (metaInfo.length() < maxMetaLength){
72 | char[] ws = new char[maxMetaLength - metaInfo.length()];
73 | Arrays.fill(ws, ' ');
74 | metaInfo = metaInfo + new String(ws);
75 | }
76 | }
77 |
78 | logString(type, metaInfo +" ## " + msg + "\n");
79 | }
80 |
81 | /**
82 | * Sets the current log level.
83 | * @param level the level determining the log messages to display.
84 | */
85 | public static void setLogLevel(Level level) {
86 | logLevel = level;
87 | }
88 |
89 | /**
90 | * Sets the file to output the log to.
91 | * @param f the file to write log entries to
92 | */
93 | public static void setLogFile(File f){
94 | outputFile = f;
95 | }
96 |
97 | /**
98 | * Changes the output stream for a given log level.
99 | * @param level the level to change the log destination of
100 | * @param out the new output stream
101 | */
102 | public void changeOutputStream(Level level, OutputStream out){
103 | if(out != null){
104 | outputs.put(level, out);
105 | }
106 | }
107 |
108 | /**
109 | * @return some meta information about the current context
110 | */
111 | private static String getMetaInfo() {
112 | Exception e = new Exception();
113 | e.fillInStackTrace();
114 | StackTraceElement[] stack = e.getStackTrace();
115 | GregorianCalendar calendar = new GregorianCalendar();
116 | String t = "";
117 | String x = "";
118 | /*
119 | * 2 means this method and the calling method are ignored. thus, any method
120 | * calling getMetaInfo should have been called from outside.
121 | */
122 | StackTraceElement ls = stack[2];
123 | t += x;
124 | t += ls.getClassName() + "." + ls.getMethodName() + ":" + ls.getLineNumber();
125 | return String.format("%02d:%02d:%02d",
126 | calendar.get(Calendar.HOUR_OF_DAY),
127 | calendar.get(Calendar.MINUTE),
128 | calendar.get(Calendar.SECOND)) + " " +
129 | Thread.currentThread().getId() + " " +
130 | t;
131 | }
132 |
133 | /**
134 | * Writes a string to the log file.
135 | * @param s the string to write
136 | */
137 | private static synchronized void logToFile(String s){
138 |
139 | if(outputFile == null) return;
140 |
141 | if (writer == null){
142 | try {
143 | writer = new FileWriter(outputFile, true);
144 | } catch (IOException ignored) {}
145 | }
146 |
147 | if(writer != null){
148 | try {
149 | writer.append(s);
150 | writer.flush();
151 | } catch (IOException ignored) {}
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/util/IOUtil.java:
--------------------------------------------------------------------------------
1 | package massim.util;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 | import org.w3c.dom.Document;
6 |
7 | import javax.xml.transform.*;
8 | import javax.xml.transform.dom.DOMSource;
9 | import javax.xml.transform.stream.StreamResult;
10 | import java.io.File;
11 | import java.io.FileWriter;
12 | import java.io.IOException;
13 | import java.nio.charset.StandardCharsets;
14 | import java.nio.file.Files;
15 | import java.nio.file.Paths;
16 | import java.util.regex.Matcher;
17 | import java.util.regex.Pattern;
18 |
19 | /**
20 | * Helper for some I/O operations.
21 | */
22 | public abstract class IOUtil {
23 |
24 | /**
25 | * Matches all shortest occurrences of "$(...)".
26 | */
27 | private static Pattern pattern = Pattern.compile("\"\\$\\(([^$]*)??\\)\""); //
28 |
29 | /**
30 | * Tries to read a JSON object from the given file/path.
31 | * @param path the path of the file to read
32 | * @return the JSON object parsed from the string or an empty JSON object if the string was not a valid object
33 | * @throws IOException if the file could not be read
34 | */
35 | public static JSONObject readJSONObject(String path) throws IOException {
36 | try {
37 | return new JSONObject(readString(path));
38 | } catch(JSONException e){
39 | Log.log(Log.Level.ERROR, "Error in JSON object");
40 | return new JSONObject();
41 | }
42 | }
43 |
44 | /**
45 | * Reads a JSON object replacing occurrences of "$(file)" with the content of file.
46 | * @param path path to the root JSON file
47 | * @return a JSON object parsed from the file or an empty object if the string was invalid
48 | * @throws IOException if one of the used files could not be read
49 | */
50 | public static JSONObject readJSONObjectWithImport(String path) throws IOException {
51 | try{
52 | return new JSONObject(readWithReplace(path));
53 | } catch(JSONException e){
54 | Log.log(Log.Level.ERROR, "Invalid configuration");
55 | }
56 | return new JSONObject();
57 | }
58 |
59 | /**
60 | * Reads a string from the given file, replacing instances of $(path) with the contents of the file at that path.
61 | * Must be cycle-free to terminate.
62 | * $(path) entries in referenced files are handled recursively,
63 | * the path always being relative to the referencing file.
64 | * @param path the path of the root file
65 | * @return a string where all occurrences of $(...) have been replaced recursively
66 | * @throws IOException if any one file could not be read
67 | */
68 | public static String readWithReplace(String path) throws IOException {
69 | String text = readString(path);
70 | Matcher m = pattern.matcher(text);
71 | StringBuffer result = new StringBuffer();
72 | File file = new File(path).getAbsoluteFile();
73 | Log.log(Log.Level.DEBUG, "Reading file " + file.getAbsolutePath());
74 | String subPath = "";
75 | while(m.find()){
76 | try {
77 | subPath = file.getParent() + "/" + m.group(1);
78 | m.appendReplacement(result, readWithReplace(subPath));
79 | } catch(NullPointerException e){
80 | e.printStackTrace();
81 | Log.log(Log.Level.ERROR, "Could not insert file " + subPath + " into " + path);
82 | }
83 | }
84 | m.appendTail(result);
85 | return result.toString();
86 | }
87 |
88 | /**
89 | * Reads text content from a given file.
90 | * @param path the path to the file
91 | * @return a string of the file's text content
92 | * @throws IOException if the file could not be read
93 | */
94 | public static String readString(String path) throws IOException {
95 | return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
96 | }
97 |
98 | /**
99 | * Writes a JSON object to file.
100 | * @param json the JSON object to output
101 | * @param file the file to write to
102 | * @return whether writing succeeded
103 | */
104 | public static boolean writeJSONToFile(JSONObject json, File file){
105 | File dir = file.getParentFile();
106 | if(!dir.exists()) dir.mkdirs();
107 | try {
108 | FileWriter out = new FileWriter(file);
109 | out.write(json.toString(4));
110 | out.flush();
111 | out.close();
112 | } catch (IOException e) {
113 | e.printStackTrace();
114 | return false;
115 | }
116 | return true;
117 | }
118 |
119 | /**
120 | * Writes an XML object to file.
121 | * @param doc the XML document to output
122 | * @param file the file to write to
123 | * @param pretty whether to
124 | * @return whether writing succeeded
125 | */
126 | public static boolean writeXMLToFile(Document doc, File file, boolean pretty){
127 | File dir = file.getParentFile();
128 | if(!dir.exists()) dir.mkdirs();
129 | try {
130 | Transformer transformer = TransformerFactory.newInstance().newTransformer();
131 | if (pretty) {
132 | transformer.setOutputProperty(OutputKeys.INDENT, "yes");
133 | transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
134 | }
135 | Result output = new StreamResult(file);
136 | Source input = new DOMSource(doc);
137 | transformer.transform(input, output);
138 | } catch (TransformerException e) {
139 | e.printStackTrace();
140 | return false;
141 | }
142 | return true;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/docs/protocol.md:
--------------------------------------------------------------------------------
1 | # MASSim Protocol Documentation
2 |
3 | The MASSim protocol defines sequences of JSON messages which are exchanged between the agents and the MASSim server. Agents communicate with the server using standard TCP sockets.
4 |
5 | ## Sequence
6 |
7 | An agent may initiate communication by sending an `AUTH-REQUEST` message, containing its login credentials.
8 |
9 | The server tries to authenticate the agent and answers with an `AUTH-RESPONSE` message.
10 |
11 | If the result was positive, the server now sends various messages over the socket depending on its state:
12 |
13 | * a `SIM-START` message, whenever a new simulation starts
14 | * a `REQUEST-ACTION` message right before each simulation step
15 | * a `SIM-END` message after each simulation
16 | * a `BYE` message when all matches have been run, just before the server closes all sockets
17 |
18 | An agent should respond with an `ACTION` message to all `REQUEST-ACTION` messages it receives (within the time limit).
19 |
20 |
21 | Aside from these necessary messages, the clients may also send optional `STATUS-REQUEST` messages which are answered by `STATUS-RESPONSE` messages containing some information about the current server configuration.
22 |
23 | ## Reconnection
24 |
25 | If an agent loses the connection to the server, it may reconnect using the standard `AUTH-REQUEST` message. Auhtentication proceeds as before. If authentication was successful and the agent reconnects into a running simulation, the `SIM-START` message is sent again. If it coincides with a new simulation step, the order of `SIM-START` and `REQUEST-ACTION` messages is not guaranteed.
26 |
27 | ## Message formats
28 |
29 | __Each message is terminated by a separate `0 byte`.__ The server buffers everything up to the 0 byte and tries to parse a JSON string from that.
30 |
31 | Each message is a JSON object following the base format
32 |
33 | ```json
34 | {
35 | "type": "some-type",
36 | "content": {...}
37 | }
38 | ```
39 |
40 | where value of `type` denotes the type of the message. The JSON object under the `content` key depends on the message type.
41 |
42 | ### AUTH-REQUEST
43 |
44 | * Who? - Agents
45 | * Why? - Initiate communication with the server.
46 |
47 | ```json
48 | {
49 | "type": "auth-request",
50 | "content": {
51 | "user": "agentA1",
52 | "pw": "1"
53 | }
54 | }
55 | ```
56 |
57 | * __user__: username of the agent that is configured in the server
58 | * __pw__: the agent's password to authenticate with
59 |
60 | ### AUTH-RESPONSE
61 |
62 | * Who? - Server
63 | * Why? - Tell agent the result of authentication after an `AUTH-REQUEST` message.
64 |
65 | ```json
66 | {
67 | "type": "auth-response",
68 | "content": {"result": "ok"}
69 | }
70 | ```
71 |
72 | * __result__: the result of the authentication; either __"ok"__ or __"fail"__
73 |
74 | ### SIM-START
75 |
76 | * Who? - Server
77 | * Why? - Indicate that a simulation has started or is currently running.
78 |
79 | ```json
80 | {
81 | "type": "sim-start",
82 | "content": {
83 | "time": 1489514146201,
84 | "percept": {
85 | ...
86 | }
87 | }
88 | }
89 | ```
90 |
91 | * __time__: when the message was created
92 | * __percept__: static simulation info
93 |
94 | The contents of the percept object depend on the scenario (see Percepts section of [scenario.md](scenario.md)).
95 |
96 | ### REQUEST-ACTION
97 |
98 | * Who? - Server
99 | * Why? - Indicate a new simulation step, convey simulation state and request an action.
100 |
101 | ```json
102 | {
103 | "type": "request-action",
104 | "content": {
105 | "id": 2,
106 | "time": 1556636930397,
107 | "deadline": 1556636934400,
108 | "step": 27,
109 | "percept": {
110 | ...
111 | }
112 | }
113 | }
114 | ```
115 |
116 | * __deadline__: the time by which the server expects an `ACTION` message
117 | * __id__: the action-id; this id must be used in the `ACTION` message so that it can be associated with the correct request (which prevents older actions from being executed if they arrive too late)
118 | * __step__: the current simulation step
119 | * __percept__: the current simulation state
120 |
121 | The contents of the percept object depend on the scenario (see Percepts section of [scenario.md](scenario.md)).
122 |
123 | ### ACTION
124 |
125 | * Who? - Agent
126 | * Why? - Respond to `REQUEST-ACTION` message
127 |
128 | ```json
129 | {
130 | "type": "action",
131 | "content": {
132 | "id": 2,
133 | "type": "move",
134 | "p": ["n"]
135 | }
136 | }
137 | ```
138 |
139 | * __id__: the action-id that was received in the `REQUEST-ACTION` message
140 | * __type__: the type of action to use
141 | * __p__: an array of parameters for the action
142 |
143 | ### SIM-END
144 |
145 | * Who? - Server
146 | * Why? - Indicate that a simulation ended and give results.
147 |
148 | ```json
149 | {
150 | "type": "sim-end",
151 | "content": {
152 | "score": 9001,
153 | "ranking": 1,
154 | "time": 1556638423323
155 | }
156 | }
157 | ```
158 |
159 | * __ranking__: the team's rank at the end of the simulation
160 | * __score__: the team's final score
161 |
162 | ### BYE
163 |
164 | * Who? - Server
165 | * Why? - Indicate that all simulations have finished and the sockets will be closed.
166 |
167 | ```json
168 | {
169 | "type": "bye",
170 | "content": {}
171 | }
172 | ```
173 |
174 | ### STATUS-REQUEST
175 |
176 | * Who? - Agent
177 | * Why? - Ask about current server configuration.
178 |
179 | ```json
180 | {
181 | "type": "status-request",
182 | "content": {}
183 | }
184 | ```
185 |
186 | ### STATUS-RESPONSE
187 |
188 | * Who? - Server
189 | * Why? - Answers a status request.
190 |
191 | ```json
192 | {
193 | "type": "status-response",
194 | "content": {
195 | "teams":["A","B"],
196 | "time":1588865434131,
197 | "teamSizes":[15,30,50],
198 | "currentSimulation":0
199 | }
200 | }
201 | ```
202 |
203 | * __teams__: the teams that are currently playing (empty if the simulation hasn't started yet)
204 | * __time__: the server time when the message was created
205 | * __teamSizes__: how many agents play in each simulation per team (the size of this array corresponds to the number of simulations)
206 | * __currentSimulation__: the index of the current simulation (starts at 0, will be -1 if the first simulation has not started yet)
--------------------------------------------------------------------------------
/javaagents/src/main/java/massim/javaagents/Scheduler.java:
--------------------------------------------------------------------------------
1 | package massim.javaagents;
2 |
3 | import eis.AgentListener;
4 | import eis.EnvironmentListener;
5 | import eis.exceptions.ActException;
6 | import eis.exceptions.AgentException;
7 | import eis.exceptions.PerceiveException;
8 | import eis.exceptions.RelationException;
9 | import eis.iilang.EnvironmentState;
10 | import eis.iilang.Percept;
11 | import massim.eismassim.EnvironmentInterface;
12 | import massim.javaagents.agents.Agent;
13 | import massim.javaagents.agents.BasicAgent;
14 | import org.json.JSONObject;
15 |
16 | import java.io.IOException;
17 | import java.nio.file.Files;
18 | import java.nio.file.Paths;
19 | import java.util.*;
20 |
21 | /**
22 | * A scheduler for agent creation and execution.
23 | * EISMASSim scheduling needs to be enabled (via config), so that getAllPercepts()
24 | * blocks until new percepts are available!
25 | * (Also, queued and notifications should be disabled)
26 | */
27 | public class Scheduler implements AgentListener, EnvironmentListener{
28 |
29 | /**
30 | * Holds configured agent data.
31 | */
32 | private class AgentConf {
33 | String name;
34 | String entity;
35 | String team;
36 | String className;
37 |
38 | AgentConf(String name, String entity, String team, String className){
39 | this.name = name;
40 | this.entity = entity;
41 | this.team = team;
42 | this.className = className;
43 | }
44 | }
45 |
46 | private EnvironmentInterface eis;
47 | private List agentConfigurations = new Vector<>();
48 | private Map agents = new HashMap<>();
49 |
50 | /**
51 | * Create a new scheduler based on the given configuration file
52 | * @param path path to a java agents configuration file
53 | */
54 | Scheduler(String path) {
55 | parseConfig(path);
56 | }
57 |
58 | /**
59 | * Parses the java agents config.
60 | * @param path the path to the config
61 | */
62 | private void parseConfig(String path) {
63 | try {
64 | var config = new JSONObject(new String(Files.readAllBytes(Paths.get(path, "javaagentsconfig.json"))));
65 | var agents = config.optJSONArray("agents");
66 | if(agents != null){
67 | for (int i = 0; i < agents.length(); i++) {
68 | var agentBlock = agents.getJSONObject(i);
69 | var count = agentBlock.getInt("count");
70 | var startIndex = agentBlock.getInt("start-index");
71 | var agentPrefix = agentBlock.getString("agent-prefix");
72 | var entityPrefix = agentBlock.getString("entity-prefix");
73 | var team = agentBlock.getString("team");
74 | var agentClass = agentBlock.getString("class");
75 |
76 | for (int index = startIndex; index < startIndex + count; index++) {
77 | agentConfigurations.add(
78 | new AgentConf(agentPrefix + index, entityPrefix + index, team, agentClass));
79 | }
80 | }
81 | }
82 | } catch (IOException e) {
83 | e.printStackTrace();
84 | }
85 | }
86 |
87 | /**
88 | * Connects to an Environment Interface
89 | * @param ei the interface to connect to
90 | */
91 | void setEnvironment(EnvironmentInterface ei) {
92 | this.eis = ei;
93 | MailService mailService = new MailService();
94 | for (AgentConf agentConf: agentConfigurations) {
95 |
96 | Agent agent = null;
97 | switch(agentConf.className){
98 | case "BasicAgent":
99 | agent = new BasicAgent(agentConf.name, mailService);
100 | break;
101 | // [add further types here]
102 | default:
103 | System.out.println("Unknown agent type/class " + agentConf.className);
104 | }
105 | if(agent == null) continue;
106 |
107 | mailService.registerAgent(agent, agentConf.team);
108 |
109 | try {
110 | ei.registerAgent(agent.getName());
111 | } catch (AgentException e) {
112 | e.printStackTrace();
113 | }
114 |
115 | try {
116 | ei.associateEntity(agent.getName(), agentConf.entity);
117 | System.out.println("associated agent \"" + agent.getName() + "\" with entity \"" + agentConf.entity + "\"");
118 | } catch (RelationException e) {
119 | e.printStackTrace();
120 | }
121 |
122 | ei.attachAgentListener(agent.getName(), this);
123 | agents.put(agentConf.name, agent);
124 | }
125 | ei.attachEnvironmentListener(this);
126 | }
127 |
128 | /**
129 | * Steps all agents and relevant infrastructure.
130 | */
131 | void step() {
132 | // retrieve percepts for all agents
133 | List newPerceptAgents = new ArrayList<>();
134 | agents.values().forEach(ag -> {
135 | try {
136 | var addList = new ArrayList();
137 | var delList = new ArrayList();
138 | eis.getPercepts(ag.getName()).values().forEach(pUpdate -> {
139 | addList.addAll(pUpdate.getAddList());
140 | delList.addAll(pUpdate.getDeleteList());
141 | });
142 | if (!addList.isEmpty() || !delList.isEmpty()) newPerceptAgents.add(ag);
143 | ag.setPercepts(addList, delList);
144 | } catch (PerceiveException ignored) { }
145 | });
146 |
147 | // step all agents which have new percepts
148 | newPerceptAgents.forEach(agent -> {
149 | eis.iilang.Action action = agent.step();
150 | if (action != null) {
151 | try {
152 | eis.performAction(agent.getName(), action);
153 | } catch (ActException e) {
154 | System.out.println("Could not perform action " + action.getName() + " for " + agent.getName());
155 | }
156 | }
157 | });
158 |
159 | if(newPerceptAgents.size() == 0) try {
160 | Thread.sleep(100); // wait a bit in case no agents have been executed
161 | } catch (InterruptedException ignored) {}
162 | }
163 |
164 | @Override
165 | public void handlePercept(String agent, Percept percept) {
166 | agents.get(agent).handlePercept(percept);
167 | }
168 |
169 | @Override
170 | public void handleStateChange(EnvironmentState newState) {}
171 |
172 | @Override
173 | public void handleFreeEntity(String entity, Collection agents) {}
174 |
175 | @Override
176 | public void handleDeletedEntity(String entity, Collection agents) {}
177 |
178 | @Override
179 | public void handleNewEntity(String entity) {}
180 | }
181 |
--------------------------------------------------------------------------------
/monitor/js/ctrl.ts:
--------------------------------------------------------------------------------
1 | import { Redraw, StaticWorld, DynamicWorld, ConnectionState, Pos } from './interfaces';
2 | import { MapCtrl, minScale, maxScale } from './map';
3 | import { compareAgent, compareNumbered } from './util';
4 |
5 | export interface ViewModel {
6 | state: ConnectionState
7 | static?: StaticWorld
8 | dynamic?: DynamicWorld
9 | taskName?: string
10 | hover?: Pos
11 | teamNames: string[]
12 | }
13 |
14 | export class Ctrl {
15 | readonly vm: ViewModel;
16 | readonly replay: ReplayCtrl | undefined;
17 | readonly map: MapCtrl;
18 | maps: MapCtrl[];
19 |
20 | constructor(readonly redraw: Redraw, replayPath?: string) {
21 | this.vm = {
22 | state: 'connecting',
23 | teamNames: [],
24 | };
25 |
26 | if (replayPath) this.replay = new ReplayCtrl(this, replayPath);
27 | else this.connect();
28 |
29 | this.map = new MapCtrl(this);
30 | this.maps = [];
31 | }
32 |
33 | private connect(): void {
34 | const protocol = document.location.protocol === 'https:' ? 'wss:' : 'ws:';
35 | const path = document.location.pathname.substr(0, document.location.pathname.lastIndexOf('/'));
36 | const ws = new WebSocket(protocol + '//' + document.location.host + path + '/live/monitor');
37 |
38 | ws.onmessage = (msg) => {
39 | const data = JSON.parse(msg.data);
40 | console.log(data);
41 | if (data.grid) this.setStatic(data);
42 | else this.setDynamic(data);
43 | this.redraw();
44 | };
45 |
46 | ws.onopen = () => {
47 | console.log('Connected');
48 | this.vm.state = 'online';
49 | this.redraw();
50 | };
51 |
52 | ws.onclose = () => {
53 | console.log('Disconnected');
54 | setTimeout(() => this.connect(), 5000);
55 | this.vm.state = 'offline';
56 | this.redraw();
57 | };
58 | }
59 |
60 | setStatic(st?: StaticWorld) {
61 | if (st) {
62 | st.blockTypes.sort(compareNumbered);
63 | this.vm.teamNames = Object.keys(st.teams);
64 | this.vm.teamNames.sort(compareNumbered);
65 | }
66 | this.vm.static = st;
67 | this.resetTransform();
68 | }
69 |
70 | resetTransform() {
71 | const margin = 2;
72 | const grid = this.vm.static?.grid;
73 | if (!grid) return;
74 | const scale = Math.max(minScale, Math.min(maxScale, Math.min(window.innerWidth, window.innerHeight) / (2 * margin + Math.max(grid.width, grid.height))));
75 | this.map.vm.transform = {
76 | x: (window.innerWidth - scale * (grid.width + 2 * margin)) / 2 + scale * margin,
77 | y: (window.innerHeight - scale * (grid.height + 2 * margin)) / 2 + scale * margin,
78 | scale,
79 | };
80 | }
81 |
82 | setDynamic(dynamic?: DynamicWorld) {
83 | if (dynamic) dynamic.entities.sort(compareAgent);
84 | this.vm.dynamic = dynamic;
85 | }
86 |
87 | toggleMaps() {
88 | if (this.vm.dynamic && !this.maps.length) {
89 | this.maps = this.vm.dynamic.entities.map(agent => {
90 | const ctrl = new MapCtrl(this);
91 | ctrl.vm.selected = agent.id;
92 | return ctrl;
93 | });
94 | } else {
95 | this.maps = [];
96 | }
97 | this.redraw();
98 | }
99 |
100 | setHover(pos?: Pos) {
101 | const changed = (!pos && this.vm.hover) || (pos && !this.vm.hover) || (pos && this.vm.hover && (pos.x != this.vm.hover.x || pos.y != this.vm.hover.y));
102 | this.vm.hover = pos;
103 | if (changed) this.redraw();
104 | }
105 | }
106 |
107 | export class ReplayCtrl {
108 | public step = -1;
109 |
110 | private suffix: string;
111 | private timer: NodeJS.Timeout | undefined;
112 |
113 | private cache = new Map();
114 |
115 | constructor(readonly root: Ctrl, readonly path: string) {
116 | if (path.endsWith('/')) this.path = path.substr(0, path.length - 1);
117 | this.suffix = location.pathname == '/' ? `?sri=${Math.random().toString(36).slice(-8)}` : '';
118 |
119 | this.loadStatic();
120 | }
121 |
122 | stop() {
123 | if (this.timer) clearInterval(this.timer);
124 | this.timer = undefined;
125 | this.root.redraw();
126 | }
127 |
128 | start() {
129 | if (!this.timer) this.timer = setInterval(() => {
130 | if (this.root.vm.state !== 'connecting') this.setStep(this.step + 1);
131 | }, 1000);
132 | this.root.redraw();
133 | }
134 |
135 | loadStatic() {
136 | const xhr = new XMLHttpRequest();
137 | xhr.open('GET', `${this.path}/static.json${this.suffix}`);
138 | xhr.onload = () => {
139 | if (xhr.status === 200) {
140 | this.root.setStatic(JSON.parse(xhr.responseText));
141 | this.setStep(this.step);
142 | } else {
143 | this.root.vm.state = 'error';
144 | }
145 | this.root.redraw();
146 | };
147 | xhr.onerror = () => {
148 | this.root.vm.state = 'error';
149 | this.root.redraw();
150 | };
151 | xhr.send();
152 | }
153 |
154 | loadDynamic(step: number) {
155 | // got from cache
156 | const entry = this.cache.get(step);
157 | if (entry) {
158 | this.root.setDynamic(entry);
159 | this.root.vm.state = (this.root.vm.dynamic && this.root.vm.dynamic.step == step) ? 'online' : 'connecting';
160 | this.root.redraw();
161 | return;
162 | }
163 |
164 | const onerror = () => {
165 | this.root.vm.state = 'error';
166 | this.stop();
167 | this.root.redraw();
168 | };
169 |
170 | const group = step > 0 ? Math.floor(step / 5) * 5 : 0;
171 | const xhr = new XMLHttpRequest();
172 | xhr.open('GET', `${this.path}/${group}.json${this.suffix}`);
173 | xhr.onload = () => {
174 | if (xhr.status === 200) {
175 | const response = JSON.parse(xhr.responseText);
176 |
177 | // write to cache
178 | if (this.cache.size > 100) this.cache.clear();
179 | for (const s in response) this.cache.set(parseInt(s, 10), response[s]);
180 |
181 | if (response[step]) {
182 | this.root.setDynamic(response[step]);
183 | this.root.vm.state = (this.root.vm.dynamic && this.root.vm.dynamic.step == step) ? 'online' : 'connecting';
184 | this.root.redraw();
185 | return;
186 | }
187 | }
188 |
189 | // status !== 200 or !response[step]
190 | onerror();
191 | };
192 | xhr.onerror = onerror;
193 | xhr.send();
194 | }
195 |
196 | setStep(s: number) {
197 | // keep step in bounds
198 | this.step = Math.max(-1, s);
199 | if (this.root.vm.static && this.step >= this.root.vm.static.steps) {
200 | this.stop();
201 | this.step = this.root.vm.static.steps - 1;
202 | }
203 |
204 | // show connecting after a while
205 | this.root.vm.state = 'connecting';
206 | setTimeout(() => this.root.redraw(), 500);
207 |
208 | // update url
209 | if (history.replaceState) history.replaceState({}, document.title, '#' + this.step);
210 |
211 | this.loadDynamic(this.step);
212 | }
213 |
214 | name() {
215 | const parts = this.path.split('/');
216 | return parts[parts.length - 1];
217 | }
218 |
219 | toggle() {
220 | if (this.timer) this.stop();
221 | else this.start();
222 | }
223 |
224 | playing() {
225 | return !!this.timer;
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/server/src/main/java/massim/FrontDesk.java:
--------------------------------------------------------------------------------
1 | package massim;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.net.ServerSocket;
7 | import java.net.Socket;
8 | import java.nio.charset.StandardCharsets;
9 |
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import massim.config.ServerConfig;
14 | import massim.protocol.messages.AuthRequestMessage;
15 | import massim.protocol.messages.AuthResponseMessage;
16 | import massim.protocol.messages.Message;
17 | import massim.protocol.messages.StatusRequestMessage;
18 | import massim.protocol.messages.StatusResponseMessage;
19 | import massim.util.Log;
20 |
21 | /**
22 | * This is where all initial network requests go in.
23 | * @author ta10
24 | */
25 | class FrontDesk {
26 |
27 | private boolean stopped = false;
28 | private final ServerSocket serverSocket;
29 | private final Thread thread;
30 | private final AgentManager agentManager;
31 |
32 | private final Status simStatus = new Status();
33 |
34 | /**
35 | * Creates a new listener waiting for incoming connections.
36 | * @param agentMng the agent connection manager
37 | * @throws IOException if socket with the given data cannot be opened
38 | */
39 | FrontDesk(AgentManager agentMng, ServerConfig config) throws IOException {
40 | setTeamSizes(config.teamSizes.toArray(new Integer[0]));
41 | agentManager = agentMng;
42 | serverSocket = new ServerSocket(config.port, config.backlog, null);
43 | thread = new Thread(() -> {
44 | while (!stopped) {
45 | try {
46 | Log.log(Log.Level.DEBUG, "Waiting for connection...");
47 | Socket s = serverSocket.accept();
48 | Log.log(Log.Level.DEBUG,"Got a connection.");
49 | Thread t = new Thread(() -> handleSocket(s));
50 | t.start();
51 | } catch (IOException e) {
52 | Log.log(Log.Level.DEBUG,"Stop listening");
53 | }
54 | }
55 | });
56 | }
57 |
58 | /**
59 | * Starts listening on the socket.
60 | */
61 | void open() {
62 | thread.start();
63 | }
64 |
65 | /**
66 | * Stops listening.
67 | */
68 | void close() {
69 | try {
70 | stopped = true;
71 | serverSocket.close();
72 | } catch (IOException e) {
73 | e.printStackTrace();
74 | }
75 | }
76 |
77 | /**
78 | * Creates and sends an auth-response message on the given socket.
79 | * @param s the socket to send on
80 | * @param result whether the authentication was successful
81 | */
82 | private void sendAuthResponse(Socket s, String result) {
83 | sendMessage(s, new AuthResponseMessage(System.currentTimeMillis(), result));
84 | }
85 |
86 | private void sendStatusResponse(Socket s) {
87 | sendMessage(s, buildStatusResponse());
88 | }
89 |
90 | private void sendMessage(Socket s, Message msg) {
91 | try {
92 | var out = s.getOutputStream();
93 | out.write(msg.toJson().toString().getBytes());
94 | out.write(0);
95 | } catch (IOException e) {
96 | Log.log(Log.Level.CRITICAL, msg.getMessageType() + " message could not be sent.");
97 | e.printStackTrace();
98 | }
99 | }
100 |
101 | /**
102 | * Tries to perform agent authentication on the new socket.
103 | * @param s the socket to use
104 | */
105 | private void handleSocket(Socket s) {
106 | try {
107 | InputStream is = s.getInputStream();
108 | ByteArrayOutputStream buffer = new ByteArrayOutputStream();
109 | int b;
110 | while (true) {
111 | b = is.read();
112 | if (b == 0) break; // message completely read
113 | else if (b == -1) return; // stream ended
114 | else buffer.write(b);
115 | }
116 |
117 | String received = buffer.toString(StandardCharsets.UTF_8);
118 | JSONObject json = null;
119 | try {
120 | json = new JSONObject(received);
121 | } catch(JSONException e){
122 | Log.log(Log.Level.ERROR, "Invalid JSON object received: " + received);
123 | }
124 | Message msg = Message.buildFromJson(json);
125 |
126 | if(msg != null){
127 | if(msg instanceof AuthRequestMessage) {
128 | AuthRequestMessage auth = (AuthRequestMessage) msg;
129 | Log.log(Log.Level.NORMAL, "got authentication: username=" + auth.getUsername() + " password="
130 | + auth.getPassword() + " address=" + s.getInetAddress().getHostAddress());
131 | // check credentials and act accordingly
132 | if (agentManager.auth(auth.getUsername(), auth.getPassword())) {
133 | Log.log(Log.Level.NORMAL, auth.getUsername() + " authentication successful");
134 | sendAuthResponse(s, AuthResponseMessage.OK);
135 | agentManager.handleNewConnection(s, auth.getUsername());
136 | } else {
137 | Log.log(Log.Level.ERROR, "Got invalid authentication from: " + s.getInetAddress().getHostAddress());
138 | sendAuthResponse(s, AuthResponseMessage.FAIL);
139 | try {
140 | s.close();
141 | } catch (IOException ignored) {}
142 | }
143 | }
144 | else if (msg instanceof StatusRequestMessage) {
145 | Log.log(Log.Level.DEBUG, "Got status request from: " + s.getInetAddress().getHostAddress());
146 | sendStatusResponse(s);
147 | }
148 | else{
149 | Log.log(Log.Level.ERROR, "Expected AuthRequest, Received message of type: " + msg.getClass());
150 | }
151 | }
152 | else{
153 | Log.log(Log.Level.ERROR, "Cannot handle message: " + received);
154 | }
155 | } catch (IOException e) {
156 | Log.log(Log.Level.ERROR, "Error while receiving authentication message");
157 | e.printStackTrace();
158 | }
159 | }
160 |
161 | public void setTeams(String[] teams) {
162 | synchronized (simStatus) {
163 | simStatus.teams = teams;
164 | }
165 | }
166 |
167 | private void setTeamSizes(Integer[] teamSizes) {
168 | synchronized (simStatus) {
169 | simStatus.teamSizes = teamSizes;
170 | }
171 | }
172 |
173 | public void setCurrentSimulation(int currentSimulation) {
174 | synchronized (simStatus) {
175 | simStatus.currentSimulation = currentSimulation;
176 | }
177 | }
178 |
179 | private StatusResponseMessage buildStatusResponse() {
180 | synchronized (simStatus) {
181 | return new StatusResponseMessage(System.currentTimeMillis(), simStatus.teams, simStatus.teamSizes,
182 | simStatus.currentSimulation);
183 | }
184 | }
185 |
186 | static class Status {
187 | String[] teams = new String[0];
188 | Integer[] teamSizes = new Integer[0];
189 | int currentSimulation = -1;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------