├── .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 | 8 | {/* */} 9 | 10 | 11 | 12 | 13 | 14 | {/* */} 15 | {errors > 0 && 16 | 17 | } 18 | {/* */} 19 | {errors > 1 && 20 | 21 | } 22 | {/* */} 23 | {errors > 2 && 24 | 25 | } 26 | {errors > 3 && 27 | 28 | } 29 | {/* */} 30 | {errors > 4 && 31 | 32 | } 33 | {errors > 5 && 34 | 35 | } 36 | 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 | --------------------------------------------------------------------------------