@scrabble-solver/scrabble-solver 2.10.1 → 2.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +26 -26
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-development/7.pack_ +0 -0
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/images-manifest.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/131.js +1399 -117
- package/.next/server/chunks/{515.js → 176.js} +771 -679
- package/.next/server/chunks/210.js +122 -0
- package/.next/server/chunks/{939.js → 290.js} +3 -3
- package/.next/server/chunks/44.js +33 -33
- package/.next/server/chunks/50.js +5 -9
- package/.next/server/chunks/{413.js → 579.js} +136 -87
- package/.next/server/chunks/664.js +537 -350
- package/.next/server/chunks/859.js +56 -23
- package/.next/server/chunks/865.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/pages/_app.js +23 -5
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +2 -2
- package/.next/server/pages/_error.js +206 -25
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +12 -11
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +10 -10
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +14 -13
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +10 -10
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -3
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +2 -2
- package/.next/server/pages/index.js +44 -128
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +4 -4
- package/.next/static/FJkPF91uL-OCAJKTTpVSP/_buildManifest.js +1 -0
- package/.next/static/{hf94cues-LcXZRCpAzQ6w → FJkPF91uL-OCAJKTTpVSP}/_ssgManifest.js +0 -0
- package/.next/static/chunks/361-d16f336a9752a55a.js +1 -0
- package/.next/static/chunks/724-eb48df4d1ba3df8b.js +5 -0
- package/.next/static/chunks/framework-2c79e2a64abdb08b.js +33 -0
- package/.next/static/chunks/main-f11614d8aa7ee555.js +1 -0
- package/.next/static/chunks/pages/404-24f9617eeb8d6dc1.js +1 -0
- package/.next/static/chunks/pages/_app-959e495f0f221247.js +24 -0
- package/.next/static/chunks/pages/_error-8353112a01355ec2.js +1 -0
- package/.next/static/chunks/pages/index-1e30dafa41bddb80.js +1 -0
- package/.next/static/chunks/webpack-59c5c889f52620d6.js +1 -0
- package/.next/static/css/aafd07997120f1e4.css +1 -0
- package/.next/static/css/cb5b206454513f3c.css +1 -0
- package/.next/static/css/eb9d57f7103525ab.css +1 -0
- package/.next/trace +52 -52
- package/package.json +21 -21
- package/public/og.png +0 -0
- package/src/components/Board/Board.module.scss +1 -2
- package/src/components/Board/BoardPure.tsx +2 -0
- package/src/components/Board/components/Cell/Cell.module.scss +77 -54
- package/src/components/Board/components/Cell/Cell.tsx +9 -0
- package/src/components/Board/components/Cell/CellPure.tsx +11 -2
- package/src/components/NavButtons/NavButtons.tsx +11 -1
- package/src/components/Rack/RackTile.tsx +10 -1
- package/src/components/RemainingTiles/Character.module.scss +0 -1
- package/src/components/RemainingTiles/Character.tsx +1 -0
- package/src/components/Solver/Solver.module.scss +85 -0
- package/src/components/Solver/Solver.tsx +75 -0
- package/src/components/Solver/index.ts +1 -0
- package/src/components/Tile/Tile.module.scss +51 -7
- package/src/components/Tile/Tile.tsx +3 -0
- package/src/components/Tile/TilePure.tsx +8 -1
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useIsTouchDevice.ts +7 -0
- package/src/icons/ExclamationSquareFill.svg +4 -0
- package/src/icons/index.ts +1 -0
- package/src/pages/api/dictionary/[locale]/[word].ts +2 -1
- package/src/pages/index.module.scss +1 -75
- package/src/pages/index.tsx +11 -73
- package/src/parameters/index.ts +2 -0
- package/src/service-worker/average.ts +9 -0
- package/src/service-worker/routeSolveRequests.ts +46 -7
- package/src/state/selectors.ts +20 -0
- package/src/state/slices/boardInitialState.ts +27 -0
- package/src/styles/mixins.scss +6 -1
- package/src/styles/variables.scss +6 -3
- package/.next/InjectManifest.js.nft.json +0 -1
- package/.next/static/chunks/368-8b386c3106556f62.js +0 -1
- package/.next/static/chunks/546-447e243fc9de2c59.js +0 -1
- package/.next/static/chunks/framework-4556c45dd113b893.js +0 -1
- package/.next/static/chunks/main-a75cf611e061d8f8.js +0 -1
- package/.next/static/chunks/pages/404-932294135c3206dd.js +0 -1
- package/.next/static/chunks/pages/_app-8f0df20f771045ed.js +0 -1
- package/.next/static/chunks/pages/_error-a4ba2246ff8fb532.js +0 -1
- package/.next/static/chunks/pages/index-8af7a9d7a2cd98a7.js +0 -1
- package/.next/static/chunks/webpack-5752944655d749a0.js +0 -1
- package/.next/static/css/6b1833fd19d3a74a.css +0 -1
- package/.next/static/css/a6154e4ca046ca13.css +0 -1
- package/.next/static/css/bad53af6f8616677.css +0 -1
- package/.next/static/hf94cues-LcXZRCpAzQ6w/_buildManifest.js +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
@import 'styles/mixins';
|
|
2
|
+
|
|
1
3
|
.tile {
|
|
4
|
+
--background-color: transparent;
|
|
5
|
+
|
|
2
6
|
position: relative;
|
|
3
|
-
background-color:
|
|
7
|
+
background-color: var(--background-color);
|
|
4
8
|
transition: var(--transition);
|
|
5
9
|
|
|
6
10
|
&.raised {
|
|
@@ -8,24 +12,34 @@
|
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
&.points1 {
|
|
11
|
-
background-color: var(--color--yellow);
|
|
15
|
+
--background-color: var(--color--yellow);
|
|
16
|
+
|
|
17
|
+
background-color: var(--background-color);
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
&.points2 {
|
|
15
|
-
background-color: var(--color--green);
|
|
21
|
+
--background-color: var(--color--green);
|
|
22
|
+
|
|
23
|
+
background-color: var(--background-color);
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
&.points3,
|
|
19
27
|
&.points4 {
|
|
20
|
-
background-color: var(--color--blue);
|
|
28
|
+
--background-color: var(--color--blue);
|
|
29
|
+
|
|
30
|
+
background-color: var(--background-color);
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
&.points5 {
|
|
24
|
-
background-color: var(--color--red);
|
|
34
|
+
--background-color: var(--color--red);
|
|
35
|
+
|
|
36
|
+
background-color: var(--background-color);
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
&.blank {
|
|
28
|
-
background-color: white;
|
|
40
|
+
--background-color: white;
|
|
41
|
+
|
|
42
|
+
background-color: var(--background-color);
|
|
29
43
|
|
|
30
44
|
.character {
|
|
31
45
|
color: black;
|
|
@@ -33,7 +47,9 @@
|
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
&.highlighted {
|
|
36
|
-
background-color: var(--color--primary);
|
|
50
|
+
--background-color: var(--color--primary);
|
|
51
|
+
|
|
52
|
+
background-color: var(--background-color);
|
|
37
53
|
color: white;
|
|
38
54
|
|
|
39
55
|
.character {
|
|
@@ -44,6 +60,13 @@
|
|
|
44
60
|
color: inherit;
|
|
45
61
|
}
|
|
46
62
|
}
|
|
63
|
+
|
|
64
|
+
&.invalid {
|
|
65
|
+
--background-color: var(--color--red--light);
|
|
66
|
+
|
|
67
|
+
background-color: var(--background-color);
|
|
68
|
+
color: var(--color--error);
|
|
69
|
+
}
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
.character {
|
|
@@ -52,6 +75,7 @@
|
|
|
52
75
|
padding: 0;
|
|
53
76
|
box-sizing: border-box;
|
|
54
77
|
background-color: transparent;
|
|
78
|
+
color: inherit;
|
|
55
79
|
border: none;
|
|
56
80
|
font-weight: bold;
|
|
57
81
|
text-transform: uppercase;
|
|
@@ -72,6 +96,8 @@
|
|
|
72
96
|
}
|
|
73
97
|
|
|
74
98
|
.points {
|
|
99
|
+
@include text-stroke(var(--background-color), 1px);
|
|
100
|
+
|
|
75
101
|
position: absolute;
|
|
76
102
|
font-weight: bold;
|
|
77
103
|
user-select: none;
|
|
@@ -88,3 +114,21 @@
|
|
|
88
114
|
left: 9%;
|
|
89
115
|
}
|
|
90
116
|
}
|
|
117
|
+
|
|
118
|
+
.alert {
|
|
119
|
+
$size: 30%;
|
|
120
|
+
|
|
121
|
+
position: absolute;
|
|
122
|
+
width: $size;
|
|
123
|
+
height: $size;
|
|
124
|
+
|
|
125
|
+
[dir='ltr'] & {
|
|
126
|
+
top: 0;
|
|
127
|
+
right: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[dir='rtl'] & {
|
|
131
|
+
bottom: 0;
|
|
132
|
+
right: 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -23,6 +23,7 @@ interface Props {
|
|
|
23
23
|
highlighted?: boolean;
|
|
24
24
|
inputRef?: RefObject<HTMLInputElement>;
|
|
25
25
|
isBlank?: boolean;
|
|
26
|
+
isValid?: boolean;
|
|
26
27
|
placeholder?: string;
|
|
27
28
|
points?: number;
|
|
28
29
|
raised?: boolean;
|
|
@@ -41,6 +42,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
41
42
|
highlighted,
|
|
42
43
|
inputRef: ref,
|
|
43
44
|
isBlank,
|
|
45
|
+
isValid,
|
|
44
46
|
placeholder,
|
|
45
47
|
points,
|
|
46
48
|
raised,
|
|
@@ -82,6 +84,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
82
84
|
inputRef={inputRef}
|
|
83
85
|
inputStyle={inputStyle}
|
|
84
86
|
isBlank={isBlank}
|
|
87
|
+
isValid={isValid}
|
|
85
88
|
placeholder={placeholder}
|
|
86
89
|
points={points}
|
|
87
90
|
pointsFormatted={pointsFormatted}
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
RefObject,
|
|
10
10
|
} from 'react';
|
|
11
11
|
|
|
12
|
+
import { ExclamationSquareFill } from 'icons';
|
|
13
|
+
|
|
12
14
|
import styles from './Tile.module.scss';
|
|
13
15
|
|
|
14
16
|
interface Props {
|
|
@@ -21,6 +23,7 @@ interface Props {
|
|
|
21
23
|
inputRef: RefObject<HTMLInputElement>;
|
|
22
24
|
inputStyle?: CSSProperties;
|
|
23
25
|
isBlank?: boolean;
|
|
26
|
+
isValid?: boolean;
|
|
24
27
|
placeholder?: string;
|
|
25
28
|
points?: number;
|
|
26
29
|
pointsFormatted?: string;
|
|
@@ -43,6 +46,7 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
43
46
|
inputRef,
|
|
44
47
|
inputStyle,
|
|
45
48
|
isBlank,
|
|
49
|
+
isValid,
|
|
46
50
|
placeholder,
|
|
47
51
|
points,
|
|
48
52
|
pointsFormatted,
|
|
@@ -56,8 +60,9 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
56
60
|
}) => (
|
|
57
61
|
<div
|
|
58
62
|
className={classNames(styles.tile, className, {
|
|
59
|
-
[styles.highlighted]: highlighted,
|
|
60
63
|
[styles.blank]: isBlank,
|
|
64
|
+
[styles.highlighted]: highlighted,
|
|
65
|
+
[styles.invalid]: !isValid,
|
|
61
66
|
[styles.raised]: raised,
|
|
62
67
|
[styles.points1]: points === 1,
|
|
63
68
|
[styles.points2]: points === 2,
|
|
@@ -90,6 +95,8 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
90
95
|
{pointsFormatted}
|
|
91
96
|
</span>
|
|
92
97
|
)}
|
|
98
|
+
|
|
99
|
+
{!isValid && <ExclamationSquareFill className={styles.alert} />}
|
|
93
100
|
</div>
|
|
94
101
|
);
|
|
95
102
|
|
package/src/components/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { default as ResultsInput } from './ResultsInput';
|
|
|
20
20
|
export { default as Screen } from './Screen';
|
|
21
21
|
export { default as Settings } from './Settings';
|
|
22
22
|
export { default as Sidebar } from './Sidebar';
|
|
23
|
+
export { default as Solver } from './Solver';
|
|
23
24
|
export { default as Splash } from './Splash';
|
|
24
25
|
export { default as SquareButton } from './SquareButton';
|
|
25
26
|
export { default as SvgFontCss } from './SvgFontCss';
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as useDirection } from './useDirection';
|
|
2
2
|
export { default as useIsTablet } from './useIsTablet';
|
|
3
|
+
export { default as useIsTouchDevice } from './useIsTouchDevice';
|
|
3
4
|
export { default as useLanguage } from './useLanguage';
|
|
4
5
|
export { default as useLocalStorage } from './useLocalStorage';
|
|
5
6
|
export { default as usePortal } from './usePortal';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/exclamation-square-fill/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" fill="currentColor" />
|
|
4
|
+
</svg>
|
package/src/icons/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { default as Cross } from './Cross.svg';
|
|
|
11
11
|
export { default as CrossFill } from './CrossFill.svg';
|
|
12
12
|
export { default as DashCircleFill } from './DashCircleFill.svg';
|
|
13
13
|
export { default as Eraser } from './Eraser.svg';
|
|
14
|
+
export { default as ExclamationSquareFill } from './ExclamationSquareFill.svg';
|
|
14
15
|
export { default as Flag } from './Flag.svg';
|
|
15
16
|
export { default as FlagEs } from './FlagEs.svg';
|
|
16
17
|
export { default as FlagFa } from './FlagFa.svg';
|
|
@@ -12,7 +12,8 @@ interface RequestData {
|
|
|
12
12
|
words: string[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const MAXIMUM_COLLISIONS_COUNT = scrabble['en-US'].maximumCharactersCount;
|
|
16
|
+
const MAXIMUM_WORDS_COUNT = MAXIMUM_COLLISIONS_COUNT + 1;
|
|
16
17
|
|
|
17
18
|
const dictionary = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {
|
|
18
19
|
const meta = getServerLoggingData(request);
|
|
@@ -43,80 +43,6 @@
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
.
|
|
47
|
-
$board-size: 15;
|
|
48
|
-
$tile-size-max: 60px;
|
|
49
|
-
$tile-border-size: 1px;
|
|
50
|
-
|
|
51
|
-
flex: 1;
|
|
52
|
-
padding: 0 var(--spacing--xl);
|
|
53
|
-
max-height: $board-size * ($tile-size-max + $tile-border-size) + $tile-border-size;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.content {
|
|
57
|
-
display: flex;
|
|
58
|
-
align-items: center;
|
|
59
|
-
justify-content: center;
|
|
60
|
-
height: 100%;
|
|
61
|
-
gap: var(--spacing--xl);
|
|
62
|
-
|
|
63
|
-
@include tablet {
|
|
64
|
-
gap: var(--spacing--l);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.boardContainer {
|
|
69
|
-
display: flex;
|
|
70
|
-
position: relative;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.sidebar {
|
|
74
|
-
display: flex;
|
|
75
|
-
flex-direction: column;
|
|
76
|
-
flex: 0 0 var(--sidebar--width);
|
|
77
|
-
gap: var(--spacing--xl);
|
|
78
|
-
|
|
79
|
-
@include tablet {
|
|
80
|
-
gap: var(--spacing--l);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.dictionary {
|
|
85
|
-
display: flex;
|
|
86
|
-
flex-direction: column;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.dictionaryOutput {
|
|
90
|
-
flex: 1;
|
|
91
|
-
border-bottom: var(--border);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.dictionaryInput {
|
|
95
|
-
flex: 0 0 auto;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.resultsContainer {
|
|
46
|
+
.solver {
|
|
99
47
|
flex: 1;
|
|
100
|
-
position: relative;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.rackContainer {
|
|
104
|
-
position: relative;
|
|
105
|
-
z-index: 1;
|
|
106
|
-
flex: 0 0 auto;
|
|
107
|
-
display: flex;
|
|
108
|
-
justify-content: center;
|
|
109
|
-
margin: var(--spacing--xl) var(--spacing--l);
|
|
110
|
-
|
|
111
|
-
@include tablet {
|
|
112
|
-
margin: var(--spacing--l);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.rack {
|
|
117
|
-
border: var(--border);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.submitInput {
|
|
121
|
-
display: none;
|
|
122
48
|
}
|
package/src/pages/index.tsx
CHANGED
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { AnimationEvent,
|
|
4
|
+
import { AnimationEvent, FunctionComponent, useEffect, useState } from 'react';
|
|
5
5
|
import Modal from 'react-modal';
|
|
6
6
|
import { useDispatch } from 'react-redux';
|
|
7
|
-
import { useEffectOnce
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
Dictionary,
|
|
12
|
-
DictionaryInput,
|
|
13
|
-
KeyMap,
|
|
14
|
-
Logo,
|
|
15
|
-
NavButtons,
|
|
16
|
-
Rack,
|
|
17
|
-
RemainingTiles,
|
|
18
|
-
Results,
|
|
19
|
-
Settings,
|
|
20
|
-
Splash,
|
|
21
|
-
SvgFontFix,
|
|
22
|
-
Well,
|
|
23
|
-
Words,
|
|
24
|
-
} from 'components';
|
|
25
|
-
import { useDirection, useIsTablet, useLanguage, useLocalStorage } from 'hooks';
|
|
7
|
+
import { useEffectOnce } from 'react-use';
|
|
8
|
+
|
|
9
|
+
import { KeyMap, Logo, NavButtons, RemainingTiles, Settings, Solver, Splash, SvgFontFix, Words } from 'components';
|
|
10
|
+
import { useDirection, useLanguage, useLocalStorage } from 'hooks';
|
|
26
11
|
import { LOCALE_FEATURES } from 'i18n';
|
|
27
|
-
import {
|
|
28
|
-
import { COMPONENTS_SPACING, COMPONENTS_SPACING_MOBILE, DICTIONARY_HEIGHT } from 'parameters';
|
|
12
|
+
import { INITIALIZATION_DURATION } from 'parameters';
|
|
29
13
|
import { registerServiceWorker } from 'serviceWorkerManager';
|
|
30
|
-
import { initialize, localStorage, reset,
|
|
14
|
+
import { initialize, localStorage, reset, selectLocale, useTypedSelector } from 'state';
|
|
31
15
|
|
|
32
16
|
import styles from './index.module.scss';
|
|
33
17
|
|
|
@@ -40,30 +24,16 @@ interface Props {
|
|
|
40
24
|
const Index: FunctionComponent<Props> = ({ version }) => {
|
|
41
25
|
const dispatch = useDispatch();
|
|
42
26
|
const locale = useTypedSelector(selectLocale);
|
|
43
|
-
const isTablet = useIsTablet();
|
|
44
27
|
const [showKeyMap, setShowKeyMap] = useState(false);
|
|
45
28
|
const [showRemainingTiles, setShowRemainingTiles] = useState(false);
|
|
46
29
|
const [showSettings, setShowSettings] = useState(false);
|
|
47
30
|
const [showWords, setShowWords] = useState(false);
|
|
48
|
-
const [
|
|
49
|
-
const [contentRef, { height: contentHeight, width: contentWidth }] = useMeasure<HTMLDivElement>();
|
|
50
|
-
const [resultsContainerRef, { height: resultsContainerHeight, width: resultsContainerWidth }] =
|
|
51
|
-
useMeasure<HTMLDivElement>();
|
|
52
|
-
const config = useTypedSelector(selectConfig);
|
|
53
|
-
const cellSize = getCellSize(config, contentWidth - resultsContainerWidth, contentHeight);
|
|
54
|
-
const isInitializedInitial = contentWidth > 0 && boardHeight > 0 && resultsContainerWidth > 0;
|
|
55
|
-
const [isInitialized, setIsInitialized] = useState(isInitializedInitial);
|
|
56
|
-
const resultsHeight = boardHeight - DICTIONARY_HEIGHT - (isTablet ? COMPONENTS_SPACING_MOBILE : COMPONENTS_SPACING);
|
|
31
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
57
32
|
|
|
58
33
|
const handleClear = () => {
|
|
59
34
|
dispatch(reset());
|
|
60
35
|
};
|
|
61
36
|
|
|
62
|
-
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
63
|
-
event.preventDefault();
|
|
64
|
-
dispatch(solveSlice.actions.submit());
|
|
65
|
-
};
|
|
66
|
-
|
|
67
37
|
const handleSplashAnimationEnd = (event: AnimationEvent<HTMLDivElement>) => {
|
|
68
38
|
if (event.target === event.currentTarget && !localStorage.getHasVisited()) {
|
|
69
39
|
setShowSettings(true);
|
|
@@ -71,8 +41,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
71
41
|
}
|
|
72
42
|
};
|
|
73
43
|
|
|
74
|
-
|
|
75
|
-
useDirection(direction);
|
|
44
|
+
useDirection(LOCALE_FEATURES[locale].direction);
|
|
76
45
|
useLanguage(locale);
|
|
77
46
|
useLocalStorage();
|
|
78
47
|
|
|
@@ -81,7 +50,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
81
50
|
|
|
82
51
|
setTimeout(() => {
|
|
83
52
|
setIsInitialized(true);
|
|
84
|
-
},
|
|
53
|
+
}, INITIALIZATION_DURATION);
|
|
85
54
|
});
|
|
86
55
|
|
|
87
56
|
useEffect(() => {
|
|
@@ -111,44 +80,13 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
111
80
|
/>
|
|
112
81
|
</div>
|
|
113
82
|
|
|
114
|
-
<
|
|
115
|
-
<div className={styles.content} ref={contentRef}>
|
|
116
|
-
<form className={styles.boardContainer} onSubmit={handleSubmit}>
|
|
117
|
-
{contentWidth > 0 && <Board cellSize={cellSize} innerRef={boardRef} />}
|
|
118
|
-
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
119
|
-
</form>
|
|
120
|
-
|
|
121
|
-
<div className={styles.sidebar} style={{ height: boardHeight + 1 }}>
|
|
122
|
-
<Well className={styles.resultsContainer} ref={resultsContainerRef}>
|
|
123
|
-
{resultsContainerWidth > 0 && resultsContainerHeight > 0 && (
|
|
124
|
-
<Results height={resultsHeight} width={resultsContainerWidth} />
|
|
125
|
-
)}
|
|
126
|
-
</Well>
|
|
127
|
-
|
|
128
|
-
<Well>
|
|
129
|
-
<div className={styles.dictionary} style={{ height: DICTIONARY_HEIGHT }}>
|
|
130
|
-
<Dictionary className={styles.dictionaryOutput} />
|
|
131
|
-
<DictionaryInput className={styles.dictionaryInput} />
|
|
132
|
-
</div>
|
|
133
|
-
</Well>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<form className={styles.rackContainer} onSubmit={handleSubmit}>
|
|
139
|
-
<Rack className={styles.rack} />
|
|
140
|
-
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
141
|
-
</form>
|
|
83
|
+
<Solver className={styles.solver} />
|
|
142
84
|
</div>
|
|
143
85
|
|
|
144
86
|
<Settings isOpen={showSettings} onClose={() => setShowSettings(false)} />
|
|
145
|
-
|
|
146
87
|
<KeyMap isOpen={showKeyMap} onClose={() => setShowKeyMap(false)} />
|
|
147
|
-
|
|
148
88
|
<Words isOpen={showWords} onClose={() => setShowWords(false)} />
|
|
149
|
-
|
|
150
89
|
<RemainingTiles isOpen={showRemainingTiles} onClose={() => setShowRemainingTiles(false)} />
|
|
151
|
-
|
|
152
90
|
<Splash forceShow={!isInitialized} onAnimationEnd={handleSplashAnimationEnd} />
|
|
153
91
|
</>
|
|
154
92
|
);
|
package/src/parameters/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Trie } from '@kamilmielnik/trie';
|
|
1
2
|
import { getConfig } from '@scrabble-solver/configs';
|
|
2
3
|
import { BLANK } from '@scrabble-solver/constants';
|
|
3
4
|
import { solve } from '@scrabble-solver/solver';
|
|
4
5
|
import { Board, Locale, Tile } from '@scrabble-solver/types';
|
|
5
6
|
import { registerRoute } from 'workbox-routing';
|
|
6
7
|
|
|
8
|
+
import average from './average';
|
|
7
9
|
import { revalidateDictionary } from './dictionaries';
|
|
8
10
|
import getTrie from './getTrie';
|
|
9
11
|
|
|
@@ -11,24 +13,61 @@ const headers = {
|
|
|
11
13
|
'Content-Type': 'application/json; charset=utf-8',
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
const MIN_MEASUREMENTS = 5;
|
|
17
|
+
const localDurations: number[] = [];
|
|
18
|
+
const serverDurations: number[] = [];
|
|
19
|
+
|
|
20
|
+
const isSlowDevice = (): boolean | undefined => {
|
|
21
|
+
if (localDurations.length < MIN_MEASUREMENTS || serverDurations.length < MIN_MEASUREMENTS) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const count = Math.min(localDurations.length, serverDurations.length);
|
|
26
|
+
return average(localDurations.slice(0, count)) > average(serverDurations.slice(0, count));
|
|
27
|
+
};
|
|
28
|
+
|
|
14
29
|
const routeSolveRequests = () => {
|
|
15
30
|
registerRoute(
|
|
16
31
|
({ url }) => url.origin === location.origin && url.pathname === '/api/solve',
|
|
17
32
|
async ({ request }) => {
|
|
18
33
|
const { board, characters, configId, locale } = await request.clone().json();
|
|
34
|
+
|
|
35
|
+
const solveLocal = async (trie: Trie) => {
|
|
36
|
+
const config = getConfig(configId)[locale as Locale];
|
|
37
|
+
const tiles = characters.map((character: string) => new Tile({ character, isBlank: character === BLANK }));
|
|
38
|
+
const resultsJson = solve(trie, config, Board.fromJson(board), tiles);
|
|
39
|
+
const json = JSON.stringify(resultsJson);
|
|
40
|
+
return new Response(json, { headers });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const solveServer = () => fetch(request);
|
|
44
|
+
|
|
19
45
|
const trie = await getTrie(locale);
|
|
20
46
|
|
|
21
|
-
if (
|
|
22
|
-
const response = await
|
|
47
|
+
if (trie && typeof isSlowDevice() === 'undefined') {
|
|
48
|
+
const response = await Promise.race([
|
|
49
|
+
(async () => {
|
|
50
|
+
const start = Date.now();
|
|
51
|
+
const result = await solveLocal(trie);
|
|
52
|
+
localDurations.push(Date.now() - start);
|
|
53
|
+
return result;
|
|
54
|
+
})(),
|
|
55
|
+
(async () => {
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
const result = await solveServer();
|
|
58
|
+
serverDurations.push(Date.now() - start);
|
|
59
|
+
return result;
|
|
60
|
+
})(),
|
|
61
|
+
]);
|
|
62
|
+
|
|
23
63
|
revalidateDictionary(locale);
|
|
24
64
|
return response;
|
|
25
65
|
}
|
|
26
66
|
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return new Response(json, { headers });
|
|
67
|
+
const handleSolve = trie && !isSlowDevice() ? () => solveLocal(trie) : () => solveServer();
|
|
68
|
+
const response = await handleSolve();
|
|
69
|
+
revalidateDictionary(locale);
|
|
70
|
+
return response;
|
|
32
71
|
},
|
|
33
72
|
'POST',
|
|
34
73
|
);
|
package/src/state/selectors.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
2
|
import { getLocaleConfig } from '@scrabble-solver/configs';
|
|
3
|
+
import { BLANK } from '@scrabble-solver/constants';
|
|
3
4
|
import { Cell, Config, isError, Result, Tile } from '@scrabble-solver/types';
|
|
4
5
|
|
|
5
6
|
import i18n, { LOCALE_FEATURES } from 'i18n';
|
|
@@ -62,6 +63,14 @@ export const selectCellIsFiltered = createSelector([selectCellFilter, selectPoin
|
|
|
62
63
|
return cellFilter.some((cell) => cell.x === x && cell.y === y);
|
|
63
64
|
});
|
|
64
65
|
|
|
66
|
+
export const selectCellIsValid = createSelector([selectConfig, selectCell], (config, cell) => {
|
|
67
|
+
if (!cell.hasTile()) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return config.tiles.some((tile) => tile.character === cell.tile.character);
|
|
72
|
+
});
|
|
73
|
+
|
|
65
74
|
export const selectResults = createSelector([selectResultsRoot], (results) => results.results);
|
|
66
75
|
|
|
67
76
|
export const selectResultsQuery = createSelector([selectResultsRoot], (results) => results.query);
|
|
@@ -140,6 +149,17 @@ export const selectCharacterPoints = createSelector(
|
|
|
140
149
|
},
|
|
141
150
|
);
|
|
142
151
|
|
|
152
|
+
export const selectCharacterIsValid = createSelector(
|
|
153
|
+
[selectConfig, selectCharacter],
|
|
154
|
+
(config: Config, character: string | null) => {
|
|
155
|
+
if (character === null || character === BLANK) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return config.tiles.some((tile) => tile.character === character);
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
143
163
|
export const selectTilePoints = createSelector([selectConfig, selectTile], (config: Config, tile: Tile | null) => {
|
|
144
164
|
return config.getTilePoints(tile);
|
|
145
165
|
});
|
|
@@ -7,4 +7,31 @@ const { configId, locale } = settingsInitialState;
|
|
|
7
7
|
const { boardHeight, boardWidth } = getLocaleConfig(configId, locale);
|
|
8
8
|
const boardInitialState: Board = Board.create(boardWidth, boardHeight);
|
|
9
9
|
|
|
10
|
+
// const createOxyphenbutazone = () => {
|
|
11
|
+
// // Tiles: oypbaze
|
|
12
|
+
|
|
13
|
+
// const board = Board.fromStringArray([
|
|
14
|
+
// ' x hen ut on ',
|
|
15
|
+
// 'puer or amas j',
|
|
16
|
+
// 'a led a er a',
|
|
17
|
+
// 'c ki i elf c',
|
|
18
|
+
// 'i snot n is u',
|
|
19
|
+
// 'f t o w do l',
|
|
20
|
+
// 'y e moa er a',
|
|
21
|
+
// 'i r solar t',
|
|
22
|
+
// 'n v h t i',
|
|
23
|
+
// 'g i i bitten',
|
|
24
|
+
// ' e n v g',
|
|
25
|
+
// ' w g e ',
|
|
26
|
+
// ' e ',
|
|
27
|
+
// ' d ',
|
|
28
|
+
// ' ',
|
|
29
|
+
// ]);
|
|
30
|
+
|
|
31
|
+
// board.rows[4][3].tile.isBlank = true;
|
|
32
|
+
// board.rows[9][11].tile.isBlank = true;
|
|
33
|
+
|
|
34
|
+
// return board;
|
|
35
|
+
// };
|
|
36
|
+
|
|
10
37
|
export default boardInitialState;
|
package/src/styles/mixins.scss
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@mixin tablet
|
|
1
|
+
@mixin tablet {
|
|
2
2
|
@media (max-width: 1024px) {
|
|
3
3
|
@content;
|
|
4
4
|
}
|
|
@@ -73,3 +73,8 @@
|
|
|
73
73
|
font-size: var(--font--size--s);
|
|
74
74
|
font-family: var(--font--family);
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
@mixin text-stroke($color, $size: 1px) {
|
|
78
|
+
text-shadow: $size 0 $color, (-$size) 0 $color, 0 $size $color, 0 (-$size) $color, (-$size) (-$size) $color,
|
|
79
|
+
(-$size) $size $color, $size (-$size) $color, $size $size $color;
|
|
80
|
+
}
|
|
@@ -2,6 +2,7 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
|
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
4
|
--border--color: #cdcdcd;
|
|
5
|
+
--border--color--light: #d9d9d9;
|
|
5
6
|
--border--radius: 5px;
|
|
6
7
|
--border--width: 1px;
|
|
7
8
|
--border: var(--border--width) solid var(--border--color);
|
|
@@ -41,13 +42,15 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
|
|
|
41
42
|
--color--blue: #c7d8f9;
|
|
42
43
|
--color--blue--light: #dde4f6;
|
|
43
44
|
--color--light-blue: #7bb5e5;
|
|
44
|
-
--color--light-blue--light: #
|
|
45
|
+
--color--light-blue--light: #b8d5ed;
|
|
45
46
|
--color--dark-blue: #1868ad;
|
|
46
|
-
--color--dark-blue--light: #
|
|
47
|
+
--color--dark-blue--light: #86aed1;
|
|
47
48
|
--color--red: #f7c2aa;
|
|
48
49
|
--color--red--light: #fbe0d4;
|
|
49
50
|
--color--violet: #78387f;
|
|
50
|
-
--color--violet--light: #
|
|
51
|
+
--color--violet--light: #b284b8;
|
|
52
|
+
--color--orange: #fbc997;
|
|
53
|
+
--color--pink: #f19393;
|
|
51
54
|
|
|
52
55
|
--font--family: 'Open Sans', sans-serif;
|
|
53
56
|
--font--family--title: 'Lato', sans-serif;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"files":["../public/service-worker.js","../public/service-worker.js.map","../package.json"]}
|