diff --git a/src/components/Game.js b/src/components/Game.js index 091a651..5878ff3 100644 --- a/src/components/Game.js +++ b/src/components/Game.js @@ -58,6 +58,7 @@ function GameMessage({gameState}) { function Game({dispatchGameState, gameState, isDaily}) { const gameOver = gameIsSolvedQ(gameState.foundSolutions); + const gridSize = Math.sqrt(gameState.letters.length); return (
@@ -85,7 +86,7 @@ function Game({dispatchGameState, gameState, isDaily}) { )} -
+
{gameState.letters.map((letter, index) => ( -
{boxes}
+
{boxes}
{word}
); diff --git a/src/logic/gameInit.js b/src/logic/gameInit.js index 098e8a9..2d87de6 100644 --- a/src/logic/gameInit.js +++ b/src/logic/gameInit.js @@ -67,13 +67,15 @@ export function gameInit({ return {...savedState, playedIndexes: [], result: ""}; } - const gridSize = 4; - difficultyLevel = isDaily ? getDifficultyLevelForDay() : difficultyLevel || 3; const [minWordLength, maxWordLength] = getShapeSizeForDifficulty(difficultyLevel); + // if the min word length is >=5, set the grid size to 5; otherwise, set it to 4 + // This helps prevent shapes that have a low number of possible placements + const gridSize = minWordLength >= 5 ? 5 : 4; + const [letters, shapes, officialSolutions] = getGame({ gridSize, minWordLength, diff --git a/src/logic/getColumnIndex.js b/src/logic/getColumnIndex.js new file mode 100644 index 0000000..3dae6df --- /dev/null +++ b/src/logic/getColumnIndex.js @@ -0,0 +1,9 @@ +// Gets the column index of a given index in a grid, assuming a square grid +export function getColumnIndex(index, gridSize) { + // error if index exceeds the grid size + if (index >= gridSize * gridSize || index < 0) { + throw new Error("Index is not within grid size"); + } + + return index % gridSize; +} diff --git a/src/logic/getColumnIndex.test.js b/src/logic/getColumnIndex.test.js new file mode 100644 index 0000000..12baa26 --- /dev/null +++ b/src/logic/getColumnIndex.test.js @@ -0,0 +1,56 @@ +import {getColumnIndex} from "./getColumnIndex"; + +describe("getColumnIndex", () => { + test("returns the column index of a given index (grid size 4)", () => { + const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + const gridSize = 4; + const expectedColumns = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]; + const result = indexes.map((index) => getColumnIndex(index, gridSize)); + expect(result).toEqual(expectedColumns); + }); + + test("returns the column index of a given index (grid size 5)", () => { + const indexes = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, + ]; + const gridSize = 5; + const expectedColumns = [ + 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, + ]; + const result = indexes.map((index) => getColumnIndex(index, gridSize)); + expect(result).toEqual(expectedColumns); + }); + + test("returns the column index of a given index (grid size 1)", () => { + const index = 0; + const gridSize = 1; + const expectedColumn = 0; + const result = getColumnIndex(index, gridSize); + expect(result).toEqual(expectedColumn); + }); + + test("errors if index exceeds gridSize", () => { + const index = 4; + const gridSize = 2; + expect(() => getColumnIndex(index, gridSize)).toThrow( + "Index is not within grid size", + ); + }); + + test("errors if index is negative", () => { + const index = -1; + const gridSize = 2; + expect(() => getColumnIndex(index, gridSize)).toThrow( + "Index is not within grid size", + ); + }); + + test("errors if gridSize is 0", () => { + const index = 0; + const gridSize = 0; + expect(() => getColumnIndex(index, gridSize)).toThrow( + "Index is not within grid size", + ); + }); +}); diff --git a/src/logic/getLettersAndShapes.js b/src/logic/getLettersAndShapes.js index 298635a..de1ceaf 100644 --- a/src/logic/getLettersAndShapes.js +++ b/src/logic/getLettersAndShapes.js @@ -4,6 +4,7 @@ import {trie} from "./trie"; import {shuffleArray} from "@skedwards88/word_logic"; import {omitDuplicateWordsAcrossShapes} from "./omitDuplicateWordsAcrossShapes"; import {centerIndexes} from "./centerIndexes"; +import {omitShapesThatExceedSize} from "./omitShapesThatExceedSize"; export function getLettersAndShapes({ gridSize, @@ -23,7 +24,16 @@ export function getLettersAndShapes({ trie: trie, }); - const shuffledWordIndexes = shuffleArray(wordIndexes, pseudoRandomGenerator); + // Remove wordIndexes that exceed a shape width or height of gridSize - 1 + const wordIndexesOfAppropriateSize = omitShapesThatExceedSize({ + wordIndexes, + gridSize, + }); + + const shuffledWordIndexes = shuffleArray( + wordIndexesOfAppropriateSize, + pseudoRandomGenerator, + ); // Figure out what shape each word makes // by centering the word indexes in the grid diff --git a/src/logic/getRowIndex.js b/src/logic/getRowIndex.js new file mode 100644 index 0000000..5b0f150 --- /dev/null +++ b/src/logic/getRowIndex.js @@ -0,0 +1,9 @@ +// Gets the row index of a given index in a grid, assuming a square grid +export function getRowIndex(index, gridSize) { + // error if index exceeds the grid size + if (index >= gridSize * gridSize || index < 0) { + throw new Error("Index is not within grid size"); + } + + return Math.floor(index / gridSize); +} diff --git a/src/logic/getRowIndex.test.js b/src/logic/getRowIndex.test.js new file mode 100644 index 0000000..5401f96 --- /dev/null +++ b/src/logic/getRowIndex.test.js @@ -0,0 +1,56 @@ +import {getRowIndex} from "./getRowIndex"; + +describe("getRowIndex", () => { + test("returns the row index of a given index (grid size 4)", () => { + const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + const gridSize = 4; + const expectedRows = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]; + const result = indexes.map((index) => getRowIndex(index, gridSize)); + expect(result).toEqual(expectedRows); + }); + + test("returns the row index of a given index (grid size 5)", () => { + const indexes = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, + ]; + const gridSize = 5; + const expectedRows = [ + 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, + ]; + const result = indexes.map((index) => getRowIndex(index, gridSize)); + expect(result).toEqual(expectedRows); + }); + + test("returns the row index of a given index (grid size 1)", () => { + const index = 0; + const gridSize = 1; + const expectedRow = 0; + const result = getRowIndex(index, gridSize); + expect(result).toEqual(expectedRow); + }); + + test("errors if index exceeds gridSize", () => { + const index = 4; + const gridSize = 2; + expect(() => getRowIndex(index, gridSize)).toThrow( + "Index is not within grid size", + ); + }); + + test("errors if index is negative", () => { + const index = -1; + const gridSize = 2; + expect(() => getRowIndex(index, gridSize)).toThrow( + "Index is not within grid size", + ); + }); + + test("errors if gridSize is 0", () => { + const index = 0; + const gridSize = 0; + expect(() => getRowIndex(index, gridSize)).toThrow( + "Index is not within grid size", + ); + }); +}); diff --git a/src/logic/omitShapesThatExceedSize.js b/src/logic/omitShapesThatExceedSize.js new file mode 100644 index 0000000..de1bb9f --- /dev/null +++ b/src/logic/omitShapesThatExceedSize.js @@ -0,0 +1,22 @@ +import {getColumnIndex} from "./getColumnIndex"; +import {getRowIndex} from "./getRowIndex"; + +// Remove wordIndexes that exceed a shape width or height of gridSize - 1 +export function omitShapesThatExceedSize({wordIndexes, gridSize}) { + const filteredWordIndexes = wordIndexes.filter((indexes) => { + const rowIndexes = indexes.map((index) => getRowIndex(index, gridSize)); + const columnIndexes = indexes.map((index) => + getColumnIndex(index, gridSize), + ); + const maxRow = Math.max(...rowIndexes); + const minRow = Math.min(...rowIndexes); + const maxColumn = Math.max(...columnIndexes); + const minColumn = Math.min(...columnIndexes); + + return ( + maxRow - minRow < gridSize - 1 && maxColumn - minColumn < gridSize - 1 + ); + }); + + return filteredWordIndexes; +} diff --git a/src/logic/omitShapesThatExceedSize.test.js b/src/logic/omitShapesThatExceedSize.test.js new file mode 100644 index 0000000..b923e7e --- /dev/null +++ b/src/logic/omitShapesThatExceedSize.test.js @@ -0,0 +1,103 @@ +import {omitShapesThatExceedSize} from "./omitShapesThatExceedSize"; + +describe("omitShapesThatExceedSize", () => { + test("removes wordIndexes that exceed a shape width or height of gridSize - 1 (grid size 5)", () => { + const wordIndexes = [ + [0, 1, 2, 3], // width 4, height 1 + [2, 3, 4, 0, 1], // width 5, height 1 + [15, 18, 19, 20, 16, 17], // width 5, height 2 + [10, 11, 12, 13, 14, 15, 16], // width 5, height 2 + [0, 1, 2, 3, 5, 6, 7, 8], // width 4, height 2 + [15, 10, 5, 0], // width 1, height 4 + [2, 7, 12, 17, 22], // width 1, height 5 + [0, 5, 10, 15, 20, 2], // width 2, height 5 + [3, 7, 11, 15], // width 4, height 4 + [0, 6, 12, 18, 24], // width 5, height 5 + ]; + const gridSize = 5; + const expectedWordIndexes = [ + [0, 1, 2, 3], // width 4, height 1 + [0, 1, 2, 3, 5, 6, 7, 8], // width 4, height 2 + [15, 10, 5, 0], // width 1, height 4 + [3, 7, 11, 15], // width 4, height 4 + ]; + const result = omitShapesThatExceedSize({wordIndexes, gridSize}); + expect(result).toEqual(expectedWordIndexes); + }); + + test("removes wordIndexes that exceed a shape width or height of gridSize - 1 (grid size 4)", () => { + const wordIndexes = [ + [1, 2, 3], // width 3, height 1 + [0, 1, 2, 3], // width 4, height 1 + [2, 3, 7, 1], // width 3, height 2 + [2, 3, 4, 0, 1], // width 4, height 2 + [7, 8, 9, 10, 11, 12], // width 4, height 3 + [8, 4, 9, 10, 12], // width 3, height 3 + [12, 8, 4, 0], // width 1, height 4 + [12, 8, 4], // width 1, height 3 + [0, 4, 8, 12, 5], // width 2, height 4 + [0, 4, 8, 5], // width 2, height 3 + [3, 6, 9, 12], // width 4, height 4 + ]; + const gridSize = 4; + const expectedWordIndexes = [ + [1, 2, 3], // width 3, height 1 + [2, 3, 7, 1], // width 3, height 2 + [8, 4, 9, 10, 12], // width 3, height 3 + [12, 8, 4], // width 1, height 3 + [0, 4, 8, 5], // width 2, height 3 + ]; + const result = omitShapesThatExceedSize({wordIndexes, gridSize}); + expect(result).toEqual(expectedWordIndexes); + }); + + test("works with empty wordIndexes input", () => { + const wordIndexes = []; + const gridSize = 4; + const expectedWordIndexes = []; + const result = omitShapesThatExceedSize({wordIndexes, gridSize}); + expect(result).toEqual(expectedWordIndexes); + }); + + test("works with singleton wordIndexes input", () => { + const wordIndexes = [[0, 1, 2, 3]]; + const gridSize = 4; + const expectedWordIndexes = []; + const result = omitShapesThatExceedSize({wordIndexes, gridSize}); + expect(result).toEqual(expectedWordIndexes); + }); + + test("does not remove any indexes if all are within size", () => { + const wordIndexes = [ + [1, 2, 3], // width 3, height 1 + [2, 3, 7, 1], // width 3, height 2 + [8, 4, 9, 10, 12], // width 3, height 3 + [12, 8, 4], // width 1, height 3 + [0, 4, 8, 5], // width 2, height 3 + ]; + const gridSize = 4; + const expectedWordIndexes = [ + [1, 2, 3], // width 3, height 1 + [2, 3, 7, 1], // width 3, height 2 + [8, 4, 9, 10, 12], // width 3, height 3 + [12, 8, 4], // width 1, height 3 + [0, 4, 8, 5], // width 2, height 3 + ]; + const result = omitShapesThatExceedSize({wordIndexes, gridSize}); + expect(result).toEqual(expectedWordIndexes); + }); + + test("removes all input if all exceed size", () => { + const wordIndexes = [ + [0, 1, 2, 3], // width 4, height 1 + [2, 3, 4, 0, 1], // width 4, height 2 + [7, 8, 9, 10, 11, 12], // width 4, height 3 + [12, 8, 4, 0], // width 1, height 4 + [3, 6, 9, 12], // width 4, height 4 + ]; + const gridSize = 4; + const expectedWordIndexes = []; + const result = omitShapesThatExceedSize({wordIndexes, gridSize}); + expect(result).toEqual(expectedWordIndexes); + }); +}); diff --git a/src/styles/App.css b/src/styles/App.css index 6d2b2ba..78469cb 100644 --- a/src/styles/App.css +++ b/src/styles/App.css @@ -1,7 +1,8 @@ html { - --board-box-diam: min(10vmax, 20vmin); + --board-box-diam-4: min(10vmax, 20vmin); + --board-box-diam-5: min(8vmax, 16vmin); --shape-box-diam: min(5vmin, 3vmax); - --default-font-size: calc(var(--board-box-diam) * 0.4); + --default-font-size: calc(var(--board-box-diam-4) * 0.4); --dark-color: rgb(55 54 71); --light-color: rgb(239 239 239); @@ -108,14 +109,22 @@ button:disabled { background-color: var(--dark-color); touch-action: none; justify-content: center; - grid-template-columns: repeat(4, var(--board-box-diam)); - grid-template-rows: repeat(4, var(--board-box-diam)); justify-self: center; align-items: center; justify-items: center; align-self: center; } +#board.size4 { + grid-template-columns: repeat(4, var(--board-box-diam-4)); + grid-template-rows: repeat(4, var(--board-box-diam-4)); +} + +#board.size5 { + grid-template-columns: repeat(5, var(--board-box-diam-5)); + grid-template-rows: repeat(5, var(--board-box-diam-5)); +} + .letterBox { width: 95%; height: 95%; @@ -128,7 +137,6 @@ button:disabled { background-color: transparent; width: 60%; height: 60%; - font-size: calc(var(--default-font-size) * 1.2); color: var(--dark-color); display: flex; align-items: center; @@ -144,6 +152,14 @@ button:disabled { color: var(--light-color); } +#board.size4 > .letter { + font-size: calc(var(--board-box-diam-4) * 0.5); +} + +#board.size5 > .letter { + font-size: calc(var(--board-box-diam-4) * 0.5); +} + #shapes { display: grid; grid-area: shapes; @@ -161,9 +177,17 @@ button:disabled { .shape { display: grid; + margin: calc(var(--shape-box-diam) * 0.4); +} + +.shape.size4 { grid-template-columns: repeat(4, var(--shape-box-diam)); grid-template-rows: repeat(4, var(--shape-box-diam)); - margin: calc(var(--shape-box-diam) * 0.4); +} + +.shape.size5 { + grid-template-columns: repeat(5, var(--shape-box-diam)); + grid-template-rows: repeat(5, var(--shape-box-diam)); } .shapeBox.filled {