Преглед изворни кода

Implementeer ophalen en starten puzzeltocht vanuit de webapp

Harry de Boer пре 6 година
родитељ
комит
1f03d0147e

+ 55 - 0
src/main/java/puzzeltocht/controller/EventController.java

@@ -0,0 +1,55 @@
+package puzzeltocht.controller;
+
+import org.springframework.http.*;
+import org.springframework.web.bind.annotation.*;
+import puzzeltocht.controller.dto.EventDto;
+import puzzeltocht.controller.dto.MissionDto;
+import puzzeltocht.controller.dto.TeamCreateDto;
+import puzzeltocht.controller.dto.TeamUpdateDto;
+import puzzeltocht.domain.Mission;
+import puzzeltocht.service.EventService;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("api")
+public class EventController {
+    private EventService eventService;
+
+    public EventController(EventService es) {
+        this.eventService = es;
+    }
+
+    @RequestMapping(method = RequestMethod.GET, path ="event")
+    public List<EventDto> event() {
+        return eventService.currentEvents().stream()
+                .map(EventDto::new)
+                .collect(Collectors.toList());
+    }
+
+    @RequestMapping(method = RequestMethod.GET, path ="event/{id}")
+    public ResponseEntity<EventDto> event(@PathVariable UUID id) {
+        EventDto e = new EventDto(eventService.get(id));
+        return ResponseEntity.ok(e);
+    }
+
+    @RequestMapping(method = RequestMethod.POST, path ="event/{eventId}/team")
+    public ResponseEntity<UUID> teamCreate(@PathVariable UUID eventId, @RequestBody TeamCreateDto team) {
+        UUID teamId = eventService.createTeam(eventId, team);
+        return ResponseEntity.ok(teamId);
+    }
+
+    @RequestMapping(method = RequestMethod.PUT, path ="event/{eventId}/team/{teamId}")
+    public ResponseEntity<MissionDto> teamUpdate(
+            @PathVariable UUID eventId,
+            @PathVariable UUID teamId,
+            @RequestBody TeamUpdateDto update) {
+        Mission m = eventService.updateMission(eventId, teamId, update);
+        MissionDto dto = new MissionDto(m, update.getLocation());
+        return ResponseEntity.ok(dto);
+    }
+
+}
+

+ 0 - 37
src/main/java/puzzeltocht/controller/RouteController.java

@@ -1,37 +0,0 @@
-package puzzeltocht.controller;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RestController;
-import puzzeltocht.dto.Event;
-import puzzeltocht.dto.EventType;
-import puzzeltocht.dto.Location;
-
-import java.util.Collections;
-import java.util.List;
-
-@RestController
-@RequestMapping("api")
-public class RouteController {
-
-    @RequestMapping(method = RequestMethod.GET, path ="route")
-    public List<Long> routes() {
-        return Collections.singletonList(0L);
-    }
-
-    @RequestMapping(method = RequestMethod.GET, path ="route/{id}")
-    public ResponseEntity<Event> route(@PathVariable Long id) {
-        if (id != 0L) {
-            return ResponseEntity.badRequest().build();
-        }
-
-        Location loc = new Location(51.244640, 6.038359);
-        Event mockEvent = new Event(EventType.START, loc, "Welkom bij de Summercamp puzzeltocht!");
-
-        return new ResponseEntity<>(mockEvent, HttpStatus.OK);
-    }
-}
-

+ 29 - 0
src/main/java/puzzeltocht/controller/dto/EventDto.java

@@ -0,0 +1,29 @@
+package puzzeltocht.controller.dto;
+
+import puzzeltocht.domain.Event;
+
+import java.util.UUID;
+
+public class EventDto {
+    private final UUID id;
+    private final String title;
+    private final String description;
+
+    public EventDto(Event e) {
+        this.id = e.getId();
+        this.title = e.getTitle();
+        this.description = e.getDescription();
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 22 - 0
src/main/java/puzzeltocht/controller/dto/LocationDto.java

@@ -0,0 +1,22 @@
+package puzzeltocht.controller.dto;
+
+import puzzeltocht.domain.Location;
+
+public class LocationDto {
+    private final double latitude;
+    private final double longitude;
+
+    public LocationDto(Location l) {
+        this.latitude = l.getLatitude();
+        this.longitude = l.getLongitude();
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+
+}

+ 42 - 0
src/main/java/puzzeltocht/controller/dto/MissionDto.java

@@ -0,0 +1,42 @@
+package puzzeltocht.controller.dto;
+
+import puzzeltocht.domain.Location;
+import puzzeltocht.domain.Mission;
+
+import java.util.UUID;
+
+public class MissionDto {
+    private final UUID id;
+    private final String title;
+    private final String description;
+    private final Long distanceToTarget;
+    private final LocationDto target;
+
+    public MissionDto(Mission m, Location current) {
+        this.id = m.getId();
+        this.title = m.getTitle();
+        this.description = m.getDescription();
+        this.target = m.getTarget() == null ? null : new LocationDto(m.getTarget());
+        this.distanceToTarget = m.getTarget() == null ? 0 : Math.round(m.getTarget().distanceTo(current));
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Long getDistanceToTarget() {
+        return distanceToTarget;
+    }
+
+    public LocationDto getTarget() {
+        return target;
+    }
+}

+ 13 - 0
src/main/java/puzzeltocht/controller/dto/TeamCreateDto.java

@@ -0,0 +1,13 @@
+package puzzeltocht.controller.dto;
+
+public class TeamCreateDto {
+    private final String name;
+
+    public TeamCreateDto(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 29 - 0
src/main/java/puzzeltocht/controller/dto/TeamUpdateDto.java

@@ -0,0 +1,29 @@
+package puzzeltocht.controller.dto;
+
+import puzzeltocht.domain.Location;
+
+import java.util.UUID;
+
+public class TeamUpdateDto {
+    private final UUID missionId;
+    private final Location location;
+    private final String answer;
+
+    public TeamUpdateDto(UUID missionId, Location location, String answer) {
+        this.missionId = missionId;
+        this.location = location;
+        this.answer = answer;
+    }
+
+    public UUID getMissionId() {
+        return missionId;
+    }
+
+    public Location getLocation() {
+        return location;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+}

+ 46 - 0
src/main/java/puzzeltocht/domain/Event.java

@@ -0,0 +1,46 @@
+package puzzeltocht.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class Event {
+    private final UUID id;
+    private final String title;
+    private final String description;
+    private final Route route;
+    private final List<Team> teams = new ArrayList<>();
+
+    public Event(String title, String description, Route r) {
+        this.id = UUID.randomUUID();
+        this.title = title;
+        this.description = description;
+        this.route = r;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void addTeam(Team t) {
+        teams.add(t);
+    }
+
+    public List<Team> getTeams() {
+        return Collections.unmodifiableList(teams);
+    }
+
+    public Mission nextMission(Mission m) {
+        return route.nextMission(m);
+    }
+
+}

+ 38 - 0
src/main/java/puzzeltocht/domain/Location.java

@@ -0,0 +1,38 @@
+package puzzeltocht.domain;
+
+public class Location {
+    private double latitude;
+    private double longitude;
+
+    public Location(double latitude, double longitude) {
+        this.latitude = latitude;
+        this.longitude = longitude;
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+
+    /**
+     * Calculate distance between two points in latitude and longitude taking
+     * into account height difference. Uses Haversine method as its base.
+     *
+     * @return Distance in meters
+     */
+    public double distanceTo(Location other) {
+        final int R = 6371; // Radius of the earth
+
+        double latDistance = Math.toRadians(other.latitude - this.latitude);
+        double lonDistance = Math.toRadians(other.longitude - this.longitude);
+        double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
+                + Math.cos(Math.toRadians(this.latitude)) * Math.cos(Math.toRadians(other.latitude))
+                * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+        // convert to meters
+        return R * c * 1000;
+    }
+}

+ 57 - 0
src/main/java/puzzeltocht/domain/Mission.java

@@ -0,0 +1,57 @@
+package puzzeltocht.domain;
+
+import java.util.UUID;
+
+public class Mission {
+    private final UUID id;
+    private final MissionType type;
+    private final String title;
+    private final String description;
+    private Location target;
+    private String answer;
+
+    public Mission(MissionType type, String title, String description) {
+        this.id = UUID.randomUUID();
+        this.type = type;
+        this.title = title;
+        this.description = description;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public MissionType getType() {
+        return type;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Location getTarget() {
+        return target;
+    }
+
+    public void setTarget(Location target) {
+        if (!(type == MissionType.START || type == MissionType.LOCATE)){
+            throw new IllegalStateException("target ongeldig voor type " + type);
+        }
+        this.target = target;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        if (!(type == MissionType.QUESTION)){
+            throw new IllegalStateException("target ongeldig voor type " + type);
+        }
+        this.answer = answer;
+    }
+}

+ 8 - 0
src/main/java/puzzeltocht/domain/MissionType.java

@@ -0,0 +1,8 @@
+package puzzeltocht.domain;
+
+public enum MissionType {
+    START,
+    FINISH,
+    LOCATE,
+    QUESTION,
+}

+ 42 - 0
src/main/java/puzzeltocht/domain/Route.java

@@ -0,0 +1,42 @@
+package puzzeltocht.domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class Route {
+    private UUID id;
+    private String name;
+    private final List<Mission> missions = new ArrayList<>();
+
+    public Route(String name) {
+        this.id = UUID.randomUUID();
+        this.name = name;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<Mission> getMissions() {
+        return Collections.unmodifiableList(missions);
+    }
+
+    public void addMission(Mission m) {
+        missions.add(m);
+    }
+
+    public Mission nextMission(Mission m) {
+        // indexOf == -1 als m null is dus dan krijgen we precies de eerste
+        int idx = missions.indexOf(m) + 1;
+        if (idx < missions.size()) {
+            return missions.get(idx);
+        }
+        return m;
+    }
+}

+ 31 - 0
src/main/java/puzzeltocht/domain/Team.java

@@ -0,0 +1,31 @@
+package puzzeltocht.domain;
+
+import java.util.UUID;
+
+public class Team {
+    private final UUID id;
+    private final String name;
+    private Mission currentMission;
+
+    public Team(String name) {
+        this.id = UUID.randomUUID();
+        this.name = name;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setCurrentMission(Mission currentMission) {
+        this.currentMission = currentMission;
+    }
+
+    public Mission getCurrentMission() {
+        return currentMission;
+    }
+
+}

+ 0 - 25
src/main/java/puzzeltocht/dto/Event.java

@@ -1,25 +0,0 @@
-package puzzeltocht.dto;
-
-public class Event {
-    private EventType type;
-    private Location location;
-    private String description;
-
-    public Event(EventType type, Location location, String description) {
-        this.type = type;
-        this.location = location;
-        this.description = description;
-    }
-
-    public EventType getType() {
-        return type;
-    }
-
-    public Location getLocation() {
-        return location;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-}

+ 0 - 5
src/main/java/puzzeltocht/dto/EventType.java

@@ -1,5 +0,0 @@
-package puzzeltocht.dto;
-
-public enum EventType {
-    START
-}

+ 0 - 20
src/main/java/puzzeltocht/dto/Location.java

@@ -1,20 +0,0 @@
-package puzzeltocht.dto;
-
-public class Location {
-    private double longitude;
-    private double latitude;
-
-    public Location(double longitude, double latitude) {
-        this.longitude = longitude;
-        this.latitude = latitude;
-    }
-
-    public double getLongitude() {
-        return longitude;
-    }
-
-    public double getLatitude() {
-        return latitude;
-    }
-
-}

+ 48 - 0
src/main/java/puzzeltocht/service/EventService.java

@@ -0,0 +1,48 @@
+package puzzeltocht.service;
+
+import org.springframework.stereotype.Service;
+import puzzeltocht.controller.dto.TeamCreateDto;
+import puzzeltocht.controller.dto.TeamUpdateDto;
+import puzzeltocht.domain.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Service
+public class EventService {
+
+    private final RouteService routeService;
+    private final TeamService teamService;
+
+    private final List<Event> events = new ArrayList<>();
+
+    public EventService(RouteService rs, TeamService ts) {
+        this.routeService = rs;
+        this.teamService = ts;
+        this.events.add(demoEvent());
+    }
+
+    public List<Event> currentEvents() {
+        return events;
+    }
+
+    public Event get(UUID id) {
+        return events.stream()
+                .filter(r -> r.getId().equals(id))
+                .findFirst().orElseThrow(IllegalStateException::new);
+    }
+
+    private Event demoEvent() {
+        Route r = routeService.mockRoute();
+        return new Event("Summercamp puzzeltocht", "", r);
+    }
+
+    public Mission updateMission(UUID eventId, UUID teamId, TeamUpdateDto update) {
+        return teamService.updateMission(get(eventId), teamId, update);
+    }
+
+    public UUID createTeam(UUID eventId, TeamCreateDto team) {
+        return teamService.create(get(eventId), team);
+    }
+}

+ 37 - 0
src/main/java/puzzeltocht/service/MissionService.java

@@ -0,0 +1,37 @@
+package puzzeltocht.service;
+
+import org.springframework.stereotype.Service;
+import puzzeltocht.controller.dto.TeamUpdateDto;
+import puzzeltocht.domain.Location;
+import puzzeltocht.domain.Mission;
+
+@Service
+public class MissionService {
+
+    public boolean evaluate(Mission m, TeamUpdateDto update) {
+        switch (m.getType()) {
+            case START:
+            case LOCATE:
+                return evaluateLocate(m.getTarget(), update.getLocation());
+            case FINISH:
+                return true;
+            case QUESTION:
+                return evaluateQuestion(m.getAnswer(), update.getAnswer());
+            default:
+                return false;
+        }
+    }
+
+    private boolean evaluateLocate(Location target, Location current) {
+        // distance in meters
+        return target.distanceTo(current) < 10;
+    }
+
+    private boolean evaluateQuestion(String targetAnswer, String givenAnswer) {
+        return clean(targetAnswer).equals(clean(givenAnswer));
+    }
+
+    private String clean(String s) {
+        return s == null ? null : s.trim().toLowerCase();
+    }
+}

+ 46 - 0
src/main/java/puzzeltocht/service/RouteService.java

@@ -0,0 +1,46 @@
+package puzzeltocht.service;
+
+import org.springframework.stereotype.Service;
+import puzzeltocht.domain.Location;
+import puzzeltocht.domain.Mission;
+import puzzeltocht.domain.MissionType;
+import puzzeltocht.domain.Route;
+
+@Service
+public class RouteService {
+    public Route mockRoute() {
+        Route r = new Route("Summercamp demo");
+        Mission mVoordeur = new Mission(MissionType.START, "Naar het startpunt",
+                "Ga door de voordeur naar buiten (dus niet bij de BBQ), hier begint de tocht.");
+        mVoordeur.setTarget(new Location(51.24503, 6.03933));
+        Mission mDoeltjes = new Mission(MissionType.LOCATE, "Doeltjes",
+                "Loop naar het mini-voetbalveldje.");
+        mDoeltjes.setTarget(new Location(51.24447, 6.03887));
+        Mission mJohan = new Mission(MissionType.QUESTION, "Hoofdrekenen",
+                "Wat is 31000000000 + 600000000 + 51000000 + 490000 + 3800 + 26?");
+        mJohan.setAnswer("+31651493826");
+        Mission mUitgang = new Mission(MissionType.LOCATE, "Uitgang", "Loop naar de uitgang van het terrein.");
+        mUitgang.setTarget(new Location(51.24463, 6.03826));
+        Mission mVerboden = new Mission(MissionType.QUESTION, "Strafrecht",
+                "Welk artikelnummer in het wetboek van strafrecht geeft aan dat en terrein 'verboden voor onbevoegden' is?");
+        mVerboden.setAnswer("461");
+        Mission mTrampoline = new Mission(MissionType.LOCATE, "Trampoline", "Loop naar de trampoline.");
+        mTrampoline.setTarget(new Location(51.24488, 6.03870));
+        Mission mMarco = new Mission(MissionType.QUESTION, "De gymnast",
+                "Wie van de summercamp deelnemers springt er graag op een trampoline?");
+        mMarco.setAnswer("Marco");
+        Mission mBbq = new Mission(MissionType.LOCATE, "BBQ", "Loop naar de BBQ.");
+        mBbq.setTarget(new Location(51.24505, 6.03901));
+        Mission mFinish = new Mission(MissionType.FINISH, "Finish", "Dit is het einde van de puzzeltocht!");
+        r.addMission(mVoordeur);
+        r.addMission(mDoeltjes);
+        r.addMission(mJohan);
+        r.addMission(mUitgang);
+        r.addMission(mVerboden);
+        r.addMission(mTrampoline);
+        r.addMission(mMarco);
+        r.addMission(mBbq);
+        r.addMission(mFinish);
+        return r;
+    }
+}

+ 44 - 0
src/main/java/puzzeltocht/service/TeamService.java

@@ -0,0 +1,44 @@
+package puzzeltocht.service;
+
+import org.springframework.stereotype.Service;
+import puzzeltocht.controller.dto.TeamCreateDto;
+import puzzeltocht.controller.dto.TeamUpdateDto;
+import puzzeltocht.domain.Event;
+import puzzeltocht.domain.Mission;
+import puzzeltocht.domain.Team;
+
+import java.util.UUID;
+
+@Service
+public class TeamService {
+    private final MissionService missionService;
+
+    public TeamService(MissionService ms) {
+        this.missionService = ms;
+    }
+
+    public UUID create(Event event, TeamCreateDto team) {
+        Team t = new Team(team.getName());
+        event.addTeam(t);
+        updateMission(event, t.getId(), null);
+        return t.getId();
+    }
+
+    public Mission updateMission(Event event, UUID teamId, TeamUpdateDto update) {
+        Team t = find(event, teamId);
+        Mission m = t.getCurrentMission();
+
+        if (m == null || missionService.evaluate(m, update)) {
+            m = event.nextMission(m);
+            t.setCurrentMission(m);
+        }
+
+        return t.getCurrentMission();
+    }
+
+    private Team find(Event event, UUID teamId) {
+        return event.getTeams().stream()
+                .filter(t -> t.getId().equals(teamId))
+                .findFirst().orElseThrow(IllegalArgumentException::new);
+    }
+}

+ 37 - 5
src/main/resources/static/index.html

@@ -2,17 +2,49 @@
 <html lang="nl">
 <head>
     <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Puzzeltocht</title>
+    <meta title="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Puzzeltochten</title>
     <script src="main.js"></script>
     <link rel="stylesheet" href="main.css">
 </head>
 <body>
+
+<header>
+    <h1>Puzzeltochten</h1>
+    <div id="unsupported">Deze browser ondersteunt de vereiste functionaliteit voor deze applicatie niet</div>
+</header>
+
+<div id="join">
     <header>
-        <h1>Summercamp puzzeltocht</h1>
+        <h2>Kies teamnaam en puzzeltocht</h2>
     </header>
-    <div id="unsupported">Deze browser ondersteunt de vereiste functionaliteit voor deze applicatie</div>
+
+    <form id="joinForm">
+        <label for="teamName">Teamnaam: </label>
+        <input type="text" id="teamName"/>
+        <label for="events">Puzzeltocht:</label>
+        <div id="events">
+            <label><input name="eventId" type="radio"></label>
+        </div>
+        <button type="submit" id="joinButton" disabled>Wij doen mee!</button>
+    </form>
+</div>
+
+<section id="mission" style="display:none">
+    <header>
+        <h1 id="missionTitle">Laden...</h1>
+    </header>
+    <p id="missionDescription"></p>
+    <div id="questionMission">
+        <label for="questionAnwser">Antwoord: </label>
+        <input type="text" id="questionAnwser" required/>
+        <button id="answerButton">Verstuur antwoord</button>
+    </div>
+</section>
+
+<footer>
     <div id="location">Locatie onbekend</div>
-    <img id="map" alt="Kaart" src="" />
+</footer>
+
 </body>
 </html>

+ 25 - 2
src/main/resources/static/main.css

@@ -1,4 +1,5 @@
 html, body {
+    position: relative;
     height: 100vh;
     overflow: hidden;
     margin: 0; padding: 0;
@@ -13,14 +14,36 @@ html, body {
     box-sizing: inherit;
 }
 
-header {
+body > header, body > footer {
     margin: 0;
     padding: 1vh;
-    background-color: #444;
+    background-color: #c23350;
     color: #eee;
     font-family: Arial, Helvetica, sans-serif;
 }
 
+header {
+    font-family: Arial, Helvetica, sans-serif;
+}
+
+body > footer {
+    position: absolute;
+    bottom: 0;
+    width: 100vw;
+}
+
+#joinForm {
+    display: flex;
+    flex-direction: column;
+    padding: 1vmin;
+    width: 20vw;
+    min-width: 10em;
+}
+
+#joinForm > * {
+    margin-bottom: 2vmin;
+}
+
 #unsupported {
     display: none;
 }

+ 99 - 26
src/main/resources/static/main.js

@@ -22,44 +22,117 @@ class Puzzeltocht {
             document.getElementById("unsupported").style.display = "block";
         }
 
+        Puzzeltocht.fetchEvents();
+        document.getElementById("joinForm").addEventListener("submit", (e) => this.joinEvent(e));
+        console.log("puzzeltocht initialised");
+    }
+
+    static fetchEvents() {
+        fetch("/api/event", {method: "GET"})
+            .then(r => r.json())
+            .then(j => Puzzeltocht.showEvents(j))
+            .catch(r => console.log(r));
+    }
+
+    static showEvents(eventJson) {
+        let events = document.getElementById("events");
+        events.innerHTML = "";
+        eventJson.forEach((e) => {
+            events.innerHTML += '<label><input name="eventId" value="' + e.id + '" type="radio">' + e.title + '</label>';
+        });
+        document.getElementById("joinButton").disabled = false;
+    }
+
+    joinEvent(e) {
+        e.preventDefault();
+        let form = document.getElementById("joinForm");
+        this.eventId = form.elements["eventId"].value;
+        this.teamNaam = document.getElementById("teamName").value;
+
+        if (this.eventId === "" || this.teamNaam === "") {
+            console.log("niet gevuld");
+            return;
+        }
+
+        document.getElementById("joinButton").disabled = true;
+
+        fetch("/api/event/" + encodeURIComponent(this.eventId) + "/team", {
+                method: "POST",
+                headers: {"Content-Type": "application/json"},
+                body: JSON.stringify(this.teamNaam)
+            })
+            .then(r => r.json())
+            .then(j => p.startEvent(j))
+            .catch(r => {
+                console.log(r);
+                document.getElementById("joinButton").disabled = false;
+            });
+
+        return false;
+    }
+
+    startEvent(teamId) {
+        this.teamId = teamId;
+        this.mission = null;
+        document.getElementById("join").style.display = "none";
+        document.getElementById("mission").style.display = "block";
+        document.getElementById("questionMission").style.display = "none";
+        this.enableLocation()
+    }
+
+    updateLocation(pos) {
+        let l = document.getElementById("location");
+        let ts = Date.now() - pos.timestamp;
+        l.innerText = "Locatie (" + Puzzeltocht.lat(pos) + ", " + Puzzeltocht.lon(pos) + ") "
+            + "(+- " + pos.coords.accuracy.toFixed(1) + "m, bijgewerkt " + ts + "s geleden)";
+
+        if (this.lastUpdate === undefined || Date.now() - this.lastUpdate > 5000) {
+            this.sendLocation(pos);
+            this.lastUpdate = Date.now();
+        }
+    }
+
+    sendLocation(pos) {
+        let r = {
+            missionId: this.mission == null ? null : this.mission.id,
+            location: {latitude: Puzzeltocht.lat(pos), longitude: Puzzeltocht.lon(pos)},
+            answer: null,
+        };
+        fetch("/api/event/" + encodeURIComponent(this.eventId) + "/team/" + encodeURIComponent(this.teamId), {
+            method: "PUT",
+            headers: {"Content-Type": "application/json"},
+            body: JSON.stringify(r)
+        })
+            .then(r => r.json())
+            .then(j => p.updateMission(j))
+            .catch(r => console.log(r));
+    }
+
+    updateMission(m) {
+        this.mission = m;
+        document.getElementById("missionTitle").innerText = m.title + " (" + m.distanceToTarget + "m)";
+        document.getElementById("missionDescription").innerText = m.description;
+    }
+
+    enableLocation() {
         const geoOpts = {
             enableHighAccuracy: true,
-            maximumAge        : 10000,
-            timeout           : 9500
+            maximumAge: 10000,
+            timeout: 9500
         };
 
-        navigator.geolocation.getCurrentPosition(
-            (pos) => p.updateLocation(pos),
-            (err) => console.log(err),
-            geoOpts);
         navigator.geolocation.watchPosition(
             (pos) => p.updateLocation(pos),
             (err) => console.log(err),
             geoOpts);
-
-        console.log("puzzeltocht initialised");
     }
 
-    updateLocation(pos) {
-        let l = document.getElementById("location");
-        let lat = pos.coords.latitude;
-        let long = pos.coords.longitude;
-        let ts = new Date(pos.timestamp);
-        l.innerText = "positie: " + lat + ", "  + long + ", " + pos.coords.accuracy + " (updated " + ts.toUTCString() + ")";
-        if (this.lastUpdate === undefined || this.lastUpdate + 30000 < pos.timestamp) {
-            this.lastUpdate = pos.timestamp;
-            Puzzeltocht.updateMap(pos);
-        }
+    static lat(pos) {
+        return pos.coords.latitude.toFixed(5);
     }
 
-    static updateMap(pos) {
-        let img = document.getElementById("map");
-        let lat = pos.coords.latitude;
-        let long = pos.coords.longitude;
-        let url = "https://staticmap.openstreetmap.de/staticmap.php?center=";
-        url = url + lat + "," + long + "&zoom=18&size=256x256&maptype=mapnik";
-        url = url + "&markers=" + lat + "," + long + ",lightblue1";
-        img.src = url;
+    static lon(pos) {
+        return pos.coords.longitude.toFixed(5);
     }
 
 }