diff --git a/TODO.md b/TODO.md index c243f18..601dac5 100644 --- a/TODO.md +++ b/TODO.md @@ -28,3 +28,19 @@ For screenshots: ] const pieces = [{"letters":[["P","U","Z"],["L","",""],["A","",""]],"solutionTop":3,"solutionLeft":2},{"letters":[["B"],["R"],["A"]],"solutionTop":2,"solutionLeft":9},{"letters":[["","T","H"],["","E",""],["O","R","D"]],"solutionTop":6,"solutionLeft":5},{"letters":[["M","E"]],"solutionTop":5,"solutionLeft":3},{"letters":[["","I",""],["I","N","K"]],"solutionTop":5,"solutionLeft":8},{"letters":[["W"]],"solutionTop":8,"solutionLeft":4},{"letters":[["G"]],"solutionTop":5,"solutionLeft":1},{"letters":[["Y"]],"solutionTop":6,"solutionLeft":2},{"letters":[["Z","L","E"],["","E",""],["","T",""]],"solutionTop":3,"solutionLeft":5}]; + +// todo later PR: + +- add some custom daily puzzles +- Use the hasVisited state to announce +- relocate to new file: + - getWordValidityGrids + - countingGrid +- just rename dispatch params to dispatcher everywhere +- put arrayToGrid to common or to word-logic package +- consolidate control buttons into hamburger menu (and add share button) +- add link to sponsors? +- piecesOverlapQ should use getGridFromPieces +- rules = use line-spacing in css instead of multiple br +- rules = use the icons instead of words +- improve large screen diff --git a/src/components/App.js b/src/components/App.js index fcfdbb0..a1b7801 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -3,6 +3,8 @@ import Game from "./Game"; import Heart from "./Heart"; import Rules from "./Rules"; import Stats from "./Stats"; +import CustomCreation from "./CustomCreation"; +import CustomShare from "./CustomShare"; import ControlBar from "./ControlBar"; import { handleAppInstalled, @@ -10,16 +12,23 @@ import { } from "../common/handleInstall"; import Settings from "./Settings"; import {gameInit} from "../logic/gameInit"; +import {customInit} from "../logic/customInit"; import getDailySeed from "../common/getDailySeed"; import {gameReducer} from "../logic/gameReducer"; import {parseUrlQuery} from "../logic/parseUrlQuery"; import {getInitialState} from "../common/getInitialState"; import {hasVisitedSince} from "../common/hasVisitedSince"; +import {handleShare} from "../common/handleShare"; +import {convertGridToRepresentativeString} from "../logic/convertGridToRepresentativeString"; +import {getGridFromPieces} from "../logic/getGridFromPieces"; +import {crosswordValidQ, pickRandomIntBetween} from "@skedwards88/word_logic"; +import {trie} from "../logic/trie"; +import {resizeGrid} from "../logic/resizeGrid"; 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, numLetters] = parseUrlQuery(); + const [isCustom, seed, numLetters] = parseUrlQuery(); // 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 @@ -47,6 +56,7 @@ export default function App() { { seed, numLetters, + isCustom, }, gameInit, ); @@ -57,9 +67,53 @@ export default function App() { gameInit, ); + const [customState, dispatchCustomState] = React.useReducer( + gameReducer, + {}, + customInit, + ); + // todo consolidate lastVisited and setLastOpened? const [, setLastOpened] = React.useState(Date.now()); + function handleCustomGeneration() { + // If there is nothing to share, display a message with errors + if (!customState.pieces.some((piece) => piece.boardTop >= 0)) { + throw new Error("Add some letters to the board first!"); + } + + // Validate the grid + // - The UI restricts the grid size, so don't need to validate that + // - Make sure all letters are connected + // - Make sure all horizontal and vertical words are known + const grid = getGridFromPieces({ + pieces: customState.pieces, + gridSize: customState.gridSize, + solution: false, + }); + + const {gameIsSolved, reason} = crosswordValidQ({ + grid: grid, + trie: trie, + }); + + // If the board is not valid, display a message with errors + if (!gameIsSolved) { + throw new Error(reason); + } + + // Center and resize/pad the grid + // Convert the grid to a representative string + const resizedGrid = resizeGrid(grid); + const cipherShift = pickRandomIntBetween(5, 9); + const representativeString = convertGridToRepresentativeString( + resizedGrid, + cipherShift, + ); + + return representativeString; + } + function handleVisibilityChange() { // If the visibility of the app changes to become visible, // update the state to force the app to re-render. @@ -119,6 +173,13 @@ export default function App() { ); }, [dailyGameState]); + React.useEffect(() => { + window.localStorage.setItem( + "crossjigCustomCreation", + JSON.stringify(customState), + ); + }, [customState]); + switch (display) { case "rules": return ; @@ -182,6 +243,110 @@ export default function App() { ); + case "custom": + return ( +
+
+ + + +
+ +
+ ); + + case "customError": + return ( +
+
{`Your game isn't ready to share yet: \n\n${customState.invalidReason}`}
+ +
+ ); + + case "customShare": + return ( + + ); + default: return (
diff --git a/src/components/Board.js b/src/components/Board.js index 5fb7bcb..f76a2be 100644 --- a/src/components/Board.js +++ b/src/components/Board.js @@ -85,12 +85,18 @@ function getHorizontalValidityGrid({grid, originalWords}) { return horizontalValidityGrid; } -function getWordValidityGrids({pieces, gridSize}) { - const originalWords = getWordsFromPieces({ - pieces, - gridSize, - solution: true, - }); +export function getWordValidityGrids({ + pieces, + gridSize, + includeOriginalSolution = true, +}) { + const originalWords = includeOriginalSolution + ? getWordsFromPieces({ + pieces, + gridSize, + solution: true, + }) + : []; const grid = getGridFromPieces({pieces, gridSize, solution: false}); @@ -117,15 +123,24 @@ export default function Board({ gameIsSolved, dispatchGameState, indicateValidity, + customCreation = false, }) { const boardPieces = pieces.filter( (piece) => piece.boardTop >= 0 && piece.boardLeft >= 0, ); - const overlapGrid = countingGrid(gridSize, gridSize, boardPieces); + const overlapGrid = customCreation + ? undefined + : countingGrid(gridSize, gridSize, boardPieces); + const [horizontalValidityGrid, verticalValidityGrid] = indicateValidity - ? getWordValidityGrids({pieces, gridSize}) + ? getWordValidityGrids({ + pieces, + gridSize, + includeOriginalSolution: !customCreation, + }) : [undefined, undefined]; + const pieceElements = boardPieces.map((piece) => ( setDisplay("rules")} > + + +
+ + ); +} diff --git a/src/components/GameOver.js b/src/components/GameOver.js index 4acf5cf..757319d 100644 --- a/src/components/GameOver.js +++ b/src/components/GameOver.js @@ -33,7 +33,6 @@ export default function GameOver({dispatchGameState, gameState, setDisplay}) { appName="Crossjig" text="Check out this word puzzle!" url="https://crossjig.com" - query="puzzle" seed={`${gameState.seed}_${gameState.numLetters}`} > diff --git a/src/components/Piece.js b/src/components/Piece.js index 47ebc94..c430df2 100644 --- a/src/components/Piece.js +++ b/src/components/Piece.js @@ -106,6 +106,7 @@ export default function Piece({ pieceColIndex={colIndex} overlapping={ isOnBoard && + overlapGrid && overlapGrid[piece.boardTop + rowIndex][ piece.boardLeft + colIndex ] > 1 diff --git a/src/components/Rules.js b/src/components/Rules.js index 06e1c91..eb1ebee 100644 --- a/src/components/Rules.js +++ b/src/components/Rules.js @@ -29,6 +29,9 @@ export default function Rules({setDisplay}) { challenge is easiest on Monday and gets harder over the week.

+ Click the pencil to build your own puzzle to share with friends. +
+