From 5123c03b8cce9bc0b3ecfc5c492f02a8e1d2eba4 Mon Sep 17 00:00:00 2001 From: skedwards88 Date: Thu, 2 Nov 2023 08:45:02 -0700 Subject: [PATCH] connectivity --- src/logic/generatePuzzle.js | 23 +++++++++-- src/logic/generatePuzzle.test.js | 66 +++++++++++++++++++++++++++++++ src/logic/getConnectivityScore.js | 48 ++++++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/logic/generatePuzzle.test.js create mode 100644 src/logic/getConnectivityScore.js diff --git a/src/logic/generatePuzzle.js b/src/logic/generatePuzzle.js index c46b39b..43d65df 100644 --- a/src/logic/generatePuzzle.js +++ b/src/logic/generatePuzzle.js @@ -4,16 +4,21 @@ import { centerGrid } from "./centerGrid"; import { getMaxShifts } from "./getMaxShifts"; import { makePieces } from "./makePieces"; import { shuffleArray } from "@skedwards88/word_logic"; +import { getConnectivityScore } from "./getConnectivityScore"; -export function generatePuzzle({ gridSize, minLetters, seed }) { +export function generatePuzzle({ gridSize, minLetters, minConnectivity = 20, seed }) { let count = 0; let foundPuzzleWithAcceptableSingletons = false; + let foundPuzzleWithAcceptableConnectivity = false; const maxFractionSingles = 0.1; // Create a new seedable random number generator let pseudoRandomGenerator = seed ? seedrandom(seed) : seedrandom(); - while (!foundPuzzleWithAcceptableSingletons) { + while ( + !foundPuzzleWithAcceptableSingletons || + !foundPuzzleWithAcceptableConnectivity + ) { count++; // Generate an interconnected grid of words @@ -51,13 +56,25 @@ export function generatePuzzle({ gridSize, minLetters, seed }) { foundPuzzleWithAcceptableSingletons = numSingletons / numPieces < maxFractionSingles; - if (foundPuzzleWithAcceptableSingletons || count > 100) { + const connectivityScore = getConnectivityScore({ + pieces: pieceData, + gridSize, + }); + foundPuzzleWithAcceptableConnectivity = connectivityScore >= minConnectivity; + + if ( + (foundPuzzleWithAcceptableSingletons && + foundPuzzleWithAcceptableConnectivity) || + count > 100 + ) { + console.log(count) return { pieces: pieceData, maxShiftLeft: maxShiftLeft, maxShiftRight: maxShiftRight, maxShiftUp: maxShiftUp, maxShiftDown: maxShiftDown, + count, }; } } diff --git a/src/logic/generatePuzzle.test.js b/src/logic/generatePuzzle.test.js new file mode 100644 index 0000000..9462690 --- /dev/null +++ b/src/logic/generatePuzzle.test.js @@ -0,0 +1,66 @@ +import { generatePuzzle } from "./generatePuzzle"; +import {getConnectivityScore} from "./getConnectivityScore" + +function getAverage(array) { + const sum = array.reduce( + (currentSum, currentValue) => currentSum + currentValue, + 0 + ); + return sum / array.length; +} + +// todo these tests were just playing around to see how grid size affects connectivity and count +// would like to take a fresh look to see: +// - if the count and connectivity (average and spread) changes as grid size changes for a set number of letters +// - if/how much the count changes as you force a higher connectivity +describe("generatePuzzle", () => { + test("count does not increase ", () => { + const numIterations = 500; + const numLetters = 20; + const gridSizes = [8, 7, 6]; + + let averages = {}; + + gridSizes.forEach((gridSize) => { + let connectivityScores = []; + let counts = []; + + for (let iteration = 0; iteration < numIterations; iteration++) { + const { pieces, count } = generatePuzzle({ + gridSize: gridSize, + minLetters: numLetters, + }); + counts.push(count); + const connectivityScore = getConnectivityScore({ pieces, gridSize }); + connectivityScores.push(connectivityScore); + } + + averages[gridSize] = { + averageCount: getAverage(counts), + averageConnectivity: getAverage(connectivityScores), + }; + }); + + // The average number of attempts to generate the puzzle + // for each grid size should not be more than this + // const countTolerance = 2; + // expect( + // Math.abs( + // averages[Math.min(...gridSizes)].averageCount - + // averages[Math.max(...gridSizes)].averageCount + // ) + // ).toBeLessThanOrEqual(countTolerance); + + // The average connectivity for the smaller grid should be higher than + // that of the larger grid + gridSizes.forEach((gridSize) => + console.log(`${gridSize}: ${averages[gridSize].averageCount}`) + ); + gridSizes.forEach((gridSize) => + console.log(`${gridSize}: ${averages[gridSize].averageConnectivity}`) + ); + // expect( + // averages[Math.min(...gridSizes)].averageConnectivity + // ).toBeGreaterThanOrEqual(averages[Math.max(...gridSizes)].averageConnectivity); + }); +}); diff --git a/src/logic/getConnectivityScore.js b/src/logic/getConnectivityScore.js new file mode 100644 index 0000000..dfb9106 --- /dev/null +++ b/src/logic/getConnectivityScore.js @@ -0,0 +1,48 @@ +export function getConnectivityScore({ pieces, gridSize }) { + // Convert the pieces to a grid of bools indicating + // whether there is a letter at each position in the grid + let grid = Array.from({ length: gridSize }, () => + Array(gridSize).fill(false) + ); + for (let index = 0; index < pieces.length; index++) { + const letters = pieces[index].letters; + let top = pieces[index].solutionTop; + for (let rowIndex = 0; rowIndex < letters.length; rowIndex++) { + let left = pieces[index].solutionLeft; + for (let colIndex = 0; colIndex < letters[rowIndex].length; colIndex++) { + if (letters[rowIndex][colIndex]) { + grid[top][left] = true; + } + left += 1; + } + top += 1; + } + } + + // The connectivity score is the percentage + // of letters with at least 2 neighbors that are in a different line + // (e.g.top and right counts, top and bottom does not) + let numLetters = 0; + let numLettersWithConnectivity = 0; + for (let rowIndex = 0; rowIndex < grid.length; rowIndex++) { + for (let colIndex = 0; colIndex < grid[0].length; colIndex++) { + const isLetter = grid[rowIndex][colIndex]; + if (isLetter) { + numLetters++; + const hasRightNeighbor = grid?.[rowIndex + 1]?.[colIndex]; + const hasLeftNeighbor = grid?.[rowIndex - 1]?.[colIndex]; + const hasBottomNeighbor = grid?.[rowIndex]?.[colIndex + 1]; + const hasTopNeighbor = grid?.[rowIndex]?.[colIndex - 1]; + if ( + (hasLeftNeighbor && hasTopNeighbor) || + (hasTopNeighbor && hasRightNeighbor) || + (hasRightNeighbor && hasBottomNeighbor) || + (hasBottomNeighbor && hasLeftNeighbor) + ) { + numLettersWithConnectivity++; + } + } + } + } + return 100 * (numLettersWithConnectivity / numLetters); +}