A Python/CGI framework for creating your own customizable location-based collaborative scavenger hunts. Works on any mobile device with a modern browser, such as iPhone and Android. No app installation required.
For Christmas 2015, I decided I'd like to gift my family an experience instead of material gifts. There are a few services out there that provide location-based scavenger hunts, but none (that I could find) are free or open-source. So, I created my own. And now, this is my gift to you.
Sample questions included are for a Christmas-based hunt in Cincinnati, but can easily be repurposed for your own uses (for any occasion, in any city).
Features:
- Multi-user, collaborative game. Everyone competes collaboratively, but against each other for the final prize.
- Number of users limited only by resources on game server (i.e. virtually unlimited)
- Teams may also compete against each other (one login per team)
- Define your own questions (in order)
- Answers may be text-based, location-based, or both
- Each question may have an arbitrary number of hints (opening a new hint will cost a user some points)
- Several Easter eggs / plot-twists
- "Gullible challenges" between users (one user must convince another of an obviously incorrect answer)
- Admin panel to modify score, current question, disabled questions
Techy stuff:
- On client side, browser-based gameplay. Works on any mobile device with a modern browser (Chrome recommended). No app installation required.
- Server side runs a Python script in CGI environment (TLS recommended)
- Session management. Each client may login from any IP (as is required as mobile phones hop around the city throughout the challenge). Cookie-based sessions ensure that a specific user is logged in from at most 1 browser.
A question linking to a geocache:
Embedded HTML, in this case a YouTube video:
A question including an interactive HTML5 Simon game:
To run a scavenger hunt, you will need a web server under your control.
Following is one recommended configuration:
- Amazon EC2 instance
- Latest Ubuntu LTS
- Automatic Security Updates
sudo apt-get install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
- Automatic Security Updates
- Firewall
- All ports closed, except game server port (e.g. 55864) and possibly SSH
- Web server configuration
- Disable HTTP on port 80
- Run latest version of TLS on obscure high port number (e.g. 55864)
- Strong Ciphers for Apache, nginx and Lighttpd
- Enable Python in CGI
- lighttpd (see Sample lighttpd Config below)
sudo apt-get install lighttpd
- Setting up a simple SSL configuration
- Strong SSL Security on lighttpd
- Installing Lighttpd with Python CGI support
Regardless of your web server, you'll need a few Python goodies available on the server:
- pip for Python package management
sudo apt-get install python-pip
- dill as alternative for Python's pickle
sudo pip install dill
- geopy for geolocation in Python
sudo pip install geopy
Following should be set up per-game:
- Fill out
game_config.py
with your game parametersUSERS
table (in a list of "(username, login hash)" tuples)ADMIN_USERS
list- Name of game arch-nemesis (e.g. "The Grinch")
- UTC time that game is expected to end
- Define questions. Refer to the Question class for more info. Sample questions included are for a Christmas-based hunt in Cincinnati, but can easily be repurposed for your own uses (for any occasion, in any city).
- If using the (included) Simon question, be sure to update required end score and URL on line 114 of
simon_says.js
. Theq=14
must match the question number ingame_config.py
. - Set up a login method. Login URL =
https://your-server-url.here:55864/cgi-bin/game.py?l=hash
(wherehash
comes from theUSERS
list ingame_config.py
). Methods:- Email a personalized login link to every user (no sharing login links). This is the preferred method (see Client Setup below).
- Print a QR code for each user using the personalized login link
For ideas on devising creative questions:
- Use the included Simon game
- Hide a clue inside a real geocache, and then link to the geocache as part of a question
- Use Google Voice for a custom voicemail greeting revealing a required clue
- Use Twilio to do neat things with text messages
- Use IFTTT for some neat conditional clues
If you're using QR codes for login, users may need to download a barcode reader, such as QR Reader for iPhone or Barcode Scanner for Android. Further, you may not be able to control what browser opens after having scanned a QR code, which may be problematic (e.g. Safari on iPhone has been observed to be wonky).
Chrome is the recommended mobile browser, both on iPhone and Android. For that reason, perhaps emailing a personalized login link to every user is the preferred method.
When a client logs in the first time, he/she will have to grant two privileges:
- Accept the self-signed certificate (unless you have a CA-signed one). Usually, it's Advanced --> Proceed.
- Allow Chrome to detect/use your location
If you don't accept/allow both of these, then you'll have a sad time. A nerd should supervise the process of initial logins.
Ensure that everyone reads the instructions (on Question 1) before anyone attempts to answer Question 1.
Questions are served, one at a time, in order, beginning with question #1 (which also provides some basic instructions). All users playing the game always see the same question. When one user answers a question correctly, all users see the next question. Automatic refreshes are intentionally omitted to keep from frustrating users while mid-input. There is a refresh link that can/should be used frequently to see the latest game updates. All users will be able to see the current scoreboard.
Some questions require a correct text answer. Others require a user to be in the "right" location. Other questions have both constraints.
When the last question is correctly answered, the game is over ... maybe. For more details, see the Spoiler wiki page.
Each game can have any number of administrators, who do not participate but ensure that the game continues without any problems and on schedule. From the admin panel, an admin has the ability to:
- Manually edit current scores (form at the top)
- Set the current question (radio buttons in the
Cur
column) - Disable (future) questions (to expedite a long game) (checkboxes in the
Dis
column) - Reset the game (see Resetting a Game below)
An admin could use a browser on a phone, tablet, or even computer. No need for location services.
To start a brand new game, or in case of catastrophic failure, admins have the ability to delete all persistent game state. This can be done via the admin panel, or by accessing the following URL directly:
https://your-server-url.here:55864/cgi-bin/reset.py?l=hash
(wherehash
comes from an admin in theUSERS
list ingame_config.py
)
Non-admins cannot reset games.
After a game reset, all players (including admins) must close all tabs and re-login.
server.modules = (
"mod_access",
"mod_alias",
"mod_compress",
"mod_redirect",
"mod_cgi",
)
server.document-root = "/var/www"
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )
server.errorlog = "/var/log/lighttpd/error.log"
server.pid-file = "/var/run/lighttpd.pid"
server.username = "www-data"
server.groupname = "www-data"
server.port = 80
index-file.names = ( "index.html", "index.lighttpd.html" )
url.access-deny = ( "~", ".inc" )
static-file.exclude-extensions = ( ".py", ".php", ".pl", ".fcgi" )
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype = ( "application/javascript", "text/css", "text/html", "text/plain" )
# default listening port for IPv6 falls back to the IPv4 port
## Use ipv6 if available
#include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
$HTTP["url"] =~ "^/cgi-bin/" {
cgi.assign = (".py" => "/usr/bin/python")
}
$SERVER["socket"] == ":55864" {
ssl.engine = "enable"
ssl.pemfile = "/etc/lighttpd/certs/lighttpd.pem"
}
ssl.honor-cipher-order = "enable"
ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
ssl.use-compression = "disable"
setenv.add-response-header = (
"Strict-Transport-Security" => "max-age=63072000; includeSubdomains; preload",
"X-Frame-Options" => "DENY",
"X-Content-Type-Options" => "nosniff"
)
ssl.use-sslv2 = "disable"
ssl.use-sslv3 = "disable"
Software I wrote is governed by the GNU v2 license.
For included software (not written by me):
- HTML5-Simon-Says -- GNU v3 license
- Modified success criteria to redirect back to gameplay once minimum score has been attained
- MIDI.js -- MIT license
- Skeleton CSS -- MIT license
- Modified various elements for more efficient vertical spacing
- CSS Notification Boxes -- Unknown license. Consult author/website.