├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
└── index.html
└── src
├── App.css
├── App.js
├── components
├── Figure.js
├── Header.js
├── Notification.js
├── Popup.js
├── Word.js
└── WrongLetters.js
├── helpers
└── helpers.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Hangman Game
2 |
3 | Original code from [Brad Traversy](https://github.com/bradtraversy/vanillawebprojects/blob/master/hangman/). The original uses vanilla JavaScript. This has been convered into React.
4 |
5 | ## How To Play The Game
6 |
7 | Select a letter to figure out a hidden word in a set amount of chances
8 |
9 | ## Project Specifications
10 |
11 | - Display hangman pole and figure using SVG
12 | - Generate a random word
13 | - Display word in UI with correct letters
14 | - Display wrong letters
15 | - Show notification when select a letter twice
16 | - Show popup on win or lose
17 | - Play again button to reset game
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hangman-react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.5.0",
8 | "@testing-library/user-event": "^7.2.1",
9 | "random-words": "^1.1.1",
10 | "react": "^16.13.1",
11 | "react-dom": "^16.13.1",
12 | "react-scripts": "3.4.1"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": "react-app"
22 | },
23 | "browserslist": {
24 | "production": [
25 | ">0.2%",
26 | "not dead",
27 | "not op_mini all"
28 | ],
29 | "development": [
30 | "last 1 chrome version",
31 | "last 1 firefox version",
32 | "last 1 safari version"
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | Hangman
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body, #root {
6 | background-color: #34495e;
7 | color: #fff;
8 | font-family: Arial, Helvetica, sans-serif;
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | height: 80vh;
13 | margin: 0;
14 | overflow: hidden;
15 | }
16 |
17 | h1 {
18 | margin: 20px 0 0;
19 | }
20 |
21 | .game-container {
22 | padding: 20px 30px;
23 | position: relative;
24 | margin: auto;
25 | height: 350px;
26 | width: 450px;
27 | }
28 |
29 | .figure-container {
30 | fill: transparent;
31 | stroke: #fff;
32 | stroke-width: 4px;
33 | stroke-linecap: round;
34 | }
35 |
36 | .figure-part {
37 | display: none;
38 | }
39 |
40 | .wrong-letters-container {
41 | position: absolute;
42 | top: 20px;
43 | right: 20px;
44 | display: flex;
45 | flex-direction: column;
46 | text-align: right;
47 | }
48 |
49 | .wrong-letters-container p {
50 | margin: 0 0 5px;
51 | }
52 |
53 | .wrong-letters-container span {
54 | font-size: 24px;
55 | }
56 |
57 | .word {
58 | display: flex;
59 | position: absolute;
60 | bottom: 10px;
61 | left: 50%;
62 | transform: translateX(-50%);
63 | }
64 |
65 | .letter {
66 | border-bottom: 3px solid #2980b9;
67 | display: inline-flex;
68 | font-size: 30px;
69 | align-items: center;
70 | justify-content: center;
71 | margin: 0 3px;
72 | height: 50px;
73 | width: 20px;
74 | }
75 |
76 | .popup-container {
77 | background-color: rgba(0, 0, 0, 0.3);
78 | position: fixed;
79 | top: 0;
80 | bottom: 0;
81 | left: 0;
82 | right: 0;
83 | /* display: flex; */
84 | display: none;
85 | align-items: center;
86 | justify-content: center;
87 | }
88 |
89 | .popup {
90 | background: #2980b9;
91 | border-radius: 5px;
92 | box-shadow: 0 15px 10px 3px rgba(0, 0, 0, 0.1);
93 | padding: 20px;
94 | text-align: center;
95 | }
96 |
97 | .popup button {
98 | cursor: pointer;
99 | background-color: #fff;
100 | color: #2980b9;
101 | border: 0;
102 | margin-top: 20px;
103 | padding: 12px 20px;
104 | font-size: 16px;
105 | }
106 |
107 | .popup button:active {
108 | transform: scale(0.98);
109 | }
110 |
111 | .popup button:focus {
112 | outline: 0;
113 | }
114 |
115 | .notification-container {
116 | background-color: rgba(0, 0, 0, 0.3);
117 | border-radius: 10px 10px 0 0;
118 | padding: 15px 20px;
119 | position: absolute;
120 | bottom: -50px;
121 | transition: transform 0.3s ease-in-out;
122 | }
123 |
124 | .notification-container p {
125 | margin: 0;
126 | }
127 |
128 | .notification-container.show {
129 | transform: translateY(-50px);
130 | }
131 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Header from './components/Header';
3 | import Figure from './components/Figure';
4 | import WrongLetters from './components/WrongLetters';
5 | import Word from './components/Word';
6 | import Popup from './components/Popup';
7 | import Notification from './components/Notification';
8 | import { showNotification as show, checkWin } from './helpers/helpers';
9 |
10 | import './App.css';
11 |
12 | const words = ['application', 'programming', 'interface', 'wizard'];
13 | let selectedWord = words[Math.floor(Math.random() * words.length)];
14 |
15 | function App() {
16 | const [playable, setPlayable] = useState(true);
17 | const [correctLetters, setCorrectLetters] = useState([]);
18 | const [wrongLetters, setWrongLetters] = useState([]);
19 | const [showNotification, setShowNotification] = useState(false);
20 |
21 | useEffect(() => {
22 | const handleKeydown = event => {
23 | const { key, keyCode } = event;
24 | if (playable && keyCode >= 65 && keyCode <= 90) {
25 | const letter = key.toLowerCase();
26 | if (selectedWord.includes(letter)) {
27 | if (!correctLetters.includes(letter)) {
28 | setCorrectLetters(currentLetters => [...currentLetters, letter]);
29 | } else {
30 | show(setShowNotification);
31 | }
32 | } else {
33 | if (!wrongLetters.includes(letter)) {
34 | setWrongLetters(currentLetters => [...currentLetters, letter]);
35 | } else {
36 | show(setShowNotification);
37 | }
38 | }
39 | }
40 | }
41 | window.addEventListener('keydown', handleKeydown);
42 |
43 | return () => window.removeEventListener('keydown', handleKeydown);
44 | }, [correctLetters, wrongLetters, playable]);
45 |
46 | function playAgain() {
47 | setPlayable(true);
48 |
49 | // Empty Arrays
50 | setCorrectLetters([]);
51 | setWrongLetters([]);
52 |
53 | const random = Math.floor(Math.random() * words.length);
54 | selectedWord = words[random];
55 | }
56 |
57 | return (
58 | <>
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | >
68 | );
69 | }
70 |
71 | export default App;
72 |
--------------------------------------------------------------------------------
/src/components/Figure.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Figure = ({ wrongLetters }) => {
4 | const errors = wrongLetters.length
5 |
6 | return (
7 |
37 | )
38 | }
39 |
40 | export default Figure
41 |
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // rafce
4 | const Header = () => {
5 | return (
6 | <>
7 | Hangman
8 | Find the hidden word - Enter a letter
9 | >
10 | )
11 | }
12 |
13 | export default Header
14 |
--------------------------------------------------------------------------------
/src/components/Notification.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Notification = ({ showNotification }) => {
4 | return (
5 |
6 |
You have already entered this letter
7 |
8 | )
9 | }
10 |
11 | export default Notification
12 |
--------------------------------------------------------------------------------
/src/components/Popup.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { checkWin } from '../helpers/helpers';
3 |
4 | const Popup = ({correctLetters, wrongLetters, selectedWord, setPlayable, playAgain}) => {
5 | let finalMessage = '';
6 | let finalMessageRevealWord = '';
7 | let playable = true;
8 |
9 | if( checkWin(correctLetters, wrongLetters, selectedWord) === 'win' ) {
10 | finalMessage = 'Congratulations! You won! 😃';
11 | playable = false;
12 | } else if( checkWin(correctLetters, wrongLetters, selectedWord) === 'lose' ) {
13 | finalMessage = 'Unfortunately you lost. 😕';
14 | finalMessageRevealWord = `...the word was: ${selectedWord}`;
15 | playable = false;
16 | }
17 |
18 | useEffect(() => {
19 | setPlayable(playable);
20 | });
21 |
22 | return (
23 |
24 |
25 |
{finalMessage}
26 | {finalMessageRevealWord}
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default Popup
34 |
--------------------------------------------------------------------------------
/src/components/Word.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Word = ({ selectedWord, correctLetters }) => {
4 |
5 | return (
6 |
7 | {selectedWord.split('').map((letter, i) => {
8 | return (
9 |
10 | {correctLetters.includes(letter) ? letter : ''}
11 |
12 | )
13 | })}
14 |
15 | )
16 | }
17 |
18 | export default Word
19 |
--------------------------------------------------------------------------------
/src/components/WrongLetters.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const WrongLetters = ({ wrongLetters }) => {
4 |
5 | return (
6 |
7 |
8 | {wrongLetters.length > 0 &&
9 |
Wrong
10 | }
11 | {wrongLetters
12 | .map((letter, i) =>
{letter})
13 | .reduce((prev, curr) => prev === null ? [curr] : [prev, ', ', curr], null)}
14 |
15 |
16 | )
17 | }
18 |
19 | export default WrongLetters
20 |
--------------------------------------------------------------------------------
/src/helpers/helpers.js:
--------------------------------------------------------------------------------
1 | export function showNotification(setter) {
2 | setter(true);
3 | setTimeout(() => {
4 | setter(false);
5 | }, 2000);
6 | }
7 |
8 | export function checkWin(correct, wrong, word) {
9 | let status = 'win';
10 |
11 | // Check for win
12 | word.split('').forEach(letter => {
13 | if(!correct.includes(letter)){
14 | status = '';
15 | }
16 | });
17 |
18 | // Check for lose
19 | if(wrong.length === 6) status = 'lose';
20 |
21 | return status
22 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
12 |
--------------------------------------------------------------------------------