Hangman with Vue 3.0
Tags: vue, vue3, compostionapi
Published: 2020-10-25 11:29:24

# 1. Table of Contents
- [1. Table of Contents](#1-table-of-contents)
- [2. Introduction](#2-introduction)
- [3. Requirements](#3-requirements)
	- [3.1. Nodejs & NPM](#31-nodejs--npm)
	- [3.2. Vuejs 3.0](#32-vuejs-30)
	- [3.3. A code Editor](#33-a-code-editor)
- [4. Creating the project](#4-creating-the-project)
- [5. Initial Setup](#5-initial-setup)
- [6. Starting Up](#6-starting-up)
- [7. Making the Game](#7-making-the-game)
	- [7.1. Snippet](#71-snippet)
	- [7.2. GameFigure Component](#72-gamefigure-component)
	- [7.3. GameWord Component](#73-gameword-component)
	- [7.4. WrongLetters Component](#74-wrongletters-component)
	- [7.5. LetterNotification Component](#75-letternotification-component)
	- [7.6. GameOverPopup Component](#76-gameoverpopup-component)
- [8. Additional Stuff](#8-additional-stuff)
	- [8.1. Making Globally Managed State](#81-making-globally-managed-state)
	- [8.2. Using The `addWord` method](#82-using-the-addword-method)
	- [8.3. Creating the GuessedWords View](#83-creating-the-guessedwords-view)
	- [8.4. Configuring Router](#84-configuring-router)
	- [8.5. Adding Navigation](#85-adding-navigation)

# 2. Introduction
I had taken Brad Traversy's hangman game, which he made in his [20 Web Projects With Vanilla JavaScript](https://www.udemy.com/course/web-projects-with-vanilla-javascript/) Udemy course, and remade it using [Vue 3](https://v3.vuejs.org/), I also added a few things of my own and changed some styles. In this article I am not going to focus on explaining the styles, just copy & paste them form this post.
You can find the code for the actual game in his [GitHub repo](https://github.com/bradtraversy/vanillawebprojects/tree/master/hangman) for the course. You can find the the code for this project in this [repo](https://github.com/abasit027/Hangman). You can also find a live demo of the game [here](https://vue3-hangman.netlify.app/)

In this article I am going to tell you how I did it. In this way you can learn about the new features of Vue 3. You can learn about the differences between the v2 & v3 on the official v3 [migration guide](https://v3.vuejs.org/guide/migration/introduction.html#overview). However the new features that I have used are as follows:

* [Typescript](https://v3.vuejs.org/guide/typescript-support.html#typescript-support) - Vue3 have full support for typescript, since it is completely rewritten in typescript. 
* [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api) - A new API, in addition to the old Options API which still has full support, that makes things a lot easier.
* [Reactivity API](https://v3.vuejs.org/guide/reactivity.html#what-is-reactivity) - A new addition in Vue3 it exposes functions to make reactive variables & objects, create computed properties, watcher functions and much more. This API is a must when using **Composition API**.
* [Fragments](https://v3.vuejs.org/guide/migration/fragments.html#overview) - Vue now supports fragments, if you don't know what fragments are then we are going discuss about fragments later.

So let's get started!

# 3. Requirements
 Requirements for this project are:

## 3.1. Nodejs & NPM
Nodejs is required to run the Vue CLI and compiler. We also need a package manger, I use npm but you use yarn if you want to.
 
If you don't have it then download the installer for the latest LTS version from their [website](https://nodejs.org/en/) and install it, make sure you also install NPM.

## 3.2. Vuejs 3.0
Obviously, that's the title.
If you already have installed the newest version of the vue cli than good, else just run the following command to install it.
``` bash
npm i -g @vue/cli
```

## 3.3. A code Editor
Personally I prefer [VSCode](#) (and so do most of the developers).
If you are using VSCode then sure you install the [Vetur extension](https://github.com/vuejs/vetur). You can use any other code editor if you want to.

# 4. Creating the project
Open up your commandline and change your directory to where you want to make this project. Initialize a new vue project by running the following command:
``` bash
vue create hangman
```
It will ask you about a preset:

![Preset](https://dev-to-uploads.s3.amazonaws.com/i/xrs9m3468a1sxsn8coe6.png)

Select manually and hit enter.

Next it will ask about what features do we want:

![Features](https://dev-to-uploads.s3.amazonaws.com/i/9h1i91z1zz3n33ctwh5w.png)

For our project we will be using typescript, scss, vue-router, and eslint. So select the following and hit enter.

![Selected Features](https://dev-to-uploads.s3.amazonaws.com/i/4c80j2nve5vh2w2b3xx2.png)

Next it will ask which version of vue do we want to use:

![Version](https://dev-to-uploads.s3.amazonaws.com/i/5tba4mdpxjencukekoau.png)

Select 3.x(Preview) and hit enter.

Next, it will ask us a couple of yes/no questions. Answer as following:

![Y/N Questions](https://dev-to-uploads.s3.amazonaws.com/i/pk54p4dcenizk5981rio.png)

Next, it will ask us which CSS Pre-processor do want to use. Select Sass/SCSS (with node-sass) and hit enter.

Then, it will ask us to pick a linter config. Select ESLint + Standard config and hit enter. It will also ask us about some additional linting features:

![Lint Features](https://dev-to-uploads.s3.amazonaws.com/i/2mspafr8betpz5n2bn1w.png)

Select both and hit enter.

Then it will ask us where we want to put our config for different stuff. Select what you want and hit enter. It will also ask us if we want to save these settings as a preset for future project answer what you want and hit enter.

Once the setup is complete then in your cd into hangman. If you are using VSCode then type
``` bash
code .
```
and hit enter this will open code with the project folder. Now you can close your command prompt. Form now on we will be using VSCode's integrated terminal.

# 5. Initial Setup
Open VSCode's integrated terminal and run the following command
``` bash
npm run serve
```
This will start the vue compiler with development mode & start a dev server at localhost:8080 and open it in the browser and it will look like this:
![Initial App](https://dev-to-uploads.s3.amazonaws.com/i/vt6p5kgr8h12ezvgwevv.png)

We also need to install an npm package [random-words](https://www.npmjs.com/package/random-words), as the name suggests we are going to use it to get a random word every time. So run the following in the project root folder in any shell:
``` bash
npm i random-words
```

Open the `main.ts` file in the src folder, it will look like this :

``` ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

createApp(App)
	.use(router)
	.mount('#app');
```

Here we can see the new approach to create new objects, e.g. new app, new router, new store, e.t.c. in Vue3, Vue2 provided us with classes that we could use to create new object, but Vue3 provides us with functions to create objects that are the basis of our app. As you can see here we are importing the new `createApp` function from `vue` with which we are creating a new app. Since this functions returns us our app therefore we have to use this app to define global stuff, e.g. plugins, components, e.t.c. And we can longer do this in our configuration files of the plugins.

Now in our project directory in the src folder open up the App.vue file.

```vue
<template>
	<div id="nav">
		<router-link to="/">Home</router-link> |
		<router-link to="/about">About</router-link>
	</div>
	<router-view />
</template>

<style lang="scss">
#app {
	font-family: Avenir, Helvetica, Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	text-align: center;
	color: #2c3e50;
}

#nav {
	padding: 30px;

	a {
		font-weight: bold;
		color: #2c3e50;

		&.router-link-exact-active {
			color: #42b983;
		}
	}
}
</style>
```

Remove every thing except the `<router-view />` from the template and copy and paste the following styles instead in the style:
``` scss
* {
		box-sizing: border-box;
}

body {
	margin: 0;
	padding: 50px 0 0 0;
	background-color: #2b2b6d;
	color: #ffffff;
	font-family: Tahoma;

	display: grid;
	place-content: center;
	place-items: center;
	text-align: center;
}

h1,
h2,
h3,
h4 {
	font-weight: 500;
}

main {
	position: relative;
	width: 800px;
}
```

Now in the views directory open `Home.vue`. It will look like this

``` vue
<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src

export default defineComponent({
  name: "Home",
  components: {
    HelloWorld
  }
});
</script>
```

Remove every thing form the `<div>` in template also remove the import of the `HelloWorld.vue` and also remove it from the components option.

In the components directory in the src folder delete the `HelloWorld.vue` file.

Now in the browser it will just be a purple color.


# 6. Starting Up

We are going to build our game in the `Home.vue` file so open it up. In here we are going to use a new feature in Vue3 fragments. In the template we are now going to replace that div with a simple `<header>` and a `<main>` tag. It will now look like this:
``` html
<header>
	<h1>Hangman</h1>
	<p>Find the hidden word enter a letter</p>
</header>

<main></main>
```
Fragments allow us to have multiple root nodes. So now we don't require to wrap all of this up in a div to have only one root node. In the browser it will now show us a header.

So now let's break up our game and see how it is going to work. In an hangman game a random word is chosen and we are told the no. of letters in that word. We have to guess the word by guessing one letter at a time if that letter is present in the word then it is written at position(s) it is present at in the word, if the letter is not present then it joins the list of wrong letters and the next body part of a stick-man is drawn. The total number of body parts of stick-man is 6(1 head, 1 stick for neck & belly, 2 arms and 2 legs). If we guess the word before the the drawing of the stick-man is complete then we win, else if the we can't guess the word and the drawing of the stick-man is complete then the stick-man is hanged and we lose. If a letter that have already been called is called again whether it was correct or wrong it doesn't count and we are notified that the letter have already been called.

If we understand all of the above then we will see that there is actually a lot stuff that needs to be done. We require the following:
1. A random word (we are going to use the random-words package for this)
2. Show visually how many letters are in the word.
3. Functionality to enter letters one by one.
4. If the letter is correct then show it in its place.
5. Else if the letter is wrong then it joins the list of wrong letters, which we need to show on the screen.
6. And the next body part of the stick-man is drawn.
7. If a letter is entered again then we need to show a notification saying letter has already been entered.
8. If the user correctly guesses the word before the man is hanged then we need to stop the play and show a popup saying they won.
9. If the drawing of the stick-man is complete then we need to stop the play and show them a popup saying that they lost and also tell them the correct word.
10. On the popup we also need to have a play again button.
11. For additional features and to learn some more things we are also going to build a page that tells the users the words that they have correctly guessed.
12. For this purpose we also need a global state.

If we see above requirements then we need two views. We are also going to build different components for the word, wrong letters, stick-man, notification and popup to simplify our work.

# 7. Making the Game

In the script tag of the `Home.vue` file you will notice that the component has been defined with a `defineComponent()` method. This method is used only when using type script to get proper type inference.

In the `defineComponent` add a the new `setup()` method, this method is the whole new **Composition API** this allows us to group our functionality together which will be far apart in the old **Options API**. This method is called when creating the component and it returns state and methods for our component. It takes up to two arguments, but we will talk about them later.

In the `setup()` method the variables we declare are not reactive, if we want to make any variable reactive then we can do so with the new `ref()` method, we just  have to import the for vue.

``` ts
import { defineComponent, ref } from 'vue';
```

Then in the `setup` method we need quite a few reactive variables:

``` ts
const word = ref('');

const correctLetters = ref<Array<string>>([]);

const wrongLetters = ref<Array<string>>([]);

const notification = ref(false);
const popup = ref(false);
const status = ref('');
```

The `ref()` method takes the initial value of a variable as the argument and returns it wrapped within an object with a value property, which can then be used to access or mutate the value of the reactive variable. This is used to create *pass by reference* functionality because in JS the primitive types are passed by value and not by reference. This allows us to pass its value across our app without losing its reactivity.

We have defined 6 variables so far. Let's see what they are for:
* **Word** is the word which needs to be guessed,
* **correctLetters** it is the array of letters correctly guessed,
* **wrongLetters** it is the array of letters entered that were wrong,
* **notification** and **popup** are boolean for either of them to be visible,
* **status** is the status of the game . It is an empty string if the game is in play or else won or lost

We also define a boolean variable for starting and stopping the game:

``` ts
let playable = true;
```

Going ahead , now we are going to import the `random-words` at the top:

``` ts
import randomWord from 'random-words';
```

The `randomWord` method is going to give us a random word every time we call it.

Next we are going to define `play` method:

``` ts
const play = () => {
	word.value = randomWord();
	correctLetters.value = [];
	wrongLetters.value = [];
	status.value = '';
	playable = true;
	popup.value = false;
};
```
Here, we are setting the values of each variable to it's initial value. Except `word` which we are setting to a random word. Next we are going to define `gameOver` method:

``` ts
const gameOver = (result: string) => {
	playable = false;

	status.value = result;

	popup.value = true;
};
```
This method takes in the result of the game sets the `playable` to `false`, the value of `popup` to true to show the popup and sets the value of `status` to `result`

Next up, we are going to create the `showNotification` method which sets the value of `notification` to `true` and sets it to `false` again after 1s(1000ms)

``` ts
const showNotification = () => {
	notification.value = true;
	setTimeout(() => (notification.value = false), 1000);
};
```

After that, we are going to create a method for event listener for *keydown* event that we are going to add in a lifecycle method of the component. This method obviously takes in a `KeyboardEvent` as an argument. Then we destructure it to take the `key` & `keyCode` out of it. Then we check if the game is `playable` and if the `keyCode` is between 60 & 90, it means if is key entered is an lowercase or uppercase letter. If all those conditions are met then we transform the `key` to lowercase then check if the current `word` includes the `key`. If it does then we check if the array of `correctLetters` doesn't include the `key`, if doesn't then we set the value of `correctLetters` to an array whose first element is `key` and copy the `correctLetters` to this new array with spread operator (this creates an array that all the elements of `correctLetters` plus the `key`) else we call the `showNotification()` method. If the `word` doesn't includes the `key` then we have same procedure for `wrongLetters` as we did for `correctLetters`.

``` ts
const keyDown = (e: KeyboardEvent) => {
	let { keyCode, key } = e;

	if (playable && keyCode >= 60 && keyCode <= 90) {
		key = key.toLowerCase();

		if (word.value.includes(key))
			!correctLetters.value.includes(key)
				? (correctLetters.value = [key, ...correctLetters.value])
				: showNotification();
		else
			!wrongLetters.value.includes(key)
				? (wrongLetters.value = [key, ...wrongLetters.value])
				: showNotification();
	}
};
```

The one thing to know about the `setup()` method is that as is name suggests it is the setup of the component means the component is created after it runs, therefore with the exception of `props` we have no access to any properties declared in the component neither we can create any lifecycle methods but we can register lifecycle hooks inside `setup()` by importing several new functions from `vue`. They have the same name as for Options API but are prefixed with `on`: i.e. `mounted` will be `onMounted`. These functions accept a callback which will be called by the component. Further more 2 lifecycle methods have been renamed:
* `destroyed` is now `unmounted` &
* `beforeDestroy` is now `beforeUnmount`.

We are going to register three lifecycle hooks:
* `onBeforeMount`: Here we are going to add the eventListener, for keyup, on the `window`.
* `onMounted`: Here we are going to call the play method.
* `onUnounted`: Here we are going to remove the event listener.

We are going to import the functions form `vue`:

``` ts
import { defineComponent, ref, onBeforeMount, onMounted, onUnmounted } from 'vue';
```
Next we are going to call these functions to register the hooks:

``` ts
onBeforeMount(() => window.addEventListener('keydown', keyDown));

onMounted(() => play());

onUnmounted(() => window.removeEventListener('keydown', keyDown));
```

At the end we need to return an object with all the variables and methods that we are going to use in the component:
``` ts
return {
	word,
	correctLetters,
	wrongLetters,
	notification,
	popup,
	status,
	play,
	gameOver,
};
```

This is all for functionality of our main view. Though we are not done with it when create all the components next we are going to import them in here and use them.

## 7.1. Snippet

Following is the snippet that we are going to use to scaffold our every component:

``` vue
<template>
	<div></div>
</template>

<script lang="ts" >
	import { defineComponent } from "vue";

	export default defineComponent({
		name: '',
	});
</script>

<style lang="scss" scoped>
</style>
```

## 7.2. GameFigure Component

The first component that we are going to create is the figure of the stick-man plus the hanging pole. In the *components* folder in the *src* directory create a new file and name it `GameFigure.vue`. Scaffold it with the above given [snippet](#61-snippet).

The template for this component is just svg:

``` html
<svg height="250" width="200">
	<!-- Rod -->
	<line x1="60" y1="20" x2="140" y2="20" />
	<line x1="140" y1="20" x2="140" y2="50" />
	<line x1="60" y1="20" x2="60" y2="230" />
	<line x1="20" y1="230" x2="100" y2="230" />

	<!-- Head -->
	<circle cx="140" cy="70" r="20" />
	<!-- Body -->
	<line x1="140" y1="90" x2="140" y2="150" />
	<!-- Arms -->
	<line x1="140" y1="120" x2="120" y2="100" />
	<line v-if="errors > 3" x1="140" y1="120" x2="160" y2="100" />
	<!-- Legs -->
	<line x1="140" y1="150" x2="120" y2="180" />
	<line x1="140" y1="150" x2="160" y2="180" />
</svg>
```

Before working on the functionality, we are going to add the styles. Copy and paste the following in the `<style>` tag:

``` scss
svg {
	fill: none;
	stroke: #fff;
	stroke-width: 3px;
	stroke-linecap: round;
}
```

The functionality for this component is very simple. It is going to get `errors`, the no. of errors made, as a prop and is going to watch `errors` as soon as `errors`' value is six it is going to emit a `gameover` event. So we are going to use the **Options API** and not the **Composition API**:

``` ts
export default defineComponent({
	name: 'GameFigure',
	props: {
		errors: {
			type: Number,
			default: 0,
			required: true,
			validator: (v: number) => v >= 0 && v <= 6,
		},
	},
	emits: ['gameover'],
	watch: {
		errors(v: number) {
			if (v === 6) this.$emit('gameover');
		},
	},
});
```

A new addition in Vue3 is the emits option, it used to document the events emitted by the component. It can be an array of events' or an object with events' names as properties whose values maybe validators for events. Here we are just using an array to tell the component emits `gameover` event.

We are going to conditionally render the body parts of the figure based on the no. of `errors` with `v-if`:

``` vue
<!-- Head -->
<circle v-if="errors > 0" cx="140" cy="70" r="20" />
<!-- Body -->
<line v-if="errors > 1" x1="140" y1="90" x2="140" y2="150" />
<!-- Arms -->
<line v-if="errors > 2" x1="140" y1="120" x2="120" y2="100" />
<line v-if="errors > 3" x1="140" y1="120" x2="160" y2="100" />
<!-- Legs -->
<line v-if="errors > 4" x1="140" y1="150" x2="120" y2="180" />
<line v-if="errors > 5" x1="140" y1="150" x2="160" y2="180" />
```

This is all we need to do for this component. Now we are going to use it in `Home.vue`.

Open `Home.vue`, import the component in the script tag and add it in the components object:

``` ts
	import GameFigure from '@/components/GameFigure.vue';
	...

	Component: {
		GameFigure,
	},
```

Now in the main tag we are going to use this component, we are going to bind the `errors` with `v-bind` to the length of `wrongLetters`:

``` vue
<main>
	<game-figure :errors="wrongLetters.length" />
</main>
```

Now, if we look in the browser we will just see the hanging pole:
![GameFigure Component Done](https://dev-to-uploads.s3.amazonaws.com/i/mng3myyenz8rj02mkh97.png)

## 7.3. GameWord Component

Next up we are going to `GameWord` component. First of create a new file in the components directory and name it `GameWord.vue` and scaffold it with the above given [snippet](#61-snippet). It has quite a bit of functionality so we are going to use the **Composition API**.

First of all copy and paste the following in the style tag:

``` scss
span {
	border-bottom: 3px solid #2980b9;
	display: inline-flex;
	font-size: 30px;
	align-items: center;
	justify-content: center;
	margin: 0 3px;
	height: 50px;
	width: 20px;
}
```

Now, for the functionality. We are going to show a dash for every unguessed letter of the word and for any guessed letters we want to show the letter above the dash. To achieve this we are going to take in the `word` and `correctLetters` as props.
Here we can set the the type of `word` to `String` but for the `correctLetters` we can only set the type to the `Array` and not `Array<string>`. The type of a prop accepts a Constructor method, existing or self made, of a class, the reason is `type` of a prop is a property and properties accepts values and not types. To provide more correct types for props we need to type cast the Constructor methods to the new `propType` interface provided by Vue3. The `propType` is a generic type which takes as an argument the type of the prop. First import it from vue and then define props:

``` ts
import { defineComponent, PropType } from 'vue';
...

props: {
	word: {
		type: String,
		required: true,
	},
	correctLetters: {
		type: Array as PropType<Array<string>>,
		required: true,
	},
},
```

As I mentioned earlier that the `setup()` method takes up to 2 arguments, which are:
* **props**: passed to the component
* **context**: it is a plain js object which exposes three component properties: *emit*, *slots* & *attrs*.

However, props is a reactive object therefore it can't be destructured, if we do so the the destructured variables won't be reactive. If we need to destructure then we can to do so by turning the properties of the `props` to reactive properties by the `toRefs` function imported from `vue`.
The context is just a plain js object, therefore it can be destructured.

First import the `toRefs` form `vue`:

``` ts
import { defineComponent, toRefs } from 'vue';
```

Then create the `setup` method after `props`, in our case we only need the `emit` method to emit the `gameover` event if all the letters are guessed. Also destructure the `props` with `toRefs`:

``` ts
setup(props, { emit }) {
	const { word, correctLetters } = toRefs(props);
},
```

Next we need to create a computed property which turns the `word` into an array of letters. Computed properties inside the `setup` component are created with the `computed` function, imported from `vue`, which takes in a callback function which returns the property. The `computed` then return the property wrapped inside an `CompuedRef` object, which works very similar to the `Ref` object except that it creates a connection between the property it is computed from to keep updating the its value.

``` ts
import { defineComponent, toRefs, computed } from 'vue';
...

const letters = computed(() => {
	const array: Array<string> = [];

	word.value.split('').map(letter => array.push(letter));

	return array;
});
```

Next up we need to watch the `correctLetters`. We can watch reactive variables using the `watch` function, imported from `vue`. The function takes in two arguments:
* the variable to watch &
* a callback function which is called every time the variables value is changed:

``` ts
import { defineComponent, PropType , toRefs, computed, watch } from 'vue';
...

watch(correctLetters, () => {
	let flag = true;

	letters.value.forEach(letter => {
		if (!correctLetters.value.includes(letter)) flag = false;
	});

	if (flag) {
		emit('gameover');
	}
});
```

At the end we need to return the computed property `letters`:

``` ts
return {
	letters,
};
```

Now in the template replace the `<div>` with `<section>` and inside of section we are going to put a the following:

``` vue
<section>
	<span v-for="(letter, i) in letters" :key="i">{{ correctLetters.includes(letter) ? letter : '' }}</span>
</section>
```

Here we are using a `<section>` and inside of the `<section>` we have a `<span>` and we are using the `v-for` directive to render a span for each object in the `letters` array we are binding the `i` (index of the letter) to the `key`. We are saying that if the `correctLetters` array includes the current letter then write the letter else its an empty string. Now whenever the user guesses a correctLetter it will be pushed to the array of `correctLetters` and prop binding will cause the loop to render again and the letter will be shown.

This all we need to do for this component. Now, lets import it in the `Home.vue` and add it to the components option:

``` ts
import GameWord from '@/components/GameWord.vue';
...

components: {
	GameFigure,
	GameWord
},
```

And now, lets use it in our `template` , after the `game-figure` component. We are going to bind the `word` & `correctLetters` prop to `word` & `correctLetters`. We are also listening for the `gameover` event and are calling the `gameOver` and passing `'won'` to the result argument:

``` vue
<game-word :word="word" :correctLetters="correctLetters" @gameover="gameOver('won')" />
```

Now, in the browser it will show us the dashes for every letter:

![GameWord component done first image](https://dev-to-uploads.s3.amazonaws.com/i/gw37samipff70epclzph.png)

If we enter a correct letter it will show us and if we enter a wrong letter it will draw the next body part of the stick-man:

![GameWord component done second image](https://dev-to-uploads.s3.amazonaws.com/i/idz9w5qmprnhy2828g68.png)

But if we make six errors or guess the word it won't let us enter any new letter but won't do any thing else:

![GameWord component done third image](https://dev-to-uploads.s3.amazonaws.com/i/5lv7i1hlxaau96m7dc5h.png)


## 7.4. WrongLetters Component

Now, we are going to create the `WrongLetters` component, which will show all the wrong letters entered. In the components directory create a new file and name it `WrongLetters.vue`, scaffold it with the above given [snippet](#61-snippet). This a fairly simple component. For the script part we only have a prop. Also for the prop import `propType` form `vue`:

``` ts
import { defineComponent, PropType } from 'vue';
... 
props: {
	wrongLetters: {
		type: Array as PropType<Array<string>>,
		required: true,
	},
},
```

In the template we have an `<aside>` tag inside of which we and `<h3>` and a `<div>` with a `<span>` on we have applied `v-for` directive that iterates over the `wrongLetters` array and show all the wrong letter. Here we have also the letter as the `key` because a letter will only occur once.

``` vue
<aside>
	<h3>Wrong Letters</h3>
	<div>
		<span v-for="letter in wrongLetters" :key="letter">{{ letter }},</span>
	</div>
</aside>
```

And lastly for the styles just copy & paste the following:

``` scss
aside {
	position: absolute;
	top: 20px;
	left: 70%;
	display: flex;
	flex-direction: column;
	text-align: right;

	span {
		font-size: 24px;
	}
}
```

Now, lets use it in the component. Import it in the `Home.vue` and add it in the components:

``` ts
import WrongLetters from '@/components/WrongLetters.vue';
...
components: {
	GameFigure,
	GameWord,
	WrongLetters,
},
```

In the template add it between the `<game-figure />` and the `<game-word />` components and bind the `wrongLetters` prop to the `wrongLetters`

``` vue
<wrong-letters :wrongLetters="wrongLetters" />
```
This is it for this component.

![Wrong Letters component done](https://dev-to-uploads.s3.amazonaws.com/i/y1gyhh542876cwzdlb31.png)

## 7.5. LetterNotification Component

Now, we are going to work on the notification that tells that the letter entered has already been entered. In the components directory make a new file and name it `LetterNotification.vue`. Scaffold it with the above given [snippet](#61-snippet). For the script tag we only have a prop `show` which, obviously, we are going to show and hide the component.

``` ts
props: {
	show: {
		type: Boolean,
		required: true,
	},
},
```

Before we work on the markup, copy & paste the following in the `<style>`:

``` scss
div {
	position: absolute;
	opacity: 0;
	top: -10%;
	left: 40%;
	background-color: #333;
	width: 300px;
	border-radius: 30px;

	transition: 0.2s all ease-in-out;

	&.show {
		opacity: 1;
		top: 1%;
	}
}
```

In the `<template>` we have a `<div>` with a `<p>` telling the user that they have already entered the letter. We also have a class binding on the div that adds or removes the class based on the truthiness of the `show`:

``` vue
<div id="notification" :class="{ show: show }">
	<p>You have already entered this letter</p>
</div>
```

Now, import it in the `Home.vue` and add it in the `components` option:

```ts
import LetterNotification from '@/components/LetterNotification.vue';
...

components: {
	GameFigure,
	GameWord,
	WrongLetters,
	LetterNotification
},
```

Now, in the template after the `<main>` tag add the component and bind the `show` prop to the `notification` variable:

``` vue
<letter-notification :show="notification" />
```

Now, in the browser if we enter a letter again it will show us notification and a second later it is going to disappear:

![LetterNotification Component done](https://dev-to-uploads.s3.amazonaws.com/i/hny2eqh0jzcr7nsjze36.png)

## 7.6. GameOverPopup Component

Add a new file in the components directory and name it `GameOverPopup.vue`. Scaffold it with the above given [snippet](#61-snippet);

The script tag for this component is simple. It emits a `playagin` event and have a `playAgain` method to emit the event. Therefore we are going to use the `Options API` to define the method:

``` ts
emits: ['playagain'],
methods: {
	playAgain() {
		this.$emit('playagain');
	},
},
```

Again, before markup add the following styles to the `<style>`:

``` scss
div {
	position: absolute;
	top: 25%;
	left: 35%;
	background-color: #191919;
	width: 400px;
	height: 300px;
	border-radius: 20px;
	display: grid;
	place-items: center;
	place-content: center;

	h3 {
		font-size: 30px;
		transform: translateY(-20px);
	}

	h4 {
		font-size: 25px;
		transform: translateY(-30px);

		span {
			font-weight: 600;
			color: #00ff7f;
		}
	}

	button {
		font-family: inherit;
		font-size: 20px;
		width: 120px;
		height: 35px;
		color: #00ff7f;
		background-color: transparent;
		border: 2px solid #00ff7f;
		border-radius: 20px;
		cursor: pointer;
		font-weight: 450;

		&:hover,
		&:focus {
			color: #191919;
			background-color: #00ff7f;
		}

		&:focus {
			outline: none;
		}
	}
}
```

The template for the component is a bit different, it is a `<div>` with a `<slot></slot>` and a `<button>` with an event listener for `click` event, on which we are calling the `playAgain` method:

``` vue
<div id="popup">
	<slot></slot>
	<button @click="playAgain">Play Again</button>
</div>
```

I have used a different approach here using template slots. If you don't know what slots are then briefly slots are used to render markup inside of a child component that was written in parent component. You can learn more about slots [here](https://v3.vuejs.org/guide/component-slots.html#slots). I have used slot here because, now, we don't have to pass in any props , including `show`, `status` and `word`.

Now, in the `Home.vue` import the component and add it to the `components` option:

``` ts
import GameOverPopup from '@/components/GameOverPopup.vue';
...

components: {
	GameFigure,
	GameWord,
	WrongLetters,
	LetterNotification,
	GameOverPopup,
},
```

In the template after the `letter-notification` component add the component:

``` vue
<game-over-popup @playagain="play" v-show="popup">
	<h3>You {{ status }} {{ status === 'won' ? '🎉' : '😢' }}</h3>
	<h4 v-if="status == 'lost'">
		The word is: <span>{{ word }}</span>
	</h4>
</game-over-popup>
```

Here we are listening for the `playagain` event and calling the `play` on it. We are using the `v-if` directive here to conditionally render it based on the truthiness of the `popup` variable. In the component we have a `<h3>` which shows the `status` and a emoji based on the value of the `status` . Then we have an `<h4>` which is only rendered if the `status` is lost that show what the correct `word` is.

When the user wins or loses all of this will first be rendered in the `Home` component and then it will passed on to the `slot` of the `GameOverPopup` component. Then we will see the popup.

![GameOverPopup Component Done (Won)](https://dev-to-uploads.s3.amazonaws.com/i/6ajouy26hv45au43ksvr.png)

![GameOverPopup Component Done (Lost)](https://dev-to-uploads.s3.amazonaws.com/i/x9usg38jmo78hronaofs.png)

And if we click the play again button the game will restart:

![GameOverPopup Component Done (Play Again)](https://dev-to-uploads.s3.amazonaws.com/i/6cmo7ar4ugnmvs51leqq.png)

Our game is now complete.

# 8. Additional Stuff

To learn a little more about Vue3 I decided to make a page that shows all the words that the user have guessed correctly. This allows us work with *vue-router v4-beta* and see how to get type annotations work for vue router, that's why we installed it in the beginning. To make this work we also need state management, but since our global state is so simple we don't need *vuex* we can just make our own globally managed state.

## 8.1. Making Globally Managed State

In the src folder create a new folder and name it `store`. Inside the folder create a new file and name it `index.ts`. Inside the file first thing import the `reactive` function from `vue`:

``` ts
import { reactive } from "vue";
```

This function works exactly the same as the `ref` method just the difference is that the `ref` function is used for creating single values reactive while the `reactive` function is used for objects.

After the import create a constant object `store` which have a `state` property which is a reactive object with a property `guessedWords` which is an array of string. The `store` also have a method `addWord` which takes in a word and pushes it to the `guessedWords` array.

``` ts
const store = {
	state: reactive({
		guessedWords: new Array<string>(),
	}),

	addWord(word: string) {
		this.state.guessedWords.push(word);
	},
};
```

At the end export the `store` as the default export for the file:

``` ts
export default store;
```

This all we need to do to create a simple globally managed state.

## 8.2. Using The `addWord` method

Now, we are going to use the `addWord` method. Open the `GameWord.vue` component import the `store`:

``` ts
import store from '@/store';
```

Then in callback function for the `watch` of the `correctLetters` when we check for the flag and are emitting the `gameover` event, before emitting call the `addWord` method form `store` and pass in the value of the `word`:

``` ts
if (flag) {
	store.addWord(word.value);

	emit('gameover');
}
```

## 8.3. Creating the GuessedWords View

In the views folder delete the `About.vue` file, and for now don't pay any attention to the errors in the router folder. Create a new file in the same folder and name it, you guessed it correctly, `GuessedWords.vue`. Scaffold it with the above given [snippet](#61-snippet). In the script tag import `store` and in the `data` function return the `state` from the `store`:

``` ts
import store from '@/store';
...

data() {
	return store.state
},
```

Now in the `<template>` we have a `<header>` inside of which we have an `<h1>` that says Hangman and a `<p>` that says 'Words correctly guessed'. After that we have a `<main>` with a `<ul>` inside of which we have an `<li>` on which we have applied the `v-for` directive that iterates over the `guessedWords` array and renders every word:

``` vue
<header>
	<h1>Hangman</h1>
	<p>Words correctly guessed</p>
</header>

<main>
	<ul>
		<li v-for="(word, i) in guessedWords" :key="i">{{ word }}</li>
	</ul>
</main>
```

Copy & paste the following styles in the `<style>` tag:

``` scss
li {
	list-style-type: none;
	font-weight: 600;
}
```

## 8.4. Configuring Router

Now, we are going to configure the `vue-router` open the `index.ts` file in the router folder. It will look like this:

``` ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from '../views/Home.vue';

const routes: Array<RouteRecordRaw> = [
	{
		path: '/',
		name: 'Home',
		component: Home,
	},
	{
		path: '/about',
		name: 'About',
		// route level code-splitting
		// this generates a separate chunk (about.[hash].js) for this route
		// which is lazy-loaded when the route is visited.
		component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
	},
];

const router = createRouter({
	history: createWebHistory(process.env.BASE_URL),
	routes,
});

export default router;
```

Here you can see the difference between the v3 and the v4 of the `vue-router`. The common difference between the Vue2 and Vue3 is that Vue2 provided classes to create to objects for every thing app, router, vuex, e.t.c. but Vue3 provides functions to create every thing. This difference is also evident here. A router is now created with the `createRouter`, similar to creating an app, that takes in an object, with configuration for the router, as an argument. There are some differences in router's configuration, the most prominent is that the `mode` option has now been removed instead now we have three different options for the three different modes. Here we are using the history mode, so we have the history option which takes in the webHistory which we can create with the `createWebHistory` method imported from `vue-router`. For the typing of the routes the `vue-router` provides the type `RouterRecordRaw`. Since we have an array of routes we have an `Array<RouterRecordRaw>`. Every thing else about the `vue-router` is same. You can find more information about the `vue-router` [here](https://next.router.vuejs.org/).

Previously we deleted the `About.vue` and that's the error the compiler is giving us that it can not find the module `About.vue`. Replace about with guessedWords and `About` with `GuessedWords`, also remove the comments:

``` ts
{
	path: '/guessedWords',
	name: 'guessedWords',
	component: () => import('../views/GuessedWords.vue'),
},
```

## 8.5. Adding Navigation

Now, we are going to add the navigation. Open the `App.vue` and before the `<router-view />` add the following:

``` vue
<nav>
	<router-link to="/">Home</router-link> |
	<router-link to="/guessedWords">Guessed Words</router-link>
</nav>
```

And the following styles at the end of the `<style>`:

``` scss
nav {
	padding: 30px;

	a {
		font-weight: bold;
		color: inherit;
		text-decoration: none;

		&.router-link-exact-active {
			color: #42b983;
		}
	}
}
```

In case you are wondering that these styles look familiar to default navigation styles when we create a new Vue app. Then you are correct, I have just changed the default color of the `<a>` tag.

Now, in the browser if we guess a word and navigate to the `guessedWords` we will see it there:

![Guessed Words functionality Done](https://dev-to-uploads.s3.amazonaws.com/i/takk0onwfasl0rf9ce3y.png)