Skip to content

Commit

Permalink
Merge pull request #28 from JollyGrin/feat/offline
Browse files Browse the repository at this point in the history
Feat/offline
  • Loading branch information
JollyGrin authored Mar 30, 2024
2 parents 93c1975 + 048d233 commit f5618a2
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 6 deletions.
4 changes: 4 additions & 0 deletions components/Connect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import styled from "@emotion/styled";
import { PlusSquareIcon } from "@chakra-ui/icons";
import { useCopyToClipboard } from "@/lib/hooks/useCopyToClipboard";
import { toast } from "react-hot-toast";
import Link from "next/link";

export const ConnectPage = () => {
const router = useRouter();
Expand Down Expand Up @@ -111,6 +112,9 @@ export const ConnectPage = () => {
{loading && <Spinner />}
Connect to Game
</Button>
<Button as={Link} href={{ pathname: "/offline" }}>
Play Offline without Map
</Button>
</VStack>
{sharedDeckId === undefined && (
<PlusSquareIcon
Expand Down
12 changes: 10 additions & 2 deletions components/Game/Hand/hand.container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
newPool,
shuffleDeck,
} from "@/components/DeckPool/PoolFns";
import { useWebGame } from "@/lib/contexts/WebGameProvider";
import { useLocalDeckStorage } from "@/lib/hooks/useLocalStorage";
import { Box, Flex, Grid } from "@chakra-ui/react";
import { useRouter } from "next/router";
Expand All @@ -19,17 +18,26 @@ import styled from "@emotion/styled";
import { flow } from "lodash";
import { ModalType } from "@/pages/game";
import { CloseIcon } from "@chakra-ui/icons";
import { WebsocketMessage } from "@/lib/gamesocket/message";

type GameData = {
gameState: WebsocketMessage | undefined;
setPlayerState: () => (props: { pool: PoolType }) => void;
};

export const HandContainer = ({
setModal,
gameState,
setPlayerState,
}: {
setModal: (type: ModalType) => void;
gameState: GameData["gameState"];
setPlayerState: GameData["setPlayerState"];
}) => {
const localName = useRouter().query?.name;
const player = Array.isArray(localName) ? localName[0] : localName;

const { starredDeck } = useLocalDeckStorage();
const { gameState, setPlayerState } = useWebGame();
const players = gameState?.content?.players as Record<
string,
{ pool?: PoolType }
Expand Down
7 changes: 6 additions & 1 deletion components/Game/game.modal-template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,24 @@ import { useRouter } from "next/router";
import { DeckImportCardType } from "../DeckPool/deck-import.type";
import { flow } from "lodash";
import { toast } from "react-hot-toast";
import { WebsocketMessage } from "@/lib/gamesocket/message";

type ModalTemplateType = {
isOpen: boolean;
modalType: ModalType;
setModalType: (type: ModalType) => void;
gameState: WebsocketMessage | undefined;
setPlayerState: () => (props: { pool: PoolType }) => void;
};
export const ModalContainer: React.FC<ModalTemplateType> = ({
isOpen,
modalType,
setModalType,
gameState,
setPlayerState,
}) => {
const isCommit = modalType === "commit";
const player = useRouter().query?.name as string;
const { gameState, setPlayerState } = useWebGame();
const players = gameState?.content?.players as Record<
string,
{ pool?: PoolType }
Expand Down Expand Up @@ -117,6 +121,7 @@ export const ModalContainer: React.FC<ModalTemplateType> = ({
}),
);

console.log({ modalType, isOpen });
return (
<>
<Modal isOpen={isOpen} onClose={() => !isCommit && onClose()}>
Expand Down
41 changes: 38 additions & 3 deletions pages/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import { WebGameProvider, useWebGame } from "@/lib/contexts/WebGameProvider";
import useDelayedTrue from "@/lib/hooks/useDelay";
import { Box, useDisclosure } from "@chakra-ui/react";
import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from "react";

export type ModalType = "hand" | "discard" | "deck" | "commit" | false;

Expand Down Expand Up @@ -48,21 +54,50 @@ const GamePage = () => {
<WebGameProvider>
<GameLayout>
<PositionModal {...positionDisclosure} />
<ModalContainer
<ModalWrapper
{...disclosure}
modalType={modalType}
setModalType={setModalType}
/>
<HeaderContainer openPositionModal={positionDisclosure.onOpen} />
{isReady && <BoardContainer self={query?.name as string} />}
<HandContainer setModal={setModalType} />
<HandWrapper {...{ setModalType }} />
</GameLayout>
<KeyboardListener onKeyPress={handleKeyPress} />
</WebGameProvider>
</>
);
};

/**
* Quick hack to reuse hand-container for offline
* */
const HandWrapper = ({
setModalType,
}: {
setModalType: Dispatch<SetStateAction<ModalType>>;
}) => {
const { gameState, setPlayerState } = useWebGame();
return (
<HandContainer setModal={setModalType} {...{ gameState, setPlayerState }} />
);
};

const ModalWrapper = (props: {
isOpen: boolean;
modalType: ModalType;
setModalType: (type: ModalType) => void;
}) => {
const { gameState, setPlayerState } = useWebGame();
return (
<ModalContainer
{...props}
gameState={gameState}
setPlayerState={setPlayerState}
/>
);
};

export default GamePage;

const BoardContainer = ({ self }: { self: string }) => {
Expand Down
133 changes: 133 additions & 0 deletions pages/offline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { HandContainer, ModalContainer } from "@/components/Game";
import { useLocalDeckStorage } from "@/lib/hooks";
import {
Box,
Button,
Divider,
HStack,
Input,
Text,
VStack,
useDisclosure,
} from "@chakra-ui/react";
import { useEffect, useMemo, useState } from "react";
import { ModalType } from "./game";
import { PoolType, newPool } from "@/components/DeckPool/PoolFns";
import { GameState, WebsocketMessage } from "@/lib/gamesocket/message";
import { useRouter } from "next/router";

const initGamestate: GameState = {
last_updated: "foobar",
gid: "offline",
players: {
offline: { pool: undefined },
},
};
const init: WebsocketMessage = {
error: "",
msgtype: "offline",
content: initGamestate,
};

const Offline = () => {
const { push, query } = useRouter();
const { star, decks } = useLocalDeckStorage();
const deck = decks?.find((deck) => deck.id === star);
const newDeck = useMemo(() => (deck ? newPool(deck) : undefined), [deck]);

const [gameState, setGameState] = useState<WebsocketMessage>(init);
const players = gameState?.content?.players as Record<
string,
{ pool?: PoolType }
>;
const playerState = players?.["offline"]?.pool;

const [modalType, setModalType] = useState<ModalType>(false);
const disclosure = useDisclosure();

function setPlayerState() {
return (props: { pool: PoolType }) => {
setGameState((prev) => ({
...prev,
content: {
...prev.content,
players: {
offline: { pool: props.pool },
},
} as GameState,
}));
};
}

useEffect(() => {
if (query.name) return;
push({ query: { name: "offline" } });
}, []);

useEffect(() => {
if (!newDeck) return;
setPlayerState()({ pool: newDeck });
}, [newDeck]);

useEffect(() => {
if (modalType) {
disclosure.onOpen();
} else {
disclosure.onClose();
}
}, [modalType, disclosure]);

return (
<>
<ModalContainer
{...disclosure}
modalType={modalType}
setModalType={setModalType}
setPlayerState={setPlayerState}
gameState={gameState}
/>
<Box h="100vh" bg="brand.primary">
<HandContainer
setModal={setModalType}
setPlayerState={setPlayerState}
gameState={gameState}
/>
<Box p="0.5rem">
<HStack gap="0.5rem">
<Text>{playerState?.hero?.name}</Text>
<Text>hp:{playerState?.hero?.hp}</Text>
<Text>move:{playerState?.hero?.move}</Text>
<Text>{playerState?.hero?.isRanged ? "Ranged" : "Melee"}</Text>
</HStack>
<HStack gap="0.5rem">
<Text>{playerState?.sidekick?.name}</Text>
<Text>hp:{playerState?.sidekick?.hp}</Text>
<Text>quantity:{playerState?.sidekick?.quantity}</Text>
<Text>{playerState?.sidekick?.isRanged ? "Ranged" : "Melee"}</Text>
</HStack>
<Text>{playerState?.hero?.specialAbility}</Text>
<Text>{playerState?.sidekick?.quote}</Text>
<Divider my="1rem" />
<HpButton state={playerState?.hero?.hp ?? 0} />
<HpButton state={playerState?.sidekick?.hp ?? 0} />
<HpButton state={0} />
<HpButton state={0} />
<HpButton state={0} />
</Box>
</Box>
</>
);
};

const HpButton = (props: { state?: number }) => {
const [hp, setHp] = useState(props?.state ?? 0);
return (
<HStack my="0.25rem">
<Button onClick={() => setHp((prev) => prev - 1)}>-</Button>
<Input maxW="5rem" isDisabled value={hp} />
<Button onClick={() => setHp((prev) => prev + 1)}>+</Button>
</HStack>
);
};

export default Offline;

0 comments on commit f5618a2

Please sign in to comment.