A daily game
1import { atom } from "jotai";
2import { atomWithRefresh, atomWithReset, RESET, selectAtom } from "jotai/utils";
3import { arrayOfSize, groupIntoRows } from "./game";
4
5export const defaultWidth = 6;
6export const defaultHeight = 6;
7export const defaultVariants = ["🍒", "🍍"];
8
9export const widthAtom = atomWithReset(defaultWidth);
10export const heightAtom = atomWithReset(defaultHeight);
11export const variantsAtom = atomWithReset(defaultVariants);
12
13export const entropyAtom = atom((get) => get(variantsAtom).length);
14export const sizeAtom = atom((get) => get(widthAtom) * get(heightAtom));
15
16export const _boardAtom = atom(
17 generateBoard(defaultWidth * defaultHeight, defaultVariants.length),
18);
19
20export const boardAtom = atom(
21 (get) => get(_boardAtom),
22 (get, set, _value?: typeof RESET) => {
23 set(sessionAtom, RESET);
24 set(_boardAtom, generateBoard(get(sizeAtom), get(entropyAtom)));
25 },
26);
27
28export const boardMatrixAtom = atom((get) =>
29 groupIntoRows(get(_boardAtom), get(widthAtom)),
30);
31
32const newCell = () => {
33 const cellAtom = atomWithReset(-1);
34 return atom(
35 (get) => get(cellAtom),
36 (get, set, reset?: typeof RESET) => {
37 if (reset === RESET) {
38 return set(cellAtom, RESET);
39 }
40
41 if (get(gameResultAtom)) {
42 return;
43 }
44
45 const options = get(variantsAtom);
46 const currentValue = get(cellAtom);
47
48 let newValue: number = currentValue + 1;
49 if (newValue === options.length) {
50 newValue = -1;
51 }
52
53 set(cellAtom, newValue);
54 },
55 );
56};
57
58const _sessionAtom = atomWithRefresh((get) =>
59 arrayOfSize(get(sizeAtom)).map(() => newCell()),
60);
61
62export const sessionAtom = atom(
63 (get) => get(_sessionAtom),
64 (get, set, _value?: typeof RESET) =>
65 get(_sessionAtom).forEach((cell) => {
66 if (get(cell) < 0) {
67 return;
68 }
69 set(cell, RESET);
70 }),
71);
72
73export const sessionMatrixAtom = atom((get) =>
74 groupIntoRows(get(_sessionAtom).map(get), get(widthAtom)),
75);
76
77export const gameResultAtom = atom(
78 (get) =>
79 get(boardMatrixAtom).toString() === get(sessionMatrixAtom).toString(),
80);
81
82export const preferencesAtom = atom(
83 (get) => {
84 return {
85 width: get(widthAtom),
86 height: get(heightAtom),
87 variants: get(variantsAtom),
88 };
89 },
90 (
91 _get,
92 set,
93 {
94 width,
95 height,
96 variants,
97 }: { width: number; height: number; variants: string[] },
98 ) => {
99 set(widthAtom, width);
100 set(heightAtom, height);
101 set(variantsAtom, variants);
102 set(boardAtom);
103 },
104);
105
106export const selectBoardSubset = (dir: "row" | "col", index: number) =>
107 selectAtom(
108 boardMatrixAtom,
109 (board) => {
110 if (dir === "row") {
111 const row = board[index];
112 if (typeof row === "undefined") {
113 throw new Error(`No board ${dir} at index ${index}`);
114 }
115 return row;
116 } else {
117 return board.map((row) => {
118 const col = row[index];
119 if (typeof col === "undefined") {
120 throw new Error(`No board ${dir} at index ${index}`);
121 }
122 return col;
123 });
124 }
125 },
126 arraysAreEqual,
127 );
128
129export const selectSessionSubset = (dir: "row" | "col", index: number) =>
130 selectAtom(
131 sessionMatrixAtom,
132 (session) => {
133 if (dir === "row") {
134 const row = session[index];
135 if (typeof row === "undefined") {
136 throw new Error(`No session ${dir} at index ${index}`);
137 }
138 return row;
139 } else {
140 return session.map((row) => {
141 const col = row[index];
142 if (typeof col === "undefined") {
143 throw new Error(`No session ${dir} at index ${index}`);
144 }
145 return col;
146 });
147 }
148 },
149 arraysAreEqual,
150 );
151
152export const selectGuessAt = (index: number) =>
153 selectAtom(sessionAtom, (cells) => {
154 const cellAtom = cells[index];
155 if (typeof cellAtom === "undefined") {
156 throw new Error(`Cell "${index}" not found.`);
157 }
158 return cellAtom;
159 });
160
161export function selectVariantAt(index: number) {
162 return selectAtom(variantsAtom, (variants) => {
163 const variant = variants[index];
164 if (typeof variant === "undefined") {
165 throw new Error(`Variant at ${index} does not exist.`);
166 }
167 return variant;
168 });
169}
170
171function generateBoard(size: number, entropy: number) {
172 return arrayOfSize(size).map(() => Math.floor(Math.random() * entropy));
173}
174
175function arraysAreEqual(a: number[], b: number[]) {
176 return a.toString() === b.toString();
177}