woensdag 10 april 2019

Puzzle tour in html5

Met een aantal collega's reizen wij al een aantal jaren naar de Ardennen om een weekend te kamperen. Bezigheden zijn: sterke verhalen vertellen bij een kampvuur, geroosterd vlees eten, al of niet alcoholhoudende drank gebruiken, een puzzletocht, sportieve activiteiten als kayakken, rotsklimmen, speologie, moutainbiken en nog meer.

Dit stuk gaat over de puzzletocht. Aanvankelijk reisde de organisator een week voor het kamp met een mountainbike naar de gekozen camping en zette een aantal punten uit op de kaart waarbij een foto van dat punt bij de beschrijving van de tocht werd gedaan. De deelnemers werden in groepjes verdeeld en moesten bij de punten iets vinden zoals een tekst op een grafsteen en later de foto namaken met de groep op de foto. Daar bleek vals te worden gespeeld: bij een groep was een keer iemand afgevallen en terug gegaan dat viel niet op op de foto's: dus moesten het volgende jaar twee foto's worden gemaakt om te garanderen dat niemand afviel. Vervolgens is hetzelfde geprobeerd zonder foto's maar door linten op te hangen: bleek ook fraudegevoelig en de noodzaak bleef om het parcours tevoren af te reizen.

Met de opkomst van mobiele telefoons met gps kwam het idee een 'app' te gaan gebruiken.

[TODO} Beschrijving van de 'Harrie app'


Nieuwe html5 'app':

Principe: zet punten uit op de kaart. Bij het bereiken van een punt krijg je 1 letter van een aantal woorden (in mijn geval zijn er 12 woorden van 8 letters). Naast het aflopen van de punten speelt dus ook mee dat je woorden kunt raden

Zet punten uit met: umap.openstreetmap.

Verrijk de punten met random woord letter id's in nodejs:
var  path = require('path'),
  fs = require('fs');

var filename = path.join(process.cwd(), process.argv[2]);

var x = new Array(12);

for (var i = 0; i < x.length; i++) {
  x[i] = new Array(8).fill(0);
}

fs.readFile( filename, 'utf8', function (err,data) {
 lines=data.split('\n')
 lines.forEach(function (val, index, array) {
   wp=Math.floor(Math.random() * 12);
   lp=Math.floor(Math.random() * 8);
   while(x[wp][lp]!=0){
     wp=Math.floor(Math.random() * 12);
     lp=Math.floor(Math.random() * 8);
   }
   x[wp][lp]=1;
  entries=lines[index].split(" ");
  console.log(entries[0]+" "+entries[1]+" "+wp+" "+lp);
 });
});
Dit hangt in de laatse regel maar een kniesoor die daarop let (afbreken met control-c). Dan.. hoe maak je dit moeilijk leesbaar? Mijn keuze viel op; 'javascript obfuscator' Bleek de antwoord string nog steeds erg leesbaar, dus... Die wat minder leesbaar gemaakt met:
jvdmeiden@jvdmeiden-UX301LAA:~/projects/Ardennen$ cat tests.cs
var words =  "berguemebeekloopvuurkorfbiertenttentstokluchtbedbarbecuegraslandklimrotsregendagkampvuurslaaphut";

var result="";
var step;
console.log(words.length);
for (step = 0; step < words.length ; step++) {
  result=result.concat(words.charAt((step*13)%words.length));
}
console.log(result);
words =  "coralinewalravencoralinewalravencoralinewalravencoralinewalravencoralinewalravencoralinewalraven";
result="";
console.log(words.length);
for (step = 0; step < words.length ; step++) {
  result=result.concat(words.charAt((step*13)%words.length));
}
console.log(result);

jvdmeiden@jvdmeiden-UX301LAA:~/projects/Ardennen$ 
jvdmeiden@jvdmeiden-UX301LAA:~/projects/Ardennen$ node tests.cs 
96
boekelaabondleugveceraukbtrdnlmrtbasveorlcigpertbagruunhgomtliobkdaekeesrurptuumkhefsaneseuttrtp
96
cvleloerwirnaanacvleloerwirnaanacvleloerwirnaanacvleloerwirnaanacvleloerwirnaanacvleloerwirnaana
jvdmeiden@jvdmeiden-UX301LAA:~/projects/Ardennen$ 

De uiteindelijke code van de 'APP', stukje html:
<!DOCTYPE html>
<!-- Author: Jan van der Meiden                                           -->
<!-- jvdmeiden@gmail.com                                                  -->
<!-- Version: 20190330.00                                                 -->
<!-- Copyright (c) 2019 Jan van der Meiden.                               -->
<!-- Copying and distribution of this file, with or without modification, -->
<!-- are permitted in any medium without royalty provided the copyright   -->
<!-- notice and this notice are preserved.                                -->
<!--                                                                      -->
<!-- Ardennen Puzzle Tocht                                                -->
<html lang="en">
  <head>
    <meta charset="utf-8">

    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-113123312-1"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'UA-113123312-1');
    </script>

    <title>Ardennen Puzzle Tocht</title>
    <script src="scripts/ardennen.js"></script>
  </head>
  <style>
    button {
      width: 100%;
      height: 100px;
      font-size: 36px;
    }
    text {
      background-color: white;
      font-size: 30px;
    }
    table {
      border:1px solid black;
    }
    td {
      text-align: center;
      font-size: 36px;
      cursor: crosshair;
      width: 51px;
      border:1px solid black;
    }
    tr {
      height: 51px;
      border:1px solid black;
    }
  </style>
  <body onLoad="init()">
    <div class="container" name="afield">
      <svg id="frame" style="background: lightgrey" >
      </svg>
    </div>
    <div class="container" name="astatus">
       <button type="button" onclick="getPos()">Get Position</button>
       <table id="words">
         <tr>
           <td>-</td>
           <td>0</td>
           <td>1</td>
           <td>2</td>
           <td>3</td>
           <td>4</td>
           <td>5</td>
           <td>6</td>
           <td>7</td>
         </tr>
         <tr>
           <td>0</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>1</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>2</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>3</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>4</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>5</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>6</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>7</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>8</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>9</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>10</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
         <tr>
           <td>11</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
           <td>-</td>
         </tr>
       </table> 
    </div>
    <div class="container" name="amap">
       <img src="Bergueme2019.png" alt="parcours" style="height:700px" download> 
    </div>

  </body>
</html>


En javascript:
var bounds = [ 50.072, 50.112, 5.517, 5.617];
//var bounds = [ 52.097, 52.119, 5.060, 5.090];
// var bounds = [ 52.097, 52.102, 5.060, 5.068];
var navID=10000;
var navCount=0;
var scale=1;
var foundP=[];
var a2148="kopshout";
var a2178="indaging";
var a2193="melkglas";
var a2224="katanker";
var a2270="dagkaart";
var a2289="halfjaar";
var a2316="startdag";
var a2337="jaarklas";
var a2371="dagtekst";
var a2372="dagbloem";
var a2440="leerjaar";
var a2486="achtkamp";
var a2517="glasraam";
var a2519="weerglas";
var a2640="golfclub";
var a2670="maltbier";
var a2677="daglelie";
var a2682="toerclub";
var a2707="draaidag";
var a2714="fietsdag";
var a2726="biermerk";
var a2785="civetkat";
var a2861="houtring";
var a2879="bierkrat";
var a2920="harthout";
var a2994="dagleven";
var a3028="rentedag";
var a3042="bierbuik";
var a3071="bockbier";

var points = [[50.075836,5.554501,6,6,0],
[50.075472,5.557559,10,7,0],
[50.073124,5.559028,6,7,0],
[50.076738,5.564436,8,4,0],
[50.072834,5.550284,11,0,0],
[50.074143,5.540789,1,3,0],
[50.08045,5.542474,2,5,0],
[50.077517,5.556518,4,2,0],
[50.083957,5.571313,2,7,0],
[50.085489,5.571801,10,6,0],
[50.084546,5.567665,8,1,0],
[50.079052,5.56185,4,7,0],
[50.081572,5.552602,7,0,0],
[50.085103,5.551153,6,2,0],
[50.083376,5.546937,10,3,0],
[50.082391,5.52828,2,3,0],
[50.086418,5.534406,9,3,0],
[50.094389,5.539298,4,3,0],
[50.097886,5.536852,0,1,0],
[50.099317,5.541615,10,1,0],
[50.098068,5.553814,3,3,0],
[50.091347,5.547194,9,5,0],
[50.090039,5.586398,2,2,0],
[50.086694,5.55625,4,4,0],
[50.086721,5.56509,7,5,0],
[50.09123,5.581344,6,5,0],
[50.090287,5.575969,11,6,0],
[50.087458,5.586988,2,4,0],
[50.093026,5.594144,4,1,0],
[50.090714,5.590711,3,2,0],
[50.100927,5.580153,6,1,0],
[50.095745,5.587471,3,5,0],
[50.095959,5.574038,5,0,0],
[50.099221,5.572665,5,3,0],
[50.089268,5.548986,5,7,0],
[50.090576,5.56715,0,4,0],
[50.096289,5.596987,0,2,0],
[50.097397,5.599111,9,2,0],
[50.100969,5.592073,10,5,0],
[50.102435,5.595388,3,4,0],
[50.111057,5.604143,7,6,0],
[50.101492,5.602276,11,3,0],
[50.095098,5.60483,9,4,0],
[50.091948,5.603754,8,0,0],
[50.096564,5.616889,8,2,0],
[50.101189,5.609658,7,2,0],
[50.103322,5.610409,1,1,0],
[50.107217,5.586269,2,0,0],
[50.105937,5.586593,10,2,0],
[50.109254,5.584091,0,3,0],
[50.106694,5.575798,1,4,0],
[50.105607,5.568137,5,1,0],
[50.109667,5.555155,0,0,0],
[50.106529,5.544941,4,6,0],
[50.10657,5.523988,5,6,0],
[50.103735,5.53359,2,1,0],
[50.103164,5.538332,11,4,0],
[50.108944,5.533483,0,5,0],
[50.110011,5.609808,2,6,0],
[50.109598,5.595571,3,7,0],
[50.091856,5.525351,3,1,0],
[50.098257,5.5193,9,6,0],
[50.094444,5.51784,1,5,0],
[50.098092,5.565777,5,5,0],
[50.110011,5.564307,7,7,0],
[50.110602,5.569918,6,4,0],
[50.093106,5.612715,6,0,0],
[50.094671,5.614856,0,7,0],
[50.09733,5.613174,8,6,0],
[50.100797,5.612147,1,7,0],
[50.109051,5.616664,3,6,0],
[50.111067,5.614008,3,0,0],
[50.107368,5.585099,8,5,0],
[50.108593,5.58525,11,5,0],
[50.107461,5.579773,8,7,0],
[50.104822,5.579531,9,1,0],
[50.087327,5.528033,11,7,0],
[50.076422,5.53093,0,6,0],
[50.078379,5.525294,9,7,0],
[50.093522,5.530093,1,6,0],
[50.106216,5.604116,5,2,0],
[50.096932,5.607947,7,4,0],
[50.097438,5.580239,7,1,0],
[50.098711,5.584177,10,0,0],
[50.102724,5.559093,9,0,0],
[50.100026,5.568491,10,4,0],
[50.102307,5.570484,1,0,0],
[50.098109,5.558304,6,3,0],
[50.086446,5.546293,11,1,0],
[50.102552,5.522561,4,5,0],
[50.100212,5.550038,8,3,0],
[50.104616,5.616395,4,0,0],
[50.107011,5.61323,5,4,0],
[50.076446,5.546626,1,2,0],
[50.084381,5.531809,7,3,0],
[50.095776,5.565965,11,2,0]
];

var words3 = "boekelaabondleugveceraukbtrdnlmrtbasveorlcigpertbagruunhgomtliobkdaekeesrurptuumkhefsaneseuttrtp";

var words2 = "";

var geo_options = {
  enableHighAccuracy: true,
  maximumAge        : 500,
  timeout           : 5000
};

function geo_error() {
  alert("Sorry, no position available.");
}

function getPos() {
  navCount=0;
  if (navigator.geolocation) {
    navID=navigator.geolocation.watchPosition(showPosition, geo_error, geo_options);
    background=document.getElementById("frame");
    background.setAttribute("style","background: lightcyan");
  } else {
    x.innerHTML = "Geolocation is not supported by this browser.";
  }
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}

function showPosition(position) {
  var svgObject=document.getElementById("frame");
  if (navCount++ > 20){
    navigator.geolocation.clearWatch(navID)
    background=document.getElementById("frame");
    background.setAttribute("style","background: lightgrey");
  }
//  if (parseFloat(position.coords.accuracy) < 15.0){
//    navigator.geolocation.clearWatch(navID)
//    background=document.getElementById("frame");
//    background.setAttribute("style","background: lightgrey");
//  }
//  var point = document.createElement('circle');
//  point.setAttribute('cx',((parseFloat(position.coords.longitude)-bounds[2])*scale+50));
//  point.setAttribute('cy',((bounds[1]-parseFloat(position.coords.latitude))*scale*1.5+50));
//  point.setAttribute('r',9);
//  point.setAttribute('fill',"grey");
//  point.setAttribute('opacity',0.7);
//  point.setAttribute('stroke',"black");
//  point.setAttribute('stroke-width',1);
//  svgObject.append(point);

  var curPos=document.getElementById("CurPos");
  curPos.setAttribute('cx',((parseFloat(position.coords.longitude)-bounds[2])*scale+50));
  curPos.setAttribute('cy',((bounds[1]-parseFloat(position.coords.latitude))*scale*1.5+50));
  curPos.setAttribute('r',Math.max(12.0,parseFloat(position.coords.accuracy)));
  curPos.setAttribute('opacity',Math.min(1.0,10/parseFloat(position.coords.accuracy)));

  var i;
  for (i = 0; i < points.length; i++) {
    if (parseFloat(position.coords.accuracy) < 30.0){
      if(getDistanceFromLatLonInKm(points[i][0],points[i][1],position.coords.latitude,position.coords.longitude) < 30.0){
        var found=document.getElementById(String("P"+i)); 
 found.setAttribute('fill',"chartreuse");
        navigator.geolocation.clearWatch(navID)
        alert("Woord: "+points[i][2]+ " letter: " + points[i][3] + "=" + words2.charAt(points[i][2]*8+points[i][3]) );
        
      }
    }
  }



}

function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(parseFloat(lat2)-parseFloat(lat1));  // deg2rad below
  var dLon = deg2rad(parseFloat(lon2)-parseFloat(lon1));
  var a =
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ;
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = (R * c)*1000; // Distance in km
  return d.toFixed(2);
}

var a8754="klaphout";
var a8929="maatglas";
var a9078="jaarclub";
var a9079="bierkaai";
var a9138="katafalk";
var a9141="houtwerf";
var a9170="houtteer";
var a9209="houtvlot";
var a9226="hardglas";
var a9252="zwartdag";
var a9305="dagzijde";
var a9382="pensioen";
var a9412="themadag";
var a9420="houtrijk";
var a9484="jaarrond";
var a9501="oogstdag";
var a9540="dagcurve";
var a9587="boetedag";
var a9628="penselen";
var a9631="pedagoge";
var a9669="houtoven";
var a9704="houtland";
var a9717="feestdag";

function init(){
    var svgObject=document.getElementById("frame");
    var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    var svgNS = svg.namespaceURI;
    scale=800/(bounds[1]-bounds[0]);
    svgObject.setAttribute("viewBox","0 0 "+((bounds[3]-bounds[2])*scale+100)+" 1350");
    svgObject.setAttribute("onclick","getPos();");
    var div = document.createElementNS(svgNS,'div');
    div.setAttribute('id',"track");
    svgObject.appendChild(div);
    div = document.createElementNS(svgNS,'div');
    div.setAttribute('id',"dots");
    svgObject.appendChild(div);

    for (var i = 0; i < points.length; i++){
      var point = document.createElementNS(svgNS,'circle');
      point.setAttribute("id", "P"+i);
      point.setAttribute('cx',(points[i][1]-bounds[2])*scale+50);
      point.setAttribute('cy',(bounds[1]-points[i][0])*scale*1.5+50);
      point.setAttribute('r',12);
      point.setAttribute('stroke',"black");
      point.setAttribute('stroke-width',1);
      point.setAttribute('fill',"red");
      svgObject.appendChild(point);

      var text = document.createElementNS(svgNS,'text');
      text.setAttribute('x',(points[i][1]-bounds[2])*scale+60);
      text.setAttribute('y',(bounds[1]-points[i][0])*scale*1.5+55);
      text.textContent = points[i][2]+"--"+points[i][3];
      svgObject.appendChild(text);
    };
    var point = document.createElementNS(svgNS,'circle');
    point.setAttribute("id", "CurPos");
    point.setAttribute('cx',-10);
    point.setAttribute('cy',-10);
    point.setAttribute('r',12);
    point.setAttribute('stroke',"black");
    point.setAttribute('stroke-width',1);
    point.setAttribute('fill',"yellow");
    point.setAttribute("opacity",.2);
    svgObject.appendChild(point);
   

    for (step = 0; step < words3.length ; step++) {
      words2=words2.concat(words3.charAt((step*37)%words3.length));
    }
 console.log(words2);
 console.log(words3);
}
In de code van javascript zijn zinloze variabelen gezet 'to confuse the russians'. Bij testen bleek de knop wel erg onhandig en is de totale svg clickable gemaakt. Verder leek het handiger feedback te geven met de achtergrond van de svg dat nog gezocht werd met gps. 3 Lessons learned:
  • MEER loggen!
  • MEER loggen!
  • Irritante veschillen in browsers: firefox focus eigert geo, firefox beta doet het erg slecht met betrekking tot precisie.
  • Tijd van de tour niet een tijdstip nemen maar uren vanaf vertrektijd.
  • Eerder beginnen, data persisteren....
  • Code bevat nog in commentaar een poging om met puntjes het gevolgde 'spoor' uit te zetten.
(Nog) Niet gelukt: gevonden letters invullen in tabel (had daar niet de meest handige opzet gekozen). En persisteren gevonden punten naar een vorm van persistent storage in de browser. En natuurlijk de kaart in svg vorm als achtergrond te nemen, maar dan moet ik de projectie snappen. Ook belangrijk: handig op de gekozen geprintte kaart, kwam van: umap openstreetmap kaart in laag OSM Landscape (Thunderforest) oftewel zie hier. Geheel bleek aardig conform verwachting te werken. Links:

Geen opmerkingen:

Een reactie posten