├── 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 | [![Continuous Integration](https://github.com/agentcontest/massim_2020/workflows/Continuous%20Integration/badge.svg)](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 | --------------------------------------------------------------------------------