18 Commits

Author SHA1 Message Date
d68c4a6c3d 1.9.4 2026-02-11 06:27:42 +01:00
23c9137985 fix: update win condition to validate hints instead of exact match 2026-02-11 06:27:42 +01:00
c3ddadeb17 1.9.3 2026-02-11 06:14:19 +01:00
36b0c18370 fix: improve desktop layout, scrolling and background 2026-02-11 06:14:19 +01:00
943c045a65 1.9.2 2026-02-11 06:00:37 +01:00
27a1ce38fb fix: rename share classes to avoid adblockers 2026-02-11 06:00:37 +01:00
63dd833f2a 1.9.1 2026-02-11 05:49:48 +01:00
fa8384c860 feat(layout): use native browser scroll for game board on desktop 2026-02-11 05:49:47 +01:00
430f370196 1.9.0 2026-02-11 05:31:50 +01:00
c8789741fc feat(guide): update playback speed options (remove 3x, add 8x/16x) 2026-02-11 05:31:49 +01:00
53ef63f25f 1.8.5 2026-02-11 05:24:12 +01:00
9b869bdb5f perf(puzzle): optimize undo logic and dragging performance 2026-02-11 05:22:32 +01:00
764763016b 1.8.4 2026-02-11 05:00:18 +01:00
5cfabf40e8 feat(i18n): add simulation translations for all languages 2026-02-11 05:00:16 +01:00
460de9fac9 1.8.3 2026-02-11 04:50:21 +01:00
a264ac75e3 perf(CustomGameModal): optimize difficulty map rendering with caching 2026-02-11 04:50:13 +01:00
43822d03ac 1.8.2 2026-02-11 04:47:09 +01:00
2e83d3ea9f feat(i18n): add translations for difficulty simulation feature 2026-02-11 04:47:02 +01:00
18 changed files with 2793 additions and 195 deletions

37
check_i18n.js Normal file
View File

@@ -0,0 +1,37 @@
const fs = require('fs');
const fileContent = fs.readFileSync('src/composables/useI18n.js', 'utf8');
// Extract the messages object
const match = fileContent.match(/const messages = ({[\s\S]*?});/);
if (!match) {
console.error('Could not find messages object');
process.exit(1);
}
// We need to make the string valid JS to eval it.
// It seems the content inside `const messages = { ... };` is valid JS object notation.
// But we need to be careful about imports or other things if we were to `eval` the whole file.
// We'll just `eval` the object part.
const messagesStr = match[1];
const messages = eval(`(${messagesStr})`);
const enKeys = Object.keys(messages.en);
const languages = Object.keys(messages);
const missing = {};
languages.forEach(lang => {
if (lang === 'en') return;
const langKeys = Object.keys(messages[lang]);
const missingKeys = enKeys.filter(k => !langKeys.includes(k));
if (missingKeys.length > 0) {
missing[lang] = missingKeys;
}
});
console.log(JSON.stringify(missing, null, 2));

View File

@@ -0,0 +1,118 @@
size,density,avg_solved_percent,min_solved_percent,max_solved_percent,avg_time_ms
5,0.1,89.40,36.00,100.00,0.03
5,0.2,74.20,8.00,100.00,0.04
5,0.3,74.20,0.00,100.00,0.04
5,0.4,80.80,8.00,100.00,0.03
5,0.5,96.80,68.00,100.00,0.03
5,0.6,97.60,84.00,100.00,0.03
5,0.7,99.20,84.00,100.00,0.03
5,0.8,100.00,100.00,100.00,0.02
5,0.9,100.00,100.00,100.00,0.02
10,0.1,56.60,19.00,86.00,0.04
10,0.2,19.80,0.00,51.00,0.05
10,0.3,15.75,0.00,73.00,0.07
10,0.4,54.05,0.00,100.00,0.13
10,0.5,91.80,59.00,100.00,0.17
10,0.6,99.80,96.00,100.00,0.07
10,0.7,99.80,96.00,100.00,0.05
10,0.8,100.00,100.00,100.00,0.04
10,0.9,100.00,100.00,100.00,0.02
15,0.1,37.04,13.33,61.78,0.05
15,0.2,9.78,0.00,26.67,0.03
15,0.3,1.89,0.00,8.00,0.03
15,0.4,11.82,0.00,61.33,0.08
15,0.5,68.20,2.67,100.00,0.14
15,0.6,99.56,96.44,100.00,0.09
15,0.7,99.78,97.33,100.00,0.07
15,0.8,100.00,100.00,100.00,0.05
15,0.9,100.00,100.00,100.00,0.03
20,0.1,22.59,5.00,41.50,0.04
20,0.2,3.25,0.00,14.50,0.04
20,0.3,0.56,0.00,5.00,0.04
20,0.4,1.46,0.00,3.25,0.07
20,0.5,36.75,1.25,99.00,0.26
20,0.6,99.80,99.00,100.00,0.23
20,0.7,99.95,99.00,100.00,0.13
20,0.8,100.00,100.00,100.00,0.09
20,0.9,100.00,100.00,100.00,0.05
25,0.1,16.00,7.84,32.80,0.06
25,0.2,0.00,0.00,0.00,0.04
25,0.3,0.05,0.00,0.64,0.06
25,0.4,0.89,0.00,9.44,0.12
25,0.5,19.13,1.12,96.48,0.35
25,0.6,99.25,94.88,100.00,0.50
25,0.7,99.90,99.36,100.00,0.24
25,0.8,100.00,100.00,100.00,0.15
25,0.9,99.97,99.36,100.00,0.08
30,0.1,7.99,0.00,16.00,0.08
30,0.2,0.17,0.00,3.33,0.07
30,0.3,0.00,0.00,0.00,0.08
30,0.4,0.38,0.00,4.11,0.18
30,0.5,5.42,0.78,21.44,0.41
30,0.6,99.42,94.78,100.00,0.94
30,0.7,99.93,99.56,100.00,0.42
30,0.8,100.00,100.00,100.00,0.22
30,0.9,100.00,100.00,100.00,0.13
35,0.1,5.65,0.00,13.80,0.11
35,0.2,0.14,0.00,2.86,0.10
35,0.3,0.00,0.00,0.00,0.13
35,0.4,0.13,0.00,0.57,0.22
35,0.5,4.42,0.41,23.18,0.56
35,0.6,91.11,8.41,100.00,1.58
35,0.7,99.97,99.67,100.00,0.60
35,0.8,100.00,100.00,100.00,0.33
35,0.9,100.00,100.00,100.00,0.17
40,0.1,2.73,0.00,9.81,0.13
40,0.2,0.00,0.00,0.00,0.13
40,0.3,0.00,0.00,0.00,0.18
40,0.4,0.03,0.00,0.31,0.27
40,0.5,2.14,0.00,9.56,0.69
40,0.6,91.44,22.31,100.00,2.92
40,0.7,99.99,99.75,100.00,0.84
40,0.8,100.00,100.00,100.00,0.43
40,0.9,100.00,100.00,100.00,0.24
45,0.1,1.78,0.00,4.69,0.17
45,0.2,0.00,0.00,0.00,0.17
45,0.3,0.00,0.00,0.00,0.24
45,0.4,0.01,0.00,0.15,0.33
45,0.5,1.44,0.40,5.14,0.96
45,0.6,81.72,6.02,100.00,5.28
45,0.7,99.94,99.41,100.00,1.28
45,0.8,100.00,100.00,100.00,0.64
45,0.9,100.00,100.00,100.00,0.30
50,0.1,1.70,0.00,5.92,0.20
50,0.2,0.00,0.00,0.00,0.23
50,0.3,0.00,0.00,0.00,0.30
50,0.4,0.01,0.00,0.16,0.38
50,0.5,0.51,0.00,1.80,0.90
50,0.6,73.26,5.60,100.00,7.94
50,0.7,99.96,99.76,100.00,1.63
50,0.8,100.00,100.00,100.00,0.83
50,0.9,100.00,100.00,100.00,0.41
60,0.1,0.17,0.00,1.67,0.24
60,0.2,0.00,0.00,0.00,0.35
60,0.3,0.00,0.00,0.00,0.50
60,0.4,0.00,0.00,0.06,0.64
60,0.5,0.23,0.00,1.67,1.24
60,0.6,35.02,1.33,100.00,10.76
60,0.7,99.97,99.83,100.00,2.96
60,0.8,100.00,100.00,100.00,1.27
60,0.9,100.00,100.00,100.00,0.62
70,0.1,0.14,0.00,1.43,0.34
70,0.2,0.00,0.00,0.00,0.54
70,0.3,0.00,0.00,0.00,0.76
70,0.4,0.00,0.00,0.00,0.96
70,0.5,0.04,0.00,0.29,1.49
70,0.6,16.40,1.00,99.71,22.21
70,0.7,99.97,99.73,100.00,4.92
70,0.8,100.00,99.92,100.00,2.03
70,0.9,100.00,100.00,100.00,0.89
80,0.1,0.00,0.00,0.00,0.44
80,0.2,0.00,0.00,0.00,0.78
80,0.3,0.00,0.00,0.00,1.07
80,0.4,0.00,0.00,0.00,1.34
80,0.5,0.01,0.00,0.08,1.97
80,0.6,1.21,0.41,2.30,3.92
80,0.7,99.98,99.94,100.00,7.79
80,0.8,100.00,100.00,100.00,3.14
80,0.9,100.00,100.00,100.00,1.31
1 size density avg_solved_percent min_solved_percent max_solved_percent avg_time_ms
2 5 0.1 89.40 36.00 100.00 0.03
3 5 0.2 74.20 8.00 100.00 0.04
4 5 0.3 74.20 0.00 100.00 0.04
5 5 0.4 80.80 8.00 100.00 0.03
6 5 0.5 96.80 68.00 100.00 0.03
7 5 0.6 97.60 84.00 100.00 0.03
8 5 0.7 99.20 84.00 100.00 0.03
9 5 0.8 100.00 100.00 100.00 0.02
10 5 0.9 100.00 100.00 100.00 0.02
11 10 0.1 56.60 19.00 86.00 0.04
12 10 0.2 19.80 0.00 51.00 0.05
13 10 0.3 15.75 0.00 73.00 0.07
14 10 0.4 54.05 0.00 100.00 0.13
15 10 0.5 91.80 59.00 100.00 0.17
16 10 0.6 99.80 96.00 100.00 0.07
17 10 0.7 99.80 96.00 100.00 0.05
18 10 0.8 100.00 100.00 100.00 0.04
19 10 0.9 100.00 100.00 100.00 0.02
20 15 0.1 37.04 13.33 61.78 0.05
21 15 0.2 9.78 0.00 26.67 0.03
22 15 0.3 1.89 0.00 8.00 0.03
23 15 0.4 11.82 0.00 61.33 0.08
24 15 0.5 68.20 2.67 100.00 0.14
25 15 0.6 99.56 96.44 100.00 0.09
26 15 0.7 99.78 97.33 100.00 0.07
27 15 0.8 100.00 100.00 100.00 0.05
28 15 0.9 100.00 100.00 100.00 0.03
29 20 0.1 22.59 5.00 41.50 0.04
30 20 0.2 3.25 0.00 14.50 0.04
31 20 0.3 0.56 0.00 5.00 0.04
32 20 0.4 1.46 0.00 3.25 0.07
33 20 0.5 36.75 1.25 99.00 0.26
34 20 0.6 99.80 99.00 100.00 0.23
35 20 0.7 99.95 99.00 100.00 0.13
36 20 0.8 100.00 100.00 100.00 0.09
37 20 0.9 100.00 100.00 100.00 0.05
38 25 0.1 16.00 7.84 32.80 0.06
39 25 0.2 0.00 0.00 0.00 0.04
40 25 0.3 0.05 0.00 0.64 0.06
41 25 0.4 0.89 0.00 9.44 0.12
42 25 0.5 19.13 1.12 96.48 0.35
43 25 0.6 99.25 94.88 100.00 0.50
44 25 0.7 99.90 99.36 100.00 0.24
45 25 0.8 100.00 100.00 100.00 0.15
46 25 0.9 99.97 99.36 100.00 0.08
47 30 0.1 7.99 0.00 16.00 0.08
48 30 0.2 0.17 0.00 3.33 0.07
49 30 0.3 0.00 0.00 0.00 0.08
50 30 0.4 0.38 0.00 4.11 0.18
51 30 0.5 5.42 0.78 21.44 0.41
52 30 0.6 99.42 94.78 100.00 0.94
53 30 0.7 99.93 99.56 100.00 0.42
54 30 0.8 100.00 100.00 100.00 0.22
55 30 0.9 100.00 100.00 100.00 0.13
56 35 0.1 5.65 0.00 13.80 0.11
57 35 0.2 0.14 0.00 2.86 0.10
58 35 0.3 0.00 0.00 0.00 0.13
59 35 0.4 0.13 0.00 0.57 0.22
60 35 0.5 4.42 0.41 23.18 0.56
61 35 0.6 91.11 8.41 100.00 1.58
62 35 0.7 99.97 99.67 100.00 0.60
63 35 0.8 100.00 100.00 100.00 0.33
64 35 0.9 100.00 100.00 100.00 0.17
65 40 0.1 2.73 0.00 9.81 0.13
66 40 0.2 0.00 0.00 0.00 0.13
67 40 0.3 0.00 0.00 0.00 0.18
68 40 0.4 0.03 0.00 0.31 0.27
69 40 0.5 2.14 0.00 9.56 0.69
70 40 0.6 91.44 22.31 100.00 2.92
71 40 0.7 99.99 99.75 100.00 0.84
72 40 0.8 100.00 100.00 100.00 0.43
73 40 0.9 100.00 100.00 100.00 0.24
74 45 0.1 1.78 0.00 4.69 0.17
75 45 0.2 0.00 0.00 0.00 0.17
76 45 0.3 0.00 0.00 0.00 0.24
77 45 0.4 0.01 0.00 0.15 0.33
78 45 0.5 1.44 0.40 5.14 0.96
79 45 0.6 81.72 6.02 100.00 5.28
80 45 0.7 99.94 99.41 100.00 1.28
81 45 0.8 100.00 100.00 100.00 0.64
82 45 0.9 100.00 100.00 100.00 0.30
83 50 0.1 1.70 0.00 5.92 0.20
84 50 0.2 0.00 0.00 0.00 0.23
85 50 0.3 0.00 0.00 0.00 0.30
86 50 0.4 0.01 0.00 0.16 0.38
87 50 0.5 0.51 0.00 1.80 0.90
88 50 0.6 73.26 5.60 100.00 7.94
89 50 0.7 99.96 99.76 100.00 1.63
90 50 0.8 100.00 100.00 100.00 0.83
91 50 0.9 100.00 100.00 100.00 0.41
92 60 0.1 0.17 0.00 1.67 0.24
93 60 0.2 0.00 0.00 0.00 0.35
94 60 0.3 0.00 0.00 0.00 0.50
95 60 0.4 0.00 0.00 0.06 0.64
96 60 0.5 0.23 0.00 1.67 1.24
97 60 0.6 35.02 1.33 100.00 10.76
98 60 0.7 99.97 99.83 100.00 2.96
99 60 0.8 100.00 100.00 100.00 1.27
100 60 0.9 100.00 100.00 100.00 0.62
101 70 0.1 0.14 0.00 1.43 0.34
102 70 0.2 0.00 0.00 0.00 0.54
103 70 0.3 0.00 0.00 0.00 0.76
104 70 0.4 0.00 0.00 0.00 0.96
105 70 0.5 0.04 0.00 0.29 1.49
106 70 0.6 16.40 1.00 99.71 22.21
107 70 0.7 99.97 99.73 100.00 4.92
108 70 0.8 100.00 99.92 100.00 2.03
109 70 0.9 100.00 100.00 100.00 0.89
110 80 0.1 0.00 0.00 0.00 0.44
111 80 0.2 0.00 0.00 0.00 0.78
112 80 0.3 0.00 0.00 0.00 1.07
113 80 0.4 0.00 0.00 0.00 1.34
114 80 0.5 0.01 0.00 0.08 1.97
115 80 0.6 1.21 0.41 2.30 3.92
116 80 0.7 99.98 99.94 100.00 7.79
117 80 0.8 100.00 100.00 100.00 3.14
118 80 0.9 100.00 100.00 100.00 1.31

View File

@@ -0,0 +1,938 @@
[
{
"size": 5,
"density": 0.1,
"avgSolved": 89.4,
"minSolved": 36,
"maxSolved": 100,
"avgTime": 0.031666799999999905
},
{
"size": 5,
"density": 0.2,
"avgSolved": 74.2,
"minSolved": 8,
"maxSolved": 100,
"avgTime": 0.03671869999999924
},
{
"size": 5,
"density": 0.3,
"avgSolved": 74.2,
"minSolved": 0,
"maxSolved": 100,
"avgTime": 0.04439559999999983
},
{
"size": 5,
"density": 0.4,
"avgSolved": 80.8,
"minSolved": 8,
"maxSolved": 100,
"avgTime": 0.0317166499999999
},
{
"size": 5,
"density": 0.5,
"avgSolved": 96.8,
"minSolved": 68,
"maxSolved": 100,
"avgTime": 0.0309604000000002
},
{
"size": 5,
"density": 0.6,
"avgSolved": 97.6,
"minSolved": 84,
"maxSolved": 100,
"avgTime": 0.031464499999999875
},
{
"size": 5,
"density": 0.7,
"avgSolved": 99.2,
"minSolved": 84,
"maxSolved": 100,
"avgTime": 0.03086874999999978
},
{
"size": 5,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.01615615000000048
},
{
"size": 5,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.02271474999999956
},
{
"size": 10,
"density": 0.1,
"avgSolved": 56.6,
"minSolved": 19,
"maxSolved": 86,
"avgTime": 0.042958299999999915
},
{
"size": 10,
"density": 0.2,
"avgSolved": 19.8,
"minSolved": 0,
"maxSolved": 51,
"avgTime": 0.050141749999999874
},
{
"size": 10,
"density": 0.3,
"avgSolved": 15.75,
"minSolved": 0,
"maxSolved": 73,
"avgTime": 0.06852290000000014
},
{
"size": 10,
"density": 0.4,
"avgSolved": 54.05,
"minSolved": 0,
"maxSolved": 100,
"avgTime": 0.12701870000000018
},
{
"size": 10,
"density": 0.5,
"avgSolved": 91.8,
"minSolved": 59,
"maxSolved": 100,
"avgTime": 0.16561034999999985
},
{
"size": 10,
"density": 0.6,
"avgSolved": 99.8,
"minSolved": 96,
"maxSolved": 100,
"avgTime": 0.07136649999999882
},
{
"size": 10,
"density": 0.7,
"avgSolved": 99.8,
"minSolved": 96,
"maxSolved": 100,
"avgTime": 0.04808134999999893
},
{
"size": 10,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.03795824999999979
},
{
"size": 10,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.024952100000000855
},
{
"size": 15,
"density": 0.1,
"avgSolved": 37.044444444444444,
"minSolved": 13.333333333333334,
"maxSolved": 61.77777777777778,
"avgTime": 0.045045850000000345
},
{
"size": 15,
"density": 0.2,
"avgSolved": 9.777777777777775,
"minSolved": 0,
"maxSolved": 26.666666666666668,
"avgTime": 0.034581349999998776
},
{
"size": 15,
"density": 0.3,
"avgSolved": 1.8888888888888886,
"minSolved": 0,
"maxSolved": 8,
"avgTime": 0.029402199999999823
},
{
"size": 15,
"density": 0.4,
"avgSolved": 11.822222222222223,
"minSolved": 0,
"maxSolved": 61.33333333333333,
"avgTime": 0.07898965000000047
},
{
"size": 15,
"density": 0.5,
"avgSolved": 68.19999999999999,
"minSolved": 2.666666666666667,
"maxSolved": 100,
"avgTime": 0.1374602999999997
},
{
"size": 15,
"density": 0.6,
"avgSolved": 99.55555555555554,
"minSolved": 96.44444444444444,
"maxSolved": 100,
"avgTime": 0.09379159999999978
},
{
"size": 15,
"density": 0.7,
"avgSolved": 99.77777777777779,
"minSolved": 97.33333333333334,
"maxSolved": 100,
"avgTime": 0.07072704999999928
},
{
"size": 15,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.050104250000000405
},
{
"size": 15,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.031362550000000766
},
{
"size": 20,
"density": 0.1,
"avgSolved": 22.5875,
"minSolved": 5,
"maxSolved": 41.5,
"avgTime": 0.04363335000000035
},
{
"size": 20,
"density": 0.2,
"avgSolved": 3.25,
"minSolved": 0,
"maxSolved": 14.499999999999998,
"avgTime": 0.03823525000000103
},
{
"size": 20,
"density": 0.3,
"avgSolved": 0.5625,
"minSolved": 0,
"maxSolved": 5,
"avgTime": 0.03880414999999786
},
{
"size": 20,
"density": 0.4,
"avgSolved": 1.4625,
"minSolved": 0,
"maxSolved": 3.25,
"avgTime": 0.06692695000000129
},
{
"size": 20,
"density": 0.5,
"avgSolved": 36.75,
"minSolved": 1.25,
"maxSolved": 99,
"avgTime": 0.25872084999999884
},
{
"size": 20,
"density": 0.6,
"avgSolved": 99.8,
"minSolved": 99,
"maxSolved": 100,
"avgTime": 0.2258772000000004
},
{
"size": 20,
"density": 0.7,
"avgSolved": 99.95,
"minSolved": 99,
"maxSolved": 100,
"avgTime": 0.13418124999999997
},
{
"size": 20,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.09264785000000053
},
{
"size": 20,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.05307699999999756
},
{
"size": 25,
"density": 0.1,
"avgSolved": 16.000000000000004,
"minSolved": 7.84,
"maxSolved": 32.800000000000004,
"avgTime": 0.05678540000000112
},
{
"size": 25,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.04278334999999842
},
{
"size": 25,
"density": 0.3,
"avgSolved": 0.048,
"minSolved": 0,
"maxSolved": 0.64,
"avgTime": 0.05884794999999983
},
{
"size": 25,
"density": 0.4,
"avgSolved": 0.8880000000000001,
"minSolved": 0,
"maxSolved": 9.44,
"avgTime": 0.11761245000000287
},
{
"size": 25,
"density": 0.5,
"avgSolved": 19.128000000000007,
"minSolved": 1.1199999999999999,
"maxSolved": 96.48,
"avgTime": 0.3490229000000021
},
{
"size": 25,
"density": 0.6,
"avgSolved": 99.24799999999998,
"minSolved": 94.88,
"maxSolved": 100,
"avgTime": 0.49611459999999996
},
{
"size": 25,
"density": 0.7,
"avgSolved": 99.904,
"minSolved": 99.36,
"maxSolved": 100,
"avgTime": 0.23916465000000073
},
{
"size": 25,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.14604994999999973
},
{
"size": 25,
"density": 0.9,
"avgSolved": 99.96799999999999,
"minSolved": 99.36,
"maxSolved": 100,
"avgTime": 0.08385419999999896
},
{
"size": 30,
"density": 0.1,
"avgSolved": 7.988888888888889,
"minSolved": 0,
"maxSolved": 16,
"avgTime": 0.08026245000000073
},
{
"size": 30,
"density": 0.2,
"avgSolved": 0.16666666666666669,
"minSolved": 0,
"maxSolved": 3.3333333333333335,
"avgTime": 0.06999999999999887
},
{
"size": 30,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.08285835000000005
},
{
"size": 30,
"density": 0.4,
"avgSolved": 0.3777777777777777,
"minSolved": 0,
"maxSolved": 4.111111111111112,
"avgTime": 0.1756041499999988
},
{
"size": 30,
"density": 0.5,
"avgSolved": 5.4222222222222225,
"minSolved": 0.7777777777777778,
"maxSolved": 21.444444444444443,
"avgTime": 0.41105620000000015
},
{
"size": 30,
"density": 0.6,
"avgSolved": 99.41666666666669,
"minSolved": 94.77777777777779,
"maxSolved": 100,
"avgTime": 0.9417500999999995
},
{
"size": 30,
"density": 0.7,
"avgSolved": 99.93333333333335,
"minSolved": 99.55555555555556,
"maxSolved": 100,
"avgTime": 0.41628955000000334
},
{
"size": 30,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.22320620000000133
},
{
"size": 30,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.13331460000000134
},
{
"size": 35,
"density": 0.1,
"avgSolved": 5.653061224489796,
"minSolved": 0,
"maxSolved": 13.795918367346937,
"avgTime": 0.11177699999999931
},
{
"size": 35,
"density": 0.2,
"avgSolved": 0.14285714285714285,
"minSolved": 0,
"maxSolved": 2.857142857142857,
"avgTime": 0.09598544999999917
},
{
"size": 35,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.1290145000000038
},
{
"size": 35,
"density": 0.4,
"avgSolved": 0.1346938775510204,
"minSolved": 0,
"maxSolved": 0.5714285714285714,
"avgTime": 0.21904799999999797
},
{
"size": 35,
"density": 0.5,
"avgSolved": 4.424489795918367,
"minSolved": 0.40816326530612246,
"maxSolved": 23.183673469387756,
"avgTime": 0.5596769500000022
},
{
"size": 35,
"density": 0.6,
"avgSolved": 91.1061224489796,
"minSolved": 8.408163265306122,
"maxSolved": 100,
"avgTime": 1.5827311000000024
},
{
"size": 35,
"density": 0.7,
"avgSolved": 99.9673469387755,
"minSolved": 99.67346938775509,
"maxSolved": 100,
"avgTime": 0.5970167499999988
},
{
"size": 35,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.33084175000000327
},
{
"size": 35,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.1685022000000032
},
{
"size": 40,
"density": 0.1,
"avgSolved": 2.734375,
"minSolved": 0,
"maxSolved": 9.8125,
"avgTime": 0.13156869999999826
},
{
"size": 40,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.13052910000000112
},
{
"size": 40,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.17675199999999905
},
{
"size": 40,
"density": 0.4,
"avgSolved": 0.03125,
"minSolved": 0,
"maxSolved": 0.3125,
"avgTime": 0.26616039999999686
},
{
"size": 40,
"density": 0.5,
"avgSolved": 2.14375,
"minSolved": 0,
"maxSolved": 9.5625,
"avgTime": 0.694316649999999
},
{
"size": 40,
"density": 0.6,
"avgSolved": 91.44375,
"minSolved": 22.3125,
"maxSolved": 100,
"avgTime": 2.9244042000000006
},
{
"size": 40,
"density": 0.7,
"avgSolved": 99.9875,
"minSolved": 99.75,
"maxSolved": 100,
"avgTime": 0.8381519999999967
},
{
"size": 40,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.4339062999999925
},
{
"size": 40,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.2375938000000076
},
{
"size": 45,
"density": 0.1,
"avgSolved": 1.7827160493827159,
"minSolved": 0,
"maxSolved": 4.691358024691358,
"avgTime": 0.1660813500000046
},
{
"size": 45,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.1715666999999968
},
{
"size": 45,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.23760415000000706
},
{
"size": 45,
"density": 0.4,
"avgSolved": 0.012345679012345678,
"minSolved": 0,
"maxSolved": 0.14814814814814814,
"avgTime": 0.333931249999992
},
{
"size": 45,
"density": 0.5,
"avgSolved": 1.439506172839506,
"minSolved": 0.39506172839506176,
"maxSolved": 5.135802469135802,
"avgTime": 0.9644125499999916
},
{
"size": 45,
"density": 0.6,
"avgSolved": 81.71851851851852,
"minSolved": 6.0246913580246915,
"maxSolved": 100,
"avgTime": 5.281324949999998
},
{
"size": 45,
"density": 0.7,
"avgSolved": 99.94074074074075,
"minSolved": 99.4074074074074,
"maxSolved": 100,
"avgTime": 1.2768960000000107
},
{
"size": 45,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.638566650000007
},
{
"size": 45,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.3004915999999923
},
{
"size": 50,
"density": 0.1,
"avgSolved": 1.7,
"minSolved": 0,
"maxSolved": 5.92,
"avgTime": 0.20294785000000387
},
{
"size": 50,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.23199789999999892
},
{
"size": 50,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.29876259999999205
},
{
"size": 50,
"density": 0.4,
"avgSolved": 0.008,
"minSolved": 0,
"maxSolved": 0.16,
"avgTime": 0.38459799999998834
},
{
"size": 50,
"density": 0.5,
"avgSolved": 0.5099999999999999,
"minSolved": 0,
"maxSolved": 1.7999999999999998,
"avgTime": 0.8961771499999941
},
{
"size": 50,
"density": 0.6,
"avgSolved": 73.258,
"minSolved": 5.6000000000000005,
"maxSolved": 100,
"avgTime": 7.937735449999991
},
{
"size": 50,
"density": 0.7,
"avgSolved": 99.96399999999998,
"minSolved": 99.76,
"maxSolved": 100,
"avgTime": 1.6324250000000062
},
{
"size": 50,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.8293270000000064
},
{
"size": 50,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.41459575000000654
},
{
"size": 60,
"density": 0.1,
"avgSolved": 0.16666666666666669,
"minSolved": 0,
"maxSolved": 1.6666666666666667,
"avgTime": 0.2432124999999928
},
{
"size": 60,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.35082704999999237
},
{
"size": 60,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.49827310000000014
},
{
"size": 60,
"density": 0.4,
"avgSolved": 0.0027777777777777775,
"minSolved": 0,
"maxSolved": 0.05555555555555555,
"avgTime": 0.6393062499999985
},
{
"size": 60,
"density": 0.5,
"avgSolved": 0.23055555555555554,
"minSolved": 0,
"maxSolved": 1.6666666666666667,
"avgTime": 1.2402395500000012
},
{
"size": 60,
"density": 0.6,
"avgSolved": 35.01805555555556,
"minSolved": 1.3333333333333335,
"maxSolved": 100,
"avgTime": 10.759754149999992
},
{
"size": 60,
"density": 0.7,
"avgSolved": 99.96944444444445,
"minSolved": 99.83333333333333,
"maxSolved": 100,
"avgTime": 2.964204100000029
},
{
"size": 60,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 1.2736664999999903
},
{
"size": 60,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.6249353999999812
},
{
"size": 70,
"density": 0.1,
"avgSolved": 0.14285714285714285,
"minSolved": 0,
"maxSolved": 1.4285714285714286,
"avgTime": 0.34277719999998907
},
{
"size": 70,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.5435105000000249
},
{
"size": 70,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.7600602999999865
},
{
"size": 70,
"density": 0.4,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.9591250999999943
},
{
"size": 70,
"density": 0.5,
"avgSolved": 0.04081632653061225,
"minSolved": 0,
"maxSolved": 0.2857142857142857,
"avgTime": 1.491010399999982
},
{
"size": 70,
"density": 0.6,
"avgSolved": 16.403061224489797,
"minSolved": 1,
"maxSolved": 99.71428571428571,
"avgTime": 22.21432699999999
},
{
"size": 70,
"density": 0.7,
"avgSolved": 99.96836734693878,
"minSolved": 99.73469387755102,
"maxSolved": 100,
"avgTime": 4.92020829999999
},
{
"size": 70,
"density": 0.8,
"avgSolved": 99.99591836734695,
"minSolved": 99.91836734693878,
"maxSolved": 100,
"avgTime": 2.0306394499999554
},
{
"size": 70,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 0.8882499500000336
},
{
"size": 80,
"density": 0.1,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.4418666499999858
},
{
"size": 80,
"density": 0.2,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 0.7795667999999978
},
{
"size": 80,
"density": 0.3,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 1.0745101999999747
},
{
"size": 80,
"density": 0.4,
"avgSolved": 0,
"minSolved": 0,
"maxSolved": 0,
"avgTime": 1.3407041500000105
},
{
"size": 80,
"density": 0.5,
"avgSolved": 0.0125,
"minSolved": 0,
"maxSolved": 0.078125,
"avgTime": 1.9724897000000283
},
{
"size": 80,
"density": 0.6,
"avgSolved": 1.21484375,
"minSolved": 0.40625,
"maxSolved": 2.296875,
"avgTime": 3.9163123999999927
},
{
"size": 80,
"density": 0.7,
"avgSolved": 99.978125,
"minSolved": 99.9375,
"maxSolved": 100,
"avgTime": 7.790070799999967
},
{
"size": 80,
"density": 0.8,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 3.1350061999999754
},
{
"size": 80,
"density": 0.9,
"avgSolved": 100,
"minSolved": 100,
"maxSolved": 100,
"avgTime": 1.3134414999999535
}
]

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "vue-nonograms-solid",
"version": "1.8.1",
"version": "1.9.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vue-nonograms-solid",
"version": "1.8.1",
"version": "1.9.4",
"dependencies": {
"fireworks-js": "^2.10.8",
"flag-icons": "^7.5.0",

View File

@@ -1,6 +1,6 @@
{
"name": "vue-nonograms-solid",
"version": "1.8.1",
"version": "1.9.4",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -0,0 +1,61 @@
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, '../src/composables/useI18n.js');
let content = fs.readFileSync(filePath, 'utf8');
const newKeys = {
'custom.simulationHelp': 'How is this calculated?',
'custom.hideMap': 'Hide difficulty map',
'custom.showMap': 'Show difficulty map',
'simulation.title': 'Difficulty Simulation',
'simulation.status.ready': 'Ready',
'simulation.status.stopped': 'Stopped',
'simulation.status.completed': 'Completed',
'simulation.status.simulating': 'Simulating {size}x{size} @ {density}%',
'simulation.start': 'Start Simulation',
'simulation.stop': 'Stop',
'simulation.table.size': 'Size',
'simulation.table.density': 'Density',
'simulation.table.solved': 'Solved (Logic)',
'simulation.empty': 'Press Start to run Monte Carlo simulation'
};
const lines = content.split('\n');
const processedLines = [];
let currentLang = null;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
// Detect start of language block
const startMatch = line.match(/^\s{2}(['"]?[\w-]+['"]?): \{/);
if (startMatch) {
currentLang = startMatch[1].replace(/['"]/g, '');
}
// Detect end of language block
if (currentLang && (line.trim() === '},' || line.trim() === '}')) {
if (currentLang !== 'pl' && currentLang !== 'en') {
// Ensure previous line has comma
if (processedLines.length > 0) {
let lastLine = processedLines[processedLines.length - 1];
if (!lastLine.trim().endsWith(',') && !lastLine.trim().endsWith('{')) {
processedLines[processedLines.length - 1] = lastLine + ',';
}
}
// Append new keys
Object.entries(newKeys).forEach(([key, value]) => {
processedLines.push(` '${key}': '${value}',`);
});
}
currentLang = null;
}
processedLines.push(line);
}
const finalContent = processedLines.join('\n');
fs.writeFileSync(filePath, finalContent);
console.log('Successfully added simulation translations to all languages.');

View File

@@ -0,0 +1,97 @@
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, '../src/composables/useI18n.js');
let content = fs.readFileSync(filePath, 'utf8');
const newKeys = {
'custom.simulationHelp': 'How is this calculated?',
'simulation.title': 'Difficulty Simulation',
'simulation.status.ready': 'Ready',
'simulation.status.stopped': 'Stopped',
'simulation.status.completed': 'Completed',
'simulation.status.simulating': 'Simulating {size}x{size} @ {density}%',
'simulation.start': 'Start Simulation',
'simulation.stop': 'Stop',
'simulation.table.size': 'Size',
'simulation.table.density': 'Density',
'simulation.table.solved': 'Solved (Logic)',
'simulation.empty': 'Press Start to run Monte Carlo simulation'
};
// Regex to match the end of a language block
// Matches " }," or " }" at the start of a line
const blockEndRegex = /^(\s{2})\},?$/gm;
let newContent = content.replace(blockEndRegex, (match, indent, offset) => {
// Determine which language we are closing
const precedingText = content.substring(0, offset);
const langMatch = precedingText.match(/^\s{2}(\w+([-]\w+)?): \{/gm);
if (!langMatch) return match;
const currentLangLine = langMatch[langMatch.length - 1];
const currentLang = currentLangLine.match(/^\s{2}(\w+([-]\w+)?): \{/)[1];
// Skip pl and en as they are already updated
if (currentLang === 'pl' || currentLang === 'en') {
return match;
}
// Check if the previous line has a comma
// We need to look at the lines before the match
// This is tricky with replace callback.
// Easier strategy: Just insert the keys.
// If the file is well formatted, the last item might or might not have a comma.
// But we can ensure *our* inserted block starts with a comma if needed?
// No, standard JS objects need comma after previous item.
// Let's assume we simply inject before the closing brace.
// We'll add a comma to the previous line if it doesn't have one?
// That's hard with regex replace on the closing brace only.
// Alternative: Split by lines and process.
return match; // Placeholder, we will process by lines below.
});
const lines = content.split('\n');
const processedLines = [];
let currentLang = null;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
// Detect start of language block
const startMatch = line.match(/^\s{2}(['"]?[\w-]+['"]?): \{/);
if (startMatch) {
currentLang = startMatch[1].replace(/['"]/g, '');
}
// Detect end of language block
if (currentLang && (line.trim() === '},' || line.trim() === '}')) {
if (currentLang !== 'pl' && currentLang !== 'en') {
// Ensure previous line has comma
if (processedLines.length > 0) {
let lastLine = processedLines[processedLines.length - 1];
if (!lastLine.trim().endsWith(',') && !lastLine.trim().endsWith('{')) {
processedLines[processedLines.length - 1] = lastLine + ',';
}
}
// Append new keys
Object.entries(newKeys).forEach(([key, value]) => {
processedLines.push(` '${key}': '${value}',`);
});
// Remove trailing comma from last inserted item if we want strictly JSON-like (but JS allows it)
// It's fine to leave it.
}
currentLang = null;
}
processedLines.push(line);
}
const finalContent = processedLines.join('\n');
fs.writeFileSync(filePath, finalContent);
console.log('Successfully added simulation translations to all languages.');

View File

@@ -186,10 +186,11 @@ onUnmounted(() => {
.game-container {
display: flex;
flex-direction: column;
align-items: center;
align-items: stretch; /* was center */
gap: 20px;
width: 100%;
padding-bottom: 50px;
padding-top: 100px; /* Space for fixed NavBar */
}
.install-banner {
@@ -202,8 +203,11 @@ onUnmounted(() => {
align-items: center;
width: 90%;
max-width: 600px;
margin-bottom: 20px;
margin: 0 auto 20px auto; /* Center it manually */
box-shadow: var(--banner-shadow);
position: sticky;
left: 0;
right: 0;
}
.install-text {
@@ -227,17 +231,27 @@ onUnmounted(() => {
.game-layout {
display: flex;
flex-direction: column;
align-items: center;
align-items: stretch;
gap: 20px;
width: 100%;
max-width: 1200px;
padding: 0 20px;
}
.board-section {
display: flex;
justify-content: center;
/* Center children (except board section which handles itself) */
.game-layout > *:not(.board-section) {
margin-left: auto;
margin-right: auto;
max-width: 1200px; /* Keep constraint for panels */
width: 100%;
position: sticky;
left: 0;
right: 0;
}
.board-section {
display: block;
width: 100%;
overflow-x: visible;
}
.fade-enter-active,

View File

@@ -14,6 +14,7 @@ const fillRate = ref(50);
const errorMsg = ref('');
const difficultyCanvas = ref(null);
const isDragging = ref(false);
const cachedBackground = ref(null);
const drawMap = () => {
const canvas = difficultyCanvas.value;
@@ -25,8 +26,11 @@ const drawMap = () => {
// Clear
ctx.clearRect(0, 0, width, height);
// Draw Gradient Background
// Optimization: Create an image data once if static, but here it's small enough.
// Use cached background if available
if (cachedBackground.value) {
ctx.putImageData(cachedBackground.value, 0, 0);
} else {
// Draw Gradient Background (Heavy calculation)
const imgData = ctx.createImageData(width, height);
const data = imgData.data;
@@ -36,10 +40,6 @@ const drawMap = () => {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Map x, y to fillRate, size
// y=0 -> size 80 (top), y=height -> size 5 (bottom)
// x=0 -> fill 10%, x=width -> fill 90%
const normalizedX = x / width;
const normalizedY = 1 - (y / height); // 0 at bottom, 1 at top
@@ -48,13 +48,8 @@ const drawMap = () => {
const { value } = calculateDifficulty(fRate, sSize);
// Color Mapping:
// Green (0%) -> Yellow (50%) -> Red (100%)
// Hue: 120 -> 0
// Color Mapping
const hue = 120 * (1 - value / 100);
// Convert HSL to RGB (Simplified)
// Saturation 100%, Lightness 50%
const [r, g, b] = hslToRgb(hue / 360, 1, 0.5);
const index = (y * width + x) * 4;
@@ -65,6 +60,8 @@ const drawMap = () => {
}
}
ctx.putImageData(imgData, 0, 0);
cachedBackground.value = imgData;
}
// Draw current position
// Map current fillRate/size to x,y
@@ -166,6 +163,8 @@ const showAdvanced = ref(false);
const toggleAdvanced = () => {
showAdvanced.value = !showAdvanced.value;
if (showAdvanced.value) {
// Reset cache when opening to ensure size is correct if canvas resized
cachedBackground.value = null;
nextTick(drawMap);
}
};
@@ -306,7 +305,7 @@ const confirm = () => {
<div class="advanced-toggle">
<button class="btn-text" @click="toggleAdvanced">
{{ showAdvanced ? 'Ukryj mapę trudności' : 'Pokaż mapę trudności' }}
{{ showAdvanced ? t('custom.hideMap') : t('custom.showMap') }}
</button>
</div>

View File

@@ -118,6 +118,8 @@ const computeCellSize = () => {
const gap = parseFloat(gapRaw);
const gridPad = parseFloat(gridPadRaw);
const isDesktop = window.matchMedia('(min-width: 769px)').matches;
let containerWidth;
if (scrollWrapper.value) {
containerWidth = scrollWrapper.value.clientWidth;
@@ -131,8 +133,15 @@ const computeCellSize = () => {
const availableForGrid = Math.max(0, containerWidth - hintWidth);
const size = Math.floor((availableForGrid - gridPad * 2 - (store.size - 1) * gap) / store.size);
if (isDesktop) {
// Desktop: Allow overflow, use comfortable size
cellSize.value = 30;
} else {
// Mobile: Fit to screen
// Keep min 18, max 36
cellSize.value = Math.max(18, Math.min(36, size));
}
};
const handleGlobalMouseUp = () => {
@@ -258,8 +267,15 @@ watch(() => store.size, async () => {
/* Desktop: Remove scroll behavior to ensure full grid visibility */
@media (min-width: 769px) {
.game-board-wrapper {
overflow-x: auto; /* Allow scrolling if grid is too large (e.g. 80x80) */
align-items: center; /* Center the grid on desktop */
overflow: visible;
width: max-content;
min-width: 100%;
margin: 0 auto; /* Center the wrapper safely */
align-items: flex-start; /* Prevent cropping when centered */
}
.game-container {
/* margin: 0 auto; - wrapper handles centering now */
}
}

View File

@@ -435,8 +435,10 @@ watch(isMobileMenuOpen, (val) => {
justify-content: space-between;
align-items: center;
z-index: 100;
position: sticky;
position: fixed;
top: 0;
left: 0;
right: 0;
}
.nav-left {

View File

@@ -3,9 +3,11 @@
import { ref, computed } from 'vue';
import { generateRandomGrid, calculateHints } from '@/utils/puzzleUtils';
import { solvePuzzle } from '@/utils/solver';
import { useI18n } from '@/composables/useI18n';
import { X, Play, Square, RotateCcw } from 'lucide-vue-next';
const emit = defineEmits(['close']);
const { t } = useI18n();
const SIZES = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50];
const DENSITIES = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9];
@@ -13,12 +15,17 @@ const SAMPLES_PER_POINT = 10; // Reduced for web performance demo
const isRunning = ref(false);
const progress = ref(0);
const currentStatus = ref('Ready');
const currentStatus = ref('');
const results = ref([]);
const simulationSpeed = ref(1); // 1 = Normal, 2 = Fast (less render updates)
let stopRequested = false;
const displayStatus = computed(() => {
if (!currentStatus.value) return t('simulation.status.ready');
return currentStatus.value;
});
const startSimulation = async () => {
if (isRunning.value) return;
isRunning.value = true;
@@ -32,12 +39,15 @@ const startSimulation = async () => {
for (const size of SIZES) {
for (const density of DENSITIES) {
if (stopRequested) {
currentStatus.value = 'Stopped';
currentStatus.value = t('simulation.status.stopped');
isRunning.value = false;
return;
}
currentStatus.value = `Simulating ${size}x${size} @ ${(density * 100).toFixed(0)}%`;
currentStatus.value = t('simulation.status.simulating', {
size: size,
density: (density * 100).toFixed(0)
});
let totalSolved = 0;
@@ -66,7 +76,7 @@ const startSimulation = async () => {
}
isRunning.value = false;
currentStatus.value = 'Completed';
currentStatus.value = t('simulation.status.completed');
};
const stopSimulation = () => {
@@ -86,7 +96,7 @@ const getRowColor = (solved) => {
<div class="modal-overlay" @click.self="emit('close')">
<div class="modal glass-panel">
<div class="header">
<h2>Difficulty Simulation</h2>
<h2>{{ t('simulation.title') }}</h2>
<button class="close-btn" @click="emit('close')">
<X />
</button>
@@ -95,7 +105,7 @@ const getRowColor = (solved) => {
<div class="content">
<div class="controls">
<div class="status-bar">
<div class="status-text">{{ currentStatus }}</div>
<div class="status-text">{{ displayStatus }}</div>
<div class="progress-track">
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
</div>
@@ -103,10 +113,10 @@ const getRowColor = (solved) => {
<div class="actions">
<button v-if="!isRunning" class="btn-neon" @click="startSimulation">
<Play class="icon" /> Start Simulation
<Play class="icon" /> {{ t('simulation.start') }}
</button>
<button v-else class="btn-neon secondary" @click="stopSimulation">
<Square class="icon" /> Stop
<Square class="icon" /> {{ t('simulation.stop') }}
</button>
</div>
</div>
@@ -115,9 +125,9 @@ const getRowColor = (solved) => {
<table class="results-table">
<thead>
<tr>
<th>Size</th>
<th>Density</th>
<th>Solved (Logic)</th>
<th>{{ t('simulation.table.size') }}</th>
<th>{{ t('simulation.table.density') }}</th>
<th>{{ t('simulation.table.solved') }}</th>
</tr>
</thead>
<tbody>
@@ -129,7 +139,7 @@ const getRowColor = (solved) => {
</tbody>
</table>
<div v-if="results.length === 0" class="empty-state">
Press Start to run Monte Carlo simulation
{{ t('simulation.empty') }}
</div>
</div>
</div>

View File

@@ -472,27 +472,27 @@ onUnmounted(() => {
</div>
</div>
<div class="share">
<div class="share-title">{{ t('win.shareTitle') }}</div>
<div class="share-buttons">
<div class="social-section">
<div class="social-header">{{ t('win.shareTitle') }}</div>
<div class="social-grid">
<!-- X (Twitter) -->
<button class="btn-neon secondary share-btn" :disabled="shareInProgress" :aria-label="t('win.shareX')" @click="shareTo('x')">
<svg viewBox="0 0 24 24" fill="currentColor" class="share-icon"><path d="M18.901 3H22l-7.21 8.26L23 21h-6.66L11.13 14.76 5.66 21H2.56l7.73-8.83L1 3h6.8l4.63 5.56L18.9 3h.001zm-1.2 15.9h1.77L6.44 5.1H4.44l13.26 13.8z"/></svg>
<button class="btn-neon secondary social-item" :disabled="shareInProgress" :aria-label="t('win.shareX')" @click="shareTo('x')">
<svg viewBox="0 0 24 24" fill="currentColor" class="social-icon"><path d="M18.901 3H22l-7.21 8.26L23 21h-6.66L11.13 14.76 5.66 21H2.56l7.73-8.83L1 3h6.8l4.63 5.56L18.9 3h.001zm-1.2 15.9h1.77L6.44 5.1H4.44l13.26 13.8z"/></svg>
</button>
<!-- Facebook -->
<button class="btn-neon secondary share-btn" :disabled="shareInProgress" :aria-label="t('win.shareFacebook')" @click="shareTo('facebook')">
<svg viewBox="0 0 24 24" fill="currentColor" class="share-icon"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.791-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
<button class="btn-neon secondary social-item" :disabled="shareInProgress" :aria-label="t('win.shareFacebook')" @click="shareTo('facebook')">
<svg viewBox="0 0 24 24" fill="currentColor" class="social-icon"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.791-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</button>
<!-- WhatsApp -->
<button class="btn-neon secondary share-btn" :disabled="shareInProgress" :aria-label="t('win.shareWhatsapp')" @click="shareTo('whatsapp')">
<svg viewBox="0 0 24 24" fill="currentColor" class="share-icon"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.008-.57-.008-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/></svg>
<button class="btn-neon secondary social-item" :disabled="shareInProgress" :aria-label="t('win.shareWhatsapp')" @click="shareTo('whatsapp')">
<svg viewBox="0 0 24 24" fill="currentColor" class="social-icon"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.008-.57-.008-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/></svg>
</button>
<!-- Download Screenshot (Compact) -->
<button class="btn-neon secondary share-btn" :disabled="shareInProgress" :aria-label="t('win.shareDownload')" @click="downloadShareImage">
<button class="btn-neon secondary social-item" :disabled="shareInProgress" :aria-label="t('win.shareDownload')" @click="downloadShareImage">
<Download :size="20" />
</button>
<!-- Download SVG (Compact) -->
<button class="btn-neon secondary share-btn" :disabled="shareInProgress" aria-label="Download SVG" @click="downloadShareSVG">
<button class="btn-neon secondary social-item" :disabled="shareInProgress" aria-label="Download SVG" @click="downloadShareSVG">
<FileCode :size="20" />
</button>
</div>
@@ -575,14 +575,14 @@ p {
margin-left: 10px;
}
.share {
.social-section {
margin-bottom: 24px;
display: flex;
flex-direction: column;
gap: 12px;
}
.share-title {
.social-header {
font-size: 0.95rem;
letter-spacing: 1px;
text-transform: uppercase;
@@ -590,14 +590,14 @@ p {
overflow-wrap: anywhere;
}
.share-buttons {
.social-grid {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.share-btn {
.social-item {
width: 44px;
height: 44px;
padding: 0;
@@ -607,14 +607,14 @@ p {
justify-content: center;
}
.share-icon {
.social-icon {
width: 22px;
height: 22px;
display: block;
}
.share-download {
.social-download {
align-self: center;
padding: 8px 18px;
font-size: 0.85rem;

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,7 @@ export function useNonogram() {
};
const stopDrag = () => {
store.endInteraction();
isDragging.value = false;
dragMode.value = null;
};

View File

@@ -9,8 +9,8 @@ export function useSolver() {
const isPlaying = ref(false);
const isProcessing = ref(false);
const speedIndex = ref(0);
const speeds = [1000, 500, 250, 125];
const speedLabels = ['x1', 'x2', 'x3', 'x4'];
const speeds = [1000, 500, 250, 125, 62];
const speedLabels = ['x1', 'x2', 'x4', 'x8', 'x16'];
const statusText = ref(t('guide.waiting'));
let intervalId = null;

View File

@@ -2,6 +2,34 @@ import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { generateRandomGrid } from '@/utils/puzzleUtils';
// Helper: Calculate hints for a line (row or column)
function calculateHints(line) {
const hints = [];
let currentRun = 0;
for (const cell of line) {
if (cell === 1) {
currentRun++;
} else {
if (currentRun > 0) {
hints.push(currentRun);
currentRun = 0;
}
}
}
if (currentRun > 0) {
hints.push(currentRun);
}
return hints.length > 0 ? hints : [0];
}
// Helper: Validate if a line matches its hints
function validateLine(line, targetHints) {
const currentHints = calculateHints(line);
if (currentHints.length !== targetHints.length) return false;
return currentHints.every((h, i) => h === targetHints[i]);
}
// Definicje zagadek (Static Puzzles)
const PUZZLES = {
easy: {
@@ -75,6 +103,7 @@ export const usePuzzleStore = defineStore('puzzle', () => {
// History for undo
const history = ref([]);
const currentTransaction = ref(null);
// Progress State
const totalCellsToFill = computed(() => {
@@ -150,18 +179,39 @@ export const usePuzzleStore = defineStore('puzzle', () => {
playerGrid.value = Array(size.value).fill().map(() => Array(size.value).fill(0));
moves.value = 0;
history.value = [];
currentTransaction.value = null;
}
function pushHistory() {
const gridCopy = playerGrid.value.map(row => [...row]);
history.value.push(gridCopy);
function startInteraction() {
currentTransaction.value = [];
}
function endInteraction() {
if (currentTransaction.value && currentTransaction.value.length > 0) {
history.value.push(currentTransaction.value);
if (history.value.length > 50) history.value.shift();
saveState();
}
currentTransaction.value = null;
}
function undo() {
if (history.value.length === 0 || isGameWon.value) return;
const previousState = history.value.pop();
playerGrid.value = previousState;
const transaction = history.value.pop();
// Handle legacy history (full grid snapshot)
if (!Array.isArray(transaction) || (transaction.length > 0 && Array.isArray(transaction[0]))) {
playerGrid.value = transaction;
} else {
// Handle new history (list of changes)
// Revert changes in reverse order
for (let i = transaction.length - 1; i >= 0; i--) {
const { r, c, oldVal } = transaction[i];
playerGrid.value[r][c] = oldVal;
}
}
moves.value++;
saveState();
}
@@ -169,8 +219,6 @@ export const usePuzzleStore = defineStore('puzzle', () => {
function toggleCell(r, c, isRightClick = false) {
if (isGameWon.value) return;
pushHistory();
const currentState = playerGrid.value[r][c];
let newState;
@@ -182,39 +230,79 @@ export const usePuzzleStore = defineStore('puzzle', () => {
newState = currentState === 1 ? 0 : 1;
}
playerGrid.value[r][c] = newState; // This triggers reactivity
if (currentState === newState) return;
// Apply change
playerGrid.value[r][c] = newState;
// Record history
const change = { r, c, oldVal: currentState, newVal: newState };
if (currentTransaction.value) {
currentTransaction.value.push(change);
} else {
// Atomic change if no interaction started
history.value.push([change]);
if (history.value.length > 50) history.value.shift();
saveState();
}
moves.value++;
checkWin();
saveState();
// saveState(); // Moved to endInteraction or atomic block
}
function setCell(r, c, state) {
if (isGameWon.value) return;
if (playerGrid.value[r][c] !== state) {
pushHistory();
const currentState = playerGrid.value[r][c];
if (currentState !== state) {
// Apply change
playerGrid.value[r][c] = state;
// Record history
const change = { r, c, oldVal: currentState, newVal: state };
if (currentTransaction.value) {
currentTransaction.value.push(change);
} else {
history.value.push([change]);
if (history.value.length > 50) history.value.shift();
saveState();
}
moves.value++;
checkWin();
saveState();
// saveState(); // Moved to endInteraction or atomic block
}
}
function checkWin() {
let correct = true;
// Calculate expected hints from solution (truth)
// We do this dynamically to ensure we always check against the rules of the board
const solutionRows = solution.value;
const solutionCols = Array(size.value).fill().map((_, c) => solution.value.map(r => r[c]));
// Check Rows
for (let r = 0; r < size.value; r++) {
for (let c = 0; c < size.value; c++) {
const playerCell = playerGrid.value[r][c];
const solutionCell = solution.value[r][c];
const isFilled = playerCell === 1;
const shouldBeFilled = solutionCell === 1;
if (isFilled !== shouldBeFilled) {
const targetHints = calculateHints(solutionRows[r]);
const playerLine = playerGrid.value[r];
if (!validateLine(playerLine, targetHints)) {
correct = false;
break;
}
}
if (correct) {
// Check Columns
for (let c = 0; c < size.value; c++) {
const targetHints = calculateHints(solutionCols[c]);
const playerLine = playerGrid.value.map(row => row[c]);
if (!validateLine(playerLine, targetHints)) {
correct = false;
break;
}
}
if (!correct) break;
}
if (correct) {
@@ -343,7 +431,9 @@ export const usePuzzleStore = defineStore('puzzle', () => {
hasUsedGuide,
guideUsageCount,
currentDensity,
markGuideUsed
markGuideUsed,
startInteraction,
endInteraction
};
});

View File

@@ -130,7 +130,6 @@
}
html {
overflow-x: hidden;
width: 100%;
}
@@ -139,18 +138,18 @@ body {
padding: 20px;
font-family: 'Segoe UI', 'Roboto', Helvetica, Arial, sans-serif;
background: var(--bg-gradient);
background-attachment: fixed;
background-size: cover;
color: var(--text-color);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
overflow-x: hidden;
}
/* Ensure no other content is visible */
#app {
width: 100%;
max-width: 100vw;
display: flex;
flex-direction: column;
align-items: center;