Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/actions/game.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Cave, DwellerCard, GameBox, Player, WoodyPlantCard } from "@/game";
import { Cave, DwellerCard, Game, GameBox, Player, WoodyPlantCard } from "@/game";
import { ScoringMode } from "@/types";

export enum GameActionType {
CreateGame = "CREATE_GAME",
LoadGame = "LOAD_GAME",
AddPlayer = "ADD_PLAYER",
SelectPlayer = "SELECT_PLAYER",
RemovePlayer = "REMOVE_PLAYER",
Expand Down Expand Up @@ -33,6 +34,21 @@ export const createGame = (payload: CreateGamePayload): CreateGameAction => ({
payload,
});

export interface LoadGamePayload {
scoringMode: ScoringMode;
game: Game;
}

export interface LoadGameAction {
type: GameActionType.LoadGame;
payload: LoadGamePayload;
}

export const loadGame = (payload: LoadGamePayload): LoadGameAction => ({
type: GameActionType.LoadGame,
payload,
});

export interface AddPlayerPayload {
player: Player;
}
Expand Down Expand Up @@ -201,6 +217,7 @@ export const removeDweller = (

export type GameAction =
| CreateGameAction
| LoadGameAction
| AddPlayerAction
| RemovePlayerAction
| SelectPlayerAction
Expand Down
7 changes: 7 additions & 0 deletions src/components/contexts/GameContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ const reducer: Reducer<State, GameAction> = (state, action) => {
game: addPlayer(game, player),
playerId: player.id,
};
} else if (action.type === GameActionType.LoadGame) {
return {
...state,
scoringMode: action.payload.scoringMode,
game: action.payload.game,
playerId: action.payload.game.players[0].id,
}
}

if (!state.game) {
Expand Down
27 changes: 25 additions & 2 deletions src/components/views/CreateGameView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { useLocation } from "wouter";

import { Stack } from "@mui/joy";

import { createGame } from "@/actions/game";
import { createGame, loadGame } from "@/actions/game";
import AppUpdateModal from "@/components/common/AppUpdateModal";
import View from "@/components/common/View";
import AppUpdateContext from "@/components/contexts/AppUpdateContext";
import GameContext from "@/components/contexts/GameContext";
import { GameBox } from "@/game";
import { importGame } from "@/game/sharing";
import { ScoringMode } from "@/types";

import CreateGameForm from "./components/CreateGameForm";
Expand All @@ -22,7 +23,9 @@ import { CreateGameFormFields } from "./types";
const CreateGameView = () => {
const [, navigate] = useLocation();
const { dispatch } = useContext(GameContext);
const { checkForUpdate } = useContext(AppUpdateContext);
const { wasUpdateRejected, checkForUpdate } =
useContext(AppUpdateContext);
const [gameState] = useLocalStorage("game", undefined);

const [defaultGameBoxes, persistGameBoxes] = useLocalStorage<GameBox[]>(
"gameBoxes",
Expand All @@ -46,6 +49,26 @@ const CreateGameView = () => {
return () => clearTimeout(handle);
}, [checkForUpdate]);

useEffect(() => {
if (!wasUpdateRejected && gameState) {
const gameImportResult = importGame();

if (gameImportResult.success) {
const { scoringMode, game } = gameImportResult;
persistGameBoxes(game.gameBoxes);
dispatch(
loadGame({
scoringMode,
game,
}),
);
navigate("/forest");
} else {
console.warn("Failed to load game", gameImportResult);
}
}
}, [wasUpdateRejected]);

const handleCreate = (values: CreateGameFormFields) => {
persistGameBoxes(values.gameBoxes);
dispatch(createGame(values));
Expand Down
10 changes: 7 additions & 3 deletions src/components/views/ExportView/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { Link } from "wouter";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ReplayIcon from "@mui/icons-material/Replay";
import { Button, Stack } from "@mui/joy";
import { useLocalStorage } from "usehooks-ts";

const Footer = () => (
<Stack
const Footer = () => {
const [, , deleteGame] = useLocalStorage("game", {});

return <Stack
direction="row"
alignItems="center"
justifyContent="space-around"
Expand All @@ -28,13 +31,14 @@ const Footer = () => (
startDecorator={<ReplayIcon />}
component={Link}
to="/new"
onClick={(() => deleteGame())}
>
<FormattedMessage
id="ScoringView.Footer.newGame"
defaultMessage="New game"
/>
</Button>
</Stack>
);
};

export default Footer;
4 changes: 3 additions & 1 deletion src/components/views/ForestView/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext } from "react";
import CountUp from "react-countup";
import { useBoolean } from "usehooks-ts";
import { useBoolean, useLocalStorage } from "usehooks-ts";
import { useLocation } from "wouter";

import PersonIcon from "@mui/icons-material/Person";
Expand All @@ -17,6 +17,7 @@ import { scorePlayer } from "@/game";
const Header = () => {
const [, navigate] = useLocation();
const { game, playerId, dispatch } = useContext(GameContext);
const [, , deleteGame] = useLocalStorage("game", {});

const {
value: isResetModalOpen,
Expand All @@ -33,6 +34,7 @@ const Header = () => {
}
};
const handleReset = () => {
deleteGame();
navigate("/new");
};

Expand Down
33 changes: 29 additions & 4 deletions src/components/views/ForestView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useContext, useMemo, useState } from "react";
import { useMediaQuery } from "usehooks-ts";
import { omit } from "lodash-es";
import { useContext, useEffect, useMemo, useState } from "react";
import { useLocalStorage, useMediaQuery } from "usehooks-ts";

import { Box, Stack } from "@mui/joy";

Expand All @@ -19,10 +20,13 @@ import GameContext from "@/components/contexts/GameContext";
import {
DwellerCard,
DwellerPosition,
Game,
WoodyPlantCard,
getDwellerCandidates,
getWoodyPlantCandidates,
} from "@/game";
import { GameDto, createPlayerExportDto } from "@/game/sharing";
import { ScoringMode } from "@/types";
import { requireGame } from "@/utils/hoc";

import Footer from "./components/Footer";
Expand All @@ -41,8 +45,22 @@ const useResponsiveSize = () => {
}
};

const gameDto: (game: Game, scoringMode: ScoringMode | null) => GameDto = (game, scoringMode) => {
return omit(
{
...game,
appVersion: import.meta.env.PACKAGE_VERSION,
scoringMode: scoringMode ?? ScoringMode.Host,
players: game.players.map(
(player) => createPlayerExportDto(game, player).player,
),
},
"deck",
);
};

const ForestView = requireGame(({ game }) => {
const { playerId, dispatch } = useContext(GameContext);
const { scoringMode, playerId, dispatch } = useContext(GameContext);

const [isAddingWoodyPlant, setIsAddingWoodyPlant] = useState(false);
const [selectedWoodyPlant, setSelectedWoodyPlant] =
Expand All @@ -56,7 +74,8 @@ const ForestView = requireGame(({ game }) => {
useState<DwellerPosition | null>();
const [selectedDweller, setSelectedDweller] = useState<DwellerCard | null>();

const forest = game.players.find((p) => p.id === playerId)?.forest;
const player = game.players.find((p) => p.id === playerId);
const forest = player?.forest;
const woodyPlantOptions = useMemo(
() => [
...getWoodyPlantCandidates(game),
Expand All @@ -79,6 +98,12 @@ const ForestView = requireGame(({ game }) => {
[game, dwellerWoodyPlantId, dwellerPosition, selectedDweller],
);

const [, persistGame] = useLocalStorage<GameDto>("game", gameDto(game, scoringMode));

useEffect(() => {
persistGame(gameDto(game, scoringMode));
}, [game]);

const WoodyPlantStackSize = useResponsiveSize();

const handleAddWoodyPlant = () => {
Expand Down
10 changes: 7 additions & 3 deletions src/components/views/ScoringView/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { Link } from "wouter";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ReplayIcon from "@mui/icons-material/Replay";
import { Button, Stack } from "@mui/joy";
import { useLocalStorage } from "usehooks-ts";

const Footer = () => (
<Stack
const Footer = () => {
const [, , deleteGame] = useLocalStorage("game", {});

return <Stack
direction="row"
alignItems="center"
justifyContent="space-around"
Expand All @@ -28,13 +31,14 @@ const Footer = () => (
startDecorator={<ReplayIcon />}
component={Link}
to="/new"
onClick={(() => deleteGame())}
>
<FormattedMessage
id="ScoringView.Footer.newGame"
defaultMessage="New game"
/>
</Button>
</Stack>
);
};

export default Footer;
2 changes: 1 addition & 1 deletion src/game/sharing/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
export const encodePlayer = (game: Game, player: Player): string =>
encode(createPlayerExportDto(game, player));

const createPlayerExportDto = (
export const createPlayerExportDto = (
game: Game,
player: Player,
): PlayerExportDto => ({
Expand Down
Loading