Skip to content

Commit 01c4e2f

Browse files
it's an actual game now
1 parent 84fbe88 commit 01c4e2f

16 files changed

+656
-444
lines changed

components/board.tsx

+25-2
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,27 @@ import { checkCorrect, getRandomItem, preloadImage } from "../lib/items";
66
import NextItemList from "./next-item-list";
77
import PlayedItemList from "./played-item-list";
88
import styles from "../styles/board.module.scss";
9+
import Hearts from "./hearts";
10+
import GameOver from "./game-over";
911

1012
interface Props {
13+
resetGame: () => void;
1114
state: GameState;
1215
setState: (state: GameState) => void;
1316
}
1417

1518
export default function Board(props: Props) {
16-
const { state, setState } = props;
19+
const { resetGame, state, setState } = props;
20+
21+
const [isDragging, setIsDragging] = React.useState(false);
22+
23+
async function onDragStart() {
24+
setIsDragging(true);
25+
}
1726

1827
async function onDragEnd(result: DropResult) {
28+
setIsDragging(false);
29+
1930
const { source, destination } = result;
2031

2132
if (
@@ -52,6 +63,7 @@ export default function Board(props: Props) {
5263
next: newNext,
5364
nextButOne: newNextButOne,
5465
played: newPlayed,
66+
lives: correct ? state.lives : state.lives - 1,
5567
badlyPlaced: correct
5668
? null
5769
: {
@@ -93,9 +105,14 @@ export default function Board(props: Props) {
93105

94106
console.log(state);
95107

108+
const score = React.useMemo(() => {
109+
return state.played.filter((item) => item.played.correct).length - 1;
110+
}, [state.played]);
111+
96112
return (
97113
<DragDropContext
98114
onDragEnd={onDragEnd}
115+
onDragStart={onDragStart}
99116
onDragUpdate={() => {
100117
setTimeout(() => {
101118
// debugger;
@@ -105,13 +122,19 @@ export default function Board(props: Props) {
105122
>
106123
<div className={styles.wrapper}>
107124
<div className={styles.top}>
108-
<NextItemList next={state.next} />
125+
<Hearts lives={state.lives} />
126+
{state.lives > 0 ? (
127+
<NextItemList next={state.next} />
128+
) : (
129+
<GameOver resetGame={resetGame} score={score} />
130+
)}
109131
</div>
110132
<div id="bottom" className={styles.bottom}>
111133
<PlayedItemList
112134
badlyPlacedIndex={
113135
state.badlyPlaced === null ? null : state.badlyPlaced.index
114136
}
137+
isDragging={isDragging}
115138
items={state.played}
116139
/>
117140
</div>

components/game-over.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import { animated, useSpring } from "react-spring";
3+
import styles from "../styles/game-over.module.scss";
4+
5+
interface Props {
6+
resetGame: () => void;
7+
score: number;
8+
}
9+
10+
export default function GameOver(props: Props) {
11+
const { resetGame, score } = props;
12+
13+
const highscore = Number(localStorage.getItem("highscore") ?? "0");
14+
15+
React.useLayoutEffect(() => {
16+
if (score > highscore) {
17+
localStorage.setItem("highscore", String(score));
18+
}
19+
}, [score, highscore]);
20+
21+
const animProps = useSpring({
22+
opacity: 1,
23+
from: { opacity: 0 },
24+
config: { duration: 500 },
25+
});
26+
27+
return (
28+
<animated.div style={animProps} className={styles.gameOver}>
29+
<span className={styles.score}>Score: {score}</span>
30+
{highscore !== 0 && (
31+
<span className={styles.highscore}>High Score: {highscore}</span>
32+
)}
33+
<button onClick={resetGame} className={styles.resetGame}>
34+
Play again
35+
</button>
36+
</animated.div>
37+
);
38+
}

components/game.tsx

+33-15
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,49 @@ import Board from "./board";
77
import Loading from "./loading";
88

99
export default function Game() {
10-
const [state, setState] = useState<GameState>({
11-
badlyPlaced: null,
12-
deck: [],
13-
imageCache: [],
14-
next: null,
15-
nextButOne: null,
16-
played: [],
17-
});
10+
const [state, setState] = useState<GameState | null>(null);
1811
const [loaded, setLoaded] = useState(false);
12+
const [items, setItems] = useState<Item[] | null>(null);
1913

2014
React.useEffect(() => {
2115
const fetchGameData = async () => {
2216
const res = await axios.get<string>("/items.json");
23-
const items = res.data.trim().split("\n");
24-
const deck: Item[] = items.map((line) => {
25-
return JSON.parse(line);
26-
});
27-
setState(await createState(deck));
28-
setLoaded(true);
17+
const items: Item[] = res.data
18+
.trim()
19+
.split("\n")
20+
.map((line) => {
21+
return JSON.parse(line);
22+
});
23+
setItems(items);
2924
};
3025

3126
fetchGameData();
3227
}, []);
3328

29+
React.useEffect(() => {
30+
(async () => {
31+
if (items !== null) {
32+
setState(await createState(items));
33+
setLoaded(true);
34+
}
35+
})();
36+
}, [items]);
37+
38+
const resetGame = React.useCallback(() => {
39+
(async () => {
40+
if (items !== null) {
41+
setState(await createState(items));
42+
}
43+
})();
44+
}, [items]);
45+
3446
return (
35-
<>{loaded ? <Board state={state} setState={setState} /> : <Loading />}</>
47+
<>
48+
{loaded && state !== null ? (
49+
<Board state={state} setState={setState} resetGame={resetGame} />
50+
) : (
51+
<Loading />
52+
)}
53+
</>
3654
);
3755
}

components/hearts.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useSpring, animated } from "react-spring";
2+
import React from "react";
3+
import styles from "../styles/hearts.module.scss";
4+
5+
interface HeartProps {
6+
have: boolean;
7+
}
8+
9+
function Heart(props: HeartProps) {
10+
const { have } = props;
11+
const { opacity } = useSpring({
12+
opacity: have ? 1 : 0.2,
13+
config: { duration: 300 },
14+
});
15+
const { scale } = useSpring({
16+
scale: have ? 1 : 0.8,
17+
config: { mass: 1, tension: 200, friction: 20, duration: 300 },
18+
delay: 200,
19+
});
20+
21+
return (
22+
<animated.img
23+
className={`${styles.heart} ${styles.used}`}
24+
style={{ opacity, scale }}
25+
src="/images/heart.svg"
26+
/>
27+
);
28+
}
29+
30+
interface Props {
31+
lives: number;
32+
}
33+
34+
export default function Hearts(props: Props) {
35+
const { lives } = props;
36+
37+
return (
38+
<div className={styles.hearts}>
39+
<Heart have={lives >= 1} />
40+
<Heart have={lives >= 2} />
41+
<Heart have={lives >= 3} />
42+
</div>
43+
);
44+
}

components/next-item-list.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from "react";
12
import { Droppable } from "react-beautiful-dnd";
23
import { Item } from "../types/item";
34
import ItemCard from "./item-card";

components/played-item-list.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@ import styles from "../styles/played-item-list.module.scss";
66

77
interface PlayedItemListProps {
88
badlyPlacedIndex: number | null;
9+
isDragging: boolean;
910
items: Item[];
1011
}
1112

1213
export default function PlayedItemList(props: PlayedItemListProps) {
13-
const { badlyPlacedIndex, items } = props;
14+
const { badlyPlacedIndex, isDragging, items } = props;
1415

1516
const [flippedId, setFlippedId] = React.useState<null | string>(null);
1617

18+
React.useEffect(() => {
19+
if (isDragging && flippedId !== null) {
20+
setFlippedId(null);
21+
}
22+
}, [flippedId, isDragging]);
23+
1724
return (
1825
<div className={styles.wrapper}>
1926
<div className={styles.listContainer}>

lib/create-state.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default async function createState(deck: Item[]): Promise<GameState> {
1212
badlyPlaced: null,
1313
deck,
1414
imageCache,
15+
lives: 3,
1516
next,
1617
nextButOne,
1718
played,

lib/items.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ export function getRandomItem(deck: Item[], played: Item[]): Item {
1010

1111
const periods: [number, number][] = [
1212
[-100000, 1000],
13-
[1000, 1800],
14-
[1800, 1920],
15-
[1920, 1960],
16-
[1960, 2020],
13+
[800, 1600],
14+
[1600, 1870],
15+
[1870, 1930],
16+
[1930, 2020],
1717
];
1818
const [fromYear, toYear] = periods[
1919
Math.floor(Math.random() * periods.length)

pages/index.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React from "react";
12
import Head from "next/head";
23
import dynamic from "next/dynamic";
34

@@ -7,8 +8,12 @@ export default function Index() {
78
return (
89
<>
910
<Head>
10-
<title>Create Next App</title>
11-
<link rel="icon" href="/favicon.ico" />
11+
<title>Wiki History Game</title>
12+
<link
13+
rel="shortcut icon"
14+
href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Ctext%20y%3D%22.9em%22%20font-size%3D%2290%22%3E%F0%9F%8F%9B%EF%B8%8F%3C%2Ftext%3E%3C%2Fsvg%3E"
15+
type="image/svg+xml"
16+
/>
1217
</Head>
1318

1419
<Game />

public/images/heart.svg

+16
Loading

0 commit comments

Comments
 (0)