Browse Source

Rewriting observable logic to use the fancy new fake redux

Kirk Trombley 5 years ago
parent
commit
c9d9139e52

+ 8 - 9
client/src/components/Game.jsx

@@ -7,13 +7,12 @@ import {
   POST_GAME,
   ERROR,
 } from "../domain/GameState";
-import { gameIdStore, gameStateStore } from "../domain/store";
 import PreGame from "./screens/PreGame";
 import PreRound from "./screens/PreRound";
 import GamePanel from "./screens/GamePanel";
 import RoundSummary from "./screens/RoundSummary";
 import PlayerScores from "./screens/PlayerScores";
-import useObservable from "../hooks/useObservable";
+import { useGameState, dispatch } from "../domain/gameStore";
 
 const componentMap = {
   [PRE_GAME]: PreGame,
@@ -25,21 +24,21 @@ const componentMap = {
 }
 
 const paramRouter = {
-  join: PRE_ROUND,
-  summary: POST_GAME,
+  join: dispatch.startGame,
+  summary: dispatch.endGame,
 }
 
 const Game = () => {
-  const gameState = useObservable(gameStateStore);
+  const gameState = useGameState();
   
   const url = new URL(window.location.href);
   for (let [param, value] of url.searchParams.entries()) {
-    const state = paramRouter[param];
-    if (state) {
+    const route = paramRouter[param];
+    if (route) {
       url.searchParams.delete(param);
       window.history.replaceState({}, document.title, url.href);
-      gameIdStore.set(value);
-      gameStateStore.set(state);
+      dispatch.setGameId(value);
+      route();
       break;
     }
   }

+ 4 - 6
client/src/components/screens/GamePanel/GamePanel.jsx

@@ -5,8 +5,7 @@ import Loading from '../../util/Loading';
 import GuessPane from "./GuessPane";
 import PositionedStreetView from "./PositionedStreetView";
 import useRoundInfo from "../../../hooks/useRoundInfo";
-import { lastRoundStore, gameStateStore } from '../../../domain/store';
-import { POST_ROUND, POST_GAME } from '../../../domain/GameState';
+import { dispatch } from '../../../domain/gameStore';
 
 const Container = styled.div`
   height: 100%;
@@ -31,13 +30,13 @@ const StreetViewWrapper = styled.div`
   flex: 3;
 `
 
-const GamePanelContainer = ({ onGameEnd }) => {
+const GamePanelContainer = () => {
   const [submitDisabled, setSubmitDisabled] = useState(false);
   const [selectedPoint, setSelectedPoint] = useState(null);
   const [finished, roundInfo] = useRoundInfo();
 
   if (finished) {
-    gameStateStore.set(POST_GAME);
+    dispatch.endGame();
     return <Loading/>
   }
 
@@ -49,14 +48,13 @@ const GamePanelContainer = ({ onGameEnd }) => {
   const handleSubmitGuess = async () => {
     setSubmitDisabled(true);
     const { score, totalScore } = await sendGuess(currentRound, selectedPoint || { timeout: true });
-    lastRoundStore.set({
+    dispatch.endRound({
       roundNum: currentRound,
       selectedPoint,
       targetPoint,
       score,
       totalScore,
     });
-    gameStateStore.set(POST_ROUND);
   }
 
   return (

+ 3 - 9
client/src/components/screens/PlayerScores/PlayerScores.jsx

@@ -5,10 +5,8 @@ import Button from "../../util/Button";
 import PlayerScoreTile from "./PlayerScoreTile";
 import usePlayerScores from '../../../hooks/usePlayerScores';
 import ClickToCopy from '../../util/ClickToCopy';
-import useObservable from '../../../hooks/useObservable';
-import { gameIdStore, gameJoinedStore, gameStateStore } from '../../../domain/store';
-import { PRE_GAME } from '../../../domain/GameState';
 import HeaderAndFooter from '../../util/HeaderAndFooter';
+import { useGameId, dispatch } from '../../../domain/gameStore';
 
 const Container = styled.div`
   display: flex;
@@ -52,12 +50,8 @@ const getUrl = gameId => {
 }
 
 export default () => {
-  const gameId = useObservable(gameIdStore);
+  const gameId = useGameId();
   const scores = usePlayerScores();
-  const onReturnToStart = () => {
-    gameJoinedStore.set(false);
-    gameStateStore.set(PRE_GAME);
-  }
 
   if (!scores) {
     return <Loading/>
@@ -77,7 +71,7 @@ export default () => {
         <LowerContainer>
           <Label>This page can be directly linked with:</Label>
           <Label><ClickToCopy text={getUrl(gameId)} /></Label>
-          <StyledButton onClick={onReturnToStart}>Return to Start</StyledButton>
+          <StyledButton onClick={dispatch.resetGame}>Return to Start</StyledButton>
         </LowerContainer>
       </Container>
     </HeaderAndFooter>

+ 5 - 7
client/src/components/screens/PreGame/PreGame.jsx

@@ -4,10 +4,8 @@ import Loading from "../../util/Loading";
 import PlayerNameInput from "./../../util/PlayerNameInput";
 import { createGame } from "../../../domain/GGSHService";
 import NewGameInput from "./NewGameInput";
-import useObservable from "../../../hooks/useObservable";
-import { playerNameStore, gameIdStore, gameJoinedStore, gameStateStore } from "../../../domain/store";
 import HeaderAndFooter from "../../util/HeaderAndFooter";
-import { PRE_ROUND } from "../../../domain/GameState";
+import { usePlayerName, dispatch } from "../../../domain/gameStore";
 
 const Container = styled.div`
   display: flex;
@@ -35,7 +33,7 @@ const Divider = styled.hr`
 
 export default () => {
   const [loading, setLoading] = useState(false);
-  const playerName = useObservable(playerNameStore);
+  const playerName = usePlayerName();
   const [timer, setTimer] = useState(300);
 
   if (loading) {
@@ -45,9 +43,9 @@ export default () => {
   const onCreateGame = async () => {
     setLoading(true);
     const gameId = await createGame(timer);
-    gameIdStore.set(gameId);
-    gameJoinedStore.set(true);
-    gameStateStore.set(PRE_ROUND);
+    dispatch.setGameId(gameId);
+    dispatch.joinGame();
+    dispatch.startGame();
   };
   const cannotCreateGame = !playerName || playerName.length === 0;
 

+ 9 - 11
client/src/components/screens/PreRound/PreRound.jsx

@@ -7,10 +7,8 @@ import PlayerNameInput from "../../util/PlayerNameInput";
 import LobbyPlayerList from "./LobbyPlayerList";
 import { joinGame } from "../../../domain/GGSHService";
 import DelayedButton from "../../util/DelayedButton";
-import useObservable from "../../../hooks/useObservable";
-import { playerNameStore, gameIdStore, gameJoinedStore, gameStateStore } from "../../../domain/store";
-import { IN_ROUND } from "../../../domain/GameState";
 import HeaderAndFooter from "../../util/HeaderAndFooter";
+import { dispatch, useGameId, usePlayerName, useGameJoined } from "../../../domain/gameStore";
 
 const InfoContainer = styled.div`
   flex: 3;
@@ -53,8 +51,8 @@ const getUrl = gameId => {
 }
 
 const LobbyInfo = () => {
-  const gameId = useObservable(gameIdStore);
-  const playerName = useObservable(playerNameStore);
+  const gameId = useGameId();
+  const playerName = usePlayerName();
 
   return (
     <InfoContainer>
@@ -62,7 +60,7 @@ const LobbyInfo = () => {
       <Label>Use this link to invite other players:</Label>
       <Label><ClickToCopy text={getUrl(gameId)} /></Label>
       <StyledDelayButton 
-        onEnd={() => gameStateStore.set(IN_ROUND)} 
+        onEnd={dispatch.startRound} 
         countDownFormatter={rem => `Click to cancel, ${rem}s...`}
       >
         Start Game
@@ -72,8 +70,8 @@ const LobbyInfo = () => {
 };
 
 const JoinForm = () => {
-  const gameId = useObservable(gameIdStore);
-  const playerName = useObservable(playerNameStore);
+  const gameId = useGameId();
+  const playerName = usePlayerName();
   const [loading, setLoading] = useState(false);
   const [failed, setFailed] = useState(false);
 
@@ -93,8 +91,8 @@ const JoinForm = () => {
       return;
     }
     setFailed(false);
-    playerNameStore.set(playerName);
-    gameJoinedStore.set(true);
+    dispatch.setPlayerName(playerName);
+    dispatch.joinGame();
   };
   const cannotJoinGame = !playerName || playerName.length === 0;
 
@@ -109,7 +107,7 @@ const JoinForm = () => {
 }
 
 const PreRound = ({ onStart }) => {
-  const joined = useObservable(gameJoinedStore);
+  const joined = useGameJoined();
 
   return (
     <HeaderAndFooter>

+ 4 - 7
client/src/components/screens/RoundSummary/RoundInfoPane.jsx

@@ -5,9 +5,7 @@ import usePlayerScores from "../../../hooks/usePlayerScores";
 import DelayedButton from "../../util/DelayedButton";
 import useRoundInfo from "../../../hooks/useRoundInfo";
 import Button from "../../util/Button";
-import useObservable from "../../../hooks/useObservable";
-import { lastRoundStore, gameStateStore } from "../../../domain/store";
-import { IN_ROUND } from "../../../domain/GameState";
+import { useLastRound, dispatch } from "../../../domain/gameStore";
 
 const useTopScorer = (roundNum) => {
   const scores = usePlayerScores();
@@ -63,10 +61,9 @@ export default () => {
     targetPoint,
     score,
     totalScore,
-  } = useObservable(lastRoundStore);
+  } = useLastRound();
   const { topPlayer, topScore } = useTopScorer(roundNum);
   const [gameFinished] = useRoundInfo()
-  const onNext = () => gameStateStore.set(IN_ROUND);
 
   return (
     <Container>
@@ -82,10 +79,10 @@ export default () => {
       {topPlayer ? <span className="round-summary__top-score">Current best is {topPlayer} with {topScore}</span> : null}
       {
         gameFinished 
-          ? <FinishedButton onClick={onNext}>
+          ? <FinishedButton onClick={dispatch.startRound}>
               View Summary
             </FinishedButton>
-          : <NextButton onEnd={onNext} countDownFormatter={rem => `Click to cancel, ${rem}s...`}>
+          : <NextButton onEnd={dispatch.startRound} countDownFormatter={rem => `Click to cancel, ${rem}s...`}>
               Next Round
             </NextButton>
       }

+ 2 - 3
client/src/components/screens/RoundSummary/RoundSummary.jsx

@@ -3,8 +3,7 @@ import styled from "styled-components";
 import useMap from "../../../hooks/useMap";
 import useMarkedPoints from "./useMarkedPoints";
 import RoundInfoPane from "./RoundInfoPane";
-import useObservable from "../../../hooks/useObservable";
-import { lastRoundStore } from "../../../domain/store";
+import { useLastRound } from "../../../domain/gameStore";
 
 const Container = styled.div`
   flex: 1;
@@ -31,7 +30,7 @@ export default () => {
   const {
     selectedPoint,
     targetPoint,
-  } = useObservable(lastRoundStore);
+  } = useLastRound();
   const mapDivRef = useRef(null);
   // TODO dynamically determine this zoom level?
   const mapRef = useMap(mapDivRef, targetPoint.lat, targetPoint.lng, 4);

+ 3 - 4
client/src/components/util/PlayerNameInput.jsx

@@ -1,8 +1,7 @@
 import React from "react";
 import styled from "styled-components";
 import Input from "./Input";
-import { playerNameStore } from "../../domain/store";
-import useObservable from "../../hooks/useObservable";
+import { usePlayerName, dispatch } from "../../domain/gameStore";
 
 const Container = styled.div`
   display: flex;
@@ -20,7 +19,7 @@ const FormInput = styled(Input)`
 `
 
 export default () => {
-  const playerName = useObservable(playerNameStore)
+  const playerName = usePlayerName();
   
   return (
     <Container>
@@ -28,7 +27,7 @@ export default () => {
       <FormInput
         type="text" 
         value={playerName || ""} 
-        onChange={({ target }) => playerNameStore.set(target.value)} 
+        onChange={({ target }) => dispatch.setPlayerName(target.value)} 
       />
     </Container>
   )

+ 9 - 9
client/src/domain/GGSHService.js

@@ -1,4 +1,4 @@
-import { playerNameStore, gameIdStore } from "./store";
+import { selector } from "./gameStore";
 
 const API_BASE = "https://kirkleon.ddns.net/terrassumptions/api";
 
@@ -15,7 +15,7 @@ export const getStatus = async () => {
 }
 
 export const createGame = async (timer) => {
-    const name = playerNameStore.get();
+    const name = selector.playerName();
     const res = await fetch(`${API_BASE}/game`, {
         method: "PUT",
         headers: {
@@ -32,7 +32,7 @@ export const createGame = async (timer) => {
 }
 
 export const gameInfo = async () => {
-    const gameId = gameIdStore.get();
+    const gameId = selector.gameId();
     const res = await fetch(`${API_BASE}/game/${gameId}`);
     if (!res.ok) {
         throw Error(res.statusText);
@@ -41,8 +41,8 @@ export const gameInfo = async () => {
 }
 
 export const joinGame = async () => {
-    const gameId = gameIdStore.get();
-    const name = playerNameStore.get();
+    const gameId = selector.gameId();
+    const name = selector.playerName();
     const res = await fetch(`${API_BASE}/game/${gameId}/join`, {
         method: "POST",
         headers: {
@@ -55,8 +55,8 @@ export const joinGame = async () => {
 }
 
 export const getCurrentRound = async () => {
-    const gameId = gameIdStore.get();
-    const name = playerNameStore.get();
+    const gameId = selector.gameId();
+    const name = selector.playerName();
     const res = await fetch(`${API_BASE}/game/${gameId}/current`, {
         headers: {
             "Authorization": `Name ${name}`
@@ -69,8 +69,8 @@ export const getCurrentRound = async () => {
 }
 
 export const sendGuess = async (round, point) => {
-    const gameId = gameIdStore.get();
-    const name = playerNameStore.get();
+    const gameId = selector.gameId();
+    const name = selector.playerName();
     const res = await fetch(`${API_BASE}/game/${gameId}/guesses/${round}`, {
         method: "POST",
         headers: {

+ 29 - 0
client/src/domain/gameStore.js

@@ -0,0 +1,29 @@
+import { PRE_GAME, PRE_ROUND, IN_ROUND, POST_ROUND, POST_GAME } from "./GameState";
+import { createStore } from "../store";
+
+export const [
+  {
+    useGameId,
+    usePlayerName,
+    useLastRound,
+    useGameJoined,
+    useGameState,
+  },
+  dispatch,
+  selector,
+] = createStore({
+  gameId: null,
+  playerName: null,
+  lastRound: null,
+  gameJoined: false,
+  gameState: PRE_GAME,
+}, {
+  setGameId: gameId => ({ gameId }),
+  setPlayerName: playerName => ({ playerName }),
+  joinGame: () => ({ gameJoined: true }),
+  resetGame: () => ({ gameJoined: false, gameState: PRE_GAME }),
+  startGame: () => ({ gameState: PRE_ROUND }),
+  startRound: () => ({ gameState: IN_ROUND }),
+  endRound: lastRound => ({ lastRound, gameState: POST_ROUND }),
+  endGame: () => ({ gameState: POST_GAME }),
+});

+ 0 - 8
client/src/domain/store.js

@@ -1,8 +0,0 @@
-import { createObservable } from "../hooks/useObservable";
-import { PRE_GAME } from "./GameState";
-
-export const gameIdStore = createObservable(null);
-export const playerNameStore = createObservable(null);
-export const lastRoundStore = createObservable(null);
-export const gameJoinedStore = createObservable(false);
-export const gameStateStore = createObservable(PRE_GAME);

+ 0 - 34
client/src/hooks/useObservable.jsx

@@ -1,34 +0,0 @@
-import { useState, useEffect } from "react";
-
-export const createObservable = (
-  initial,
-  {
-    equality = (x, y) => x === y,
-  } = {}
-) => {
-  let _val = initial;
-  let _listeners = [];
-
-  return {
-    _sub: listener => {
-      _listeners.push(listener);
-      return () => {
-        _listeners = _listeners.filter(ln => ln !== listener);
-      };
-    },
-    get: () => _val,
-    set: newVal => {
-      const old = _val;
-      _val = newVal;
-      if (!equality(old, newVal)) {
-        _listeners.forEach(ln => ln(newVal));
-      }
-    },
-  }
-};
-
-export default obs => {
-  const [val, setVal] = useState(obs.get());
-  useEffect(() => obs._sub(setVal), [obs]);
-  return val
-};

+ 2 - 3
client/src/hooks/usePlayerScores.jsx

@@ -1,10 +1,9 @@
 import { useState, useEffect } from 'react';
 import { gameInfo } from '../domain/GGSHService';
-import useObservable from './useObservable';
-import { gameIdStore } from '../domain/store';
+import { useGameId } from '../domain/gameStore';
 
 export default () => {
-  const gameId = useObservable(gameIdStore);
+  const gameId = useGameId();
   const [scores, setScores] = useState(null);
 
   useEffect(() => {

+ 3 - 4
client/src/hooks/useRoundInfo.jsx

@@ -1,11 +1,10 @@
 import { useState, useEffect } from 'react';
 import { getCurrentRound } from "../domain/GGSHService";
-import useObservable from './useObservable';
-import { gameIdStore, playerNameStore } from '../domain/store';
+import { useGameId, usePlayerName } from '../domain/gameStore';
 
 export default () => {
-  const gameId = useObservable(gameIdStore);
-  const playerName = useObservable(playerNameStore);
+  const gameId = useGameId();
+  const playerName = usePlayerName();
   const [finished, setFinished] = useState(false);
   const [roundInfo, setRoundInfo] = useState({currentRound: null, targetPoint: null, roundSeconds: null});