├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.css ├── App.jsx ├── assets │ ├── Images │ │ ├── 1.svg │ │ ├── 2.svg │ │ ├── 3.svg │ │ ├── 4.svg │ │ ├── 5.svg │ │ ├── 6.svg │ │ ├── 7.svg │ │ └── 8.svg │ └── react.svg ├── components │ ├── Button │ │ ├── Button.jsx │ │ └── getButtonStyleType.js │ ├── HangMan │ │ └── HangMan.jsx │ ├── LetterButtons │ │ └── LetterButtons.jsx │ ├── MaskedText │ │ ├── MaskedText.jsx │ │ └── MaskedTextUtility.jsx │ ├── TextInput │ │ └── TextInput.jsx │ └── TextInputForm │ │ ├── TextInputForm.jsx │ │ └── TextInputFormContainer.jsx ├── index.css ├── main.jsx └── pages │ ├── PlayGame │ └── PlayGame.jsx │ └── StartGame │ └── StartGame.jsx ├── tailwind.config.js └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | "react/prop-types": "off", 16 | 'react/jsx-no-target-blank': 'off', 17 | 'react-refresh/only-export-components': [ 18 | 'warn', 19 | { allowConstantExport: true }, 20 | ], 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hangman", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "react-router-dom": "^6.25.1" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.3.3", 19 | "@types/react-dom": "^18.3.0", 20 | "@vitejs/plugin-react": "^4.3.1", 21 | "autoprefixer": "^10.4.19", 22 | "eslint": "^8.57.0", 23 | "eslint-plugin-react": "^7.34.3", 24 | "eslint-plugin-react-hooks": "^4.6.2", 25 | "eslint-plugin-react-refresh": "^0.4.7", 26 | "postcss": "^8.4.39", 27 | "tailwindcss": "^3.4.6", 28 | "vite": "^5.3.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singhsanket143/HangMan-React/286374f5a8e7c2e054559025d6cecfab42a4b885/src/App.css -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | 3 | import PlayGame from "./pages/PlayGame/PlayGame"; 4 | import StartGame from "./pages/StartGame/StartGame"; 5 | 6 | function App() { 7 | return ( 8 | //
9 | //

Welcome to Hangman

10 | // console.log("Value coming from the hidden form is", value)} /> 11 | //
12 | 13 | <> 14 |
15 | Navbar 16 |
17 | 18 | } /> 19 | } /> 20 | not found } /> 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /src/assets/Images/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/Images/8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Button/Button.jsx: -------------------------------------------------------------------------------- 1 | import getButtonStyling from "./getButtonStyleType"; 2 | 3 | 4 | function Button({ buttonType = "button", text, styleType, onClickHandler }) { 5 | 6 | 7 | return ( 8 | 15 | 16 | ); 17 | 18 | } 19 | 20 | export default Button; -------------------------------------------------------------------------------- /src/components/Button/getButtonStyleType.js: -------------------------------------------------------------------------------- 1 | function getButtonStyling(styleType) { 2 | const primaryButtonStyling = "bg-blue-500 border border-blue-700 hover:bg-blue-700 hover:border-blue-900"; 3 | const secondaryButtonStyling = "bg-gray-500 border border-gray-700 hover:bg-gray-700 hover:border-gray-900"; 4 | const warningButtonStyling = "bg-yellow-500 border border-yellow-700 hover:bg-yellow-600 hover:border-yellow-900"; 5 | const errorButtonStyling = "bg-red-500 border border-red-700 hover:bg-red-600 hover:border-red-900"; 6 | 7 | if(styleType === "primary") { 8 | return primaryButtonStyling; 9 | } else if(styleType === "secondary") { 10 | return secondaryButtonStyling; 11 | } else if(styleType === "warning") { 12 | return warningButtonStyling; 13 | } else if(styleType === "error") { 14 | return errorButtonStyling; 15 | } else { 16 | return primaryButtonStyling; 17 | } 18 | } 19 | 20 | export default getButtonStyling; -------------------------------------------------------------------------------- /src/components/HangMan/HangMan.jsx: -------------------------------------------------------------------------------- 1 | import Level1 from '../../assets/Images/1.svg'; 2 | import Level2 from '../../assets/Images/2.svg'; 3 | import Level3 from '../../assets/Images/3.svg'; 4 | import Level4 from '../../assets/Images/4.svg'; 5 | import Level5 from '../../assets/Images/5.svg'; 6 | import Level6 from '../../assets/Images/6.svg'; 7 | import Level7 from '../../assets/Images/7.svg'; 8 | import Level8 from '../../assets/Images/8.svg'; 9 | 10 | function HangMan({ step }) { 11 | const images = [Level1, Level2, Level3, Level4, Level5, Level6, Level7, Level8]; 12 | return ( 13 |
14 | = images.length ? images[images.length-1] : images[step]} /> 15 |
16 | ) 17 | } 18 | 19 | export default HangMan; -------------------------------------------------------------------------------- /src/components/LetterButtons/LetterButtons.jsx: -------------------------------------------------------------------------------- 1 | 2 | const ALPHABETS = new Array(26).fill('').map((e, index) => String.fromCharCode(65 + index)); 3 | // 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') 4 | 5 | function LetterButtons({ text, usedLetters, onLetterClick }) { 6 | const originalCharacters = new Set(text.toUpperCase().split('')); 7 | const selectedLetters = new Set(usedLetters.join('').toUpperCase().split('')); 8 | 9 | const buttonStyle = function(letter) { 10 | if (selectedLetters.has(letter)) { 11 | return `${originalCharacters.has(letter) ? 'bg-green-600 border-green-700' : 'border-[#000] border-4 bg-red-600' } ` 12 | } else { 13 | return 'bg-blue-600 border-blue-700 hover:bg-blue-700' 14 | } 15 | } 16 | 17 | const handleClick = function(event) { 18 | const character = event.target.value; 19 | onLetterClick?.(character); 20 | } 21 | 22 | const buttons = ALPHABETS.map(letter => { 23 | return ( 24 | 33 | ); 34 | }) 35 | 36 | return ( 37 | <> 38 | {buttons} 39 | 40 | ) 41 | } 42 | 43 | export default LetterButtons; -------------------------------------------------------------------------------- /src/components/MaskedText/MaskedText.jsx: -------------------------------------------------------------------------------- 1 | import { getAllCharacters } from "./MaskedTextUtility"; 2 | 3 | /** 4 | * 5 | * @param {text} The word to be guessed 6 | * @param {usedLetters} The array of letters that have been guessed so far 7 | * @returns 8 | */ 9 | function MaskedText({ text, usedLetters }) { 10 | const letters = getAllCharacters(text, usedLetters).split(''); 11 | return ( 12 | <> 13 | {letters.map((letter, index) => { 14 | return ( 15 | {letter} 16 | ) 17 | })} 18 | 19 | ) 20 | } 21 | 22 | 23 | 24 | export default MaskedText; 25 | 26 | // H U M B L E (B and E are Guessed) -> _ _ _ B _ E -------------------------------------------------------------------------------- /src/components/MaskedText/MaskedTextUtility.jsx: -------------------------------------------------------------------------------- 1 | export function getAllCharacters(word, usedLetters) { 2 | // This function will return a string with all the characters of the word that have been guessed so far 3 | usedLetters = usedLetters.map(letter => letter.toUpperCase()); // ['b', 'e'] -> ['B', 'E'] 4 | const guessedLetters = new Set(usedLetters); // {'B', 'E'} 5 | const characters = word.toUpperCase().split('').map(char => { 6 | if (guessedLetters.has(char)) { 7 | return char; 8 | } 9 | return '_'; 10 | }); // ['_', '_', '_', 'B', '_', 'E'] 11 | return characters.join(''); // ___B_E 12 | } -------------------------------------------------------------------------------- /src/components/TextInput/TextInput.jsx: -------------------------------------------------------------------------------- 1 | function TextInput({ label, type = 'text', value, onChange }) { 2 | 3 | return ( 4 | 5 | 16 | ); 17 | 18 | } 19 | export default TextInput; -------------------------------------------------------------------------------- /src/components/TextInputForm/TextInputForm.jsx: -------------------------------------------------------------------------------- 1 | // Presentation component 2 | import TextInput from "../TextInput/TextInput"; 3 | import Button from "../Button/Button" ; 4 | 5 | function TextInputForm({ handleFormSubmit, handleTextInputChange, value, inputType, setInputType }) { 6 | return ( 7 |
8 |
9 | 15 |
16 | 17 |
18 |
24 | 25 |
26 |
31 |
32 | ); 33 | 34 | } 35 | 36 | export default TextInputForm; -------------------------------------------------------------------------------- /src/components/TextInputForm/TextInputFormContainer.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import TextInputForm from "./TextInputForm"; 3 | 4 | // Container component for TextInputForm 5 | function TextInputFormContainer({ onSubmit}) { 6 | const [value, setValue] = useState(''); 7 | const [inputType, setInputType] = useState('password'); 8 | 9 | function handleFormSubmit(event) { 10 | event.preventDefault(); 11 | console.log('Form submitted', value); 12 | onSubmit?.(value); // if onSubmit is defined, call it with the value 13 | } 14 | 15 | function handleTextInputChange(event) { 16 | console.log('Text input changed'); 17 | console.log(event.target.value); 18 | setValue(event.target.value); // whenever I Type in the input field, it will update the value 19 | } 20 | 21 | return ( 22 | // Calling the presentation layer 23 | 30 | ); 31 | 32 | } 33 | 34 | export default TextInputFormContainer; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | import { BrowserRouter } from 'react-router-dom' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')).render( 8 | 9 | 10 | 11 | 12 | 13 | , 14 | ) 15 | -------------------------------------------------------------------------------- /src/pages/PlayGame/PlayGame.jsx: -------------------------------------------------------------------------------- 1 | import { Link, useLocation } from "react-router-dom"; 2 | import MaskedText from "../../components/MaskedText/MaskedText"; 3 | import LetterButtons from "../../components/LetterButtons/LetterButtons"; 4 | import { useState } from "react"; 5 | import HangMan from "../../components/HangMan/HangMan"; 6 | 7 | function PlayGame() { 8 | 9 | const [usedLetters, setUsedLetters] = useState([]); 10 | 11 | const [step, setStep] = useState(0); 12 | 13 | const location = useLocation(); 14 | const wordSelected = location.state?.wordSelected; // If the state is not defined, it will return undefined, otherwise it will return the value of wordSelected 15 | 16 | const handleLetterClick = function(letter) { 17 | if (wordSelected.toUpperCase().includes(letter)) { 18 | console.log('Correct'); 19 | } else { 20 | console.log('Incorrect'); 21 | setStep(step + 1); 22 | } 23 | setUsedLetters([...usedLetters, letter]); 24 | } 25 | 26 | return ( 27 |
28 |

Play Game

29 | 30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 |
48 | 49 | Start Game 50 |
51 | ); 52 | } 53 | export default PlayGame; -------------------------------------------------------------------------------- /src/pages/StartGame/StartGame.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import TextInputFormContainer from "../../components/TextInputForm/TextInputFormContainer"; 3 | 4 | function StartGame() { 5 | 6 | const navigate = useNavigate(); 7 | 8 | function handleSubmit(value) { 9 | navigate('/play', { state: {wordSelected: value}}); 10 | } 11 | 12 | return ( 13 |
14 |

Start Game

15 | 16 |
17 | ); 18 | } 19 | export default StartGame; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | } 13 | 14 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------