diff --git a/src/logic/convertYYYYMMDDToDate.js b/src/common/convertYYYYMMDDToDate.js similarity index 100% rename from src/logic/convertYYYYMMDDToDate.js rename to src/common/convertYYYYMMDDToDate.js diff --git a/src/logic/convertYYYYMMDDToDate.test.js b/src/common/convertYYYYMMDDToDate.test.js similarity index 100% rename from src/logic/convertYYYYMMDDToDate.test.js rename to src/common/convertYYYYMMDDToDate.test.js diff --git a/src/logic/getInitialState.js b/src/common/getInitialState.js similarity index 100% rename from src/logic/getInitialState.js rename to src/common/getInitialState.js diff --git a/src/logic/getInitialState.test.js b/src/common/getInitialState.test.js similarity index 100% rename from src/logic/getInitialState.test.js rename to src/common/getInitialState.test.js diff --git a/src/common/handleShare.js b/src/common/handleShare.js index 5666d00..ca9488b 100644 --- a/src/common/handleShare.js +++ b/src/common/handleShare.js @@ -1,12 +1,12 @@ import sendAnalytics from "./sendAnalytics"; -export function assembleShareLink({url, seed}) { - const fullUrl = seed ? `${url}?id=${seed}` : url; +export function assembleShareLink({url, seed, query="id"}) { + const fullUrl = seed ? `${url}?${query}=${seed}` : url; return fullUrl; } -export function handleShare({appName, text, url, seed}) { - const fullUrl = assembleShareLink({url, seed}); +export function handleShare({appName, text, url, seed, query}) { + const fullUrl = assembleShareLink({url, seed, query}); navigator .share({ diff --git a/src/logic/hasVisitedSince.js b/src/common/hasVisitedSince.js similarity index 53% rename from src/logic/hasVisitedSince.js rename to src/common/hasVisitedSince.js index d150972..394becd 100644 --- a/src/logic/hasVisitedSince.js +++ b/src/common/hasVisitedSince.js @@ -1,8 +1,8 @@ import {convertYYYYMMDDToDate} from "./convertYYYYMMDDToDate"; -export function hasVisitedSince() { +export function hasVisitedSince(savedStateName, resetDateString) { let lastVisitedYYYYMMDD = JSON.parse( - localStorage.getItem("blobbleLastVisited"), + localStorage.getItem(savedStateName), ); if (!lastVisitedYYYYMMDD) { @@ -11,10 +11,7 @@ export function hasVisitedSince() { const lastVisitedDate = convertYYYYMMDDToDate(lastVisitedYYYYMMDD); - const resetDate = convertYYYYMMDDToDate("20240429"); + const resetDate = convertYYYYMMDDToDate(resetDateString); - console.log(lastVisitedDate); - console.log(resetDate); - console.log(lastVisitedDate >= resetDate); return lastVisitedDate >= resetDate; } diff --git a/src/common/hasVisitedSince.test.js b/src/common/hasVisitedSince.test.js new file mode 100644 index 0000000..8258b99 --- /dev/null +++ b/src/common/hasVisitedSince.test.js @@ -0,0 +1,26 @@ +import "jest-localstorage-mock"; +import {hasVisitedSince} from "./hasVisitedSince"; + +describe("hasVisitedSince", () => { + const stateName = "testLastVisited"; + + test("returns false if lastVisited is not set", () => { + localStorage.removeItem(stateName); + expect(hasVisitedSince(stateName, "20240429")).toBe(false); + }); + + test("returns false if lastVisited is set to a date before the reset date", () => { + localStorage.setItem(stateName, JSON.stringify("20240428")); + expect(hasVisitedSince(stateName, "20240429")).toBe(false); + }); + + test("returns true if lastVisited is set to the reset date", () => { + localStorage.setItem(stateName, JSON.stringify("20240429")); + expect(hasVisitedSince(stateName, "20240429")).toBe(true); + }); + + test("returns true if lastVisited is set to a date after the reset date", () => { + localStorage.setItem(stateName, JSON.stringify("20240430")); + expect(hasVisitedSince(stateName, "20240429")).toBe(true); + }); +}); diff --git a/src/components/App.js b/src/components/App.js index 8edf64a..d372e00 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -12,28 +12,18 @@ import {gameInit} from "../logic/gameInit"; import {gameReducer} from "../logic/gameReducer"; import getDailySeed from "../common/getDailySeed"; import {gameIsSolvedQ} from "../logic/gameIsSolvedQ"; -import {getInitialState} from "../logic/getInitialState"; -import {hasVisitedSince} from "../logic/hasVisitedSince"; - -function parseUrlQuery() { - const searchParams = new URLSearchParams(document.location.search); - const query = searchParams.get("id"); - - // The seed query consists of two parts: the seed and the difficulty level, separated by an underscore - let difficultyLevel; - let seed; - if (query) { - [seed, difficultyLevel] = query.split("_"); - difficultyLevel = parseInt(difficultyLevel); - } - - return [seed, difficultyLevel]; -} +import {getInitialState} from "../common/getInitialState"; +import {hasVisitedSince} from "../common/hasVisitedSince"; +import {parseUrlQuery} from "../logic/parseUrlQuery"; export default function App() { + // If a query string was passed, + // parse it to get the data to regenerate the game described by the query string const [seed, difficultyLevel] = parseUrlQuery(); - const hasVisited = hasVisitedSince(); + // Determine when the player last visited the game + // This is used to determine whether to show the rules or an announcement instead of the game + const hasVisited = hasVisitedSince("blobbleLastVisited", "20240429"); const [lastVisited] = React.useState(getDailySeed()); React.useEffect(() => { window.localStorage.setItem( @@ -42,13 +32,15 @@ export default function App() { ); }, [lastVisited]); + // Determine what view to show the user const savedDisplay = JSON.parse( - localStorage.getItem("blobbleDisplaySavedStateName"), + localStorage.getItem("blobbleDisplay"), ); const [display, setDisplay] = React.useState( getInitialState(savedDisplay, hasVisited), ); + // Set up states that will be used by the handleAppInstalled and handleBeforeInstallPrompt listeners const [installPromptEvent, setInstallPromptEvent] = React.useState(); const [showInstallButton, setShowInstallButton] = React.useState(true); @@ -116,7 +108,7 @@ export default function App() { React.useEffect(() => { window.localStorage.setItem( - "blobbleDisplaySavedStateName", + "blobbleDisplay", JSON.stringify(display), ); }, [display]); diff --git a/src/images/icons/heart.svg b/src/images/icons/heart.svg index 0e72525..aeb0a88 100644 --- a/src/images/icons/heart.svg +++ b/src/images/icons/heart.svg @@ -1,4 +1,4 @@ - + diff --git a/src/logic/getConnectivity.js b/src/logic/getConnectivity.js new file mode 100644 index 0000000..1d39dbf --- /dev/null +++ b/src/logic/getConnectivity.js @@ -0,0 +1,28 @@ +import {getSurroundingIndexes} from "@skedwards88/word_logic"; + +// Given indexes and a grid size +// Gets the number of neighboring (diagonals included) input indexes for each index +export function getConnectivity(indexes, gridSize) { + // error if grid size is 0 or negative + if (gridSize <= 0) { + throw new Error("Grid size must be greater than 0"); + } + + // error if index exceeds the grid size + if (indexes.some((index) => index >= gridSize * gridSize || index < 0)) { + throw new Error("Index is not within grid size"); + } + + let connectivity = 0; + indexes.forEach((index) => { + const surroundingIndexesInGrid = getSurroundingIndexes({ + index, + numColumns: gridSize, + numRows: gridSize, + }); + const surroundingIndexesInInput = surroundingIndexesInGrid.filter((neighbor) => neighbor != index && indexes.includes(neighbor)); + connectivity += surroundingIndexesInInput.length; + }); + + return connectivity; +} diff --git a/src/logic/getConnectivity.test.js b/src/logic/getConnectivity.test.js new file mode 100644 index 0000000..da06d20 --- /dev/null +++ b/src/logic/getConnectivity.test.js @@ -0,0 +1,71 @@ +import {getConnectivity} from "./getConnectivity"; + +describe("getConnectivity", () => { + test("returns the number of connections (directionally matters) between the input indexes (corner row)", () => { + const indexes = [0, 1, 2]; + const gridSize = 4; + expect(getConnectivity(indexes, gridSize)).toBe(4); + }); + + test("returns the number of connections (directionally matters) between the input indexes (corner column)", () => { + const indexes = [8, 4, 0]; + const gridSize = 4; + expect(getConnectivity(indexes, gridSize)).toBe(4); + }); + + test("returns the number of connections (directionally matters) between the input indexes (square)", () => { + const indexes = [5, 6, 10, 9]; + const gridSize = 4; + expect(getConnectivity(indexes, gridSize)).toBe(12); + }); + + test("duplicate indexes are counted twice", () => { + const indexes = [5, 6, 10, 9, 5]; + const gridSize = 4; + expect(getConnectivity(indexes, gridSize)).toBe(15); + }); + + test("works with singleton indexes input", () => { + const indexes = [5]; + const gridSize = 4; + expect(getConnectivity(indexes, gridSize)).toBe(0); + }); + + test("works with empty indexes input", () => { + const indexes = []; + const gridSize = 4; + expect(getConnectivity(indexes, gridSize)).toBe(0); + }); + + test("throws an error when the indexes array has elements not in the grid", () => { + const indexes = [0, 4, 16]; + const gridSize = 4; + expect(() => getConnectivity(indexes, gridSize)).toThrow( + "Index is not within grid size", + ); + }); + + test("throws an error when the indexes array has negative elements", () => { + const indexes = [0, 4, -1]; + const gridSize = 4; + expect(() => getConnectivity(indexes, gridSize)).toThrow( + "Index is not within grid size", + ); + }); + + test("throws an error when the gridSize is 0", () => { + const indexes = [0, 1, 2]; + const gridSize = 0; + expect(() => getConnectivity(indexes, gridSize)).toThrow( + "Grid size must be greater than 0", + ); + }); + + test("throws an error when the gridSize is negative", () => { + const indexes = [0, 1, 2]; + const gridSize = -4; + expect(() => getConnectivity(indexes, gridSize)).toThrow( + "Grid size must be greater than 0", + ); + }); +}); diff --git a/src/logic/hasVisitedSince.test.js b/src/logic/hasVisitedSince.test.js deleted file mode 100644 index ac02a83..0000000 --- a/src/logic/hasVisitedSince.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import "jest-localstorage-mock"; -import {hasVisitedSince} from "./hasVisitedSince"; - -describe("hasVisitedSince", () => { - test("returns false if lastVisited is not set", () => { - localStorage.removeItem("blobbleLastVisited"); - expect(hasVisitedSince()).toBe(false); - }); - - test("returns false if lastVisited is set to a date before the reset date", () => { - localStorage.setItem("blobbleLastVisited", JSON.stringify("20240428")); - expect(hasVisitedSince()).toBe(false); - }); - - test("returns true if lastVisited is set to the reset date", () => { - localStorage.setItem("blobbleLastVisited", JSON.stringify("20240429")); - expect(hasVisitedSince()).toBe(true); - }); - - test("returns true if lastVisited is set to a date after the reset date", () => { - localStorage.setItem("blobbleLastVisited", JSON.stringify("20240430")); - expect(hasVisitedSince()).toBe(true); - }); -}); diff --git a/src/logic/parseUrlQuery.js b/src/logic/parseUrlQuery.js new file mode 100644 index 0000000..95a2c11 --- /dev/null +++ b/src/logic/parseUrlQuery.js @@ -0,0 +1,14 @@ +export function parseUrlQuery() { + const searchParams = new URLSearchParams(document.location.search); + const query = searchParams.get("id"); + + // The seed query consists of two parts: the seed and the difficulty level, separated by an underscore + let difficultyLevel; + let seed; + if (query) { + [seed, difficultyLevel] = query.split("_"); + difficultyLevel = parseInt(difficultyLevel); + } + + return [seed, difficultyLevel]; +}