Skip to content

Commit

Permalink
it's an actual game now
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-james-watson committed Apr 25, 2021
1 parent 84fbe88 commit 01c4e2f
Show file tree
Hide file tree
Showing 16 changed files with 656 additions and 444 deletions.
27 changes: 25 additions & 2 deletions components/board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@ import { checkCorrect, getRandomItem, preloadImage } from "../lib/items";
import NextItemList from "./next-item-list";
import PlayedItemList from "./played-item-list";
import styles from "../styles/board.module.scss";
import Hearts from "./hearts";
import GameOver from "./game-over";

interface Props {
resetGame: () => void;
state: GameState;
setState: (state: GameState) => void;
}

export default function Board(props: Props) {
const { state, setState } = props;
const { resetGame, state, setState } = props;

const [isDragging, setIsDragging] = React.useState(false);

async function onDragStart() {
setIsDragging(true);
}

async function onDragEnd(result: DropResult) {
setIsDragging(false);

const { source, destination } = result;

if (
Expand Down Expand Up @@ -52,6 +63,7 @@ export default function Board(props: Props) {
next: newNext,
nextButOne: newNextButOne,
played: newPlayed,
lives: correct ? state.lives : state.lives - 1,
badlyPlaced: correct
? null
: {
Expand Down Expand Up @@ -93,9 +105,14 @@ export default function Board(props: Props) {

console.log(state);

const score = React.useMemo(() => {
return state.played.filter((item) => item.played.correct).length - 1;
}, [state.played]);

return (
<DragDropContext
onDragEnd={onDragEnd}
onDragStart={onDragStart}
onDragUpdate={() => {
setTimeout(() => {
// debugger;
Expand All @@ -105,13 +122,19 @@ export default function Board(props: Props) {
>
<div className={styles.wrapper}>
<div className={styles.top}>
<NextItemList next={state.next} />
<Hearts lives={state.lives} />
{state.lives > 0 ? (
<NextItemList next={state.next} />
) : (
<GameOver resetGame={resetGame} score={score} />
)}
</div>
<div id="bottom" className={styles.bottom}>
<PlayedItemList
badlyPlacedIndex={
state.badlyPlaced === null ? null : state.badlyPlaced.index
}
isDragging={isDragging}
items={state.played}
/>
</div>
Expand Down
38 changes: 38 additions & 0 deletions components/game-over.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from "react";
import { animated, useSpring } from "react-spring";
import styles from "../styles/game-over.module.scss";

interface Props {
resetGame: () => void;
score: number;
}

export default function GameOver(props: Props) {
const { resetGame, score } = props;

const highscore = Number(localStorage.getItem("highscore") ?? "0");

React.useLayoutEffect(() => {
if (score > highscore) {
localStorage.setItem("highscore", String(score));
}
}, [score, highscore]);

const animProps = useSpring({
opacity: 1,
from: { opacity: 0 },
config: { duration: 500 },
});

return (
<animated.div style={animProps} className={styles.gameOver}>
<span className={styles.score}>Score: {score}</span>
{highscore !== 0 && (
<span className={styles.highscore}>High Score: {highscore}</span>
)}
<button onClick={resetGame} className={styles.resetGame}>
Play again
</button>
</animated.div>
);
}
48 changes: 33 additions & 15 deletions components/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,49 @@ import Board from "./board";
import Loading from "./loading";

export default function Game() {
const [state, setState] = useState<GameState>({
badlyPlaced: null,
deck: [],
imageCache: [],
next: null,
nextButOne: null,
played: [],
});
const [state, setState] = useState<GameState | null>(null);
const [loaded, setLoaded] = useState(false);
const [items, setItems] = useState<Item[] | null>(null);

React.useEffect(() => {
const fetchGameData = async () => {
const res = await axios.get<string>("/items.json");
const items = res.data.trim().split("\n");
const deck: Item[] = items.map((line) => {
return JSON.parse(line);
});
setState(await createState(deck));
setLoaded(true);
const items: Item[] = res.data
.trim()
.split("\n")
.map((line) => {
return JSON.parse(line);
});
setItems(items);
};

fetchGameData();
}, []);

React.useEffect(() => {
(async () => {
if (items !== null) {
setState(await createState(items));
setLoaded(true);
}
})();
}, [items]);

const resetGame = React.useCallback(() => {
(async () => {
if (items !== null) {
setState(await createState(items));
}
})();
}, [items]);

return (
<>{loaded ? <Board state={state} setState={setState} /> : <Loading />}</>
<>
{loaded && state !== null ? (
<Board state={state} setState={setState} resetGame={resetGame} />
) : (
<Loading />
)}
</>
);
}
44 changes: 44 additions & 0 deletions components/hearts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useSpring, animated } from "react-spring";
import React from "react";
import styles from "../styles/hearts.module.scss";

interface HeartProps {
have: boolean;
}

function Heart(props: HeartProps) {
const { have } = props;
const { opacity } = useSpring({
opacity: have ? 1 : 0.2,
config: { duration: 300 },
});
const { scale } = useSpring({
scale: have ? 1 : 0.8,
config: { mass: 1, tension: 200, friction: 20, duration: 300 },
delay: 200,
});

return (
<animated.img
className={`${styles.heart} ${styles.used}`}
style={{ opacity, scale }}
src="/images/heart.svg"
/>
);
}

interface Props {
lives: number;
}

export default function Hearts(props: Props) {
const { lives } = props;

return (
<div className={styles.hearts}>
<Heart have={lives >= 1} />
<Heart have={lives >= 2} />
<Heart have={lives >= 3} />
</div>
);
}
1 change: 1 addition & 0 deletions components/next-item-list.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import { Droppable } from "react-beautiful-dnd";
import { Item } from "../types/item";
import ItemCard from "./item-card";
Expand Down
9 changes: 8 additions & 1 deletion components/played-item-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@ import styles from "../styles/played-item-list.module.scss";

interface PlayedItemListProps {
badlyPlacedIndex: number | null;
isDragging: boolean;
items: Item[];
}

export default function PlayedItemList(props: PlayedItemListProps) {
const { badlyPlacedIndex, items } = props;
const { badlyPlacedIndex, isDragging, items } = props;

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

React.useEffect(() => {
if (isDragging && flippedId !== null) {
setFlippedId(null);
}
}, [flippedId, isDragging]);

return (
<div className={styles.wrapper}>
<div className={styles.listContainer}>
Expand Down
1 change: 1 addition & 0 deletions lib/create-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default async function createState(deck: Item[]): Promise<GameState> {
badlyPlaced: null,
deck,
imageCache,
lives: 3,
next,
nextButOne,
played,
Expand Down
8 changes: 4 additions & 4 deletions lib/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export function getRandomItem(deck: Item[], played: Item[]): Item {

const periods: [number, number][] = [
[-100000, 1000],
[1000, 1800],
[1800, 1920],
[1920, 1960],
[1960, 2020],
[800, 1600],
[1600, 1870],
[1870, 1930],
[1930, 2020],
];
const [fromYear, toYear] = periods[
Math.floor(Math.random() * periods.length)
Expand Down
9 changes: 7 additions & 2 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import Head from "next/head";
import dynamic from "next/dynamic";

Expand All @@ -7,8 +8,12 @@ export default function Index() {
return (
<>
<Head>
<title>Create Next App</title>
<link rel="icon" href="/favicon.ico" />
<title>Wiki History Game</title>
<link
rel="shortcut icon"
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"
type="image/svg+xml"
/>
</Head>

<Game />
Expand Down
16 changes: 16 additions & 0 deletions public/images/heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 01c4e2f

Please sign in to comment.