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 {
|
:root {
|
||||||
|
box-sizing: border-box;
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
@ -76,6 +81,22 @@
|
||||||
margin: 0 auto;
|
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 {
|
.puzzle-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -149,6 +170,13 @@
|
||||||
min-width: calc(20 * var(--unit));
|
min-width: calc(20 * var(--unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #646cff;
|
color: #646cff;
|
||||||
|
|
154
src/App.tsx
154
src/App.tsx
|
@ -1,118 +1,58 @@
|
||||||
import { For } from "solid-js";
|
import { For } from "solid-js";
|
||||||
import connections from "./assets/connections.json";
|
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { shuffleArray } from "./utils";
|
import useAppModel from "./useAppModel";
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [store, setStore] = createStore({
|
const {
|
||||||
puzzleIndex: 0,
|
connections,
|
||||||
pinnedCount: 3,
|
store,
|
||||||
selected: [] as number[],
|
setStore,
|
||||||
solvedGroups: [] as Answer[],
|
handleGuess,
|
||||||
puzzle: shuffleArray(Array.from({ length: 16 }, (_, i) => i)),
|
handlePinUnpin,
|
||||||
get answers() {
|
handleSelectGame,
|
||||||
return connections[store.puzzleIndex].answers;
|
handleShuffle,
|
||||||
},
|
getFromPuzzle,
|
||||||
});
|
} = useAppModel();
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<h1>Slick Connections</h1>
|
|
||||||
<div class="puzzle">
|
<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}>
|
<For each={store.solvedGroups}>
|
||||||
{({ group, level, members }) => (
|
{({ group, level, members }) => (
|
||||||
<div class="puzzle-row">
|
<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