Skip to content

Commit 9f4cc08

Browse files
author
Aleksei Gorshkov
committed
word games - hangman and wordle
1 parent 849ddcd commit 9f4cc08

11 files changed

+370
-148
lines changed

app/globals.css

-30
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
11
@tailwind base;
22
@tailwind components;
33
@tailwind utilities;
4-
5-
:root {
6-
--foreground-rgb: 0, 0, 0;
7-
--background-start-rgb: 214, 219, 220;
8-
--background-end-rgb: 255, 255, 255;
9-
}
10-
11-
@media (prefers-color-scheme: dark) {
12-
:root {
13-
--foreground-rgb: 255, 255, 255;
14-
--background-start-rgb: 0, 0, 0;
15-
--background-end-rgb: 0, 0, 0;
16-
}
17-
}
18-
19-
body {
20-
color: rgb(var(--foreground-rgb));
21-
background: linear-gradient(
22-
to bottom,
23-
transparent,
24-
rgb(var(--background-end-rgb))
25-
)
26-
rgb(var(--background-start-rgb));
27-
}
28-
29-
@layer utilities {
30-
.text-balance {
31-
text-wrap: balance;
32-
}
33-
}

app/hangman/data.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
const words = [
3+
"Jackhammer",
4+
"Blacksmith",
5+
"Helicopter",
6+
"Exacerbate",
7+
"Earthquake",
8+
"Conclusive",
9+
"Encyclopedia",
10+
"Handwriting",
11+
"Holography",
12+
"Jackrabbit",
13+
];
14+
15+
export function randomWord() {
16+
return words[Math.floor(Math.random() * words.length)].toUpperCase();
17+
}

app/hangman/hangman.tsx

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"use client";
2+
import { useState } from "react";
3+
import cx from "clsx";
4+
import { randomWord } from "./data";
5+
6+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
7+
8+
export function Hangman({ initialWord }: { initialWord: string }) {
9+
const [guesses, setGuesses] = useState<string[]>([]);
10+
const [word, setWord] = useState(initialWord);
11+
const [failures, setFailuresLeft] = useState(7);
12+
13+
const getClassName = (letter: string) =>
14+
cx("rounded w-10 uppercase disabled:cursor-not-allowed bg-slate-200", {
15+
["bg-green-200"]: guesses.includes(letter) && word.includes(letter),
16+
["bg-red-200"]: guesses.includes(letter) && !word.includes(letter),
17+
});
18+
19+
const isGuessed =
20+
word != "" &&
21+
word
22+
.split("")
23+
.every((letter) => guesses.includes(letter.toLocaleUpperCase()));
24+
25+
const handleGuess = (letter: string) => {
26+
setGuesses((p) => p.concat(letter));
27+
if (!word.includes(letter)) {
28+
setFailuresLeft((p) => p - 1);
29+
}
30+
};
31+
32+
const handleReset = () => {
33+
setWord(randomWord());
34+
setGuesses([]);
35+
setFailuresLeft(7);
36+
};
37+
38+
return (
39+
<main className="flex justify-center items-center mt-40 flex-col">
40+
<ul className="flex uppercase gap-4 text-3xl mb-6">
41+
{word
42+
.split("")
43+
.map((letter, index) =>
44+
guesses.includes(letter.toLocaleUpperCase()) ? (
45+
<li key={index}>{letter}</li>
46+
) : (
47+
<li key={index}>_</li>
48+
)
49+
)}
50+
</ul>
51+
{failures > 0 && (
52+
<ul className="flex gap-2 text-2xl flex-wrap px-4 mb-6 max-w-[680px]">
53+
{ALPHABET.split("").map((letter, index) => (
54+
<li key={index}>
55+
<button
56+
className={getClassName(letter.toLocaleUpperCase())}
57+
disabled={guesses.includes(letter)}
58+
onClick={() => handleGuess(letter)}
59+
>
60+
{letter}
61+
</button>
62+
</li>
63+
))}
64+
</ul>
65+
)}
66+
67+
{failures > 0 ? (
68+
<p className="text-2xl">Failures left: {failures}</p>
69+
) : (
70+
<p>
71+
You lose!
72+
<br />
73+
<button
74+
className="bg-red-500 text-white px-2 py-1 rounded"
75+
onClick={handleReset}
76+
>
77+
Play again
78+
</button>
79+
</p>
80+
)}
81+
{isGuessed && (
82+
<p className="text-2xl mt-2">
83+
You win!
84+
<br />
85+
<button
86+
className="ml-2 bg-green-500 text-white px-2 py-1 rounded"
87+
onClick={handleReset}
88+
>
89+
Play again
90+
</button>
91+
</p>
92+
)}
93+
</main>
94+
);
95+
}

app/hangman/page.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Hangman } from "./hangman";
2+
import { randomWord } from "./data";
3+
4+
export default function Home() {
5+
return <Hangman initialWord={randomWord()} />;
6+
}

app/page.tsx

-113
This file was deleted.

app/wordle/data.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const words: string[] = [
2+
"apple",
3+
"beach",
4+
"cloud",
5+
"dance",
6+
"earth",
7+
"flame",
8+
"grape",
9+
"happy",
10+
"image",
11+
"jolly"
12+
];
13+
14+
const randomWord = () => {
15+
return words[Math.floor(Math.random() * words.length)].toUpperCase();
16+
}

app/wordle/page.tsx

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"use client";
2+
import { useState } from "react";
3+
import { observer } from "mobx-react-lite";
4+
import { wordleStore } from "./store";
5+
import cx from "clsx";
6+
import { stat } from "fs";
7+
8+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
9+
10+
interface RowProps {
11+
element?: { letter: string; status: string }[];
12+
}
13+
14+
const Row = observer<RowProps>(({ element }) => {
15+
if (element)
16+
return element.map(({ letter, status }, index) => (
17+
<Letter key={index} letter={letter} status={status} />
18+
));
19+
20+
return Array.from({ length: wordleStore.word.length }).map((_, index) => (
21+
<Letter key={index} />
22+
));
23+
});
24+
25+
interface LetterProps {
26+
letter?: string;
27+
status?: string;
28+
}
29+
30+
const Letter = observer<LetterProps>(({ letter = " ", status = "none" }) => (
31+
<span
32+
className={cx("bg-slate-200 p-2 w-12 h-10", {
33+
["bg-green-200"]: status === "correct",
34+
["bg-yellow-200"]: status === "incorrect",
35+
})}
36+
>
37+
{letter}
38+
</span>
39+
));
40+
41+
const Page = observer(() => {
42+
const [value, setValue] = useState("");
43+
44+
return (
45+
<main className="m-24">
46+
<div className="mb-4">
47+
<input
48+
minLength={wordleStore.word.length}
49+
maxLength={wordleStore.word.length}
50+
className="border border-slate-200"
51+
type="text"
52+
placeholder="value"
53+
value={value}
54+
onChange={(e) => setValue(e.target.value)}
55+
/>
56+
<button onClick={() => wordleStore.submit(value.toLocaleUpperCase())}>
57+
Submit
58+
</button>
59+
</div>
60+
61+
<ul className="flex flex-col gap-2">
62+
{wordleStore.table.map((tableElement, index) => (
63+
<li key={index} className="flex gap-2 justify-center items-center">
64+
<Row element={tableElement} />
65+
</li>
66+
))}
67+
</ul>
68+
</main>
69+
);
70+
});
71+
72+
export default Page;

0 commit comments

Comments
 (0)