Photo location guessing game in Go
Programming Snapshot – Go Geolocation Game

© Lead Image courtesy of Mike Schilli
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
-
PipeWire 1.0 Officially Released
PipeWire was created to take the place of the oft-troubled PulseAudio and has finally reached the 1.0 status as a major update with plenty of improvements and the usual bug fixes.
-
Rocky Linux 9.3 is Available for Download
The latest version of the RHEL alternative is now available and brings back cloud and container images for ppc64le along with plenty of new features and fixes.
-
Ubuntu Budgie Shifts How to Tackle Wayland
Ubuntu Budgie has yet to make the switch to Wayland but with a change in approaches, they're finally on track to making it happen.
-
TUXEDO's New Ultraportable Linux Workstation Released
The TUXEDO Pulse 14 blends portability with power, thanks to the AMD Ryzen 7 7840HS CPU.
-
AlmaLinux Will No Longer Be "Just Another RHEL Clone"
With the release of AlmaLinux 9.3, the distribution will be built entirely from upstream sources.
-
elementary OS 8 Has a Big Surprise in Store
When elementary OS 8 finally arrives, it will not only be based on Ubuntu 24.04 but it will also default to Wayland for better performance and security.
-
OpenELA Releases Enterprise Linux Source Code
With Red Hat restricting the source for RHEL, it was only a matter of time before those who depended on that source struck out on their own.
-
StripedFly Malware Hiding in Plain Sight as a Cryptocurrency Miner
A rather deceptive piece of malware has infected 1 million Windows and Linux hosts since 2017.
-
Experimental Wayland Support Planned for Linux Mint 21.3
As with most Linux distributions, the migration to Wayland is in full force. While some distributions have already made the move, Linux Mint has been a bit slower to do so.
-
Window Maker Live 0.96.0-0 Released
If you're a fan of the Window Maker window manager, there's a new official release of the Linux distribution that champions the old-school user interface.