I made a Voice-Activated Pokédex on Google Assistant called PokéPartner! Here's how.
Travis Woodward
Posted on January 29, 2020
"Hey Google, Talk to Poké Partner"
It goes without saying that I'm a huge Pokémon fan. Ever since the original series came to the US and I watched Ash and Pikachu try to conquer Kanto, or when I put countless hours into Pokémon Blue and every Pokemon game since then... I've been hooked. Now my own daughter is watching the new series and has been playing Let's Go Eevee, and still throughout this entire time for more than 20 years I've wanted a talking Pokedex. Well, I got fed-up with waiting, so I made my own. In this article I will walk through my idea and execution of PokéPartner, the Google Home/ Assistant application that you can talk to about Pokémon.
Resources:
Website
Sourcecode
What is PokéPartner?
PokePartner is a Google Assistant application that you can talk to in order to quickly get answers about Pokemon. I built it to solve a problem that I was having in my own household. During a battle with a Pokemon, I wanted to be able to ask my Google Home "What's strong against Grass/ Fire type Pokemon" and get an answer right away, without grabbing my phone and looking at a type chart. Initially, that was it, I just wanted to know what Pokemon were strong or weak to other Pokemon.
Designing the Application
So I had no idea how to build an application for Google Assistant. After a bit of digging around I came across Dialogflow, a user-friendly, intuitive, natural language processing tool, owned by Google. This took a lot of the mystery out of the application as it handled communication directly between the user and Google Assistant. I started to roughly design a diagram to figure out how I was going to process the data and return it to the user.
There was no way that I was going to manually add all the data I needed for an application of this caliber, I needed an API that was pre-existing and established. Luckily, PokeAPI already existed. PokeAPI provides all the Pokémon data you'll ever need in one place, easily accessible through a modern RESTful API. Perfect, exactly what I was looking for, and it was free! The only issue I had now was providing the data back to my users in a meaningful way. This means I had to actually build my own application to consume the data from PokeAPI and then return it back to my users that are using Dialogflow through Google Assistant.
Building The Backend
I ended up building a private Web API using C# and .NET Core 3.0. I built the directories into "Feature Folders" rather than Model-View-Controller (MVC) as I find the format to be cleaner, especially when you're not using traditional views. By creating feature folders, you essentially take all related files and stuff them into an easy to access directory. For larger applications, this makes a lot of sense instead of having everything scattered.
The application would be broken into three primary directories. Pokemon, Types and Services. Even though there is a Pokemon folder, there is only a model inside and that is due to right now PokePartner only supporting questions about Pokemon Types. However, the PokemonModel.cs file allows users to get PokemonTypes by their Names, which is a nice feature that I added after building the application.
The biggest problem I wanted to solve was knowing what type of Pokemon to use against any single or dual type opponent Pokemon. For this I created a TypesController and built a few different API endpoints. The controller can answer questions about Pokemon on Offense of Defense, for Single or Dual type Pokemon. It can also answer questions about Pokemon types, based on the Pokemon name alone. Here's an example:
[HttpGet("defense/{type1}/{type2}")]
public async Task<IActionResult> DualTypeChartAsDefender(string type1, string type2)
{
var types = new List<string>();
types.Add(type1);
types.Add(type2);
var noEffect = new List<string>();
var notVeryEffectiveRaw = new List<string>();
var superEffectiveRaw = new List<string>();
foreach (var t in types)
{
var apiType = await _pokeApi.RequestData($"type/{t}");
var resultsType = JsonConvert.DeserializeObject<TypesModel>(apiType);
foreach (var rt in resultsType.DamageRelations.DoubleDamageFrom)
{
superEffectiveRaw.Add(rt.Name);
}
foreach (var rt in resultsType.DamageRelations.HalfDamageFrom)
{
notVeryEffectiveRaw.Add(rt.Name);
}
foreach (var rt in resultsType.DamageRelations.NoDamageFrom)
{
noEffect.Add(rt.Name);
}
}
var json = TypesCalculator.CalculateDamage(superEffectiveRaw, notVeryEffectiveRaw, noEffect);
return Ok(json);
}
Each result on the controller will return if the Pokemon Type in question will be superEffective, notVeryEffective or have noEffect. Then we send the raw data to the TypesCalculator.cs file to handle the more detailed results. This was put into its own class and method so that it could be reused efficiently and it can return if the type matchup is superEffectiveX4, superEffectiveX2, notVeryEffectiveHalf, notVeryEffectiveQuarter, or NoEffect. Ultimately this is what gets returned back to the Google Assistant and to the user.
In order to smoothly use the data from PokeAPI, I built Models for the data I was interested in and a Service to handle all the communication between my server and PokeAPI. Due to the complex nature of Pokemon data, I decided to use a model that would work well with the nestled JSON format. I came up with the following:
public class TypesModel
{
[JsonProperty("damage_relations")]
public DamageResult DamageRelations { get; set; }
}
public class DamageResult
{
[JsonProperty("double_damage_from")]
public List<DamageData> DoubleDamageFrom { get; set; }
[JsonProperty("double_damage_to")]
public List<DamageData> DoubleDamageTo { get; set; }
[JsonProperty("half_damage_from")]
public List<DamageData> HalfDamageFrom { get; set; }
[JsonProperty("half_damage_to")]
public List<DamageData> HalfDamageTo { get; set; }
[JsonProperty("no_damage_from")]
public List<DamageData> NoDamageFrom { get; set; }
[JsonProperty("no_damage_to")]
public List<DamageData> NoDamageTo { get; set; }
}
public class DamageData
{
[JsonProperty("name")]
public string Name { get; set; }
}
And for communication with PokeAPI, my service ended up being extremely simple. I simply created an HttpClient and passed the request to the correct Uri, awaited a response and then returned the raw JSON to whichever method was requesting the data. It really is that simple to get started with RESTful APIs.
public async Task<string> RequestData(string endpoint)
{
using (var client = new HttpClient())
{
var url = new Uri($"https://pokeapi.co/api/v2/{endpoint}");
var response = await client.GetAsync(url);
var results = await response.Content.ReadAsStringAsync();
return results;
}
}
That was pretty much all I needed to do on the backend. Now it was time to move on to an area that I was unfamiliar with, Dialogflow.
Building the Interface
So I am not an expert using Dialogflow or Firebase and I probably never will be. I won't be providing a tutorial on how to use Dialogflow, or an in-depth guide on how to setup Firebase for Dialogflow. However, here is a more detailed guide on Dialogflow that I watched before I got started. This video took the hesitation out of building a Google Assistant application for me. There are two major areas of Dialogflow I need to touch on for this to make sense:
1. Intents: How users interact with your application.
For Intents I need users to be able to ask PokePartner questions like "What's super effective against Fire Type Pokemon?", or "What are Flying and Water type Pokemon weak to?" or "What is Pikachu weak to?". I was going to need at least three different Intents, so I ended up making three Intents: SingleType, DualType, and PokemonWeakness.
It was pretty easy to add these intents. I just needed to add a list of what I thought commonly asked questions would be and then the natural language processing tool built into Dialogflow would handle similar sentences on it's own. The next problem to solve was how do I take a question like "What should I use against bug type pokemon?" and get the right data? Technically you could have Dialogflow return a pre-written answer but can you imagine trying to do that for every scenario? That would be insane! Thankfully, I already wrote backend logic to handle this, by getting raw data from PokeAPI and then putting it into a format that was useful to me. But how did I get that to Dialogflow?
2. Fulfillment: Integrating webhooks into Intents, to provide meaningful results.
By writing simple javascript and using Firebase, you're able to receive POST requests from Dialogflow. You do this by using an Inline Editor on Dialogflow in Google Cloud, which is powered by Cloud Functions for Firebase. Here's a quick example of what a request looks like:
// Single Type
function singleTypeHandler(agent){
const type = agent.parameters.type.toLowerCase();
return axios.get(`/type/defense/${type}`)
.then((result) => {
agent.add(`${type} Type Pokémon are weak to ${result.data.superEffective}. So if you want to do the most damage, use one of those types.`);
if (result.data.notVeryEffective.length == 0 && result.data.noEffect.length == 0) {
// If pokemon has notVeryEffect and NoEffect
} else if (result.data.noEffect.length == 0) {
// If pokemon has Weakness but no NoEffects
agent.add(` Do not use ${result.data.notVeryEffective} As those types are not very effective against ${type} Type Pokemon.`);
} else {
// If Pokemon has NoEffect only
agent.add(`By the way, don't use ${result.data.noEffect} because it will have no effect.`);
}
});
}
And that's pretty much it. Firebase handles all the complicated pieces. If you know how to write Javascript and RESTful APIs, you're in a great position to build Google Assistant applications.
Releasing the Application:
It was finally time to publish my application. I decided to use a Google Cloud computing engine to publish the private backend API which was using C# and .NET Core. I also ended up publishing a "teaser website" to go along with the Google Assistant application, and I'm using a storage bucket for that, as the website is a simple static landing page.
Google makes it easy to publish your application by using their Actions Console. You just need to fill out the formal information and you should have your application either Approved or Denied within a few days. It took my application 3 days initially to get denied. It was denied because the name did not match the pronunciation of the name that I listed. Honestly, that was a good catch and once I fixed it, it was accepted same day.
That should vaguely cover everything. I've listed this project on my Github and the launch website can be found here. Note if you are going to attempt to use the application, just say "Hey Google, Talk to Poké Partner". But it's worth noting, you really need to over pronounce the é (Poke-EE-Partner).
Hope someone finds a use for it! I've personally had a great time using this application while playing through Pokemon Sword and Pokemon Shield. However, it should be noted that at the time of writing this article, PokeAPI has not yet added the latest generation of Pokemon to their API, so you won't be able to use any of their names yet.
I am hoping to add more to this project later, and I am happy to accept any additions to the work on Github! I'd like for it to become a free and easy to use resource for the Pokemon community.
'Pokemon Night' artwork by 魔人 王.
Posted on January 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 29, 2020