Photo location guessing game in Go
Programming Snapshot – Go Geolocation Game
A geolocation guessing game based on the popular Wordle evaluates a player's guesses based on the distance from and direction to the target location. Mike Schilli turns this concept into a desktop game in Go using the photos from his private collection.
After the resounding success of the Wordle [1] word guessing game, it didn't take long for the first look-alikes to rear their heads. One of the best is the entertaining Worldle [2] geography game, where the goal is to guess a country based on its shape. After each unsuccessful guess, Worldle helps the player with information about how far the guessed country's location is from the target and in which direction the target country lies.
Not recognizing the outline of the country in Figure 1, a player's first guess is the Principality of Liechtenstein. The Worldle server promptly tells the player that the target location is 5,371 kilometers away from and to the east of this tiny European state. The player's second guess is Belarus, but according to the Worldle server, from Belarus you'd have to travel 4,203 kilometers southeast to get to the target. Mongolia, the third attempt, overshoots the mark, because from there you'd have to go 3,476 kilometers to the southwest to arrive at the secret destination.
Slowly but surely, the player realizes that the secret country must be somewhere near India. And then, on the fourth try, Pakistan turns out to be correct! Worldle is a lot of fun, with a new country posted for guessing every day.
Private Snapshots
Now, instead of guessing countries, I thought it would be fun to randomly select a handful of photos from my vast cell phone photo collection, which has grown wildly over the years. Analyzing each photo's GPS data, the game engine makes sure the photos in one round were taken a great distance from one another. Initially, the computer selects a random photo from the set as a solution. It keeps this a secret, of course, and then shows the player a randomly selected photo, along with details of how many kilometers lie between the place where the random photo was taken and the target location, along with the compass direction in which the player has to move across the world to get there.
Armed with this information, the player now has to guess which picture from the remaining selection is the secret photo. The player clicks on the suspected match and is given some feedback on the guess, again with the distance and compass direction leading to the secret location. The goal of the game is to find the solution in as few rounds as possible – a kind of treasure hunt, if you like. As a nod to the extra consonants in Wordle, my program goes by the name of Schnitzle. There are enough photos to choose from on my cell phone, and a random generator ensures that the game always selects new pictures, so it never gets boring.
And … Action!
Figure 2 shows Schnitzle in action. As a starting image, the computer has selected a snapshot depicting yours truly, hiking in the Bavarian Alps. According to the clues, the target is 9,457.8 kilometers to the northwest (NW) from the starting image. It seems highly likely that the secret photo was taken somewhere in North America! From the selection on the right, the player then clicks on the photo of Pinnacles National Park in California (Figure 3). Schnitzle reveals that the target is 168.5 kilometers in a northwest direction from this guess. What's north of the Pinnacles? Probably the San Francisco Bay Area, where I live!
In Figure 4, the player then clicks on the photo of the parking lot at the beach in Pacifica, where I often go surfing. But you still have to travel 10.2 kilometers from the beach in a northeast direction (NE) to the location of the photo the game selected. If you know the area, you can probably guess: The destination must be somewhere in the suburbs of South San Francisco, where the giant Costco supermarket is located. In fact, that shelf filled with rotisserie chicken is the solution, as the * WINNER * message indicates (Figure 5). There are still two unclicked photos in the right-hand column, showing a bridge in Heidelberg, Germany, and one showing the sand at Esplanade Beach in the Bay Area.
Seek and Ye Shall Find
So how does the game work as a Go program? To sift through the entire photo collection downloaded from my cell phone to my hard disk takes some time, even though it is on a fast SSD. That's why the finder.go
helper program in Listing 1 plumbs the depths of the cell phone photo directory set in line 18, analyzing each JPEG image found there and reading its GPS data, if available, to cache it for later.
Listing 1
finder.go
01 package main 02 03 import ( 04 "database/sql" 05 "fmt" 06 _ "github.com/mattn/go-sqlite3" 07 exif "github.com/xor-gate/goexif2/exif" 08 "os" 09 "path/filepath" 10 rex "regexp" 11 ) 12 13 type Walker struct { 14 Db *sql.DB 15 } 16 17 func main() { 18 searchPath := "photos" 19 20 db, err := sql.Open("sqlite3", "photos.db") 21 w := &Walker{ Db: db } 22 err = filepath.Walk(searchPath, w.Visit) 23 panicOnErr(err) 24 25 db.Close() 26 } 27 28 func (w *Walker) Visit(path string, 29 f os.FileInfo, err error) error { 30 jpgMatch := rex.MustCompile("(?i)JPG$") 31 match := jpgMatch.MatchString(path) 32 if !match { 33 return nil 34 } 35 36 lat, long, err := GeoPos(path) 37 panicOnErr(err) 38 39 stmt, err := w.Db.Prepare("INSERT INTO files VALUES(?,?,?)") 40 panicOnErr(err) 41 fmt.Printf("File: %s %.2f/%.2f\n", path, lat, long) 42 _, err = stmt.Exec(path, lat, long) 43 panicOnErr(err) 44 return nil 45 } 46 47 func GeoPos(path string) (float64, 48 float64, error) { 49 f, err := os.Open(path) 50 if err != nil { 51 return 0, 0, err 52 } 53 54 x, err := exif.Decode(f) 55 if err != nil { 56 return 0, 0, err 57 } 58 59 lat, long, err := x.LatLong() 60 if err != nil { 61 return 0, 0, err 62 } 63 64 return lat, long, nil 65 }
The program feeds the results into a table in an SQLite database so that the game program can quickly select new images in each round later on, without having to comb through entire filesystem trees on every round. You can create the required empty SQLite database with the required table that assigns GPS data to file names in next to no time with a shell command such as the one in Figure 6.
Before the game can begin, the program from Listing 1 needs to run once, compiled by typing:
go build finder.go
The program uses two libraries (go-sqlite3 and goexif2) from GitHub. One drives the flat-file database, and the other reads the GPS headers from the JPEG photos.
To make the Go compiler do this without any complaints, first type
go mod init finder; go mod tidy
to specify a Go module to parse the libraries included in the source code, fetch them from GitHub if needed, and define their versions. When this is done, the go build
command produces a static binary finder
including all the compiled libraries.
As shown in Figure 7, the finder
utility from Listing 1 reads the 4,000 or so files in my phone folder in about 30 seconds and adds the photos' metadata into the files
table in the SQLite photos.db
flat-file database.
The call to the Walk
function in line 22 of Listing 1 receives the w.Visit
callback defined in line 28. The file browser calls this function for every file it finds. It always drags a type Walker
data structure along with it as a receiver, which means that it can immediately access the db
handle of the SQLite database opened previously.
For each file found, line 31 checks whether the file has a .jpg
extension (upper- or lowercase) and then runs the GeoPos()
function from line 47 to load the photo's Exif data. This will ideally include the longitude and latitude of the location where the photo was taken as floating-point numbers.
Line 39 feeds the path and GPS data into the database table with an INSERT
statement in typical SQL syntax. Later, the main schnitzle
program can pick up the image location and metadata from the database, when it is looking for new snapshots for a new game.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.