Skip to content

Commit bd95e7d

Browse files
authored
Merge pull request #918 from ifirmawan/master
Added the word scramble app
2 parents 804b80e + ff55430 commit bd95e7d

File tree

19 files changed

+2630
-0
lines changed

19 files changed

+2630
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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-refresh/only-export-components': [
16+
'warn',
17+
{ allowConstantExport: true },
18+
],
19+
},
20+
}

WordScramble/ifirmawan/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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?

WordScramble/ifirmawan/README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
### Word Scramble App
2+
3+
This React app is built with Vite and features a 15-second timer, score, and replay button. It is designed to be a mini project to learn how to use Vite + React, utilizing useState and useMemo.
4+
5+
**How to Play**
6+
7+
1. When you start the game, a scrambled word will be displayed.
8+
2. You have 15 seconds to guess the word.
9+
3. If you guess the word correctly, you will earn one point.
10+
4. If you do not guess the word correctly, you will lose one point.
11+
5. The game ends when the timer runs out or when you guess all of the words correctly.
12+
13+
**Features**
14+
15+
* **Timer:** The game has a 15-second timer. When the timer runs out, the game ends and your score is displayed.
16+
* **Score:** You earn one point for each word you guess correctly. You lose one point for each word you guess incorrectly. Your score is displayed at the top of the screen.
17+
* **Replay Button:** When the game ends, you can click the replay button to play again.
18+
19+
**How to Use Vite and React**
20+
21+
This app uses Vite to build and bundle the React code. Vite is a fast and modern build tool that can be used to develop and deploy React apps.
22+
23+
To use Vite, simply install it with the following command:
24+
25+
```
26+
npm install vite
27+
```
28+
29+
Once Vite is installed, you can create a new React app with the following command:
30+
31+
```
32+
vite init
33+
```
34+
35+
This will create a new directory for your React app. You can then navigate to the directory and install the dependencies with the following command:
36+
37+
```
38+
npm install
39+
```
40+
41+
Once the dependencies are installed, you can start the Vite development server with the following command:
42+
43+
```
44+
npm run dev
45+
```
46+
47+
The development server will run on localhost:5173. You can open the app in your browser by navigating to http://localhost:5173.
48+
49+
50+
**Conclusion**
51+
52+
This word scramble app is a simple but fun way to learn how to use Vite + React, utilizing useState and useMemo. You can customize the app to add more features, such as different difficulty levels, different categories of words, and power-ups.
53+
54+
### Screenshot
55+
56+
![alt word scrumble preview](./public/peek-word-scramble.gif)

WordScramble/ifirmawan/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>🤔 Word Scramble</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.jsx"></script>
12+
</body>
13+
</html>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "word-scramble",
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+
"prop-types": "^15.8.1",
14+
"react": "^18.2.0",
15+
"react-dom": "^18.2.0"
16+
},
17+
"devDependencies": {
18+
"@types/react": "^18.2.15",
19+
"@types/react-dom": "^18.2.7",
20+
"@vitejs/plugin-react": "^4.0.3",
21+
"eslint": "^8.45.0",
22+
"eslint-plugin-react": "^7.32.2",
23+
"eslint-plugin-react-hooks": "^4.6.0",
24+
"eslint-plugin-react-refresh": "^0.4.3",
25+
"vite": "^4.4.5"
26+
}
27+
}
111 KB
Loading
Lines changed: 1 addition & 0 deletions
Loading

WordScramble/ifirmawan/src/App.css

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#root {
2+
max-width: 1280px;
3+
margin: 0 auto;
4+
padding: 2rem;
5+
text-align: center;
6+
}
7+
.word-scramble {
8+
.answer {
9+
form {
10+
display: flex;
11+
flex-direction: column;
12+
gap: 8px;
13+
}
14+
}
15+
}
16+
form.answer-form {
17+
display: flex;
18+
flex-direction: column;
19+
align-items: center;
20+
justify-content: center;
21+
gap: 8px;
22+
}
23+
input.answer-input {
24+
width: 70%;
25+
height: 36px;
26+
font-size: 28px;
27+
line-height: 36px;
28+
}

WordScramble/ifirmawan/src/App.jsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { useEffect, useMemo, useState } from "react";
2+
import "./App.css";
3+
import words from "./assets/json/words.json";
4+
import { POINT, TIMER } from "./config";
5+
import { scramble } from "./utils";
6+
import { CountdownTimer } from "./components";
7+
8+
function App() {
9+
const [counter, setCounter] = useState(0);
10+
const [score, setScore] = useState(0);
11+
const [answer, setAnswer] = useState("");
12+
const [isWrong, setIsWrong] = useState(false);
13+
const [remainingTime, setRemainingTime] = useState(TIMER);
14+
15+
const question = useMemo(() => {
16+
const fq = words?.[counter] || null;
17+
if (fq) {
18+
return { ...fq, word: scramble(fq.word) };
19+
}
20+
}, [counter]);
21+
const totalQuestion = words.length;
22+
const buttonDisabled = answer?.trim()?.length === 0;
23+
24+
const handleOnGuess = (e) => {
25+
e.preventDefault();
26+
const isCorrect = answer === words?.[counter]?.word;
27+
if (isCorrect) {
28+
setIsWrong(false);
29+
setAnswer("");
30+
setScore(score + POINT);
31+
setCounter(counter + 1);
32+
} else {
33+
setIsWrong(true);
34+
}
35+
};
36+
37+
useEffect(() => {
38+
if (counter <= totalQuestion - 1 && remainingTime === 0) {
39+
setCounter(counter + 1);
40+
}
41+
}, [counter, totalQuestion, remainingTime]);
42+
43+
return (
44+
<div className="word-scramble">
45+
{question ? (
46+
<>
47+
<small>Guess the word in</small>
48+
<CountdownTimer {...{ remainingTime, setRemainingTime }} />
49+
<h1>{question.word}</h1>
50+
<i>
51+
<strong>Hint:</strong>&nbsp;{question.definition}
52+
</i>
53+
<div>
54+
<form onSubmit={handleOnGuess} className="answer-form">
55+
<label htmlFor="answer">Your answer</label>
56+
<input
57+
type="text"
58+
id="answer"
59+
onChange={(e) => setAnswer(e.target.value)}
60+
value={answer}
61+
className="answer-input"
62+
/>
63+
{isWrong && <small>Try again!</small>}
64+
<button type="submit" disabled={buttonDisabled}>
65+
Submit
66+
</button>
67+
</form>
68+
</div>
69+
</>
70+
) : (
71+
<>
72+
<p>Your score</p>
73+
<h1>{score}</h1>
74+
<button
75+
type="button"
76+
onClick={() => {
77+
setCounter(0);
78+
setRemainingTime(TIMER);
79+
}}
80+
>
81+
Try Again!
82+
</button>
83+
</>
84+
)}
85+
</div>
86+
);
87+
}
88+
89+
export default App;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
[
2+
{
3+
"word": "eloquent",
4+
"definition": "fluent or persuasive in speaking or writing"
5+
},
6+
{
7+
"word": "abdicate",
8+
"definition": "to give up a position of power or responsibility"
9+
},
10+
{
11+
"word": "circumlocution",
12+
"definition": "the use of many words to say something that could be said more simply"
13+
},
14+
{
15+
"word": "defenestrate",
16+
"definition": "to throw someone out of a window"
17+
},
18+
{
19+
"word": "eviscerate",
20+
"definition": "to remove the internal organs of an animal or person"
21+
},
22+
{
23+
"word": "exacerbate",
24+
"definition": "to make a problem worse"
25+
},
26+
{
27+
"word": "fiduciary",
28+
"definition": "a person who is legally responsible for managing someone else's money or property"
29+
},
30+
{
31+
"word": "gargantuan",
32+
"definition": "extremely large"
33+
},
34+
{
35+
"word": "heterogeneous",
36+
"definition": "consisting of different types of things"
37+
},
38+
{
39+
"word": "impecunious",
40+
"definition": "having very little money"
41+
},
42+
{
43+
"word": "innuendo",
44+
"definition": "an indirect or suggestive remark"
45+
},
46+
{
47+
"word": "jeopardy",
48+
"definition": "danger or risk"
49+
},
50+
{
51+
"word": "kiosk",
52+
"definition": "a small booth or stall where goods or services are sold"
53+
},
54+
{
55+
"word": "labyrinth",
56+
"definition": "a complicated system of passages or paths"
57+
},
58+
{
59+
"word": "malicious",
60+
"definition": "intended to do harm"
61+
},
62+
{
63+
"word": "nostalgic",
64+
"definition": "feeling or expressing a sentimental longing for the past"
65+
},
66+
{
67+
"word": "obsequious",
68+
"definition": "obedient or attentive to an excessive or servile degree"
69+
},
70+
{
71+
"word": "paucity",
72+
"definition": "a small number or amount"
73+
},
74+
{
75+
"word": "quixotic",
76+
"definition": "idealistic and unrealistic"
77+
},
78+
{
79+
"word": "reticent",
80+
"definition": "not revealing one's thoughts or feelings readily"
81+
},
82+
{
83+
"word": "ubiquitous",
84+
"definition": "present, appearing, or found everywhere"
85+
},
86+
{
87+
"word": "voracious",
88+
"definition": "having a very great appetite"
89+
},
90+
{
91+
"word": "xenophobia",
92+
"definition": "a strong dislike of or fear of foreigners"
93+
}
94+
]

0 commit comments

Comments
 (0)