I built a simple browser game where you compare two Pokémon and guess which one has higher total stats. The idea is simple: you see two Pokémon, choose the one you think is stronger, and then the game checks the real data.
The project started as a small JavaScript game using the PokéAPI. At first, the game fetched new Pokémon data directly from the API during gameplay. That worked, but it also created a scaling problem. Every round needed new API requests, and every player would create more requests.
To fix that, I changed the project so the game uses a local JSON file instead. The API is still used, but only once during the data-building step. During gameplay, the game loads the prepared JSON file and runs almost completely locally in the browser.
Overview
- The earlier random Pokémon generator
- The first approach: fetching data per round
- Why repeated API requests became a problem
- The solution: using a local JSON file
- Performance and reliability improvements
- Why the JSON structure is easier to expand
- Conclusion
At the beginning, I used the most direct approach. Every time the user started a new round, the game sent fetch requests to the API. I had already used this pattern in an earlier project, a random Pokémon generator, so it felt natural to reuse it.
For a simple random generator, this works fine. One button click means one request, and the result appears on the page. But a game is different. A higher or lower game needs repeated rounds, and each round needs two Pokémon. Ten rounds can easily mean twenty API requests from one player.
This is a common beginner mistake when working with APIs. Just because you can fetch data on every interaction does not always mean you should. Many APIs have rate limits, require tokens, or cost money at scale. Even when an API is free, it is still better practice to reduce unnecessary requests.
I chose the PokéAPI because it is open, free to use, and does not require authentication. That makes it perfect for learning and experimenting. But even with a free API, the scaling problem is still worth understanding.
In this post, I explain how I built the game, what problems appeared when using live API requests, and how switching to a local JSON file made the project faster, simpler, and easier to expand.
The earlier project: Random Pokémon Generator
Before building the higher or lower game, I had already worked on a smaller project: a random Pokémon generator. This project is very simple. You click a button, and it shows a random Pokémon with its name and sprite.
The generator works by creating a random Pokémon ID in JavaScript. The script then sends a request to the PokéAPI using that ID. From the response, it only takes the data needed for the project: the Pokémon name and sprite image.
That data is then displayed on the page. The logic is small, but it teaches an important idea: a website can request real external data and use it to update the page.
random ID → API request → Pokémon data → update the page
This approach is completely fine for a small tool. One click usually means one request, and the user gets a result immediately.
I also wrote a separate post about that generator and made the tool available here:
Try the Random Pokémon Generator Read the Generator Article
The random generator gave me the starting point for the higher or lower game. I reused the same basic idea, but once I turned it into an actual game loop, the weaknesses of repeated live API requests became much more obvious.
The first approach: fetching data per round
When I started building the Pokémon Higher or Lower game, I first used the same approach as the random generator. The game selected two random Pokémon IDs, fetched both Pokémon from the API, extracted their names, sprite URLs, and stats, and then calculated the total stat value.
After that, the player chose which Pokémon they thought had the higher total. The game checked the answer, updated the score, and then started the next round.
The early version worked like this:
start round → fetch Pokémon A → fetch Pokémon B → compare stats → player guesses → next round
At first, this felt fine. The game worked, the logic was easy to understand, and the API gave me the exact data I needed. But the problem becomes clear when you count the requests.
One round already needs two API calls. If one player plays ten rounds, that is around twenty requests. If several people play the game, the number grows quickly. The more successful the game becomes, the more requests it creates.
Why repeated API requests became a problem
The main issue was that the game depended on live API requests during gameplay. Every new round needed fresh data from an external service.
That creates three practical problems.
1. The game becomes slower
Even if the API responds quickly, there is still a delay. The browser has to send the request, wait for the response, parse the JSON, and then update the game. In a game with many rounds, even small delays can make the experience feel less responsive.
2. The game depends on the API being available
If the API is slow, unavailable, or temporarily blocked, the game stops working properly. That is not ideal for a browser game, especially when the data does not actually need to be live.
3. The request count scales with the number of players
This is the biggest problem. If every player creates API requests during every round, the number of requests increases with traffic. For a learning project, this may not matter much, but it is still a bad pattern to rely on if the data can be prepared in advance.
One possible improvement would be browser caching. If a Pokémon was already fetched once, the game could store it and reuse it. That would reduce repeated requests for the same Pokémon.
But caching only partially solves the issue. There are over 1000 Pokémon, and in a short play session the chance of randomly getting the same Pokémon again is not always high. The game would still depend on live API requests for new entries.
So I decided to solve the problem differently. Instead of fetching Pokémon during gameplay, I moved the required data into a local JSON file.
The solution: using a local JSON file
The better solution was to separate the data-building step from the gameplay step.
Instead of asking the API for Pokémon during every round, I wrote a separate script that fetches all required Pokémon data once and saves it into a local JSON file.
That script goes through the Pokémon list, fetches each Pokémon from the PokéAPI, extracts the fields I need, and stores them in a smaller format. For this game, I only needed a few things:
- Pokémon ID
- Pokémon name
- Sprite image URL
- Individual stats
- Total stat value
The total stat value can be calculated during the build step by adding the individual stats together. That way, the game itself does not need to do unnecessary work every time a round starts.
build script → fetch all Pokémon once → save useful fields → load JSON in the game
One important detail is how the images are handled. The JSON file does not store the actual image data. It only stores the URL to each Pokémon sprite. This keeps the JSON file much smaller and easier to load.
If the JSON file stored image data directly, it would become much larger and slower. For this project, sprite URLs are enough.
After the JSON file is created, the game no longer needs to call the API during gameplay. It loads the local JSON file once, stores the data in memory, and then selects random Pokémon from that local dataset.
The gameplay version then becomes much simpler:
load JSON once → pick two random entries → compare total stats → next round
This is a much better structure for this kind of project because Pokémon data does not need to be live. It does not change every minute like weather data, stock prices, or live sports scores.
If new Pokémon are added later, the JSON file can simply be rebuilt. That could be done manually, or it could be automated with something like a GitHub Action.
Performance and reliability improvements
The JSON approach improves the game in several ways.
Faster rounds
When the game used live API requests, every round depended on network speed. With local JSON data, the round can start almost instantly after the initial data load.
Fewer external dependencies
The game no longer depends on the PokéAPI being available during gameplay. If the API is slow or temporarily unavailable, the game can still work because the needed data is already stored locally.
Lower request count
During gameplay, the number of API requests is reduced to zero. The API is only used during the data-building process.
More consistent user experience
Every player uses the same local dataset. That means the game feels more consistent because each round no longer depends on external response times.
This shows an important principle when working with APIs: live requests are useful, but they should be used carefully. If the data does not need to change constantly, it is often better to load prepared data once and reuse it.
Why the JSON structure is easier to expand
Another advantage of the JSON approach is that the project becomes easier to extend.
Right now, the game compares total stats. But because the data is already stored locally, it would be easy to add more game modes later.
For example, the game could compare:
- HP
- Attack
- Defense
- Speed
- Generation
- Legendary status
It would also be possible to add filters, such as only showing Pokémon from a specific generation, only legendary Pokémon, or only final evolutions.
With live API requests, every new feature could require more request logic and more waiting. With local JSON data, the game already has the information available and can use it immediately.
That is why this project became more than just a small Pokémon game. It became a useful example of how preparing data in advance can make a web project faster, cleaner, and easier to expand.
Conclusion
The first version of the game worked, but it was not the best structure. Fetching Pokémon from the API during every round made the game depend too much on live requests.
Moving the required data into a local JSON file made the game faster, more reliable, and easier to expand. The API is still useful, but it is used at the right time: during the build process, not during every player interaction.
This is the main lesson from the project. APIs are powerful, but good API usage is not only about knowing how to fetch data. It is also about knowing when not to fetch data.
For static or rarely changing data, a prepared JSON file can be a better choice than repeated live requests. For real-time data, live API calls are still necessary. The important part is choosing the right structure for the type of data you are using.
Try the project
You can play the finished browser game here:
Play the Pokémon Higher or Lower Game
You can also view the source code on GitHub:
Related projects
- Random Pokémon Generator
- How I built a random Pokémon generator with JavaScript
- Country Higher or Lower Game
- How I built a Country Higher or Lower game
- View all projects
Disclaimer: This project is not affiliated with Pokémon, Nintendo, Game Freak, Creatures Inc., or any related companies. It is a personal fan-made project created for educational purposes only.
No comments:
Post a Comment