Harry Potter Game


The aim of the game is to correctly identify which of the four Hogwarts Houses the witch or wizard on screen belongs to.

The idea for this project came about when I was searching the internet for API's that were free to use and I came across Beth Fraser's Harry Potter API hosted on Heroku.

My app

My Goal

I began this project to give me some experience working with and manipulating data from an external API.

Stack

I created this project with React using create-react-app to get me up and running. For styling I am using CSS Modules functionality, but with the CSS pre-processor Sass.

To fetch the data from the API endpoint I'm using the popular Axios library.

For the subtle animations I am using the Framer Motion library.

Dive Deeper

One of my favourite aspects of this project was designing a custom hook. Much like a class in Python (or any other object-oriented language), within this hook I could write a variety of methods which would provide the majority of functionality to the game.

I wanted to create a custom hook here so that if I were to add another game to this app which used different data, or even a whole new app in React that similarly uses a shuffled array, I would be able to use the code again. Have a look below to see how it works.

javascript
1 const useShuffledObjectArray = array => {
2 const [fullArray, setFullArray] = useState([]);
3 const [currentElementIndex, setCurrentElementIndex] = useState(0);
4 const [currentElement, setCurrentElement] = useState({});
5 const [usedElements, setUsedElements] = useState([]);
6
7 const setArray = useCallback(array => {
8 const shuffled = shuffleArray(array);
9 setFullArray(shuffled);
10 setCurrentElement(shuffled[currentElementIndex]);
11 }, [currentElementIndex]);
12
13 useEffect(() => {
14 if (array) {
15 setArray(array);
16 }
17 }, [array, setArray])
18
19 const nextElement = () => {
20 setUsedElements([
21 ...usedElements,
22 currentElement
23 ])
24 setCurrentElementIndex(i => i + 1);
25 setCurrentElement(fullArray[currentElementIndex]);
26 }
27
28 const reset = newArray => {
29 setCurrentElementIndex(0)
30 setCurrentElement({})
31 setUsedElements([])
32 setArray(newArray);
33 }
34
35 return [currentElement, nextElement, currentElementIndex, reset];
36}

The custom hook takes an array when initialised as can be seen when useEffect() is called on line 13, if the array is present setArray() will be called.

This function:

  • Shuffles the array by calling an external function (which uses the Fisher-Yates shuffle).
  • Assigns the shuffled array to the fullArray variable.
  • And assigns the first element in the shuffled array as the currentElement to be used by the game.

On line 35 the hook returns the:

  • currentElement - This will always be the character that is currently displayed on screen.

  • nextElement - This provides the method which is defined on line 19. Before updating currentElement the spread syntax is used to append the element which has just been in play to the usedElements array. Currently there is no functionality using this array but I included it in case I wanted to add a feature that might use it.

    After appending the used element setCurrentElementIndex(i => i + 1) is called which increases value of currentElementIndex by 1. And then, by using that new index, we are free to set the new current element to be the next element in the shuffled array on line 25.

  • currentElementIndex - As the literal variable name suggests, this stores the current index in the array of the element which is being displayed in game. This is used to tell the player how many rounds of the game they have survived for.

  • reset - This function does just what it says on the tin. It resets all state back to the value they're initialised with, and will set up a new game with an array passed in as an argument. This is the method that is called when the player loses a game, and wishes to play another.

Lessons

Better Planning

When looking into how the popular game Wordle works I stumbled across JFB1337's comment on a Reddit post which states that the solutions for every game for the next 5 years are hard-coded into the source code.

This led me to thinking about my Harry Potter game. I fetch all of the Harry Potter data from the Harry Potter API each time the website is loaded. This is good solution for data that changes or is updated regularly (e.g. weather forecasting data), and if more characters are added to the Harry Potter API then my game will fetch the new data without any modifications. However, with over 400 characters already hosted on that API it's highly unlikely more will be added.

With this in mind I've concluded that if I were to go back and begin this particular project again I would hard-code the data I'm using, therefore not having to make any HTTP requests, which would give me the option to make a completely static web app (rendered entirely server-side) which has benefits of it's own.

Error Handling

This app could definitely do with Error Handling. Consider the custom hook explained above - when initialised, if no array is passed to the hook there would be no immediate error to warn the developer that something is wrong. Furthermore, this means that the variables and methods it returns would result in generic errors which would lead to difficult debugging.

Conclusion

I'm pleased with the outcome of this project. I successfully worked with and manipulated data from an external API as I intended, and I learnt some valuable lessons along the way.

Going Forward

I would like to integrate browser storage into the app which would persist the players 'Best Score'. This would make the game more fun and more addictive as the users try to beat their best score.