Escape game world generator

A world map generator in php.
This php script selects randomly 3 cities from a CSV file and draws these on a worldmap.
No cities wil be choosen which have could cause a drawing overlap.
Every player can see the same generated worldmap with a countdown timer.

CSV example with places and coordinates (cities.csv)

London,905,412
Amsterdam,929,414
Wellington,1722,867
Costa Rica,524,640
New Delhi,1270,514
New York,567,477
Tokio,1548,500

generate.html

<html><body bgcolor=black>
<header>
  <div class="menu_area"></div>
  <center>
<p id="demo" style="text:white;"></p>
  </center>
</header>
<style>
html, body, header {
    overflow: hidden; /* Hide scrollbars */
    height: 100%;
    text: white;
}
header {
    background-image: url('world.php');
    background-size: cover;
}
  p {
  color: white;
  font-size: large;
  font-family: Verdana, Arial, sans-serif;
  font-size: 42px;
 }
</style>
<script>
	Date.prototype.addHours= function(h){
    this.setHours(this.getHours()+h);
    return this;
}

var countDownDate = new Date().addHours(1).getTime();
  var x = setInterval(function() {
  var now = new Date().getTime();
  var distance = countDownDate - now;
  var days = Math.floor(distance / (1000 * 60 * 60 * 24));
  var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
  var seconds = Math.floor((distance % (1000 * 60)) / 1000);

  document.getElementById("demo").innerHTML = days + "d " + hours + "h "
  + minutes + "m " + seconds + "s ";

  if (distance < 0) {
    clearInterval(x);
    document.getElementById("demo").innerHTML = "EXPIRED";
  }
}, 1000);
</script>
</body></html>

Used world image to draw on.

The squares and city names are dynamically drawn using php GD lib.

apt-get install apache2 php php-gd

php example:

// create image from source
$src = imagecreatefromjpeg('world.jpg');
$dest = imagecreatetruecolor(1920, 1080);
// 1920x1080
imagecopy($dest, $src, 0, 0, 0,0, 1920, 1080);
// assign color
$white = imagecolorallocate($dest, 255, 255, 255);
// rectangle example
imagerectangle($dest, $x, $y, $x2, $y2, $white);
// add text
Imagettftext($dest, 24, 0, $x, $y, $white, $font, "Text");
// set header output as image
header('Content-Type: image/jpg');
// output to browser
imagejpeg($dest);
// output to file
imagejpeg($dest, 'generatedworld.jpg');
// Free memory
imagedestroy($dest);
imagedestroy($src);

world.php is included as image, it dynamically generates the map

<?php

putenv('GDFONTPATH=' . realpath('.'));

// Name the font to be used (note the lack of the .ttf extension)
$font = 'tahoma.ttf';
$city1="";
$city2="";
$city3="";

// Create image instances
$src = imagecreatefromjpeg('world.jpg');
$dest = imagecreatetruecolor(1920, 1080);

// Copy
imagecopy($dest, $src, 0, 0, 0,0, 1920, 1080);

$green = imagecolorallocate($dest, 255, 255, 255);



$rows = file("cities.csv");
$len = count($rows);
$rand = [];
while (count($rand) < 1) {
    $r = rand(0, $len-1);
    if (!in_array($r, $rand)) {
        $rand[] = $r;
    }
}
foreach ($rand as $r) {
    $csv = $rows[$r];
    $data = str_getcsv($csv);
    $city1="$data[0]";
    $x1=$data[1];
    $y1=$data[2];
}

imagerectangle($dest, $x1-50, $y1-50, $x1+50, $y1+50, $green);
imagerectangle($dest, $x1-49, $y1-49, $x1+49, $y1+49, $green);
imagerectangle($dest, $x1-10, $y1, $x1+10, $y1-1, $green);
imagerectangle($dest, $x1, $y1-10, $x1-1, $y1+10, $green);
Imagettftext($dest, 24, 0, $x1-50, $y1-60, $green, $font, "$city1");

while($city2 == "") {

$rows = file("cities.csv");
$len = count($rows);
$rand = [];
while (count($rand) < 1) {
    $r = rand(0, $len-1);
    if (!in_array($r, $rand)) {
        $rand[] = $r;
    }
}
foreach ($rand as $r) {
    $csv = $rows[$r];
    $data = str_getcsv($csv);
    $x2=$data[1];
    $y2=$data[2];
    $deltax=abs($x2-$x1);
    $deltay=abs($y2-$y1);
}
if($data[0] != $city1 && $deltax > 100 && $deltay > 100 ){
	$city2=$data[0];
}

}


imagerectangle($dest, $x2-50, $y2-50, $x2+50, $y2+50, $green);
imagerectangle($dest, $x2-49, $y2-49, $x2+49, $y2+49, $green);
imagerectangle($dest, $x2-10, $y2, $x2+10, $y2-1, $green);
imagerectangle($dest, $x2, $y2-10, $x2-1, $y2+10, $green);
Imagettftext($dest, 24, 0, $x2-50, $y2-60, $green, $font, "$city2");


while($city3 == "") {

$rows = file("cities.csv");
$len = count($rows);
$rand = [];
while (count($rand) < 1) {
    $r = rand(0, $len-1);
    if (!in_array($r, $rand)) {
        $rand[] = $r;
    }
}
foreach ($rand as $r) {
    $csv = $rows[$r];
    $data = str_getcsv($csv);
    $x3=$data[1];
    $y3=$data[2];
    $deltax=abs($x3-$x1);
    $deltay=abs($y3-$y1);
    $deltax1=abs($x3-$x2);
    $deltay1=abs($y3-$y2);
}
if($data[0] != $city1 && $data[0] != $city2 && $deltax > 100 && $deltay > 100 && $deltax1 > 100 && $deltay1 > 100 ){
	$city3=$data[0];
}
}
imagerectangle($dest, $x3-50, $y3-50, $x3+50, $y3+50, $green);
imagerectangle($dest, $x3-49, $y3-49, $x3+49, $y3+49, $green);
imagerectangle($dest, $x3-10, $y3, $x3+10, $y3-1, $green);
imagerectangle($dest, $x3, $y3-10, $x3-1, $y3+10, $green);
Imagettftext($dest, 24, 0, $x3-50, $y3-60, $green, $font, "$city3");


// Output and free from memory
header('Content-Type: image/jpg');
imagejpeg($dest);
imagejpeg($dest, 'generatedworld.jpg');


imagedestroy($dest);
imagedestroy($src);
?>

Movie to txt in dutch

In the past i’ve converted some VHS movies speech to text, using all kinds of tools.
Lets use some opensource tools!

pip install moviepy
pip install SpeechRecognition

Create a python script with the following:
(Called mine wav2txt.py)

import math, contextlib
import speech_recognition as sr
from moviepy.editor import AudioFileClip
movie_audio_file_name = "movieadiofile.wav"
with contextlib.closing(wave.open(movie_audio_file_name,'r')) as f:
    frames = f.getnframes()
    rate = f.getframerate()
    duration = frames / float(rate)
total_duration = math.ceil(duration / 60)
r = sr.Recognizer()
for i in range(0, total_duration):
    with sr.AudioFile(movie_audio_file_name) as source:
        audio = r.record(source, offset=i*60, duration=60)
    f = open("transcription.txt", "a")
    f.write(r.recognize_google(audio, language="nl-NL"))
    f.write(" ")
f.close()

Now convert a movie to wav using below.

ffmpeg -i /fileserver/path/koolhoven.mkv movieaudiofile.wav

run python3 wav2txt.py

output
(Note .. these are not timestamped for subtitles)
I only needed the things being said in the home movie recordings as text.

Ik zit hier in de film The James Dean aan de
wereld voorstelde en daarmee de tienerfilm ingeleverd introduceren zelden werden onrustige 10 asiel zo mooi blootgelegd als ik deze film van Nicolas bij en dat wordt dan meteen toevallig even de mooiste
titels ooit wel eens autocross vanavond kijken we naar de kom ik nog even veel zomer dat je voor het eerste meisje Zoem de eerste baantje

etc..

TM1637 7 Segment Module (with mqtt )

These tiny modules use a Clock and Data signal.
The rest of the pins are for 3.3V and Gnd.

There are many libraries you can choose from, i’ve tried several.
For now, i ended up with this one.

Another promising one is https://github.com/AKJ7/TM1637
Another one i’ve tested https://github.com/bxparks/AceSegment

Now i have to add Wifi and Mqtt so it can remotely controlled.

Connected like this

Code with Wifi/Mqtt

#include <Arduino.h>
#include <TM1637Display.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "MYSSID";
const char* password = "MYSSIDPASS";
const char* mqtt_server = "MQTT-SERVER-IP";
 
const int CLK = D6; //Set the CLK pin connection to the display
const int DIO = D5; //Set the DIO pin connection to the display
 
int numCounter = 0;
int mydata = 0;
 
// The amount of time (in milliseconds) between tests
#define TEST_DELAY   2000

const uint8_t SEG_DONE[] = {
  SEG_B | SEG_C | SEG_D | SEG_E | SEG_G,           // d
  SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F,   // O
  SEG_C | SEG_E | SEG_G,                           // n
  SEG_A | SEG_D | SEG_E | SEG_F | SEG_G            // E
  };

TM1637Display display(CLK, DIO);

WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;


void setup()
{
 display.setBrightness(0x0a); //set the diplay to maximum brightness
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}


void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);


  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}



void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[4];
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    buffer[i] = int(payload[i]);
  }
  Serial.println();
//  mydata=int(payload[0])+int(payload[1]*10);
int  n;
n = atoi(buffer);

  display.showNumberDec(n); //Display the numCounter value;


}




void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("escape/seg1ping", "seg1alive");
      // ... and resubscribe
      client.subscribe("escape/seg1data");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, MSG_BUFFER_SIZE, "seg1alive #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("escape/seg1ping", msg);
  }
}

Connect the wires puzzle

As part of my internet based escape room.

I took the idea from the Keep-talking-and-nobody-explodes game.

When starting up, is gets the configuration from a Mqtt Topic.
So i can create a different setup over the internet.

The result is also send back via MQTT.

(example play, all players are on different locations)

  • Player 1 figures out the wires.
  • Player 2 connects the wires.
  • Player 3 sees the result (correct or not) and next puzzle opens up for Player 3

I used a wire with a line. Multiple colors for jacks and sockets.
It’s also possible to connect a jack to a jack.
Loads of possibilities.

Todo:

Wifi and Mqtt part
Debounce software or hardware

Bella Ciao on Concertina

In post below i posted the score for Bella Ciao
https://www.henriaanstoot.nl/2022/12/09/generating-pdfs-from-abc-files-to-include-in-tunebooks/

Here is a quick ‘n dirty recording (sound a little distorted)

Edited with Kdenlive

I’ve imported the score as an image.
Drag a transform effect on the image.
Move the image to the bottom of the screen.
Add a keyframe.
Move to the end of the movie, add another keyframe.
Adjust the Y position to 0
Set opacity to a nice position and render

Winter cocktail

I love making cocktails for the ladies. Its cold outside, so here is my take on the Chocolate Amnesia Cocktail.

(Some make this with ice, this is a heated version)

  • 2 Part cream. ( Just Cream, not whipped, i don’t like that)
  • 4 Parts Chocolate Milk
  • 2 Parts KahlĂșa Liqueur
  • 1 Part Vodka

Mix and heat in a (sauce) pan .. enjoy

Arduino Concertina

Lets try to make a Electronic Concertina

UPDATE:
https://www.henriaanstoot.nl/2023/01/10/arduino-concertina-poc/
https://www.henriaanstoot.nl/2023/01/17/arduino-concertina-poc-2/

First design

So we need some pushbuttons … at least 14 .. for the most simple tunes.
A sensor for push and pull.
A buzzer or better yet .. a jack for earphones.
Arduino with enough pins to connect a keyboard matrix.
When using a keyboard matrix only single keypresses are detected.
So we cant do chords!

Raspberry Zero with display

I’ve installed a headless Raspbian on a Pi Zero with a 2×16 Chars lcd display. As part of the Escape Room over the internet

Using the raspberry imager:
I’ve set the username/password and ssh access in this tool.
For wifi access i’ve placed below file on the SDcard in /boot
(You can do this in the tool, but i want to make this dynamic when connected at a remote site.)
file: wpa_supplicant.conf

country=NL
update_config=1
ctrl_interface=/var/run/wpa_supplicant

network={
 scan_ssid=1
 ssid="MYSSID"
 psk="MYSSIDPASS"
}

ssh into the RPi

sudo raspi-config
Interface options and enable I2C

sudo apt-get install python3-smbus

wget https://gist.githubusercontent.com/DenisFromHR/cc863375a6e19dce359d/raw/36b82e787450d127f5019a40e0a55b08bd43435a/RPi_I2C_driver.py
and 
wget https://gist.githubusercontent.com/DenisFromHR/cc863375a6e19dce359d/raw/36b82e787450d127f5019a40e0a55b08bd43435a/examples.py

For python3 edit the example and put at the top

# requires RPi_I2C_driver.py
import RPi_I2C_driver
from time import *
unichr = chr

Run with 
python3 examples.py
lcd display with i2c backpack
I2C backpack

Below is a mockup session.

Next todo:

  • Add more hardware (like buttons) to the RPI
  • Configure an Accesspoint on this Rpi for other devices to connect to
  • Install a local Mqtt broker, which connects secure to my internet facing broker
Setup example

Started on a mobile over the internet Escape room experiment

I’ve been working on some modular gadgets which can be combined to make a complete puzzle.

I like games like Keep-talking-and-nobody-dies. (Which is a computer game, but you have to play it with multiple persons and a physical “manual” Great fun!)
https://keeptalkinggame.com/

And i like real escape rooms.
There are some puzzle “rooms” you can buy in the game store, it is okay but many are for single use only.

I’ve been following some people on youtube, i saw some great ideas but not a remote over the internet using physical knobs and switches.

This is a RFID reader with an old Amico Esp8266 Arduino. It sends RFID information to the MQTT broker

Some other tools ‘n knobs .. and stuff

I want to use Adhoc Wifi and a Mqtt/Nodered setup which uses a mqtt over the internet to get people (and their knobs) connected

I already got a lot of test schematics

Left part of the “connect the wires puzzle” right a solenoid electrical lock)

Schematic for the MQTT enabled RFID module

ESP8266 <-> RC522
D8            SDA
D5            SCK
D7           MOSI
D6           MISO
GND           GND
D1            RST
3V3           3V3
 

Code

Below will write the RFID id to “rfid/id” and resets this when you remove the tag to “rfid/id = 0”

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <MFRC522.h>

#define SS_PIN D8
#define RST_PIN D1

MFRC522 mfrc522(SS_PIN, RST_PIN);
unsigned long cardId = 0;

WiFiClient net;
PubSubClient client(net);

const char* mqtt_server = "MQTTBROKER";
const char* ssid = "MYSSID";
const char* password = "MYWIFIPASSWD";

void setup() {
  SPI.begin();
  mfrc522.PCD_Init();

  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);

  client.setServer(mqtt_server, 1883);
}

void reconnect() {
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    WiFi.begin(ssid, password);
  }

  while (!client.connected()) {
    String clientId = "NodeMCUClient-";
    clientId += String(random(0xffff), HEX);

    if (!client.connect(clientId.c_str(), "rfidclient", "...")) {
      delay(5000);
    }
  }
}

void loop() {
  reconnect();

  if (!mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  if (!mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  cardId = getCardId();

  char buffer[10];
  sprintf(buffer, "%lu", cardId);
  client.publish("rfid/id", buffer);

  uint8_t control = 0x00;

  do {
    control = 0;
    for (int i = 0; i < 3; i++) {
      if (!mfrc522.PICC_IsNewCardPresent()) {
        if (mfrc522.PICC_ReadCardSerial()) {
          control |= 0x16;
        }

        if (mfrc522.PICC_ReadCardSerial()) {
          control |= 0x16;
        }
        control += 0x1;
      }
      control += 0x4;
    }

    delay(0);
  } while (control == 13 || control == 14);

  reconnect();
  client.publish("rfid/id", "0");
  delay(500);

  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}

unsigned long getCardId() {
  byte readCard[4];
  for (int i = 0; i < 4; i++) {
    readCard[i] = mfrc522.uid.uidByte[i];
  }

  return (unsigned long)readCard[0] << 24
    | (unsigned long)readCard[1] << 16
    | (unsigned long)readCard[2] << 8
    | (unsigned long)readCard[3];
}

Schematics used for the Solenoid lock

Software is a mqtt example from the internet which toggles a PIN on the arduino.

"If something is worth doing, it's worth overdoing."