Restoring Pokémon Quiz
Next up is Pokémon Quiz. Some background first.
Back when I first built this project, my son was absolutely obsessed with Pokemon. I had also achieved my Microsoft Certified Solution Developer for .NET certification from Microsoft, specializing in C#. Good language, kinda miss it. The .NET runtime of the day was either 1.0 or 1.1, I forget which. Anyway.
There was a website called PokemonDB (which is still around) and it was the absolute best resource for Pokemon data, including stats and images. Just to keep my skills sharp, I made a program that would hit the website, scrape it, pick three random Pokemon, choose one to be the right answer, and ask a question about it, giving all three as possible answers. It would do this five times and give you a score. In the case where the question type was an image, I silhouetted the Pokemon image and presented the three silhouettes. It worked pretty well!
Opening the project
The project is in a git repo so let’s see what happens when we open the .sln
file.
Okay, this makes sense, I get one per project in the solution file. I’ll just update the targets to Framework 4.5.
It looks like it… worked? This is so much easier than the pure C project I might be pushing my luck, but what happens if I press F5 to Run?
Amazing! Microsoft did a great job with backwards-compatibility in Visual Studio. Let’s press Start Quiz and see what happens.
It’s coming back to me now… originally I was hitting PokemonDB to fetch three different Pokemon, but that was too slow at the time, and unreliable, so I scraped all the Pokemon at the time, and populated a Db4o database with the Pokemon data. Then the application would hit that database. Unfortunately I don’t have a copy of that database, so I will have to reconstruct it. That’s what the DatabasePopulator
program in the solution does. Let’s run that. The Db4o project was apparently discontinued in 2014 but I have the package source in my solution so that shouldn’t be a problem.
Building the database
After switching projects and pressing F5:
This makes sense. I’m using Html Agility Pack to parse the HTML from the site, and it was too much to ask that the site still uses the same DOM as it did before. This ain’t gonna fly. I need a new source of Pokemon data!
But first, I want to modernize this project.
The project contains a ton of crap from Visual Studio Team Services which I absolutely do not want.
$ git rm BuildProcessTemplates
and I’ve just removed thousands of lines of XML.
I will also add a .gitignore
suitable for this project and move all the projects one directory down, since the repo no longer needs to be organized along with any Team Foundation Server stuff.
Rather than having duplicated NuGet packages in each project, I’ll maintain them for the solution, since it seems like that’s how that works now. This gives me two benefits: all projects use the same version, and I don’t need to store the binaries in my repo.
Finding an alternative to scraping
So, PokeDB’s DOM has changed, but they do have a REST API now (of course they do, it’s been ten years), and I don’t really want to scrape HTML anyway. Let me see if I can just add a simple REST client to this application and make API calls.
Microsoft has a bunch, including the bare Microsoft.Net.Http
and RestSharp. But what if…? No, there can’t be… Yes! Someone has already made a NuGet package for the API! PokeApiNet will do everything I need, and provide the client models. It even supports async/await, which will make it fit right in with my synchronous formerly-fetched-from-the-DB code.
First, I will absolutely wipe my DatabasePopulator
project as well as the PokemonCommon
project which was shared by the main application and the DatabasePopulator
. This will ensure at compile-time that I’m not accidentally using any of the old DB stuff.
Next, I will add PokeNetApi as a NuGet package at the project level. Why the project level? This is a single-project solution now. This package brings in a bunch of dependencies but they are all Microsoft dependencies, except for Newtonsoft.Json which appears on the level. Always check your packages’ dependencies!
I have code changes to do now. This is a good time to explain how this old application works.
- The class
PokemonQuiz.Program
has the[STAThread]
static void Main()
function so it is invoked first. - The
Main()
function invokes QuizForm. QuizForm
is a Windows Forms class, so it builds itself fromQuizForm.Designer.cs
(machine-generated from the UI builder).- The user clicks the
cmdStartQuiz
button and the game starts.
Each question consists of selecting three random Pokémon and an attribute to guess, then it chooses which one is correct and shows the choices. If the attribute is the silhouette, then SilhouetteForm
is used to generate and display the three silhouettes.
Making the code changes to use PokeApiNet
First we import the assembly and then replace our old client with the new one.
The code to select three random Pokémon has to change to use the new client. Rather than hitting the DB to get our Pokemon
objects, we’ll ask the client for them. This requires us to await
the results of GetResourceAsync()
because it’s an asynchronous call. So then we have to declare this function async
as well.
I expected at this point that the caller of this newly-async
function would have to await
the result, but it doesn’t! Interesting, this isn’t how I’m using to async/await working. I guess that means it will synchronously wait for the function to finish execution. Is that true? According to Asynchronous Programming:
When the
await
keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
I guess this just means that we don’t suspend the calling method, that’s all. So our main thread will freeze until the Pokemon
objects are retrieved. That’s fine for now.
I found something interesting at this time. The API library is coming back with 1,100 different Pokémon, but that could include variations. For now I’ll hard-code the number of Pokémon which gives me the tradeoff of fixing this at the current generation.
I have some other changes to make now like adjusting to the new client models and their fields, but that isn’t much work.
The app also displays the Pokémon images while you select them (unless you’re guessing based on silhouette, because then it would be obvious). So we need a different way to source the images. PokeNetApi provides a class called Sprites
which lets you choose from the front or back of the Pokémon along with some variations. We’ll use the FrontDefault
and it looks like this to change it.
We need to do some changes to SilhouetteForm
but there isn’t too much. For starters we’ll remove our redundant image-processing code from QuizForm
and locate it all in SilhouetteForm
. Then we’ll transform it like this.
Finally we’re ready to run it.
There is obviously a visual freeze while the SetUpQuestion()
function is running but it works! We’ll have to replace that with a progress bar or wait cursor later.
Wrapping up, and next steps
I have some more files I can delete that are left over from Team Foundation Server and the dead DatabasePopulator
and PokemonCommon
projects. There are also some things I’d like to fix:
- make the application work on HiDPI displays (it scales funny)
- figure out why the API returns > 1,100 Pokémon and see if I have a bug
- show progress indicators while the API calls are in progress
- smooth out the user experience
All in all this was a very straightforward modernization job, made so much easier by a REST API and Microsoft Visual Studio’s backwards compatibility.