full function, no memory
This commit is contained in:
parent
7aa4a156e7
commit
bc29510bb5
3 changed files with 205 additions and 107 deletions
28
src/App.css
28
src/App.css
|
@ -1,4 +1,9 @@
|
|||
*, :before, :after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
:root {
|
||||
box-sizing: border-box;
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
|
@ -76,6 +81,22 @@
|
|||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.puzzle-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
> h1 {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.puzzle-header-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: calc(1 * var(--unit));
|
||||
}
|
||||
|
||||
.puzzle-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -149,6 +170,13 @@
|
|||
min-width: calc(20 * var(--unit));
|
||||
}
|
||||
|
||||
select {
|
||||
font-size: 0.8em;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
|
|
154
src/App.tsx
154
src/App.tsx
|
@ -1,118 +1,58 @@
|
|||
import { For } from "solid-js";
|
||||
import connections from "./assets/connections.json";
|
||||
import "./App.css";
|
||||
import { shuffleArray } from "./utils";
|
||||
import { createStore } from "solid-js/store";
|
||||
|
||||
type Connection = (typeof connections)[number];
|
||||
type Answer = Connection["answers"][number];
|
||||
|
||||
function fromIndex(index: number): [number, number] {
|
||||
const col = index % 4;
|
||||
const row = (index - col) / 4;
|
||||
return [row, col];
|
||||
}
|
||||
import useAppModel from "./useAppModel";
|
||||
|
||||
function App() {
|
||||
const [store, setStore] = createStore({
|
||||
puzzleIndex: 0,
|
||||
pinnedCount: 3,
|
||||
selected: [] as number[],
|
||||
solvedGroups: [] as Answer[],
|
||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
||||
get answers() {
|
||||
return connections[store.puzzleIndex].answers;
|
||||
},
|
||||
});
|
||||
|
||||
const getFromPuzzle = (index: number) => {
|
||||
const puzzleIndex = store.puzzle[index];
|
||||
const [groupIndex, memberIndex] = fromIndex(puzzleIndex);
|
||||
const group = store.answers[groupIndex];
|
||||
return {
|
||||
group: group.group,
|
||||
level: group.level,
|
||||
answer: store.answers[groupIndex].members[memberIndex],
|
||||
};
|
||||
};
|
||||
|
||||
const handleGuess = () => {
|
||||
const selectedAnswers = store.selected.map((x) => getFromPuzzle(x));
|
||||
const { level } = selectedAnswers[0];
|
||||
const isCorrect = selectedAnswers.every((x) => x.level === level);
|
||||
if (!isCorrect) {
|
||||
// TODO you got it wrong
|
||||
alert("wrong");
|
||||
return;
|
||||
}
|
||||
const selectedPinnedCount = store.selected.reduce(
|
||||
(acc, cur) => acc + (cur < store.pinnedCount ? 1 : 0),
|
||||
0
|
||||
);
|
||||
setStore({
|
||||
pinnedCount: store.pinnedCount - selectedPinnedCount,
|
||||
puzzle: store.puzzle.filter((x) =>
|
||||
store.selected.every((s) => store.puzzle[s] !== x)
|
||||
),
|
||||
selected: [],
|
||||
});
|
||||
const newSolvedGroup = store.answers.find((x) => x.level === level);
|
||||
if (newSolvedGroup != null) {
|
||||
setStore({
|
||||
solvedGroups: [...store.solvedGroups, newSolvedGroup],
|
||||
});
|
||||
}
|
||||
if (store.puzzle.length === 0) {
|
||||
// completely solved!
|
||||
}
|
||||
};
|
||||
|
||||
const handleShuffle = () => {
|
||||
const pinned = store.puzzle.slice(0, store.pinnedCount);
|
||||
const toShuffle = store.puzzle.slice(store.pinnedCount);
|
||||
setStore({
|
||||
puzzle: [...pinned, ...shuffleArray(toShuffle)],
|
||||
});
|
||||
};
|
||||
|
||||
const handlePinUnpin = () => {
|
||||
if (store.selected.every((x) => x < store.pinnedCount)) {
|
||||
// we are unpinning
|
||||
const puzzleStart = Array.from({ length: store.pinnedCount }, (_, i) => i)
|
||||
.filter((x) => !store.selected.includes(x))
|
||||
.map((x) => store.puzzle[x]);
|
||||
const puzzleMiddle = store.selected.map((x) => store.puzzle[x]);
|
||||
const puzzleEnd = store.puzzle.slice(store.pinnedCount);
|
||||
const newPuzzle = [...puzzleStart, ...puzzleMiddle, ...puzzleEnd];
|
||||
setStore({
|
||||
pinnedCount: store.pinnedCount - store.selected.length,
|
||||
selected: [],
|
||||
puzzle: newPuzzle,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// we are pinning
|
||||
const puzzleStart = store.puzzle.slice(0, store.pinnedCount);
|
||||
const puzzleMid = store.selected
|
||||
.filter((x) => x >= store.pinnedCount)
|
||||
.map((x) => store.puzzle[x]);
|
||||
const puzzleEnd = Array.from(
|
||||
{ length: 16 - store.pinnedCount },
|
||||
(_, i) => i + store.pinnedCount
|
||||
)
|
||||
.filter((x) => !store.selected.includes(x))
|
||||
.map((x) => store.puzzle[x]);
|
||||
setStore({
|
||||
pinnedCount: puzzleStart.length + puzzleMid.length,
|
||||
selected: [],
|
||||
puzzle: [...puzzleStart, ...puzzleMid, ...puzzleEnd]
|
||||
})
|
||||
};
|
||||
const {
|
||||
connections,
|
||||
store,
|
||||
setStore,
|
||||
handleGuess,
|
||||
handlePinUnpin,
|
||||
handleSelectGame,
|
||||
handleShuffle,
|
||||
getFromPuzzle,
|
||||
} = useAppModel();
|
||||
|
||||
return (
|
||||
<main class="container">
|
||||
<h1>Slick Connections</h1>
|
||||
<div class="puzzle">
|
||||
<header class="puzzle-header">
|
||||
<h1>Slick Connections</h1>
|
||||
<div class="puzzle-header-actions">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
handleSelectGame(store.puzzleId - 1);
|
||||
}}
|
||||
disabled={store.puzzleId === 1}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<select
|
||||
name="puzzleNumber"
|
||||
id="puzzleNumber"
|
||||
on:input={({ target: { value } }) =>
|
||||
handleSelectGame(parseInt(value, 10))
|
||||
}
|
||||
>
|
||||
<For each={connections}>
|
||||
{({ id }) => <option value={id}>{id}</option>}
|
||||
</For>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
handleSelectGame(store.puzzleId + 1);
|
||||
}}
|
||||
disabled={
|
||||
store.puzzleId === connections[connections.length - 1].id
|
||||
}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<For each={store.solvedGroups}>
|
||||
{({ group, level, members }) => (
|
||||
<div class="puzzle-row">
|
||||
|
|
130
src/useAppModel.ts
Normal file
130
src/useAppModel.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
import connections from "./assets/connections.json";
|
||||
import { shuffleArray } from "./utils";
|
||||
import { createStore } from "solid-js/store";
|
||||
|
||||
type Connection = (typeof connections)[number];
|
||||
type Answer = Connection["answers"][number];
|
||||
|
||||
function fromIndex(index: number): [number, number] {
|
||||
const col = index % 4;
|
||||
const row = (index - col) / 4;
|
||||
return [row, col];
|
||||
}
|
||||
|
||||
export default function useAppModel() {
|
||||
const [store, setStore] = createStore({
|
||||
puzzleId: 1,
|
||||
pinnedCount: 0,
|
||||
selected: [] as number[],
|
||||
solvedGroups: [] as Answer[],
|
||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
||||
get answers() {
|
||||
return connections.find((x) => x.id === store.puzzleId)!.answers;
|
||||
},
|
||||
});
|
||||
|
||||
const handleSelectGame = (newId: number) => {
|
||||
setStore({
|
||||
puzzleId: newId,
|
||||
selected: [],
|
||||
pinnedCount: 0,
|
||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
||||
solvedGroups: [],
|
||||
});
|
||||
};
|
||||
|
||||
const getFromPuzzle = (index: number) => {
|
||||
const puzzleIndex = store.puzzle[index];
|
||||
const [groupIndex, memberIndex] = fromIndex(puzzleIndex);
|
||||
const group = store.answers[groupIndex];
|
||||
return {
|
||||
group: group.group,
|
||||
level: group.level,
|
||||
answer: store.answers[groupIndex].members[memberIndex],
|
||||
};
|
||||
};
|
||||
|
||||
const handleGuess = () => {
|
||||
const selectedAnswers = store.selected.map((x) => getFromPuzzle(x));
|
||||
const { level } = selectedAnswers[0];
|
||||
const isCorrect = selectedAnswers.every((x) => x.level === level);
|
||||
if (!isCorrect) {
|
||||
// TODO you got it wrong
|
||||
alert("wrong");
|
||||
return;
|
||||
}
|
||||
const selectedPinnedCount = store.selected.reduce(
|
||||
(acc, cur) => acc + (cur < store.pinnedCount ? 1 : 0),
|
||||
0
|
||||
);
|
||||
setStore({
|
||||
pinnedCount: store.pinnedCount - selectedPinnedCount,
|
||||
puzzle: store.puzzle.filter((x) =>
|
||||
store.selected.every((s) => store.puzzle[s] !== x)
|
||||
),
|
||||
selected: [],
|
||||
});
|
||||
const newSolvedGroup = store.answers.find((x) => x.level === level);
|
||||
if (newSolvedGroup != null) {
|
||||
setStore({
|
||||
solvedGroups: [...store.solvedGroups, newSolvedGroup],
|
||||
});
|
||||
}
|
||||
if (store.puzzle.length === 0) {
|
||||
// completely solved!
|
||||
}
|
||||
};
|
||||
|
||||
const handleShuffle = () => {
|
||||
const pinned = store.puzzle.slice(0, store.pinnedCount);
|
||||
const toShuffle = store.puzzle.slice(store.pinnedCount);
|
||||
setStore({
|
||||
puzzle: [...pinned, ...shuffleArray(toShuffle)],
|
||||
});
|
||||
};
|
||||
|
||||
const handlePinUnpin = () => {
|
||||
if (store.selected.every((x) => x < store.pinnedCount)) {
|
||||
// we are unpinning
|
||||
const puzzleStart = Array.from({ length: store.pinnedCount }, (_, i) => i)
|
||||
.filter((x) => !store.selected.includes(x))
|
||||
.map((x) => store.puzzle[x]);
|
||||
const puzzleMiddle = store.selected.map((x) => store.puzzle[x]);
|
||||
const puzzleEnd = store.puzzle.slice(store.pinnedCount);
|
||||
const newPuzzle = [...puzzleStart, ...puzzleMiddle, ...puzzleEnd];
|
||||
setStore({
|
||||
pinnedCount: store.pinnedCount - store.selected.length,
|
||||
selected: [],
|
||||
puzzle: newPuzzle,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// we are pinning
|
||||
const puzzleStart = store.puzzle.slice(0, store.pinnedCount);
|
||||
const puzzleMid = store.selected
|
||||
.filter((x) => x >= store.pinnedCount)
|
||||
.map((x) => store.puzzle[x]);
|
||||
const puzzleEnd = Array.from(
|
||||
{ length: 16 - store.pinnedCount },
|
||||
(_, i) => i + store.pinnedCount
|
||||
)
|
||||
.filter((x) => !store.selected.includes(x))
|
||||
.map((x) => store.puzzle[x]);
|
||||
setStore({
|
||||
pinnedCount: puzzleStart.length + puzzleMid.length,
|
||||
selected: [],
|
||||
puzzle: [...puzzleStart, ...puzzleMid, ...puzzleEnd],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
connections,
|
||||
store,
|
||||
setStore,
|
||||
handleGuess,
|
||||
handlePinUnpin,
|
||||
handleSelectGame,
|
||||
handleShuffle,
|
||||
getFromPuzzle,
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue