Pokémon Go is a mobile phone game that released to unprecidented popular demand during the summer of 2016.
Even without the popular audience it still retains a large and busy fan base around it, producing guides and tools for every aspect of the game.
During the July 2017 a revamped gym system was introduced.
Somehow gym badge progression was overlooked in the tool department and I was the the first to release one, months after the release of the feature.I'm glad you asked.
A gym in Pokémon Go is a place where you can battle other players Pokémon.
When a player firsts has an interaction with the gym they are given a badge.
This badge has the name of the gym and a picture of the real world location.
By interacting with the gym in various ways the player can level up their badge, from the basic rank through bronze and silver to gold.
Progress between badge ranks is shown on a progress bar below the gym image.
The image is also framed differently depending on the rank.
The ways a player can improve the badge rank are thus:
All that I originally envisioned was a tool that gets the badge rank, how far through that rank you are, and how much more experience you need to earn. This information could then be used to show the user what they can do (Berry feeding etc) to progress to the next rank.
The badge page has a nice progression bar that we can look at for most of the the information. And thanks to the colour coding of each rank, deriving that is trivial.
First I took some screenshots of my own badges at different ranks and completions so I had something to work with.
To process the images I had to choose between using the GD functions of PHP, or the canvas element with Javascript. I felt that doing the work in-browser would be more appealing personally and to any other users, as it avoids wasting bandwidth and time uploading images.
All we need is a file input, an image to load the input image, and a canvas element.
The initial flow for user input is this: User selects a file, the file is loaded via FileReader API, when a valid file loads it's placed into an image element which can then be drawn to the canvas, which we can then read image data from.
The tough part is finding the progression bar, its length, and how much of it is filled in. But lets look at getting the rank first.
If you look at the example image of a gym badge (above this section) you'll see a shield icon near the bottom of it. This is a nicely sized, uniformly coloured, target to look at as it's coloured by the rank.
Conveniently this shield icon is always in the middle of the screen, so all we need to do is take a slice from the middle of the canvas using the ImageData API, and look for a continuous patch pixels that match one of the colours used for a rank.
Unfortunately playing with the ImageData API isn't something you can do when a browser has anti-finger printing measures enabled, as it'll never return the correct colour.
Scanning from the bottom up is the best route, and on our example image we find a matching rank colour (185,213,223) from 863px to 823px, so we have silver.
The progress bar is next up, this is a lot more involved, but we can start by reusing the vertical slice we used to find the rank.
We need to look for turquoise, or a white > grey > white band, and measure the height of this block.
Once we have the middle of the vertical position of the progress bar we use that to take another slice from the canvas, across the image.
On this pass we look for, and take note of, when and where the grey core of the progress bar starts and stops, and the same for any turquoise.
We combine the length of the turquoise section (if any) with the length of the grey core to give us what 100% of the bar is, and then do turquoise length / total length
to find out the completion amount.
Now we just look at how much badge experience points are needed to reach each rank, and we can easily calculate how many of each different point scoring interaction would someone need to do to reach the next rank.
The tool now did what I wanted and worked flawlessly for all of my sample dataset so I was confident that it was now ok to release to the public.
Soon after I announced the tool I found out that some phones like to save screenshots as JPEGs, which is a lossy image format, rather than the lossless PNGs I was testing on, and expecting.
The problem here is that colours get mangled and blurred when the image is compressed. (You can read about compression artifacts here) Some phones produce images that are more difficult to process than others, depending on the settings they used to save the file.
Work on supporting JPEG images became the priority, to reduce error reports, and confusion, and disappointment among users.
All the JPEG support came down to in the end is, firstly asking the file reader if an image was a PNG or JPEG, and then if an image is a JPEG allowing the colours we're looking for to be within a fuzzy tolerance, without any false positives.
Support for fuzziness actually turned out to be useful later when the game was changed to the render everything at a lower resolution, which was then scaled back up to the screens display resolution.
These resampled images could trigger a false positive location when the game was played in certain languages as the anti-aliasing of UI text would match a target colour.
Occasionally the middle slice will come across the very end of the turquoise bar, and detection will fail. In this situation we can just take another slice a couple of pixels to the side and try again.
When you have a Pokémon in a gym, and you view the gym badge associated with it it appears next to progress bar, like in the initial example image.
Certain creatures are of sufficient size that they overlap the end of the progress bar, for these situations I had to change how I measure the progress bar.
Originally I did everything in a single left to right pass, which was changed to measuring from the middle out to the left, then measuring from the middle out to the right. If the right side was shorter than expected then the length of the left side was double and used as the expected progress bar length.
The user is warned in this case, as the accuracy can't be guaranteed, and in some cases the size of the completed turquoise area can't be seen.
Another unforeseen issue came up where the OS UI might match a colour target and cause processing to fail, this was remedied by looking for a navigation bar at the bottom of the screen and measuring its height.
Another wonderful, and leading question. Yes, it did.
Why measure one progress bar when you could, with less accuracy, measure a whole screen full?
To the right you'll see an neatly anonymised screenshot of the overview page for gym badges, these all still have progress bars (unless you've reached the max rank, of course).
The first thing to do is scan the page in three columns, once again looking for turquoise, or the white > grey > white core of a progress bar.
When the game later reduced its internal resolution, these smaller progress bars became trickier to casually detect, especially with compression artifacts.
But if we do find a progress bar then we can measure that with few issues, unlike getting the rank as there is now no flat coloured image we can check.
Instead we check for the length and colour of the tip of the badge at the bottom, below the progress bar. The higher the badge rank, the longer the tip.
As we're dealing with many badges in this mode we keep the result for each badge around, and we add a UI element over each badge giving the percentage.
This UI scales with the image so the user can accurately click on any badge, the page will then show them the same progression information that the normal single badge reader offers.
Originally I tried to stay away from copying the game's UI, but I just couldn't resist cloning it.
I think I did a really good job of it too. The animations are pretty accurate, and I've expanded on it a little bit by adding the page backgrounds that suggest the type of screenshot each mode accepts.
I also added a pleasant dark mode theme, which people can toggle from the lovely menu.
Support for translations was added using a forked version of polyglot.
I made an interface for creating and submitting translations too, which can be found here.
This is a large feature I hope people are aware of.
When a user checks a badge they will see a list box at the bottom of the page that lets them add that result to a currently tracked gym, or start tracking a new gym.
Each time you update the progress of a tracked gym the time and exp for that gym is updated.
When you go to the tracked gyms section you can view a SVG chart of your progress, and what can be done to rank up.
Users can also write a note about each gym, and when they're standing at the gym, save the gyms location so it's easy to know which gym is which and where they actually are.
Tracked gyms are stored locally but can be backed up and restored should you change device, or wish to view the information on your pc etc.